summaryrefslogtreecommitdiff
path: root/gtk
diff options
context:
space:
mode:
authorMichael Natterer <mitch@imendio.com>2006-03-07 13:46:11 +0000
committerMichael Natterer <mitch@src.gnome.org>2006-03-07 13:46:11 +0000
commit6c1d990adc5eea803b4cb28956befb83e89468ee (patch)
treeca25230f26a27da2bde3f6cf1c3e9910c0e7487d /gtk
parent1f5c294851a009295c9d331110512e47324d09c0 (diff)
downloadgtk+-6c1d990adc5eea803b4cb28956befb83e89468ee.tar.gz
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.
Diffstat (limited to 'gtk')
-rw-r--r--gtk/Makefile.am4
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtk.symbols28
-rw-r--r--gtk/gtkclipboard.c197
-rw-r--r--gtk/gtkclipboard.h93
-rw-r--r--gtk/gtkselection.c211
-rw-r--r--gtk/gtkselection.h47
-rw-r--r--gtk/gtktextbuffer.c443
-rw-r--r--gtk/gtktextbuffer.h13
-rw-r--r--gtk/gtktextbufferrichtext.c704
-rw-r--r--gtk/gtktextbufferrichtext.h92
-rw-r--r--gtk/gtktextbufferserialize.c1877
-rw-r--r--gtk/gtktextbufferserialize.h43
-rw-r--r--gtk/gtktextutil.c134
-rw-r--r--gtk/gtktextutil.h11
-rw-r--r--gtk/gtktextview.c197
16 files changed, 3901 insertions, 194 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 8653b58708..84d41c83eb 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -248,6 +248,7 @@ gtk_public_h_sources = \
gtktearoffmenuitem.h \
gtktext.h \
gtktextbuffer.h \
+ gtktextbufferrichtext.h \
gtktextchild.h \
gtktextdisplay.h \
gtktextiter.h \
@@ -481,6 +482,9 @@ gtk_c_sources = \
gtktext.c \
gtktextbtree.c \
gtktextbuffer.c \
+ gtktextbufferrichtext.c \
+ gtktextbufferserialize.c\
+ gtktextbufferserialize.h\
gtktextchild.c \
gtktextdisplay.c \
gtktextiter.c \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index f7bad3354d..54b4d67c44 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -160,6 +160,7 @@
#include <gtk/gtktearoffmenuitem.h>
#include <gtk/gtktext.h>
#include <gtk/gtktextbuffer.h>
+#include <gtk/gtktextbufferrichtext.h>
#include <gtk/gtktextview.h>
#include <gtk/gtktipsquery.h>
#include <gtk/gtktoggleaction.h>
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 570eaf9da6..f8b4edb8bc 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -600,6 +600,7 @@ gtk_clipboard_get_owner
gtk_clipboard_get_type G_GNUC_CONST
gtk_clipboard_request_contents
gtk_clipboard_request_image
+gtk_clipboard_request_rich_text
gtk_clipboard_request_targets
gtk_clipboard_request_text
gtk_clipboard_set_can_store
@@ -610,9 +611,11 @@ gtk_clipboard_set_with_owner
gtk_clipboard_store
gtk_clipboard_wait_for_contents
gtk_clipboard_wait_for_image
+gtk_clipboard_wait_for_rich_text
gtk_clipboard_wait_for_targets
gtk_clipboard_wait_for_text
gtk_clipboard_wait_is_image_available
+gtk_clipboard_wait_is_rich_text_available
gtk_clipboard_wait_is_text_available
gtk_clipboard_wait_is_target_available
#endif
@@ -2607,6 +2610,23 @@ gtk_rc_style_unref
#endif
#endif
+#if IN_HEADER(__GTK_TEXT_BUFFER_RICH_TEXT_H__)
+#if IN_FILE(__GTK_TEXT_BUFFER_RICH_TEXT_C__)
+gtk_text_buffer_deserialize
+gtk_text_buffer_deserialize_get_can_create_tags
+gtk_text_buffer_deserialize_set_can_create_tags
+gtk_text_buffer_get_deserialize_formats
+gtk_text_buffer_get_serialize_formats
+gtk_text_buffer_register_deserialize_format
+gtk_text_buffer_register_deserialize_tagset
+gtk_text_buffer_register_serialize_format
+gtk_text_buffer_register_serialize_tagset
+gtk_text_buffer_serialize
+gtk_text_buffer_unregister_deserialize_format
+gtk_text_buffer_unregister_serialize_format
+#endif
+#endif
+
#if IN_HEADER(__GTK_RULER_H__)
#if IN_FILE(__GTK_RULER_C__)
gtk_ruler_draw_pos
@@ -2678,9 +2698,11 @@ gtk_selection_data_set_pixbuf
gtk_selection_data_set_text
gtk_selection_data_set_uris
gtk_selection_data_targets_include_image
+gtk_selection_data_targets_include_rich_text
gtk_selection_data_targets_include_text
gtk_selection_data_targets_include_uri
gtk_targets_include_image
+gtk_targets_include_rich_text
gtk_targets_include_text
gtk_targets_include_uri
gtk_selection_owner_set
@@ -2688,14 +2710,18 @@ gtk_selection_owner_set_for_display
gtk_selection_remove_all
gtk_target_list_add
gtk_target_list_add_image_targets
+gtk_target_list_add_rich_text_targets
gtk_target_list_add_table
gtk_target_list_add_text_targets
gtk_target_list_add_uri_targets
gtk_target_list_find
+gtk_target_list_get_type G_GNUC_CONST
gtk_target_list_new
gtk_target_list_ref
gtk_target_list_remove
gtk_target_list_unref
+gtk_target_table_new_from_list
+gtk_target_table_free
#endif
#endif
@@ -2934,6 +2960,7 @@ gtk_text_buffer_delete_selection
gtk_text_buffer_end_user_action
gtk_text_buffer_get_bounds
gtk_text_buffer_get_char_count
+gtk_text_buffer_get_copy_target_list
gtk_text_buffer_get_end_iter
gtk_text_buffer_get_has_selection
gtk_text_buffer_get_insert
@@ -2946,6 +2973,7 @@ gtk_text_buffer_get_iter_at_offset
gtk_text_buffer_get_line_count
gtk_text_buffer_get_mark
gtk_text_buffer_get_modified
+gtk_text_buffer_get_paste_target_list
gtk_text_buffer_get_selection_bound
gtk_text_buffer_get_selection_bounds
gtk_text_buffer_get_slice
diff --git a/gtk/gtkclipboard.c b/gtk/gtkclipboard.c
index 25ae16664e..8eceb1dd8b 100644
--- a/gtk/gtkclipboard.c
+++ b/gtk/gtkclipboard.c
@@ -27,6 +27,7 @@
#include "gtkinvisible.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
+#include "gtktextbufferrichtext.h"
#include "gtkintl.h"
#include "gtkalias.h"
@@ -47,6 +48,7 @@ typedef struct _GtkClipboardClass GtkClipboardClass;
typedef struct _RequestContentsInfo RequestContentsInfo;
typedef struct _RequestTextInfo RequestTextInfo;
+typedef struct _RequestRichTextInfo RequestRichTextInfo;
typedef struct _RequestImageInfo RequestImageInfo;
typedef struct _RequestTargetsInfo RequestTargetsInfo;
@@ -97,6 +99,15 @@ struct _RequestTextInfo
gpointer user_data;
};
+struct _RequestRichTextInfo
+{
+ GtkClipboardRichTextReceivedFunc callback;
+ GdkAtom *atoms;
+ gint n_atoms;
+ gint current_atom;
+ gpointer user_data;
+};
+
struct _RequestImageInfo
{
GtkClipboardImageReceivedFunc callback;
@@ -927,7 +938,7 @@ request_text_received_func (GtkClipboard *clipboard,
RequestTextInfo *info = data;
gchar *result = NULL;
- result = gtk_selection_data_get_text (selection_data);
+ result = (gchar *) gtk_selection_data_get_text (selection_data);
if (!result)
{
@@ -992,6 +1003,80 @@ gtk_clipboard_request_text (GtkClipboard *clipboard,
info);
}
+static void
+request_rich_text_received_func (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ RequestRichTextInfo *info = data;
+ guint8 *result = NULL;
+ gsize length = 0;
+
+ result = selection_data->data;
+ length = selection_data->length;
+
+ info->current_atom++;
+
+ if ((!result || length < 1) && (info->current_atom < info->n_atoms))
+ {
+ gtk_clipboard_request_contents (clipboard, info->atoms[info->current_atom],
+ request_rich_text_received_func,
+ info);
+ return;
+ }
+
+ info->callback (clipboard, selection_data->target, result, length,
+ info->user_data);
+ g_free (info->atoms);
+ g_free (info);
+}
+
+/**
+ * gtk_clipboard_request_rich_text:
+ * @clipboard: a #GtkClipboard
+ * @buffer: a #GtkTextBuffer
+ * @callback: a function to call when the text is received,
+ * or the retrieval fails. (It will always be called
+ * one way or the other.)
+ * @user_data: user data to pass to @callback.
+ *
+ * Requests the contents of the clipboard as rich text. When the rich
+ * text is later received, @callback will be called.
+ *
+ * The @text parameter to @callback will contain the resulting rich
+ * text if the request succeeded, or %NULL if it failed. The @length
+ * parameter will contain @text's length. This function can fail for
+ * various reasons, in particular if the clipboard was empty or if the
+ * contents of the clipboard could not be converted into rich text form.
+ *
+ * Since: 2.10
+ **/
+void
+gtk_clipboard_request_rich_text (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer,
+ GtkClipboardRichTextReceivedFunc callback,
+ gpointer user_data)
+{
+ RequestRichTextInfo *info;
+
+ g_return_if_fail (clipboard != NULL);
+ g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+ g_return_if_fail (callback != NULL);
+
+ info = g_new (RequestRichTextInfo, 1);
+ info->callback = callback;
+ info->atoms = NULL;
+ info->n_atoms = 0;
+ info->current_atom = 0;
+ info->user_data = user_data;
+
+ info->atoms = gtk_text_buffer_get_deserialize_formats (buffer, &info->n_atoms);
+
+ gtk_clipboard_request_contents (clipboard, info->atoms[info->current_atom],
+ request_rich_text_received_func,
+ info);
+}
+
static void
request_image_received_func (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
@@ -1143,6 +1228,8 @@ typedef struct
{
GMainLoop *loop;
gpointer data;
+ GdkAtom format; /* used by rich text */
+ gsize length; /* used by rich text */
} WaitResults;
static void
@@ -1254,6 +1341,75 @@ gtk_clipboard_wait_for_text (GtkClipboard *clipboard)
return results.data;
}
+static void
+clipboard_rich_text_received_func (GtkClipboard *clipboard,
+ GdkAtom format,
+ const guint8 *text,
+ gsize length,
+ gpointer data)
+{
+ WaitResults *results = data;
+
+ results->data = g_memdup (text, length);
+ results->format = format;
+ results->length = length;
+ g_main_loop_quit (results->loop);
+}
+
+/**
+ * gtk_clipboard_wait_for_rich_text:
+ * @clipboard: a #GtkClipboard
+ * @buffer: a #GtkTextBuffer
+ * @length: return location for the length of the returned data
+ *
+ * Requests the contents of the clipboard as rich text. This function
+ * waits for the data to be received using the main loop, so events,
+ * timeouts, etc, may be dispatched during the wait.
+ *
+ * Return value: a newly-allocated binary block of data which must
+ * be freed with g_free(), or %NULL if retrieving
+ * the selection data failed. (This could happen
+ * for various reasons, in particular if the
+ * clipboard was empty or if the contents of the
+ * clipboard could not be converted into text form.)
+ *
+ * Since: 2.10
+ **/
+guint8 *
+gtk_clipboard_wait_for_rich_text (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer,
+ GdkAtom *format,
+ gsize *length)
+{
+ WaitResults results;
+
+ g_return_val_if_fail (clipboard != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+ g_return_val_if_fail (length != NULL, NULL);
+
+ results.data = NULL;
+ results.loop = g_main_loop_new (NULL, TRUE);
+
+ gtk_clipboard_request_rich_text (clipboard, buffer,
+ clipboard_rich_text_received_func,
+ &results);
+
+ if (g_main_loop_is_running (results.loop))
+ {
+ GDK_THREADS_LEAVE ();
+ g_main_loop_run (results.loop);
+ GDK_THREADS_ENTER ();
+ }
+
+ g_main_loop_unref (results.loop);
+
+ *format = results.format;
+ *length = results.length;
+
+ return results.data;
+}
+
static void
clipboard_image_received_func (GtkClipboard *clipboard,
GdkPixbuf *pixbuf,
@@ -1362,6 +1518,45 @@ gtk_clipboard_wait_is_text_available (GtkClipboard *clipboard)
}
/**
+ * gtk_clipboard_wait_is_rich_text_available:
+ * @clipboard: a #GtkClipboard
+ * @buffer: a #GtkTextBuffer
+ *
+ * Test to see if there is rich text available to be pasted
+ * This is done by requesting the TARGETS atom and checking
+ * if it contains any of the supported rich text targets. This function
+ * waits for the data to be received using the main loop, so events,
+ * timeouts, etc, may be dispatched during the wait.
+ *
+ * This function is a little faster than calling
+ * gtk_clipboard_wait_for_rich_text() since it doesn't need to retrieve
+ * the actual text.
+ *
+ * Return value: %TRUE is there is rich text available, %FALSE otherwise.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_clipboard_wait_is_rich_text_available (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer)
+{
+ GtkSelectionData *data;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+ data = gtk_clipboard_wait_for_contents (clipboard, gdk_atom_intern_static_string ("TARGETS"));
+ if (data)
+ {
+ result = gtk_selection_data_targets_include_rich_text (data, buffer);
+ gtk_selection_data_free (data);
+ }
+
+ return result;
+}
+
+/**
* gtk_clipboard_wait_is_image_available:
* @clipboard: a #GtkClipboard
*
diff --git a/gtk/gtkclipboard.h b/gtk/gtkclipboard.h
index 9be35a6360..46e9ea6b36 100644
--- a/gtk/gtkclipboard.h
+++ b/gtk/gtkclipboard.h
@@ -30,19 +30,24 @@ G_BEGIN_DECLS
#define GTK_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CLIPBOARD, GtkClipboard))
#define GTK_IS_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CLIPBOARD))
-typedef void (* GtkClipboardReceivedFunc) (GtkClipboard *clipboard,
- GtkSelectionData *selection_data,
- gpointer data);
-typedef void (* GtkClipboardTextReceivedFunc) (GtkClipboard *clipboard,
- const gchar *text,
- gpointer data);
-typedef void (* GtkClipboardImageReceivedFunc) (GtkClipboard *clipboard,
- GdkPixbuf *pixbuf,
- gpointer data);
-typedef void (* GtkClipboardTargetsReceivedFunc) (GtkClipboard *clipboard,
- GdkAtom *atoms,
- gint n_atoms,
- gpointer data);
+typedef void (* GtkClipboardReceivedFunc) (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data);
+typedef void (* GtkClipboardTextReceivedFunc) (GtkClipboard *clipboard,
+ const gchar *text,
+ gpointer data);
+typedef void (* GtkClipboardRichTextReceivedFunc) (GtkClipboard *clipboard,
+ GdkAtom format,
+ const guint8 *text,
+ gsize length,
+ gpointer data);
+typedef void (* GtkClipboardImageReceivedFunc) (GtkClipboard *clipboard,
+ GdkPixbuf *pixbuf,
+ gpointer data);
+typedef void (* GtkClipboardTargetsReceivedFunc) (GtkClipboard *clipboard,
+ GdkAtom *atoms,
+ gint n_atoms,
+ gpointer data);
/* Should these functions have GtkClipboard *clipboard as the first argument?
* right now for ClearFunc, you may have trouble determining _which_ clipboard
@@ -86,32 +91,42 @@ void gtk_clipboard_set_text (GtkClipboard *clipboard,
void gtk_clipboard_set_image (GtkClipboard *clipboard,
GdkPixbuf *pixbuf);
-void gtk_clipboard_request_contents (GtkClipboard *clipboard,
- GdkAtom target,
- GtkClipboardReceivedFunc callback,
- gpointer user_data);
-void gtk_clipboard_request_text (GtkClipboard *clipboard,
- GtkClipboardTextReceivedFunc callback,
- gpointer user_data);
-void gtk_clipboard_request_image (GtkClipboard *clipboard,
- GtkClipboardImageReceivedFunc callback,
- gpointer user_data);
-void gtk_clipboard_request_targets (GtkClipboard *clipboard,
- GtkClipboardTargetsReceivedFunc callback,
- gpointer user_data);
-
-GtkSelectionData *gtk_clipboard_wait_for_contents (GtkClipboard *clipboard,
- GdkAtom target);
-gchar * gtk_clipboard_wait_for_text (GtkClipboard *clipboard);
-GdkPixbuf * gtk_clipboard_wait_for_image (GtkClipboard *clipboard);
-gboolean gtk_clipboard_wait_for_targets (GtkClipboard *clipboard,
- GdkAtom **targets,
- gint *n_targets);
-
-gboolean gtk_clipboard_wait_is_text_available (GtkClipboard *clipboard);
-gboolean gtk_clipboard_wait_is_image_available (GtkClipboard *clipboard);
-gboolean gtk_clipboard_wait_is_target_available (GtkClipboard *clipboard,
- GdkAtom target);
+void gtk_clipboard_request_contents (GtkClipboard *clipboard,
+ GdkAtom target,
+ GtkClipboardReceivedFunc callback,
+ gpointer user_data);
+void gtk_clipboard_request_text (GtkClipboard *clipboard,
+ GtkClipboardTextReceivedFunc callback,
+ gpointer user_data);
+void gtk_clipboard_request_rich_text (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer,
+ GtkClipboardRichTextReceivedFunc callback,
+ gpointer user_data);
+void gtk_clipboard_request_image (GtkClipboard *clipboard,
+ GtkClipboardImageReceivedFunc callback,
+ gpointer user_data);
+void gtk_clipboard_request_targets (GtkClipboard *clipboard,
+ GtkClipboardTargetsReceivedFunc callback,
+ gpointer user_data);
+
+GtkSelectionData *gtk_clipboard_wait_for_contents (GtkClipboard *clipboard,
+ GdkAtom target);
+gchar * gtk_clipboard_wait_for_text (GtkClipboard *clipboard);
+guint8 * gtk_clipboard_wait_for_rich_text (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer,
+ GdkAtom *format,
+ gsize *size);
+GdkPixbuf * gtk_clipboard_wait_for_image (GtkClipboard *clipboard);
+gboolean gtk_clipboard_wait_for_targets (GtkClipboard *clipboard,
+ GdkAtom **targets,
+ gint *n_targets);
+
+gboolean gtk_clipboard_wait_is_text_available (GtkClipboard *clipboard);
+gboolean gtk_clipboard_wait_is_rich_text_available (GtkClipboard *clipboard,
+ GtkTextBuffer *buffer);
+gboolean gtk_clipboard_wait_is_image_available (GtkClipboard *clipboard);
+gboolean gtk_clipboard_wait_is_target_available (GtkClipboard *clipboard,
+ GdkAtom target);
void gtk_clipboard_set_can_store (GtkClipboard *clipboard,
diff --git a/gtk/gtkselection.c b/gtk/gtkselection.c
index 6272f5c46b..9670353498 100644
--- a/gtk/gtkselection.c
+++ b/gtk/gtkselection.c
@@ -58,6 +58,7 @@
#include "gtkmain.h"
#include "gtkselection.h"
+#include "gtktextbufferrichtext.h"
#include "gtkintl.h"
#include "gdk-pixbuf/gdk-pixbuf.h"
@@ -213,13 +214,16 @@ gtk_target_list_new (const GtkTargetEntry *targets,
*
* Increases the reference count of a #GtkTargetList by one.
*
+ * Return value: the passed in #GtkTargetList.
**/
-void
+GtkTargetList *
gtk_target_list_ref (GtkTargetList *list)
{
- g_return_if_fail (list != NULL);
+ g_return_val_if_fail (list != NULL, NULL);
list->ref_count++;
+
+ return list;
}
/**
@@ -339,6 +343,45 @@ gtk_target_list_add_text_targets (GtkTargetList *list,
}
/**
+ * gtk_target_list_add_rich_text_targets:
+ * @list: a #GtkTargetList
+ * @info: an ID that will be passed back to the application
+ * @deserializable: if %TRUE, then deserializable rich text formats
+ * will be added, serializable formats otherwise.
+ * @buffer: a #GtkTextBuffer.
+ *
+ * Appends the rich text targets registered with
+ * gtk_text_buffer_register_serialize_format() or
+ * gtk_text_buffer_register_deserialize_format() to the target list. All
+ * targets are added with the same @info.
+ *
+ * Since: 2.10
+ **/
+void
+gtk_target_list_add_rich_text_targets (GtkTargetList *list,
+ guint info,
+ gboolean deserializable,
+ GtkTextBuffer *buffer)
+{
+ GdkAtom *atoms;
+ gint n_atoms;
+ gint i;
+
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+ if (deserializable)
+ atoms = gtk_text_buffer_get_deserialize_formats (buffer, &n_atoms);
+ else
+ atoms = gtk_text_buffer_get_serialize_formats (buffer, &n_atoms);
+
+ for (i = 0; i < n_atoms; i++)
+ gtk_target_list_add (list, atoms[i], 0, info);
+
+ g_free (atoms);
+}
+
+/**
* gtk_target_list_add_image_targets:
* @list: a #GtkTargetList
* @info: an ID that will be passed back to the application
@@ -515,6 +558,72 @@ gtk_target_list_find (GtkTargetList *list,
}
/**
+ * gtk_target_table_new_from_list:
+ * @list: a #GtkTargetList
+ * @n_targets: return location for the number ot targets in the table
+ *
+ * This function creates an #GtkTargetEntry array that contains the
+ * same targets as the passed %list. The returned table is newly
+ * allocated and should be freed using gtk_target_table_free() when no
+ * longer needed.
+ *
+ * Return value: the new table.
+ *
+ * Since: 2.10
+ **/
+GtkTargetEntry *
+gtk_target_table_new_from_list (GtkTargetList *list,
+ gint *n_targets)
+{
+ GtkTargetEntry *targets;
+ GList *tmp_list;
+ gint i;
+
+ g_return_val_if_fail (list != NULL, NULL);
+ g_return_val_if_fail (n_targets != NULL, NULL);
+
+ *n_targets = g_list_length (list->list);
+ targets = g_new0 (GtkTargetEntry, *n_targets);
+
+ for (i = 0, tmp_list = list->list;
+ i < *n_targets;
+ i++, tmp_list = g_list_next (tmp_list))
+ {
+ GtkTargetPair *pair = tmp_list->data;
+
+ targets[i].target = gdk_atom_name (pair->target);
+ targets[i].flags = pair->flags;
+ targets[i].info = pair->info;
+ }
+
+ return targets;
+}
+
+/**
+ * gtk_target_table_free:
+ * @targets: a #GtkTargetEntry array
+ * @n_targets: the number of entries in the array
+ *
+ * This function frees a target table as returned by
+ * gtk_target_table_new_from_list()
+ *
+ * Since: 2.10
+ **/
+void
+gtk_target_table_free (GtkTargetEntry *targets,
+ gint n_targets)
+{
+ gint i;
+
+ g_return_if_fail (targets == NULL || n_targets > 0);
+
+ for (i = 0; i < n_targets; i++)
+ g_free (targets[i].target);
+
+ g_free (targets);
+}
+
+/**
* gtk_selection_owner_set_for_display:
* @display: the #Gdkdisplay where the selection is set
* @widget: new selection owner (a #GdkWidget), or %NULL.
@@ -1608,7 +1717,54 @@ gtk_targets_include_text (GdkAtom *targets,
return result;
}
-
+
+/**
+ * gtk_targets_include_rich_text:
+ * @targets: an array of #GdkAtom<!-- -->s
+ * @n_targets: the length of @targets
+ * @buffer: a #GtkTextBuffer
+ *
+ * Determines if any of the targets in @targets can be used to
+ * provide rich text.
+ *
+ * Return value: %TRUE if @targets include a suitable target for rich text,
+ * otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_targets_include_rich_text (GdkAtom *targets,
+ gint n_targets,
+ GtkTextBuffer *buffer)
+{
+ GdkAtom *rich_targets;
+ gint n_rich_targets;
+ gint i, j;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+ rich_targets = gtk_text_buffer_get_deserialize_formats (buffer,
+ &n_rich_targets);
+
+ for (i = 0; i < n_targets; i++)
+ {
+ for (j = 0; j < n_rich_targets; j++)
+ {
+ if (targets[i] == rich_targets[j])
+ {
+ result = TRUE;
+ goto done;
+ }
+ }
+ }
+
+ done:
+ g_free (rich_targets);
+
+ return result;
+}
+
/**
* gtk_selection_data_targets_include_text:
* @selection_data: a #GtkSelectionData object
@@ -1639,6 +1795,42 @@ gtk_selection_data_targets_include_text (GtkSelectionData *selection_data)
}
/**
+ * gtk_selection_data_targets_include_rich_text:
+ * @selection_data: a #GtkSelectionData object
+ * @buffer: a #GtkTextBuffer
+ *
+ * Given a #GtkSelectionData object holding a list of targets,
+ * determines if any of the targets in @targets can be used to
+ * provide rich text.
+ *
+ * Return value: %TRUE if @selection_data holds a list of targets,
+ * and a suitable target for rich text is included,
+ * otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_selection_data_targets_include_rich_text (GtkSelectionData *selection_data,
+ GtkTextBuffer *buffer)
+{
+ GdkAtom *targets;
+ gint n_targets;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+ init_atoms ();
+
+ if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets))
+ {
+ result = gtk_targets_include_rich_text (targets, n_targets, buffer);
+ g_free (targets);
+ }
+
+ return result;
+}
+
+/**
* gtk_targets_include_image:
* @targets: an array of #GdkAtom<!-- -->s
* @n_targets: the length of @targets
@@ -2756,6 +2948,19 @@ gtk_selection_data_get_type (void)
return our_type;
}
+GType
+gtk_target_list_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ our_type = g_boxed_type_register_static (I_("GtkTargetList"),
+ (GBoxedCopyFunc) gtk_target_list_ref,
+ (GBoxedFreeFunc) gtk_target_list_unref);
+
+ return our_type;
+}
+
static int
gtk_selection_bytes_per_item (gint format)
{
diff --git a/gtk/gtkselection.h b/gtk/gtkselection.h
index 038a16a972..9e97ae2a6d 100644
--- a/gtk/gtkselection.h
+++ b/gtk/gtkselection.h
@@ -31,6 +31,7 @@
#include <gdk/gdk.h>
#include <gtk/gtkenums.h>
#include <gtk/gtkwidget.h>
+#include <gtk/gtktextiter.h>
G_BEGIN_DECLS
@@ -38,6 +39,7 @@ typedef struct _GtkTargetList GtkTargetList;
typedef struct _GtkTargetEntry GtkTargetEntry;
#define GTK_TYPE_SELECTION_DATA (gtk_selection_data_get_type ())
+#define GTK_TYPE_TARGET_LIST (gtk_target_list_get_type ())
/* The contents of a selection are returned in a GtkSelectionData
* structure. selection/target identify the request. type specifies
@@ -87,19 +89,23 @@ struct _GtkTargetPair {
GtkTargetList *gtk_target_list_new (const GtkTargetEntry *targets,
guint ntargets);
-void gtk_target_list_ref (GtkTargetList *list);
+GtkTargetList *gtk_target_list_ref (GtkTargetList *list);
void gtk_target_list_unref (GtkTargetList *list);
void gtk_target_list_add (GtkTargetList *list,
GdkAtom target,
guint flags,
guint info);
-void gtk_target_list_add_text_targets (GtkTargetList *list,
- guint info);
-void gtk_target_list_add_image_targets (GtkTargetList *list,
- guint info,
- gboolean writable);
-void gtk_target_list_add_uri_targets (GtkTargetList *list,
- guint info);
+void gtk_target_list_add_text_targets (GtkTargetList *list,
+ guint info);
+void gtk_target_list_add_rich_text_targets (GtkTargetList *list,
+ guint info,
+ gboolean deserializable,
+ GtkTextBuffer *buffer);
+void gtk_target_list_add_image_targets (GtkTargetList *list,
+ guint info,
+ gboolean writable);
+void gtk_target_list_add_uri_targets (GtkTargetList *list,
+ guint info);
void gtk_target_list_add_table (GtkTargetList *list,
const GtkTargetEntry *targets,
guint ntargets);
@@ -109,6 +115,11 @@ gboolean gtk_target_list_find (GtkTargetList *list,
GdkAtom target,
guint *info);
+GtkTargetEntry * gtk_target_table_new_from_list (GtkTargetList *list,
+ gint *n_targets);
+void gtk_target_table_free (GtkTargetEntry *targets,
+ gint n_targets);
+
/* Public interface */
gboolean gtk_selection_owner_set (GtkWidget *widget,
@@ -153,16 +164,21 @@ gboolean gtk_selection_data_get_targets (GtkSelectionData *selection_d
GdkAtom **targets,
gint *n_atoms);
gboolean gtk_selection_data_targets_include_text (GtkSelectionData *selection_data);
+gboolean gtk_selection_data_targets_include_rich_text (GtkSelectionData *selection_data,
+ GtkTextBuffer *buffer);
gboolean gtk_selection_data_targets_include_image (GtkSelectionData *selection_data,
gboolean writable);
gboolean gtk_selection_data_targets_include_uri (GtkSelectionData *selection_data);
-gboolean gtk_targets_include_text (GdkAtom *targets,
- gint n_targets);
-gboolean gtk_targets_include_image (GdkAtom *targets,
- gint n_targets,
- gboolean writable);
-gboolean gtk_targets_include_uri (GdkAtom *targets,
- gint n_targets);
+gboolean gtk_targets_include_text (GdkAtom *targets,
+ gint n_targets);
+gboolean gtk_targets_include_rich_text (GdkAtom *targets,
+ gint n_targets,
+ GtkTextBuffer *buffer);
+gboolean gtk_targets_include_image (GdkAtom *targets,
+ gint n_targets,
+ gboolean writable);
+gboolean gtk_targets_include_uri (GdkAtom *targets,
+ gint n_targets);
/* Called when a widget is destroyed */
@@ -186,6 +202,7 @@ GType gtk_selection_data_get_type (void) G_GNUC_CONST;
GtkSelectionData *gtk_selection_data_copy (GtkSelectionData *data);
void gtk_selection_data_free (GtkSelectionData *data);
+GType gtk_target_list_get_type (void) G_GNUC_CONST;
G_END_DECLS
diff --git a/gtk/gtktextbuffer.c b/gtk/gtktextbuffer.c
index e65c71bf3a..b6d2788c09 100644
--- a/gtk/gtktextbuffer.c
+++ b/gtk/gtktextbuffer.c
@@ -1,5 +1,6 @@
/* GTK - The GIMP Toolkit
* gtktextbuffer.c Copyright (C) 2000 Red Hat, Inc.
+ * Copyright (C) 2004 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -24,17 +25,17 @@
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
-
#include <config.h>
#include <string.h>
#include <stdarg.h>
-
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
#include "gtkclipboard.h"
+#include "gtkdnd.h"
#include "gtkinvisible.h"
#include "gtkmarshalers.h"
#include "gtktextbuffer.h"
+#include "gtktextbufferrichtext.h"
#include "gtktextbtree.h"
#include "gtktextiterprivate.h"
#include "gtkprivate.h"
@@ -42,6 +43,22 @@
#include "gtkalias.h"
+#define GTK_TEXT_BUFFER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TEXT_BUFFER, GtkTextBufferPrivate))
+
+typedef struct _GtkTextBufferPrivate GtkTextBufferPrivate;
+
+struct _GtkTextBufferPrivate
+{
+ GtkTargetList *copy_target_list;
+ GtkTargetEntry *copy_target_entries;
+ gint n_copy_target_entries;
+
+ GtkTargetList *paste_target_list;
+ GtkTargetEntry *paste_target_entries;
+ gint n_paste_target_entries;
+};
+
+
typedef struct _ClipboardRequest ClipboardRequest;
struct _ClipboardRequest
@@ -74,25 +91,18 @@ enum {
/* Construct */
PROP_TAG_TABLE,
-
+
/* Normal */
PROP_TEXT,
- PROP_HAS_SELECTION
-};
-
-enum {
- TARGET_STRING,
- TARGET_TEXT,
- TARGET_COMPOUND_TEXT,
- TARGET_UTF8_STRING,
- TARGET_TEXT_BUFFER_CONTENTS
+ PROP_HAS_SELECTION,
+ PROP_COPY_TARGET_LIST,
+ PROP_PASTE_TARGET_LIST
};
static void gtk_text_buffer_init (GtkTextBuffer *tkxt_buffer);
static void gtk_text_buffer_class_init (GtkTextBufferClass *klass);
static void gtk_text_buffer_finalize (GObject *object);
-
static void gtk_text_buffer_real_insert_text (GtkTextBuffer *buffer,
GtkTextIter *iter,
const gchar *text,
@@ -127,6 +137,8 @@ static void update_selection_clipboards (GtkTextBuffer *buffer);
static GtkTextBuffer *create_clipboard_contents_buffer (GtkTextBuffer *buffer);
+static void gtk_text_buffer_free_target_lists (GtkTextBuffer *buffer);
+
static GObjectClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = { 0 };
@@ -138,6 +150,8 @@ static void gtk_text_buffer_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
+static void gtk_text_buffer_notify (GObject *object,
+ GParamSpec *pspec);
GType
@@ -177,6 +191,7 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
object_class->finalize = gtk_text_buffer_finalize;
object_class->set_property = gtk_text_buffer_set_property;
object_class->get_property = gtk_text_buffer_get_property;
+ object_class->notify = gtk_text_buffer_notify;
klass->insert_text = gtk_text_buffer_real_insert_text;
klass->insert_pixbuf = gtk_text_buffer_real_insert_pixbuf;
@@ -227,7 +242,39 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
P_("Has selection"),
P_("Whether the buffer has some text currently selected"),
FALSE,
- G_PARAM_READABLE));
+ GTK_PARAM_READABLE));
+
+ /**
+ * GtkTextBuffer:copy-target-list:
+ *
+ * The list of targets this buffer supports for clipboard copying
+ * and as DND source.
+ *
+ * Since: 2.10
+ */
+ g_object_class_install_property (object_class,
+ PROP_COPY_TARGET_LIST,
+ g_param_spec_boxed ("copy-target-list",
+ P_("Copy target list"),
+ P_("The list of targets this buffer supports for clipboard copying and DND source"),
+ GTK_TYPE_TARGET_LIST,
+ GTK_PARAM_READABLE));
+
+ /**
+ * GtkTextBuffer:paste-target-list:
+ *
+ * The list of targets this buffer supports for clipboard pasting
+ * and as DND destination.
+ *
+ * Since: 2.10
+ */
+ g_object_class_install_property (object_class,
+ PROP_PASTE_TARGET_LIST,
+ g_param_spec_boxed ("paste-target-list",
+ P_("Paste target list"),
+ P_("The list of targets this buffer supports for clipboard pasting and DND destination"),
+ GTK_TYPE_TARGET_LIST,
+ GTK_PARAM_READABLE));
signals[INSERT_TEXT] =
g_signal_new (I_("insert_text"),
@@ -378,7 +425,9 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
NULL, NULL,
_gtk_marshal_VOID__VOID,
G_TYPE_NONE,
- 0);
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GtkTextBufferPrivate));
}
static void
@@ -386,6 +435,9 @@ gtk_text_buffer_init (GtkTextBuffer *buffer)
{
buffer->clipboard_contents_buffers = NULL;
buffer->tag_table = NULL;
+
+ /* allow copying of arbiatray stuff in the internal rich text format */
+ gtk_text_buffer_register_serialize_tagset (buffer, NULL);
}
static void
@@ -428,9 +480,9 @@ gtk_text_buffer_set_property (GObject *object,
case PROP_TAG_TABLE:
set_table (text_buffer, g_value_get_object (value));
break;
-
+
case PROP_TEXT:
- gtk_text_buffer_set_text (text_buffer,
+ gtk_text_buffer_set_text (text_buffer,
g_value_get_string (value), -1);
break;
@@ -454,28 +506,48 @@ gtk_text_buffer_get_property (GObject *object,
case PROP_TAG_TABLE:
g_value_set_object (value, get_table (text_buffer));
break;
-
+
case PROP_TEXT:
- {
- GtkTextIter start, end;
-
- gtk_text_buffer_get_start_iter (text_buffer, &start);
- gtk_text_buffer_get_end_iter (text_buffer, &end);
-
- g_value_set_string (value,
- gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE));
- break;
- }
+ {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_start_iter (text_buffer, &start);
+ gtk_text_buffer_get_end_iter (text_buffer, &end);
+
+ g_value_set_string (value,
+ gtk_text_buffer_get_text (text_buffer,
+ &start, &end, FALSE));
+ break;
+ }
case PROP_HAS_SELECTION:
g_value_set_boolean (value, text_buffer->has_selection);
break;
+ case PROP_COPY_TARGET_LIST:
+ g_value_set_boxed (value, gtk_text_buffer_get_copy_target_list (text_buffer));
+ break;
+
+ case PROP_PASTE_TARGET_LIST:
+ g_value_set_boxed (value, gtk_text_buffer_get_paste_target_list (text_buffer));
+ break;
+
default:
break;
}
}
+static void
+gtk_text_buffer_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ if (!strcmp (pspec->name, "copy-target-list") ||
+ !strcmp (pspec->name, "paste-target-list"))
+ {
+ gtk_text_buffer_free_target_lists (GTK_TEXT_BUFFER (object));
+ }
+}
+
/**
* gtk_text_buffer_new:
* @table: a tag table, or NULL to create a new one
@@ -498,11 +570,14 @@ static void
gtk_text_buffer_finalize (GObject *object)
{
GtkTextBuffer *buffer;
+ GtkTextBufferPrivate *priv;
buffer = GTK_TEXT_BUFFER (object);
remove_all_selection_clipboards (buffer);
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
if (buffer->tag_table)
{
_gtk_text_tag_table_remove_buffer (buffer->tag_table, buffer);
@@ -520,7 +595,9 @@ gtk_text_buffer_finalize (GObject *object)
free_log_attr_cache (buffer->log_attr_cache);
buffer->log_attr_cache = NULL;
-
+
+ gtk_text_buffer_free_target_lists (buffer);
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@@ -2840,22 +2917,36 @@ clipboard_get_selection_cb (GtkClipboard *clipboard,
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
{
- if (selection_data->target ==
- gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+ if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
{
/* Provide the address of the buffer; this will only be
* used within-process
*/
gtk_selection_data_set (selection_data,
- gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+ selection_data->target,
8, /* bytes */
(void*)&buffer,
sizeof (buffer));
}
+ else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+ {
+ guint8 *str;
+ gsize len;
+
+ str = gtk_text_buffer_serialize (buffer, buffer,
+ selection_data->target,
+ &start, &end, &len);
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, /* bytes */
+ str, len);
+ g_free (str);
+ }
else
{
gchar *str;
-
+
str = gtk_text_iter_get_visible_text (&start, &end);
gtk_selection_data_set_text (selection_data, str, -1);
g_free (str);
@@ -2870,8 +2961,11 @@ create_clipboard_contents_buffer (GtkTextBuffer *buffer)
contents = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer));
- g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard"), GINT_TO_POINTER (1));
-
+ g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard-source"),
+ buffer);
+ g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard"),
+ GINT_TO_POINTER (1));
+
return contents;
}
@@ -2882,31 +2976,50 @@ clipboard_get_contents_cb (GtkClipboard *clipboard,
guint info,
gpointer data)
{
- GtkTextBuffer *contents;
+ GtkTextBuffer *contents = GTK_TEXT_BUFFER (data);
- contents = GTK_TEXT_BUFFER (data);
-
g_assert (contents); /* This should never be called unless we own the clipboard */
- if (selection_data->target ==
- gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+ if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
{
/* Provide the address of the clipboard buffer; this will only
* be used within-process. OK to supply a NULL value for contents.
*/
gtk_selection_data_set (selection_data,
- gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+ selection_data->target,
8, /* bytes */
(void*)&contents,
sizeof (contents));
}
+ else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+ {
+ GtkTextBuffer *clipboard_source_buffer;
+ GtkTextIter start, end;
+ guint8 *str;
+ gsize len;
+
+ clipboard_source_buffer = g_object_get_data (G_OBJECT (contents),
+ "gtk-text-buffer-clipboard-source");
+
+ gtk_text_buffer_get_bounds (contents, &start, &end);
+
+ str = gtk_text_buffer_serialize (clipboard_source_buffer, contents,
+ selection_data->target,
+ &start, &end, &len);
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, /* bytes */
+ str, len);
+ g_free (str);
+ }
else
{
gchar *str;
GtkTextIter start, end;
-
+
gtk_text_buffer_get_bounds (contents, &start, &end);
-
+
str = gtk_text_iter_get_visible_text (&start, &end);
gtk_selection_data_set_text (selection_data, str, -1);
g_free (str);
@@ -3052,7 +3165,8 @@ selection_data_get_buffer (GtkSelectionData *selection_data,
if (gdk_window_get_window_type (owner) == GDK_WINDOW_FOREIGN)
return NULL;
- if (selection_data->type != gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+ if (selection_data->type !=
+ gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
return NULL;
if (selection_data->length != sizeof (src_buffer))
@@ -3100,6 +3214,62 @@ restore_iter (const GtkTextIter *iter,
#endif
static void
+clipboard_rich_text_received (GtkClipboard *clipboard,
+ GdkAtom format,
+ const guint8 *text,
+ gsize length,
+ gpointer data)
+{
+ ClipboardRequest *request_data = data;
+ GtkTextIter insert_point;
+ gboolean retval = TRUE;
+ GError *error = NULL;
+ GtkTextBufferPrivate *priv;
+
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (request_data->buffer);
+
+ if (text != NULL && length > 0)
+ {
+ pre_paste_prep (request_data, &insert_point);
+
+ if (request_data->interactive)
+ gtk_text_buffer_begin_user_action (request_data->buffer);
+
+ if (!request_data->interactive ||
+ gtk_text_iter_can_insert (&insert_point,
+ request_data->default_editable))
+ {
+ retval = gtk_text_buffer_deserialize (request_data->buffer,
+ request_data->buffer,
+ format,
+ &insert_point,
+ text, length,
+ &error);
+ }
+
+ if (!retval)
+ {
+ g_warning ("error pasting: %s\n", error->message);
+ g_clear_error (&error);
+ }
+
+ if (request_data->interactive)
+ gtk_text_buffer_end_user_action (request_data->buffer);
+
+ if (retval)
+ {
+ post_paste_cleanup (request_data);
+ return;
+ }
+ }
+
+ /* Request the text selection instead */
+ gtk_clipboard_request_text (clipboard,
+ clipboard_text_received,
+ data);
+}
+
+static void
paste_from_buffer (ClipboardRequest *request_data,
GtkTextBuffer *src_buffer,
const GtkTextIter *start,
@@ -3145,7 +3315,8 @@ clipboard_clipboard_buffer_received (GtkClipboard *clipboard,
{
ClipboardRequest *request_data = data;
GtkTextBuffer *src_buffer;
-
+ GtkTextBufferPrivate *priv;
+
src_buffer = selection_data_get_buffer (selection_data, request_data);
if (src_buffer)
@@ -3155,7 +3326,7 @@ clipboard_clipboard_buffer_received (GtkClipboard *clipboard,
if (g_object_get_data (G_OBJECT (src_buffer), "gtk-text-buffer-clipboard"))
{
gtk_text_buffer_get_bounds (src_buffer, &start, &end);
-
+
paste_from_buffer (request_data, src_buffer,
&start, &end);
}
@@ -3168,21 +3339,27 @@ clipboard_clipboard_buffer_received (GtkClipboard *clipboard,
}
else
{
- /* Request the text selection instead */
- gtk_clipboard_request_text (clipboard,
- clipboard_text_received,
- data);
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (request_data->buffer);
+
+ if (gtk_clipboard_wait_is_rich_text_available (clipboard,
+ request_data->buffer))
+ {
+ /* Request rich text */
+ gtk_clipboard_request_rich_text (clipboard,
+ request_data->buffer,
+ clipboard_rich_text_received,
+ data);
+ }
+ else
+ {
+ /* Request the text selection instead */
+ gtk_clipboard_request_text (clipboard,
+ clipboard_text_received,
+ data);
+ }
}
}
-static const GtkTargetEntry targets[] = {
- { "STRING", 0, TARGET_STRING },
- { "TEXT", 0, TARGET_TEXT },
- { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
- { "UTF8_STRING", 0, TARGET_UTF8_STRING },
- { "GTK_TEXT_BUFFER_CONTENTS", 0, TARGET_TEXT_BUFFER_CONTENTS }
-};
-
typedef struct
{
GtkClipboard *clipboard;
@@ -3192,7 +3369,13 @@ typedef struct
static void
update_selection_clipboards (GtkTextBuffer *buffer)
{
- GSList *tmp_list = buffer->selection_clipboards;
+ GtkTextBufferPrivate *priv;
+ GSList *tmp_list = buffer->selection_clipboards;
+
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ gtk_text_buffer_get_copy_target_list (buffer);
+
while (tmp_list)
{
GtkTextIter start;
@@ -3214,7 +3397,9 @@ update_selection_clipboards (GtkTextBuffer *buffer)
/* Even if we already have the selection, we need to update our
* timestamp.
*/
- if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
+ if (!gtk_clipboard_set_with_owner (clipboard,
+ priv->copy_target_entries,
+ priv->n_copy_target_entries,
clipboard_get_selection_cb,
clipboard_clear_selection_cb,
G_OBJECT (buffer)))
@@ -3525,6 +3710,8 @@ cut_or_copy (GtkTextBuffer *buffer,
gboolean interactive,
gboolean default_editable)
{
+ GtkTextBufferPrivate *priv;
+
/* We prefer to cut the selected region between selection_bound and
* insertion point. If that region is empty, then we cut the region
* between the "anchor" and the insertion point (this is for
@@ -3534,7 +3721,11 @@ cut_or_copy (GtkTextBuffer *buffer,
*/
GtkTextIter start;
GtkTextIter end;
-
+
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ gtk_text_buffer_get_copy_target_list (buffer);
+
if (!gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
{
/* Let's try the anchor thing */
@@ -3560,14 +3751,18 @@ cut_or_copy (GtkTextBuffer *buffer,
gtk_text_buffer_insert_range (contents, &ins, &start, &end);
- if (!gtk_clipboard_set_with_data (clipboard, targets, G_N_ELEMENTS (targets),
+ if (!gtk_clipboard_set_with_data (clipboard,
+ priv->copy_target_entries,
+ priv->n_copy_target_entries,
clipboard_get_contents_cb,
clipboard_clear_contents_cb,
contents))
g_object_unref (contents);
else
- gtk_clipboard_set_can_store (clipboard, (GtkTargetEntry *)targets, G_N_ELEMENTS (targets) -1);
-
+ gtk_clipboard_set_can_store (clipboard,
+ priv->copy_target_entries + 1,
+ priv->n_copy_target_entries - 1);
+
if (delete_region_after)
{
if (interactive)
@@ -3699,6 +3894,126 @@ gtk_text_buffer_end_user_action (GtkTextBuffer *buffer)
}
}
+static void
+gtk_text_buffer_free_target_lists (GtkTextBuffer *buffer)
+{
+ GtkTextBufferPrivate *priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ if (priv->copy_target_list)
+ {
+ gtk_target_list_unref (priv->copy_target_list);
+ priv->copy_target_list = NULL;
+
+ gtk_target_table_free (priv->copy_target_entries,
+ priv->n_copy_target_entries);
+ priv->copy_target_entries = NULL;
+ priv->n_copy_target_entries = 0;
+ }
+
+ if (priv->paste_target_list)
+ {
+ gtk_target_list_unref (priv->paste_target_list);
+ priv->paste_target_list = NULL;
+
+ gtk_target_table_free (priv->paste_target_entries,
+ priv->n_paste_target_entries);
+ priv->paste_target_entries = NULL;
+ priv->n_paste_target_entries = 0;
+ }
+}
+
+static GtkTargetList *
+gtk_text_buffer_get_target_list (GtkTextBuffer *buffer,
+ gboolean deserializable,
+ GtkTargetEntry **entries,
+ gint *n_entries)
+{
+ GtkTargetList *target_list;
+
+ target_list = gtk_target_list_new (NULL, 0);
+
+ gtk_target_list_add (target_list,
+ gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+ GTK_TARGET_SAME_APP,
+ GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS);
+
+ gtk_target_list_add_rich_text_targets (target_list,
+ GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT,
+ deserializable,
+ buffer);
+
+ gtk_target_list_add_text_targets (target_list,
+ GTK_TEXT_BUFFER_TARGET_INFO_TEXT);
+
+ *entries = gtk_target_table_new_from_list (target_list, n_entries);
+
+ return target_list;
+}
+
+/**
+ * gtk_text_buffer_get_copy_target_list:
+ * @buffer: a #GtkTextBuffer
+ *
+ * This function returns the list of targets this text buffer can
+ * provide for copying and as DND source. The targets in the list are
+ * added with %info values from the #GtkTextBufferTargetInfo enum,
+ * using gtk_target_list_add_rich_text_targets() and
+ * gtk_target_list_add_text_targets()
+ *
+ * Return value: the #GtkTargetList
+ *
+ * Since: 2.10
+ **/
+GtkTargetList *
+gtk_text_buffer_get_copy_target_list (GtkTextBuffer *buffer)
+{
+ GtkTextBufferPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ if (! priv->copy_target_list)
+ priv->copy_target_list =
+ gtk_text_buffer_get_target_list (buffer, FALSE,
+ &priv->copy_target_entries,
+ &priv->n_copy_target_entries);
+
+ return priv->copy_target_list;
+}
+
+/**
+ * gtk_text_buffer_get_paste_target_list:
+ * @buffer: a #GtkTextBuffer
+ *
+ * This function returns the list of targets this text buffer supports
+ * for pasting and as DND destination. The targets in the list are
+ * added with %info values from the #GtkTextBufferTargetInfo enum,
+ * using gtk_target_list_add_rich_text_targets() and
+ * gtk_target_list_add_text_targets()
+ *
+ * Return value: the #GtkTargetList
+ *
+ * Since: 2.10
+ **/
+GtkTargetList *
+gtk_text_buffer_get_paste_target_list (GtkTextBuffer *buffer)
+{
+ GtkTextBufferPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+ priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ if (! priv->paste_target_list)
+ priv->paste_target_list =
+ gtk_text_buffer_get_target_list (buffer, TRUE,
+ &priv->paste_target_entries,
+ &priv->n_paste_target_entries);
+
+ return priv->paste_target_list;
+}
+
/*
* Logical attribute cache
*/
diff --git a/gtk/gtktextbuffer.h b/gtk/gtktextbuffer.h
index 04aac94f0a..7d69a600d5 100644
--- a/gtk/gtktextbuffer.h
+++ b/gtk/gtktextbuffer.h
@@ -41,6 +41,16 @@ G_BEGIN_DECLS
* GtkTextBTree is the PRIVATE internal representation of it.
*/
+/* these values are used as "info" for the targets contained in the
+ * lists returned by gtk_text_buffer_get_copy,paste_target_list()
+ */
+typedef enum
+{
+ GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS,
+ GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT,
+ GTK_TEXT_BUFFER_TARGET_INFO_TEXT
+} GtkTextBufferTargetInfo;
+
typedef struct _GtkTextBTree GtkTextBTree;
typedef struct _GtkTextLogAttrCache GtkTextLogAttrCache;
@@ -367,6 +377,9 @@ gboolean gtk_text_buffer_delete_selection (GtkTextBuffer *buffer,
void gtk_text_buffer_begin_user_action (GtkTextBuffer *buffer);
void gtk_text_buffer_end_user_action (GtkTextBuffer *buffer);
+GtkTargetList * gtk_text_buffer_get_copy_target_list (GtkTextBuffer *buffer);
+GtkTargetList * gtk_text_buffer_get_paste_target_list (GtkTextBuffer *buffer);
+
/* INTERNAL private stuff */
void _gtk_text_buffer_spew (GtkTextBuffer *buffer);
diff --git a/gtk/gtktextbufferrichtext.c b/gtk/gtktextbufferrichtext.c
new file mode 100644
index 0000000000..12cb00b3b8
--- /dev/null
+++ b/gtk/gtktextbufferrichtext.c
@@ -0,0 +1,704 @@
+/* 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"
diff --git a/gtk/gtktextbufferrichtext.h b/gtk/gtktextbufferrichtext.h
new file mode 100644
index 0000000000..96fc3c969b
--- /dev/null
+++ b/gtk/gtktextbufferrichtext.h
@@ -0,0 +1,92 @@
+/* gtkrichtext.h
+ *
+ * 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.
+ */
+
+#ifndef __GTK_TEXT_BUFFER_RICH_TEXT_H__
+#define __GTK_TEXT_BUFFER_RICH_TEXT_H__
+
+#include <gtk/gtktextbuffer.h>
+
+G_BEGIN_DECLS
+
+typedef guint8 * (* GtkTextBufferSerializeFunc) (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data);
+typedef gboolean (* GtkTextBufferDeserializeFunc) (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *data,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error);
+
+GdkAtom gtk_text_buffer_register_serialize_format (GtkTextBuffer *buffer,
+ const gchar *mime_type,
+ GtkTextBufferSerializeFunc function,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy);
+GdkAtom gtk_text_buffer_register_serialize_tagset (GtkTextBuffer *buffer,
+ const gchar *tagset_name);
+
+GdkAtom gtk_text_buffer_register_deserialize_format (GtkTextBuffer *buffer,
+ const gchar *mime_type,
+ GtkTextBufferDeserializeFunc function,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy);
+GdkAtom gtk_text_buffer_register_deserialize_tagset (GtkTextBuffer *buffer,
+ const gchar *tagset_name);
+
+void gtk_text_buffer_unregister_serialize_format (GtkTextBuffer *buffer,
+ GdkAtom format);
+void gtk_text_buffer_unregister_deserialize_format (GtkTextBuffer *buffer,
+ GdkAtom format);
+
+void gtk_text_buffer_deserialize_set_can_create_tags (GtkTextBuffer *buffer,
+ GdkAtom format,
+ gboolean can_create_tags);
+gboolean gtk_text_buffer_deserialize_get_can_create_tags (GtkTextBuffer *buffer,
+ GdkAtom format);
+
+GdkAtom * gtk_text_buffer_get_serialize_formats (GtkTextBuffer *buffer,
+ gint *n_formats);
+GdkAtom * gtk_text_buffer_get_deserialize_formats (GtkTextBuffer *buffer,
+ gint *n_formats);
+
+guint8 * gtk_text_buffer_serialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GdkAtom format,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length);
+gboolean gtk_text_buffer_deserialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GdkAtom format,
+ GtkTextIter *iter,
+ const guint8 *data,
+ gsize length,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GTK_TEXT_BUFFER_RICH_TEXT_H__ */
diff --git a/gtk/gtktextbufferserialize.c b/gtk/gtktextbufferserialize.c
new file mode 100644
index 0000000000..4a869d5c99
--- /dev/null
+++ b/gtk/gtktextbufferserialize.c
@@ -0,0 +1,1877 @@
+/* gtktextbufferserialize.c
+ *
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004 Nokia Corporation
+ *
+ * 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.
+ */
+
+/* FIXME: We should use other error codes for the
+ * parts that deal with the format errors
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "gdk-pixbuf/gdk-pixdata.h"
+#include "gtktextbufferserialize.h"
+
+#include "gtkintl.h"
+
+
+typedef struct
+{
+ GString *tag_table_str;
+ GString *text_str;
+ GHashTable *tags;
+ GtkTextIter start, end;
+
+ gint n_pixbufs;
+ GList *pixbufs;
+ gint tag_id;
+ GHashTable *tag_id_tags;
+} SerializationContext;
+
+static gchar *
+serialize_value (GValue *value)
+{
+ if (g_value_type_transformable (value->g_type, G_TYPE_STRING))
+ {
+ GValue text_value = { 0 };
+ gchar *tmp;
+
+ g_value_init (&text_value, G_TYPE_STRING);
+ g_value_transform (value, &text_value);
+
+ tmp = g_markup_escape_text (g_value_get_string (&text_value), -1);
+ g_value_unset (&text_value);
+
+ return tmp;
+ }
+ else if (value->g_type == GDK_TYPE_COLOR)
+ {
+ GdkColor *color = g_value_get_boxed (value);
+
+ return g_strdup_printf ("%x:%x:%x", color->red, color->green, color->blue);
+ }
+ else if (g_type_is_a (value->g_type, GDK_TYPE_DRAWABLE))
+ {
+ /* Don't do anything */
+ }
+ else
+ {
+ g_warning ("Type %s is not serializable\n", g_type_name (value->g_type));
+ }
+
+ return NULL;
+}
+
+static gboolean
+deserialize_value (const gchar *str,
+ GValue *value)
+{
+ if (g_value_type_transformable (G_TYPE_STRING, value->g_type))
+ {
+ GValue text_value = { 0 };
+ gboolean retval;
+
+ g_value_init (&text_value, G_TYPE_STRING);
+ g_value_set_static_string (&text_value, str);
+
+ retval = g_value_transform (&text_value, value);
+ g_value_unset (&text_value);
+
+ return retval;
+ }
+ else if (value->g_type == G_TYPE_BOOLEAN)
+ {
+ gboolean v;
+
+ v = strcmp (str, "TRUE") == 0;
+
+ g_value_set_boolean (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == G_TYPE_INT)
+ {
+ gchar *tmp;
+ int v;
+
+ v = strtol (str, &tmp, 10);
+
+ if (tmp == NULL || tmp == str)
+ return FALSE;
+
+ g_value_set_int (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == G_TYPE_DOUBLE)
+ {
+ gchar *tmp;
+ gdouble v;
+
+ v = g_ascii_strtod (str, &tmp);
+
+ if (tmp == NULL || tmp == str)
+ return FALSE;
+
+ g_value_set_double (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == GDK_TYPE_COLOR)
+ {
+ GdkColor color;
+ const gchar *old;
+ gchar *tmp;
+
+ old = str;
+ color.red = strtol (old, &tmp, 16);
+
+ if (tmp == NULL || tmp == old)
+ return FALSE;
+
+ old = tmp;
+ if (*old++ != ':')
+ return FALSE;
+
+ color.green = strtol (old, &tmp, 16);
+ if (tmp == NULL || tmp == old)
+ return FALSE;
+
+ old = tmp;
+ if (*old++ != ':')
+ return FALSE;
+
+ color.blue = strtol (old, &tmp, 16);
+
+ if (tmp == NULL || tmp == old || *tmp != '\0')
+ return FALSE;
+
+ g_value_set_boxed (value, &color);
+
+ return TRUE;
+ }
+ else if (G_VALUE_HOLDS_ENUM (value))
+ {
+ GEnumClass *class = G_ENUM_CLASS (g_type_class_peek (value->g_type));
+ GEnumValue *enum_value;
+
+ enum_value = g_enum_get_value_by_name (class, str);
+
+ if (enum_value)
+ {
+ g_value_set_enum (value, enum_value->value);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ g_warning ("Type %s can not be deserialized\n", g_type_name (value->g_type));
+ }
+
+ return FALSE;
+}
+
+/* Checks if a param is set, or if it's the default value */
+static gboolean
+is_param_set (GObject *object,
+ GParamSpec *pspec,
+ GValue *value)
+{
+ /* We need to special case some attributes here */
+ if (strcmp (pspec->name, "background-gdk") == 0)
+ {
+ gboolean is_set;
+
+ g_object_get (object, "background-set", &is_set, NULL);
+
+ if (is_set)
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else if (strcmp (pspec->name, "foreground-gdk") == 0)
+ {
+ gboolean is_set;
+
+ g_object_get (object, "foreground-set", &is_set, NULL);
+
+ if (is_set)
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ gboolean is_set;
+ gchar *is_set_name;
+
+ is_set_name = g_strdup_printf ("%s-set", pspec->name);
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), is_set_name) == NULL)
+ {
+ g_free (is_set_name);
+ return FALSE;
+ }
+ else
+ {
+ g_object_get (object, is_set_name, &is_set, NULL);
+
+ if (!is_set)
+ {
+ g_free (is_set_name);
+ return FALSE;
+ }
+
+ g_free (is_set_name);
+
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ if (g_param_value_defaults (pspec, value))
+ {
+ g_value_unset (value);
+
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+}
+
+static void
+serialize_tag (gpointer key,
+ gpointer data,
+ gpointer user_data)
+{
+ SerializationContext *context = user_data;
+ GtkTextTag *tag = data;
+ gchar *tag_name;
+ gint tag_id;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ int i;
+
+ g_string_append (context->tag_table_str, " <tag ");
+
+ /* Handle anonymous tags */
+ if (tag->name)
+ {
+ tag_name = g_markup_escape_text (tag->name, -1);
+ g_string_append_printf (context->tag_table_str, "name=\"%s\"", tag_name);
+ g_free (tag_name);
+ }
+ else
+ {
+ tag_id = GPOINTER_TO_INT (g_hash_table_lookup (context->tag_id_tags, tag));
+
+ g_string_append_printf (context->tag_table_str, "id=\"%d\"", tag_id);
+ }
+
+ g_string_append_printf (context->tag_table_str, " priority=\"%d\">\n", tag->priority);
+
+ /* Serialize properties */
+ pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (tag), &n_pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ GValue value = { 0 };
+ gchar *tmp, *tmp2;
+
+ if (!(pspecs[i]->flags & G_PARAM_READABLE) ||
+ !(pspecs[i]->flags & G_PARAM_WRITABLE))
+ continue;
+
+ if (!is_param_set (G_OBJECT (tag), pspecs[i], &value))
+ continue;
+
+ /* Now serialize the attr */
+ tmp2 = serialize_value (&value);
+
+ if (tmp2)
+ {
+ tmp = g_markup_escape_text (pspecs[i]->name, -1);
+ g_string_append_printf (context->tag_table_str, " <attr name=\"%s\" ", tmp);
+ g_free (tmp);
+
+ tmp = g_markup_escape_text (g_type_name (pspecs[i]->value_type), -1);
+ g_string_append_printf (context->tag_table_str, "type=\"%s\" value=\"%s\" />\n", tmp, tmp2);
+
+ g_free (tmp);
+ g_free (tmp2);
+ }
+
+ g_value_unset (&value);
+ }
+
+ g_free (pspecs);
+
+ g_string_append (context->tag_table_str, " </tag>\n");
+}
+
+static void
+serialize_tags (SerializationContext *context)
+{
+ g_string_append (context->tag_table_str, " <text_view_markup>\n");
+ g_string_append (context->tag_table_str, " <tags>\n");
+ g_hash_table_foreach (context->tags, serialize_tag, context);
+ g_string_append (context->tag_table_str, " </tags>\n");
+}
+
+#if 0
+static void
+dump_tag_list (const gchar *str,
+ GList *list)
+{
+ g_print ("%s: ", str);
+
+ if (!list)
+ g_print ("(empty)");
+ else
+ {
+ while (list)
+ {
+ g_print ("%s ", ((GtkTextTag *)list->data)->name);
+ list = list->next;
+ }
+ }
+
+ g_print ("\n");
+}
+#endif
+
+static void
+find_list_delta (GSList *old_list,
+ GSList *new_list,
+ GList **added,
+ GList **removed)
+{
+ GSList *tmp;
+ GList *tmp_added, *tmp_removed;
+
+ tmp_added = NULL;
+ tmp_removed = NULL;
+
+ /* Find added tags */
+ tmp = new_list;
+ while (tmp)
+ {
+ if (!g_slist_find (old_list, tmp->data))
+ tmp_added = g_list_prepend (tmp_added, tmp->data);
+
+ tmp = tmp->next;
+ }
+
+ *added = tmp_added;
+
+ /* Find removed tags */
+ tmp = old_list;
+ while (tmp)
+ {
+ if (!g_slist_find (new_list, tmp->data))
+ tmp_removed = g_list_prepend (tmp_removed, tmp->data);
+
+ tmp = tmp->next;
+ }
+
+ /* We reverse the list here to match the xml semantics */
+ *removed = g_list_reverse (tmp_removed);
+}
+
+static void
+serialize_section_header (GString *str,
+ const gchar *name,
+ gint length)
+{
+ g_return_if_fail (strlen (name) == 26);
+
+ g_string_append (str, name);
+
+ g_string_append_c (str, length >> 24);
+
+ g_string_append_c (str, (length >> 16) & 0xff);
+ g_string_append_c (str, (length >> 8) & 0xff);
+ g_string_append_c (str, length & 0xff);
+}
+
+static void
+serialize_text (GtkTextBuffer *buffer,
+ SerializationContext *context)
+{
+ GtkTextIter iter, old_iter;
+ GSList *tag_list, *new_tag_list;
+ GQueue *active_tags;
+ int i;
+
+ g_string_append (context->text_str, "<text>");
+
+ iter = context->start;
+ tag_list = NULL;
+ active_tags = g_queue_new ();
+
+ do
+ {
+ GList *added, *removed;
+ GList *tmp;
+ gchar *tmp_text, *escaped_text;
+
+ new_tag_list = gtk_text_iter_get_tags (&iter);
+ find_list_delta (tag_list, new_tag_list, &added, &removed);
+
+ /* Handle removed tags */
+ tmp = removed;
+ while (tmp)
+ {
+ GtkTextTag *tag = tmp->data;
+
+ g_string_append (context->text_str, "</apply_tag>");
+
+ /* We might need to drop some of the tags and re-add them afterwards */
+ while (g_queue_peek_head (active_tags) != tag &&
+ !g_queue_is_empty (active_tags))
+ {
+ added = g_list_prepend (added, g_queue_pop_head (active_tags));
+ g_string_append_printf (context->text_str, "</apply_tag>");
+ }
+
+ g_queue_pop_head (active_tags);
+
+ tmp = tmp->next;
+ }
+
+ /* Handle added tags */
+ tmp = added;
+ while (tmp)
+ {
+ GtkTextTag *tag = tmp->data;
+ gchar *tag_name;
+
+ /* Add it to the tag hash table */
+ g_hash_table_insert (context->tags, tag, tag);
+
+ if (tag->name)
+ {
+ tag_name = g_markup_escape_text (tag->name, -1);
+
+ g_string_append_printf (context->text_str, "<apply_tag name=\"%s\">", tag_name);
+ g_free (tag_name);
+ }
+ else
+ {
+ gpointer tag_id;
+
+ /* We've got an anonymous tag, find out if it's been
+ used before */
+ if (!g_hash_table_lookup_extended (context->tag_id_tags, tag, NULL, &tag_id))
+ {
+ tag_id = GINT_TO_POINTER (context->tag_id++);
+
+ g_hash_table_insert (context->tag_id_tags, tag, tag_id);
+ }
+
+ g_string_append_printf (context->text_str, "<apply_tag id=\"%d\">", GPOINTER_TO_INT (tag_id));
+ }
+ g_queue_push_head (active_tags, tag);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (tag_list);
+ tag_list = new_tag_list;
+
+ old_iter = iter;
+
+ /* Now try to go to either the next tag toggle, or if a pixbuf appears */
+ while (TRUE)
+ {
+ gunichar ch = gtk_text_iter_get_char (&iter);
+
+ if (ch == 0xFFFC)
+ {
+ GdkPixbuf *pixbuf = gtk_text_iter_get_pixbuf (&iter);
+
+ if (pixbuf)
+ {
+ /* Append the text before the pixbuf */
+ tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+ escaped_text = g_markup_escape_text (tmp_text, -1);
+ g_free (tmp_text);
+
+ /* Forward so we don't get the 0xfffc char */
+ gtk_text_iter_forward_char (&iter);
+ old_iter = iter;
+
+ g_string_append (context->text_str, escaped_text);
+ g_free (escaped_text);
+
+ g_string_append_printf (context->text_str, "<pixbuf index=\"%d\" />", context->n_pixbufs);
+
+ context->n_pixbufs++;
+ context->pixbufs = g_list_prepend (context->pixbufs, pixbuf);
+ }
+ }
+ else if (ch == 0)
+ {
+ break;
+ }
+ else
+ gtk_text_iter_forward_char (&iter);
+
+ if (gtk_text_iter_toggles_tag (&iter, NULL))
+ break;
+ }
+
+ /* We might have moved too far */
+ if (gtk_text_iter_compare (&iter, &context->end) > 0)
+ iter = context->end;
+
+ /* Append the text */
+ tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+ escaped_text = g_markup_escape_text (tmp_text, -1);
+ g_free (tmp_text);
+
+ g_string_append (context->text_str, escaped_text);
+ g_free (escaped_text);
+ }
+ while (!gtk_text_iter_equal (&iter, &context->end));
+
+ /* Close any open tags */
+ for (i = 0; i < g_queue_get_length (active_tags); i++) {
+ g_string_append (context->text_str, "</apply_tag>");
+ }
+ g_queue_free (active_tags);
+ g_string_append (context->text_str, "</text>\n</text_view_markup>\n");
+}
+
+static void
+serialize_pixbufs (SerializationContext *context,
+ GString *text)
+{
+ GList *list;
+
+ for (list = context->pixbufs; list != NULL; list = list->next)
+ {
+ GdkPixbuf *pixbuf = list->data;
+ GdkPixdata pixdata;
+ guint8 *tmp;
+ guint len;
+
+ gdk_pixdata_from_pixbuf (&pixdata, pixbuf, FALSE);
+ tmp = gdk_pixdata_serialize (&pixdata, &len);
+
+ serialize_section_header (text, "GTKTEXTBUFFERPIXBDATA-0001", len);
+ g_string_append_len (text, (gchar *) tmp, len);
+ g_free (tmp);
+ }
+}
+
+guint8 *
+_gtk_text_buffer_serialize_rich_text (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data)
+{
+ SerializationContext context;
+ GString *text;
+
+ context.tags = g_hash_table_new (NULL, NULL);
+ context.text_str = g_string_new (NULL);
+ context.tag_table_str = g_string_new (NULL);
+ context.start = *start;
+ context.end = *end;
+ context.n_pixbufs = 0;
+ context.pixbufs = NULL;
+ context.tag_id = 0;
+ context.tag_id_tags = g_hash_table_new (NULL, NULL);
+
+ /* We need to serialize the text before the tag table so we know
+ what tags are used */
+ serialize_text (content_buffer, &context);
+ serialize_tags (&context);
+
+ text = g_string_new (NULL);
+ serialize_section_header (text, "GTKTEXTBUFFERCONTENTS-0001",
+ context.tag_table_str->len + context.text_str->len);
+
+ g_string_append_len (text, context.tag_table_str->str, context.tag_table_str->len);
+ g_string_append_len (text, context.text_str->str, context.text_str->len);
+
+ context.pixbufs = g_list_reverse (context.pixbufs);
+ serialize_pixbufs (&context, text);
+
+ g_hash_table_destroy (context.tags);
+ g_list_free (context.pixbufs);
+ g_string_free (context.text_str, TRUE);
+ g_string_free (context.tag_table_str, TRUE);
+ g_hash_table_destroy (context.tag_id_tags);
+
+ *length = text->len;
+
+ return (guint8 *) g_string_free (text, FALSE);
+}
+
+typedef enum
+{
+ STATE_START,
+ STATE_TEXT_VIEW_MARKUP,
+ STATE_TAGS,
+ STATE_TAG,
+ STATE_ATTR,
+ STATE_TEXT,
+ STATE_APPLY_TAG,
+ STATE_PIXBUF
+} ParseState;
+
+typedef struct
+{
+ gchar *text;
+ GdkPixbuf *pixbuf;
+ GSList *tags;
+} TextSpan;
+
+typedef struct
+{
+ GtkTextTag *tag;
+ gint prio;
+} TextTagPrio;
+
+typedef struct
+{
+ GSList *states;
+
+ GList *headers;
+
+ GtkTextBuffer *buffer;
+
+ /* Tags that are defined in <tag> elements */
+ GHashTable *defined_tags;
+
+ /* Tags that are anonymous */
+ GHashTable *anonymous_tags;
+
+ /* Tag name substitutions */
+ GHashTable *substitutions;
+
+ /* Current tag */
+ GtkTextTag *current_tag;
+
+ /* Priority of current tag */
+ gint current_tag_prio;
+
+ /* Id of current tag */
+ gint current_tag_id;
+
+ /* Tags and their priorities */
+ GList *tag_priorities;
+
+ GSList *tag_stack;
+
+ GList *spans;
+
+ gboolean create_tags;
+
+ gboolean parsed_text;
+ gboolean parsed_tags;
+} ParseInfo;
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ ("Line %d character %d: %s"),
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+
+static gboolean
+check_id_or_name (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gint *id,
+ const gchar **name,
+ GError **error)
+{
+ gboolean has_id = FALSE;
+ gboolean has_name = FALSE;
+ int i;
+
+ *id = 0;
+ *name = NULL;
+
+ for (i = 0; attribute_names[i] != NULL; i++)
+ {
+ if (strcmp (attribute_names[i], "name") == 0)
+ {
+ *name = attribute_values[i];
+
+ if (has_id)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Both \"id\" and \"name\" were found on the <%s> element"),
+ element_name);
+ return FALSE;
+ }
+
+ if (has_name)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("The attribute \"name\" were found twice on the <%s> element"),
+ element_name);
+ return FALSE;
+ }
+
+ has_name = TRUE;
+ }
+ else if (strcmp (attribute_names[i], "id") == 0)
+ {
+ gchar *tmp;
+
+ if (has_name)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Both \"id\" and \"name\" were found on the <%s> element"),
+ element_name);
+ return FALSE;
+ }
+
+ if (has_id)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("The attribute \"id\" were found twice on the <%s> element"),
+ element_name);
+ return FALSE;
+ }
+
+ has_id = TRUE;
+
+ /* Try parsing the integer */
+ *id = strtol (attribute_values[i], &tmp, 10);
+
+ if (tmp == NULL || tmp == attribute_values[i])
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> element has invalid id \"%s\""), attribute_values[i]);
+ return FALSE;
+ }
+ }
+ }
+
+ if (!has_id && !has_name)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> element neither a \"name\" nor an \"id\" element"), element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gboolean allow_unknown_attrs,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+ va_list args;
+ const char *name;
+ const char **retloc;
+ int n_attrs;
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+ }
+
+ va_end (args);
+
+ if (!retval)
+ return retval;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ int j;
+ gboolean found;
+
+ found = FALSE;
+ j = 0;
+ while (j < n_attrs)
+ {
+ if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+ {
+ retloc = attrs[j].retloc;
+
+ if (*retloc != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" repeated twice on the same <%s> element"),
+ attrs[j].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ *retloc = attribute_values[i];
+ found = TRUE;
+ }
+
+ ++j;
+ }
+
+ if (!found && !allow_unknown_attrs)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[i], element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ out:
+ return retval;
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GtkTextTag *
+tag_exists (GMarkupParseContext *context,
+ const gchar *name,
+ gint id,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *real_name;
+
+ if (info->create_tags)
+ {
+ /* If we have an anonymous tag, just return it directly */
+ if (!name)
+ return g_hash_table_lookup (info->anonymous_tags,
+ GINT_TO_POINTER (id));
+
+ /* First, try the substitutions */
+ real_name = g_hash_table_lookup (info->substitutions, name);
+
+ if (real_name)
+ return gtk_text_tag_table_lookup (info->buffer->tag_table, real_name);
+
+ /* Next, try the list of defined tags */
+ if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+ return gtk_text_tag_table_lookup (info->buffer->tag_table, name);
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" has not been defined."), name);
+
+ return NULL;
+ }
+ else
+ {
+ GtkTextTag *tag;
+
+ if (!name)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Anonymous tag found and tags can not be created."));
+ return NULL;
+ }
+
+ tag = gtk_text_tag_table_lookup (info->buffer->tag_table, name);
+
+ if (tag)
+ return tag;
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" does not exist in buffer and tags can not be created."), name);
+
+ return NULL;
+ }
+}
+
+typedef struct
+{
+ const gchar *id;
+ gint length;
+ const gchar *start;
+} Header;
+
+static GdkPixbuf *
+get_pixbuf_from_headers (GList *headers,
+ int id,
+ GError **error)
+{
+ Header *header;
+ GdkPixdata pixdata;
+ GdkPixbuf *pixbuf;
+
+ header = g_list_nth_data (headers, id);
+
+ if (!header)
+ return NULL;
+
+ if (!gdk_pixdata_deserialize (&pixdata, header->length,
+ (const guint8 *) header->start, error))
+ return NULL;
+
+ pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, error);
+
+ return pixbuf;
+}
+
+static void
+parse_apply_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *priority;
+ gint id;
+ GtkTextTag *tag;
+
+ g_assert (peek_state (info) == STATE_TEXT ||
+ peek_state (info) == STATE_APPLY_TAG);
+
+ if (ELEMENT_IS ("apply_tag"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
+ "priority", &priority, NULL))
+ return;
+
+ if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
+ &id, &name, error))
+ return;
+
+
+ tag = tag_exists (context, name, id, info, error);
+
+ if (!tag)
+ return;
+
+ info->tag_stack = g_slist_prepend (info->tag_stack, tag);
+
+ push_state (info, STATE_APPLY_TAG);
+ }
+ else if (ELEMENT_IS ("pixbuf"))
+ {
+ int int_id;
+ GdkPixbuf *pixbuf;
+ TextSpan *span;
+ const gchar *pixbuf_id;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
+ "index", &pixbuf_id, NULL))
+ return;
+
+ int_id = atoi (pixbuf_id);
+ pixbuf = get_pixbuf_from_headers (info->headers, int_id, error);
+
+ span = g_new0 (TextSpan, 1);
+ span->pixbuf = pixbuf;
+ span->tags = NULL;
+
+ info->spans = g_list_prepend (info->spans, span);
+
+ if (!pixbuf)
+ return;
+
+ push_state (info, STATE_PIXBUF);
+ }
+ else
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, peek_state(info) == STATE_TEXT ? "text" : "apply_tag");
+}
+
+static void
+parse_attr_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *type, *value;
+ GType gtype;
+ GValue gvalue = { 0 };
+ GParamSpec *pspec;
+
+ g_assert (peek_state (info) == STATE_TAG);
+
+ if (ELEMENT_IS ("attr"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
+ "name", &name, "type", &type, "value", &value, NULL))
+ return;
+
+ gtype = g_type_from_name (type);
+
+ if (gtype == G_TYPE_INVALID)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid attribute type"), type);
+ return;
+ }
+
+ if (!(pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info->current_tag), name)))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid attribute name"), name);
+ return;
+ }
+
+ g_value_init (&gvalue, gtype);
+
+ if (!deserialize_value (value, &gvalue))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" could not be converted to a value of type \"%s\" for attribute \"%s\""),
+ value, type, name);
+ return;
+ }
+
+ if (g_param_value_validate (pspec, &gvalue))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value of for attribute \"%s\""),
+ value, name);
+ g_value_unset (&gvalue);
+ return;
+ }
+
+ g_object_set_property (G_OBJECT (info->current_tag),
+ name, &gvalue);
+
+ g_value_unset (&gvalue);
+
+ push_state (info, STATE_ATTR);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "tag");
+ }
+}
+
+
+static gchar *
+get_tag_name (ParseInfo *info,
+ const gchar *tag_name)
+{
+ gchar *name;
+ gint i;
+
+ name = g_strdup (tag_name);
+
+ if (!info->create_tags)
+ return name;
+
+ i = 0;
+
+ while (gtk_text_tag_table_lookup (info->buffer->tag_table, name) != NULL)
+ {
+ g_free (name);
+ name = g_strdup_printf ("%s-%d", tag_name, ++i);
+ }
+
+ if (i != 0)
+ {
+ g_hash_table_insert (info->substitutions, g_strdup (tag_name), g_strdup (name));
+ }
+
+ return name;
+}
+
+static void
+parse_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *priority;
+ gchar *tag_name;
+ gint id;
+ gint prio;
+ gchar *tmp;
+
+ g_assert (peek_state (info) == STATE_TAGS);
+
+ if (ELEMENT_IS ("tag"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
+ "priority", &priority, NULL))
+ return;
+
+ if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
+ &id, &name, error))
+ return;
+
+ if (name)
+ {
+ if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" already defined"), name);
+ return;
+ }
+ }
+
+ prio = strtol (priority, &tmp, 10);
+
+ if (tmp == NULL || tmp == priority)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" has invalid priority \"%s\""), name, priority);
+ return;
+ }
+
+ if (name)
+ {
+ tag_name = get_tag_name (info, name);
+ info->current_tag = gtk_text_tag_new (tag_name);
+ g_free (tag_name);
+ }
+ else
+ {
+ info->current_tag = gtk_text_tag_new (NULL);
+ info->current_tag_id = id;
+ }
+
+ info->current_tag_prio = prio;
+
+ push_state (info, STATE_TAG);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "tags");
+ }
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ if (ELEMENT_IS ("text_view_markup"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TEXT_VIEW_MARKUP);
+ break;
+ }
+ else
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Outermost element in text must be <text_view_markup> not <%s>"),
+ element_name);
+ break;
+ case STATE_TEXT_VIEW_MARKUP:
+ if (ELEMENT_IS ("tags"))
+ {
+ if (info->parsed_tags)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <tags> element has already been specified"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TAGS);
+ break;
+ }
+ else if (ELEMENT_IS ("text"))
+ {
+ if (info->parsed_text)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <text> element has already been specified"));
+ return;
+ }
+ else if (!info->parsed_tags)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <text> element can't occur before a <tags> element"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TEXT);
+ break;
+ }
+ else
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "text_view_markup");
+ break;
+ case STATE_TAGS:
+ parse_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_TAG:
+ parse_attr_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_TEXT:
+ case STATE_APPLY_TAG:
+ parse_apply_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gint
+sort_tag_prio (TextTagPrio *a,
+ TextTagPrio *b)
+{
+ if (a->prio < b->prio)
+ return -1;
+ else if (a->prio > b->prio)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+ gchar *tmp;
+ GList *list;
+
+ switch (peek_state (info))
+ {
+ case STATE_TAGS:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+ info->parsed_tags = TRUE;
+
+ /* Sort list and add the tags */
+ info->tag_priorities = g_list_sort (info->tag_priorities,
+ (GCompareFunc)sort_tag_prio);
+ list = info->tag_priorities;
+ while (list)
+ {
+ TextTagPrio *prio = list->data;
+
+ if (info->create_tags)
+ gtk_text_tag_table_add (info->buffer->tag_table, prio->tag);
+
+ g_object_unref (prio->tag);
+ prio->tag = NULL;
+
+ list = list->next;
+ }
+
+ break;
+ case STATE_TAG:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TAGS);
+
+ if (info->current_tag->name)
+ {
+ /* Add tag to defined tags hash */
+ tmp = g_strdup (info->current_tag->name);
+ g_hash_table_insert (info->defined_tags,
+ tmp, tmp);
+ }
+ else
+ {
+ g_hash_table_insert (info->anonymous_tags,
+ GINT_TO_POINTER (info->current_tag_id),
+ info->current_tag);
+ }
+
+ if (info->create_tags)
+ {
+ TextTagPrio *prio;
+
+ /* add the tag to the list */
+ prio = g_new0 (TextTagPrio, 1);
+ prio->prio = info->current_tag_prio;
+ prio->tag = info->current_tag;
+
+ info->tag_priorities = g_list_prepend (info->tag_priorities, prio);
+ }
+
+ info->current_tag = NULL;
+ break;
+ case STATE_ATTR:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TAG);
+ break;
+ case STATE_APPLY_TAG:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_APPLY_TAG ||
+ peek_state (info) == STATE_TEXT);
+
+ /* Pop tag */
+ info->tag_stack = g_slist_delete_link (info->tag_stack,
+ info->tag_stack);
+
+ break;
+ case STATE_TEXT:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+ info->spans = g_list_reverse (info->spans);
+ info->parsed_text = TRUE;
+ break;
+ case STATE_TEXT_VIEW_MARKUP:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_START);
+ break;
+ case STATE_PIXBUF:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_APPLY_TAG ||
+ peek_state (info) == STATE_TEXT);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+all_whitespace (const char *text,
+ int text_len)
+{
+ const char *p;
+ const char *end;
+
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ if (!g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+ TextSpan *span;
+
+ if (all_whitespace (text, text_len) &&
+ peek_state (info) != STATE_TEXT &&
+ peek_state (info) != STATE_APPLY_TAG)
+ return;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ g_assert_not_reached (); /* gmarkup shouldn't do this */
+ break;
+ case STATE_TEXT:
+ case STATE_APPLY_TAG:
+ if (text_len == 0)
+ return;
+
+ span = g_new0 (TextSpan, 1);
+ span->text = g_strndup (text, text_len);
+ span->tags = g_slist_copy (info->tag_stack);
+
+ info->spans = g_list_prepend (info->spans, span);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+parse_info_init (ParseInfo *info,
+ GtkTextBuffer *buffer,
+ gboolean create_tags,
+ GList *headers)
+{
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+
+ info->create_tags = create_tags;
+ info->headers = headers;
+ info->defined_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ info->substitutions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ info->anonymous_tags = g_hash_table_new_full (NULL, NULL, NULL, NULL);
+ info->tag_stack = NULL;
+ info->spans = NULL;
+ info->parsed_text = FALSE;
+ info->parsed_tags = FALSE;
+ info->current_tag = NULL;
+ info->current_tag_prio = -1;
+ info->tag_priorities = NULL;
+
+ info->buffer = buffer;
+}
+
+static void
+text_span_free (TextSpan *span)
+{
+ g_free (span->text);
+ g_slist_free (span->tags);
+ g_free (span);
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ GSList *slist;
+ GList *list;
+
+ slist = info->tag_stack;
+ while (slist)
+ {
+ g_free (slist->data);
+
+ slist = slist->next;
+ }
+
+ g_slist_free (info->tag_stack);
+ g_slist_free (info->states);
+
+ g_hash_table_destroy (info->substitutions);
+ g_hash_table_destroy (info->defined_tags);
+
+ if (info->current_tag)
+ g_object_unref (info->current_tag);
+
+ list = info->spans;
+ while (list)
+ {
+ text_span_free (list->data);
+
+ list = list->next;
+ }
+ g_list_free (info->spans);
+
+ list = info->tag_priorities;
+ while (list)
+ {
+ TextTagPrio *prio = list->data;
+
+ if (prio->tag)
+ g_object_unref (prio->tag);
+ g_free (prio);
+
+ list = list->next;
+ }
+ g_list_free (info->tag_priorities);
+
+}
+
+static void
+insert_text (ParseInfo *info,
+ GtkTextIter *iter)
+{
+ GtkTextIter start_iter;
+ GtkTextMark *mark;
+ GList *tmp;
+ GSList *tags;
+
+ start_iter = *iter;
+
+ mark = gtk_text_buffer_create_mark (info->buffer, "deserialize_insert_point",
+ &start_iter, TRUE);
+
+ tmp = info->spans;
+ while (tmp)
+ {
+ TextSpan *span = tmp->data;
+
+ if (span->text)
+ gtk_text_buffer_insert (info->buffer, iter, span->text, -1);
+ else
+ {
+ gtk_text_buffer_insert_pixbuf (info->buffer, iter, span->pixbuf);
+ g_object_unref (span->pixbuf);
+ }
+ gtk_text_buffer_get_iter_at_mark (info->buffer, &start_iter, mark);
+
+ /* Apply tags */
+ tags = span->tags;
+ while (tags)
+ {
+ GtkTextTag *tag = tags->data;
+
+ gtk_text_buffer_apply_tag (info->buffer, tag,
+ &start_iter, iter);
+
+ tags = tags->next;
+ }
+
+ gtk_text_buffer_move_mark (info->buffer, mark, iter);
+
+ tmp = tmp->next;
+ }
+
+ gtk_text_buffer_delete_mark (info->buffer, mark);
+}
+
+
+
+static int
+read_int (const guchar *start)
+{
+ int result;
+
+ result =
+ start[0] << 24 |
+ start[1] << 16 |
+ start[2] << 8 |
+ start[3];
+
+ return result;
+}
+
+static gboolean
+header_is (Header *header,
+ const gchar *id)
+{
+ return (strncmp (header->id, id, strlen (id)) == 0);
+}
+
+static GList *
+read_headers (const gchar *start,
+ gint len,
+ GError **error)
+{
+ int i = 0;
+ int section_len;
+ Header *header;
+ GList *headers = NULL;
+
+ while (i < len)
+ {
+ if (i + 30 >= len)
+ goto error;
+
+ if (strncmp (start + i, "GTKTEXTBUFFERCONTENTS-0001", 26) == 0 ||
+ strncmp (start + i, "GTKTEXTBUFFERPIXBDATA-0001", 26) == 0)
+ {
+ section_len = read_int ((const guchar *) start + i + 26);
+
+ if (i + 30 + section_len > len)
+ goto error;
+
+ header = g_new0 (Header, 1);
+ header->id = start + i;
+ header->length = section_len;
+ header->start = start + i + 30;
+
+ i += 30 + section_len;
+
+ headers = g_list_prepend (headers, header);
+ }
+ else
+ break;
+ }
+
+ return g_list_reverse (headers);
+
+ error:
+ g_list_foreach (headers, (GFunc) g_free, NULL);
+ g_list_free (headers);
+
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Serialized data is malformed"));
+
+ return NULL;
+}
+
+static gboolean
+deserialize_text (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ const gchar *text,
+ gint len,
+ gboolean create_tags,
+ GError **error,
+ GList *headers)
+{
+ GMarkupParseContext *context;
+ ParseInfo info;
+ gboolean retval = FALSE;
+
+
+ static GMarkupParser rich_text_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+ };
+
+ parse_info_init (&info, buffer, create_tags, headers);
+
+ context = g_markup_parse_context_new (&rich_text_parser,
+ 0, &info, NULL);
+
+ if (!g_markup_parse_context_parse (context,
+ text,
+ len,
+ error))
+ goto out;
+
+ if (!g_markup_parse_context_end_parse (context, error))
+ goto out;
+
+ retval = TRUE;
+
+ /* Now insert the text */
+ insert_text (&info, iter);
+
+ out:
+ parse_info_free (&info);
+
+ g_markup_parse_context_free (context);
+
+ return retval;
+}
+
+gboolean
+_gtk_text_buffer_deserialize_rich_text (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *text,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error)
+{
+ GList *headers;
+ Header *header;
+ gboolean retval;
+
+ headers = read_headers ((gchar *) text, length, error);
+
+ if (!headers)
+ return FALSE;
+
+ header = headers->data;
+ if (!header_is (header, "GTKTEXTBUFFERCONTENTS-0001"))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Serialized data is malformed. First section isn't GTKTEXTBUFFERCONTENTS-0001"));
+
+ retval = FALSE;
+ goto out;
+ }
+
+ retval = deserialize_text (content_buffer, iter,
+ header->start, header->length,
+ create_tags, error, headers->next);
+
+ out:
+ g_list_foreach (headers, (GFunc)g_free, NULL);
+ g_list_free (headers);
+
+ return retval;
+}
diff --git a/gtk/gtktextbufferserialize.h b/gtk/gtktextbufferserialize.h
new file mode 100644
index 0000000000..63a749e662
--- /dev/null
+++ b/gtk/gtktextbufferserialize.h
@@ -0,0 +1,43 @@
+/* gtktextbufferserialize.h
+ *
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * 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.
+ */
+
+#ifndef __GTK_TEXT_BUFFER_SERIALIZE_H__
+#define __GTK_TEXT_BUFFER_SERIALIZE_H__
+
+#include <gtk/gtktextbuffer.h>
+
+guint8 * _gtk_text_buffer_serialize_rich_text (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data);
+
+gboolean _gtk_text_buffer_deserialize_rich_text (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *data,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error);
+
+
+#endif /* __GTK_TEXT_BUFFER_SERIALIZE_H__ */
diff --git a/gtk/gtktextutil.c b/gtk/gtktextutil.c
index 6678e4e3b6..11758778d7 100644
--- a/gtk/gtktextutil.c
+++ b/gtk/gtktextutil.c
@@ -26,12 +26,19 @@
#include <config.h>
#include "gtktextutil.h"
-#include "gtkintl.h"
+#include "gtktextview.h"
+
+#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
+
+#include "gtktextdisplay.h"
+#include "gtktextbuffer.h"
#include "gtkmenuitem.h"
+#include "gtkintl.h"
#include "gtkalias.h"
#define DRAG_ICON_MAX_WIDTH 250
-#define DRAG_ICON_LAYOUT_BORDER 2
+#define DRAG_ICON_MAX_HEIGHT 250
+#define DRAG_ICON_LAYOUT_BORDER 5
#define DRAG_ICON_MAX_LINES 7
#define ELLIPSIS_CHARACTER "\xe2\x80\xa6"
@@ -177,7 +184,7 @@ limit_layout_lines (PangoLayout *layout)
*
* Creates a drag and drop icon from @text.
**/
-GdkPixmap*
+GdkPixmap *
_gtk_text_util_create_drag_icon (GtkWidget *widget,
gchar *text,
gsize len)
@@ -238,3 +245,124 @@ _gtk_text_util_create_drag_icon (GtkWidget *widget,
return drawable;
}
+
+static void
+gtk_text_view_set_attributes_from_style (GtkTextView *text_view,
+ GtkTextAttributes *values,
+ GtkStyle *style)
+{
+ values->appearance.bg_color = style->base[GTK_STATE_NORMAL];
+ values->appearance.fg_color = style->text[GTK_STATE_NORMAL];
+
+ if (values->font)
+ pango_font_description_free (values->font);
+
+ values->font = pango_font_description_copy (style->font_desc);
+}
+
+GdkPixmap *
+_gtk_text_util_create_rich_drag_icon (GtkWidget *widget,
+ GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GdkDrawable *drawable = NULL;
+ gint pixmap_height, pixmap_width;
+ gint layout_width, layout_height;
+ GtkTextBuffer *new_buffer;
+ GtkTextLayout *layout;
+ GtkTextAttributes *style;
+ PangoContext *ltr_context, *rtl_context;
+ GtkTextIter iter;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (end != NULL, NULL);
+
+ new_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer));
+ gtk_text_buffer_get_start_iter (new_buffer, &iter);
+
+ gtk_text_buffer_insert_range (new_buffer, &iter, start, end);
+
+ gtk_text_buffer_get_start_iter (new_buffer, &iter);
+
+ layout = gtk_text_layout_new ();
+
+ ltr_context = gtk_widget_create_pango_context (widget);
+ pango_context_set_base_dir (ltr_context, PANGO_DIRECTION_LTR);
+ rtl_context = gtk_widget_create_pango_context (widget);
+ pango_context_set_base_dir (rtl_context, PANGO_DIRECTION_RTL);
+
+ gtk_text_layout_set_contexts (layout, ltr_context, rtl_context);
+
+ g_object_unref (ltr_context);
+ g_object_unref (rtl_context);
+
+ style = gtk_text_attributes_new ();
+
+ layout_width = widget->allocation.width;
+
+ if (GTK_IS_TEXT_VIEW (widget))
+ {
+ gtk_widget_ensure_style (widget);
+ gtk_text_view_set_attributes_from_style (GTK_TEXT_VIEW (widget),
+ style, widget->style);
+
+ layout_width = layout_width
+ - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_LEFT)
+ - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_RIGHT);
+ }
+
+ style->direction = gtk_widget_get_direction (widget);
+ style->wrap_mode = PANGO_WRAP_WORD_CHAR;
+
+ gtk_text_layout_set_default_style (layout, style);
+ gtk_text_attributes_unref (style);
+
+ gtk_text_layout_set_buffer (layout, new_buffer);
+ gtk_text_layout_set_cursor_visible (layout, FALSE);
+ gtk_text_layout_set_screen_width (layout, layout_width);
+
+ gtk_text_layout_validate (layout, DRAG_ICON_MAX_HEIGHT);
+ gtk_text_layout_get_size (layout, &layout_width, &layout_height);
+
+ g_print ("%s: layout size %d %d\n", G_STRFUNC, layout_width, layout_height);
+
+ layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH);
+ layout_height = MIN (layout_height, DRAG_ICON_MAX_HEIGHT);
+
+ pixmap_width = layout_width + DRAG_ICON_LAYOUT_BORDER * 2;
+ pixmap_height = layout_height + DRAG_ICON_LAYOUT_BORDER * 2;
+
+ g_print ("%s: pixmap size %d %d\n", G_STRFUNC, pixmap_width, pixmap_height);
+
+ drawable = gdk_pixmap_new (widget->window,
+ pixmap_width + 2, pixmap_height + 2, -1);
+
+ gdk_draw_rectangle (drawable,
+ widget->style->base_gc [GTK_WIDGET_STATE (widget)],
+ TRUE,
+ 0, 0,
+ pixmap_width + 1,
+ pixmap_height + 1);
+
+ gtk_text_layout_draw (layout, widget, drawable,
+ widget->style->text_gc [GTK_WIDGET_STATE (widget)],
+ - (1 + DRAG_ICON_LAYOUT_BORDER),
+ - (1 + DRAG_ICON_LAYOUT_BORDER),
+ 0, 0,
+ pixmap_width, pixmap_height, NULL);
+
+ gdk_draw_rectangle (drawable,
+ widget->style->black_gc,
+ FALSE,
+ 0, 0,
+ pixmap_width + 1,
+ pixmap_height + 1);
+
+ g_object_unref (layout);
+ g_object_unref (new_buffer);
+
+ return drawable;
+}
diff --git a/gtk/gtktextutil.h b/gtk/gtktextutil.h
index 27b900e435..e69a925af1 100644
--- a/gtk/gtktextutil.h
+++ b/gtk/gtktextutil.h
@@ -31,6 +31,7 @@
#include <gtk/gtkwidget.h>
#include <gtk/gtkmenushell.h>
#include <gtk/gtkeditable.h>
+#include <gtk/gtktextbuffer.h>
G_BEGIN_DECLS
@@ -47,8 +48,12 @@ void _gtk_text_util_append_special_char_menuitems (GtkMenuShell *me
G_END_DECLS
-GdkPixmap* _gtk_text_util_create_drag_icon (GtkWidget *widget,
- gchar *text,
- gsize len);
+GdkPixmap* _gtk_text_util_create_drag_icon (GtkWidget *widget,
+ gchar *text,
+ gsize len);
+GdkPixmap* _gtk_text_util_create_rich_drag_icon (GtkWidget *widget,
+ GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end);
#endif /* __GTK_TEXT_UTIL_H__ */
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 8b4bec7512..3049fae6dc 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -39,6 +39,7 @@
#include "gtkseparatormenuitem.h"
#include "gtksettings.h"
#include "gtkstock.h"
+#include "gtktextbufferrichtext.h"
#include "gtktextdisplay.h"
#include "gtktextview.h"
#include "gtkimmulticontext.h"
@@ -314,6 +315,9 @@ static void gtk_text_view_mark_set_handler (GtkTextBuffer *buffer,
const GtkTextIter *location,
GtkTextMark *mark,
gpointer data);
+static void gtk_text_view_target_list_notify (GtkTextBuffer *buffer,
+ const GParamSpec *pspec,
+ gpointer data);
static void gtk_text_view_get_cursor_location (GtkTextView *text_view,
GdkRectangle *pos);
static void gtk_text_view_get_virtual_cursor_pos (GtkTextView *text_view,
@@ -414,10 +418,6 @@ static gint text_window_get_width (GtkTextWindow *win);
static gint text_window_get_height (GtkTextWindow *win);
-static const GtkTargetEntry target_table[] = {
- { "GTK_TEXT_BUFFER_CONTENTS", GTK_TARGET_SAME_APP, 0 },
-};
-
static GtkContainerClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = { 0 };
@@ -1062,11 +1062,8 @@ gtk_text_view_init (GtkTextView *text_view)
text_view->tabs = NULL;
text_view->editable = TRUE;
- gtk_drag_dest_set (widget,
- 0,
- target_table, G_N_ELEMENTS (target_table),
+ gtk_drag_dest_set (widget, 0, NULL, 0,
GDK_ACTION_COPY | GDK_ACTION_MOVE);
- gtk_drag_dest_add_text_targets (widget);
text_view->virtual_cursor_x = -1;
text_view->virtual_cursor_y = -1;
@@ -1190,6 +1187,9 @@ gtk_text_view_set_buffer (GtkTextView *text_view,
g_signal_handlers_disconnect_by_func (text_view->buffer,
gtk_text_view_mark_set_handler,
text_view);
+ g_signal_handlers_disconnect_by_func (text_view->buffer,
+ gtk_text_view_target_list_notify,
+ text_view);
g_object_unref (text_view->buffer);
text_view->dnd_mark = NULL;
@@ -1225,7 +1225,13 @@ gtk_text_view_set_buffer (GtkTextView *text_view,
text_view->first_para_pixels = 0;
g_signal_connect (text_view->buffer, "mark_set",
- G_CALLBACK (gtk_text_view_mark_set_handler), text_view);
+ G_CALLBACK (gtk_text_view_mark_set_handler),
+ text_view);
+ g_signal_connect (text_view->buffer, "notify::paste-target-list",
+ G_CALLBACK (gtk_text_view_target_list_notify),
+ text_view);
+
+ gtk_text_view_target_list_notify (text_view->buffer, NULL, text_view);
if (GTK_WIDGET_REALIZED (text_view))
{
@@ -5984,21 +5990,6 @@ gtk_text_view_reset_im_context (GtkTextView *text_view)
}
}
-static gchar*
-_gtk_text_view_get_selected_text (GtkTextView *text_view)
-{
- GtkTextBuffer *buffer;
- GtkTextIter start, end;
- gchar *text = NULL;
-
- buffer = gtk_text_view_get_buffer (text_view);
-
- if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
- text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
-
- return text;
-}
-
/*
* DND feature
*/
@@ -6008,29 +5999,30 @@ drag_begin_cb (GtkWidget *widget,
GdkDragContext *context,
gpointer data)
{
- GtkTextView *text_view;
- gchar *text;
- GdkPixmap *pixmap = NULL;
+ GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+ GtkTextIter start;
+ GtkTextIter end;
+ GdkPixmap *pixmap = NULL;
g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
- text_view = GTK_TEXT_VIEW (widget);
-
- text = _gtk_text_view_get_selected_text (text_view);
- pixmap = _gtk_text_util_create_drag_icon (widget, text, -1);
+ if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ pixmap = _gtk_text_util_create_rich_drag_icon (widget, buffer, &start, &end);
if (pixmap)
- gtk_drag_set_icon_pixmap (context,
- gdk_drawable_get_colormap (pixmap),
- pixmap,
- NULL,
- -2, -2);
+ {
+ gtk_drag_set_icon_pixmap (context,
+ gdk_drawable_get_colormap (pixmap),
+ pixmap,
+ NULL,
+ -2, -2);
+ g_object_unref (pixmap);
+ }
else
- gtk_drag_set_icon_default (context);
-
- if (pixmap)
- g_object_unref (pixmap);
- g_free (text);
+ {
+ gtk_drag_set_icon_default (context);
+ }
}
static void
@@ -6038,23 +6030,19 @@ gtk_text_view_start_selection_dnd (GtkTextView *text_view,
const GtkTextIter *iter,
GdkEventMotion *event)
{
- GtkTargetList *target_list;
+ GtkTargetList *target_list;
text_view->drag_start_x = -1;
text_view->drag_start_y = -1;
text_view->pending_place_cursor_button = 0;
-
- target_list = gtk_target_list_new (target_table,
- G_N_ELEMENTS (target_table));
- gtk_target_list_add_text_targets (target_list, 0);
- g_signal_connect (text_view, "drag-begin",
+ target_list = gtk_text_buffer_get_copy_target_list (get_buffer (text_view));
+
+ g_signal_connect (text_view, "drag-begin",
G_CALLBACK (drag_begin_cb), NULL);
gtk_drag_begin (GTK_WIDGET (text_view), target_list,
GDK_ACTION_COPY | GDK_ACTION_MOVE,
1, (GdkEvent*)event);
-
- gtk_target_list_unref (target_list);
}
static void
@@ -6077,30 +6065,49 @@ gtk_text_view_drag_data_get (GtkWidget *widget,
guint info,
guint time)
{
- GtkTextView *text_view;
-
- text_view = GTK_TEXT_VIEW (widget);
+ GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
- if (selection_data->target == gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+ if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
{
- GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
-
gtk_selection_data_set (selection_data,
gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
8, /* bytes */
(void*)&buffer,
sizeof (buffer));
}
- else
+ else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
{
- gchar *str;
GtkTextIter start;
GtkTextIter end;
+ guint8 *str = NULL;
+ gsize len;
+
+ if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ {
+ /* Extract the selected text */
+ str = gtk_text_buffer_serialize (buffer, buffer,
+ selection_data->target,
+ &start, &end,
+ &len);
+ }
- str = NULL;
+ if (str)
+ {
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, /* bytes */
+ (guchar *) str, len);
+ g_free (str);
+ }
+ }
+ else
+ {
+ GtkTextIter start;
+ GtkTextIter end;
+ gchar *str = NULL;
- if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
- &start, &end))
+ if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
{
/* Extract the selected text */
str = gtk_text_iter_get_visible_text (&start, &end);
@@ -6179,7 +6186,7 @@ gtk_text_view_drag_motion (GtkWidget *widget,
/* can't accept any of the offered targets */
}
else if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
- &start, &end) &&
+ &start, &end) &&
gtk_text_iter_compare (&newplace, &start) >= 0 &&
gtk_text_iter_compare (&newplace, &end) <= 0)
{
@@ -6284,14 +6291,14 @@ insert_text_data (GtkTextView *text_view,
GtkTextIter *drop_point,
GtkSelectionData *selection_data)
{
- gchar *str;
+ guchar *str;
str = gtk_selection_data_get_text (selection_data);
if (str)
{
gtk_text_buffer_insert_interactive (get_buffer (text_view),
- drop_point, str, -1,
+ drop_point, (gchar *) str, -1,
text_view->editable);
g_free (str);
}
@@ -6329,7 +6336,7 @@ gtk_text_view_drag_data_received (GtkWidget *widget,
gtk_text_buffer_begin_user_action (buffer);
- if (selection_data->target == gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+ if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
{
GtkTextBuffer *src_buffer = NULL;
GtkTextIter start, end;
@@ -6347,7 +6354,38 @@ gtk_text_view_drag_data_received (GtkWidget *widget,
if (gtk_text_buffer_get_tag_table (src_buffer) !=
gtk_text_buffer_get_tag_table (buffer))
- copy_tags = FALSE;
+ {
+ /* try to find a suitable rich text target instead */
+ GdkAtom *atoms;
+ gint n_atoms;
+ GList *list;
+ GdkAtom target = GDK_NONE;
+
+ copy_tags = FALSE;
+
+ atoms = gtk_text_buffer_get_deserialize_formats (buffer, &n_atoms);
+
+ for (list = context->targets; list; list = g_list_next (list))
+ {
+ gint i;
+
+ for (i = 0; i < n_atoms; i++)
+ if (GUINT_TO_POINTER (atoms[i]) == list->data)
+ {
+ target = atoms[i];
+ break;
+ }
+ }
+
+ g_free (atoms);
+
+ if (target != GDK_NONE)
+ {
+ gtk_drag_get_data (widget, context, target, time);
+ gtk_text_buffer_end_user_action (buffer);
+ return;
+ }
+ }
if (gtk_text_buffer_get_selection_bounds (src_buffer,
&start,
@@ -6371,9 +6409,28 @@ gtk_text_view_drag_data_received (GtkWidget *widget,
}
}
}
+ else if (selection_data->length > 0 &&
+ info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+ {
+ gboolean retval;
+ GError *error = NULL;
+
+ retval = gtk_text_buffer_deserialize (buffer, buffer,
+ selection_data->target,
+ &drop_point,
+ (guint8 *) selection_data->data,
+ selection_data->length,
+ &error);
+
+ if (!retval)
+ {
+ g_warning ("error pasting: %s\n", error->message);
+ g_clear_error (&error);
+ }
+ }
else
insert_text_data (text_view, &drop_point, selection_data);
-
+
done:
gtk_drag_finish (context, success,
success && context->action == GDK_ACTION_MOVE,
@@ -6826,6 +6883,14 @@ gtk_text_view_mark_set_handler (GtkTextBuffer *buffer,
}
static void
+gtk_text_view_target_list_notify (GtkTextBuffer *buffer,
+ const GParamSpec *pspec,
+ gpointer data)
+{
+ gtk_drag_dest_set_target_list (data, gtk_text_buffer_get_paste_target_list (buffer));
+}
+
+static void
gtk_text_view_get_cursor_location (GtkTextView *text_view,
GdkRectangle *pos)
{