summaryrefslogtreecommitdiff
path: root/gtk
diff options
context:
space:
mode:
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)
{