diff options
Diffstat (limited to 'gtk')
-rw-r--r-- | gtk/Makefile.am | 4 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtk.symbols | 28 | ||||
-rw-r--r-- | gtk/gtkclipboard.c | 197 | ||||
-rw-r--r-- | gtk/gtkclipboard.h | 93 | ||||
-rw-r--r-- | gtk/gtkselection.c | 211 | ||||
-rw-r--r-- | gtk/gtkselection.h | 47 | ||||
-rw-r--r-- | gtk/gtktextbuffer.c | 443 | ||||
-rw-r--r-- | gtk/gtktextbuffer.h | 13 | ||||
-rw-r--r-- | gtk/gtktextbufferrichtext.c | 704 | ||||
-rw-r--r-- | gtk/gtktextbufferrichtext.h | 92 | ||||
-rw-r--r-- | gtk/gtktextbufferserialize.c | 1877 | ||||
-rw-r--r-- | gtk/gtktextbufferserialize.h | 43 | ||||
-rw-r--r-- | gtk/gtktextutil.c | 134 | ||||
-rw-r--r-- | gtk/gtktextutil.h | 11 | ||||
-rw-r--r-- | gtk/gtktextview.c | 197 |
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 \ @@ -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) { |