diff --git a/ChangeLog b/ChangeLog index 7d14086ea7..17114f390a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2006-08-28 Michael Natterer + + * gtk/gtktextbufferserialize.c (serialize_text): don't write out + for tags that have already been closed by the logic + which turns overlapping spans into XML-able trees. Fixes broken + XML when there are overlapping tags in the buffer. Also free two + leaked GLists and did some cleanup. + + * tests/Makefile.am + * tests/testrichtext.c: new test which creates randomly tagged + GtkTextBuffers and serializes/deserializes them. + 2006-08-26 Matthias Clasen * gtk/gtkstatusicon.c (gtk_status_icon_reset_image_data): diff --git a/gtk/gtktextbufferserialize.c b/gtk/gtktextbufferserialize.c index 66eb18d63e..cacf6c61d1 100644 --- a/gtk/gtktextbufferserialize.c +++ b/gtk/gtktextbufferserialize.c @@ -434,14 +434,13 @@ serialize_text (GtkTextBuffer *buffer, { GtkTextIter iter, old_iter; GSList *tag_list, *new_tag_list; - GQueue *active_tags; - int i; + GSList *active_tags; g_string_append (context->text_str, ""); iter = context->start; tag_list = NULL; - active_tags = g_queue_new (); + active_tags = NULL; do { @@ -453,29 +452,33 @@ serialize_text (GtkTextBuffer *buffer, find_list_delta (tag_list, new_tag_list, &added, &removed); /* Handle removed tags */ - tmp = removed; - while (tmp) + for (tmp = removed; tmp; tmp = tmp->next) { GtkTextTag *tag = tmp->data; - g_string_append (context->text_str, ""); + /* Only close the tag if we didn't close it before (by using + * the stack logic in the while() loop below) + */ + if (g_slist_find (active_tags, tag)) + { + g_string_append (context->text_str, ""); - /* 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, ""); - } + /* Drop all tags that were opened after this one (which are + * above this on in the stack) + */ + while (active_tags->data != tag) + { + added = g_list_prepend (added, active_tags->data); + active_tags = g_slist_remove (active_tags, active_tags->data); + g_string_append_printf (context->text_str, ""); + } - g_queue_pop_head (active_tags); - - tmp = tmp->next; + active_tags = g_slist_remove (active_tags, active_tags->data); + } } /* Handle added tags */ - tmp = added; - while (tmp) + for (tmp = added; tmp; tmp = tmp->next) { GtkTextTag *tag = tmp->data; gchar *tag_name; @@ -505,14 +508,16 @@ serialize_text (GtkTextBuffer *buffer, g_string_append_printf (context->text_str, "", GPOINTER_TO_INT (tag_id)); } - g_queue_push_head (active_tags, tag); - tmp = tmp->next; + active_tags = g_slist_prepend (active_tags, tag); } g_slist_free (tag_list); tag_list = new_tag_list; + g_list_free (added); + g_list_free (removed); + old_iter = iter; /* Now try to go to either the next tag toggle, or if a pixbuf appears */ @@ -570,10 +575,10 @@ serialize_text (GtkTextBuffer *buffer, while (!gtk_text_iter_equal (&iter, &context->end)); /* Close any open tags */ - for (i = 0; i < g_queue_get_length (active_tags); i++) { + for (tag_list = active_tags; tag_list; tag_list = tag_list->next) g_string_append (context->text_str, ""); - } - g_queue_free (active_tags); + + g_slist_free (active_tags); g_string_append (context->text_str, "\n\n"); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 32bac901e4..d9458dfd95 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -57,6 +57,7 @@ noinst_PROGRAMS = \ testprint \ testrgb \ testrecentchooser \ + testrichtext \ testselection \ $(testsocket_programs) \ testspinbutton \ @@ -113,6 +114,7 @@ testnouiprint_DEPENDENCIES = $(TEST_DEPS) testprint_DEPENDENCIES = $(TEST_DEPS) testrecentchooser_DEPENDENCIES = $(TEST_DEPS) testrgb_DEPENDENCIES = $(TEST_DEPS) +testrichtext_DEPENDENCIES = $(TEST_DEPS) testselection_DEPENDENCIES = $(TEST_DEPS) testsocket_DEPENDENCIES = $(DEPS) testsocket_child_DEPENDENCIES = $(DEPS) @@ -163,6 +165,7 @@ testnouiprint_LDADD = $(LDADDS) testprint_LDADD = $(LDADDS) testrecentchooser_LDADD = $(LDADDS) testrgb_LDADD = $(LDADDS) +testrichtext_LDADD = $(LDADDS) testselection_LDADD = $(LDADDS) testsocket_LDADD = $(LDADDS) testsocket_child_LDADD = $(LDADDS) diff --git a/tests/testrichtext.c b/tests/testrichtext.c new file mode 100644 index 0000000000..2a64cf7b93 --- /dev/null +++ b/tests/testrichtext.c @@ -0,0 +1,185 @@ +/* testrichtext.c + * Copyright (C) 2006 Imendio AB + * Authors: Michael Natterer, Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 +#include + +static guint32 quick_rand32_accu = 2147483563; + +static inline guint32 +quick_rand32 (void) +{ + quick_rand32_accu = 1664525 * quick_rand32_accu + 1013904223; + return quick_rand32_accu; +} + +static gboolean +delete_event (GtkWidget *widget, + GdkEventAny *event, + gpointer user_data) +{ + gtk_main_quit (); + + return TRUE; +} + +static void +text_tag_enqueue (GtkTextTag *tag, + gpointer data) +{ + GSList **slist_p = data; + *slist_p = g_slist_prepend (*slist_p, tag); +} + +static const gchar *example_text = +"vkndsk vfds vkfds vkdsv fdlksnvkfdvnkfdvnkdsnvs\n" +"kmvofdmvfdsvkv fdskvnkfdv nnd.mckfdvnknsknvdnvs" +"fdlvmfdsvlkfdsmvnskdnvfdsnvf sbskjnvlknfd cvdvnd" +"mvlfdsv vfdkjv m, ds vkfdks v df,v j kfds v d\n" +"vnfdskv kjvnfv cfdkvndfnvcm fd,vk kdsf vj d\n" +"KLJHkjh kjh klhjKLJH Kjh kjl h34kj h34kj3h klj 23 " +"kjlkjlhsdjk 34kljh klj hklj 23k4jkjkjh234kjh 52kj " +"2h34 sdaf ukklj kjl32l jkkjl 23j jkl ljk23 jkl\n" +"hjhjhj2hj23jh jh jk jk2h3 hj kjj jk jh21 jhhj32."; + +static GdkAtom +setup_buffer (GtkTextBuffer *buffer) +{ + const guint tlen = strlen (example_text); + const guint tcount = 17; + GtkTextTag *tags[tcount]; + GtkTextTagTable *ttable = gtk_text_buffer_get_tag_table (buffer); + GSList *node, *slist = NULL; + GdkAtom atom; + guint i; + + /* cleanup */ + gtk_text_buffer_set_text (buffer, "", 0); + gtk_text_tag_table_foreach (ttable, text_tag_enqueue, &slist); + for (node = slist; node; node = node->next) + gtk_text_tag_table_remove (ttable, node->data); + g_slist_free (slist); + + /* create new tags */ + for (i = 0; i < tcount; i++) + { + char *s = g_strdup_printf ("tag%u", i); + tags[i] = gtk_text_buffer_create_tag (buffer, s, + "weight", quick_rand32() >> 31 ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, + "style", quick_rand32() >> 31 ? PANGO_STYLE_OBLIQUE : PANGO_STYLE_NORMAL, + "underline", quick_rand32() >> 31, + NULL); + g_free (s); + } + + /* assign text and tags */ + gtk_text_buffer_set_text (buffer, example_text, -1); + for (i = 0; i < tcount * 5; i++) + { + gint a = quick_rand32() % tlen, b = quick_rand32() % tlen; + GtkTextIter start, end; + gtk_text_buffer_get_iter_at_offset (buffer, &start, MIN (a, b)); + gtk_text_buffer_get_iter_at_offset (buffer, &end, MAX (a, b)); + gtk_text_buffer_apply_tag (buffer, tags[i % tcount], &start, &end); + } + + /* return serialization format */ + atom = gtk_text_buffer_register_deserialize_tagset (buffer, NULL); + gtk_text_buffer_deserialize_set_can_create_tags (buffer, atom, TRUE); + + return atom; +} + +static gboolean +test_serialize_deserialize (GtkTextBuffer *buffer, + GdkAtom atom, + GError **error) +{ + GtkTextIter start, end; + guint8 *spew; + gsize spew_length; + gboolean success; + + gtk_text_buffer_get_bounds (buffer, &start, &end); + + spew = gtk_text_buffer_serialize (buffer, buffer, atom, + &start, &end, &spew_length); + + success = gtk_text_buffer_deserialize (buffer, buffer, atom, &end, + spew, spew_length, error); + + g_free (spew); + + return success; +} + +gint +main (gint argc, + gchar *argv[]) +{ + GtkWidget *window; + GtkWidget *sw; + GtkWidget *view; + GtkTextBuffer *buffer; + GdkAtom atom; + guint i, broken = 0; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 400, 300); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + gtk_container_set_border_width (GTK_CONTAINER (sw), 12); + gtk_container_add (GTK_CONTAINER (window), sw); + + g_signal_connect (window, "delete-event", + G_CALLBACK (delete_event), + NULL); + + buffer = gtk_text_buffer_new (NULL); + view = gtk_text_view_new_with_buffer (buffer); + g_object_unref (buffer); + + gtk_container_add (GTK_CONTAINER (sw), view); + + gtk_widget_show_all (window); + if (0) + gtk_main (); + + for (i = 0; i < 250; i++) + { + GError *error = NULL; + g_printerr ("creating randomly tagged text buffer with accu=0x%x...\n", quick_rand32_accu); + atom = setup_buffer (buffer); + if (test_serialize_deserialize (buffer, atom, &error)) + g_printerr ("ok.\n"); + else + { + g_printerr ("FAIL: serialization/deserialization failed:\n %s\n", error->message); + broken += 1; + } + g_clear_error (&error); + } + + return broken > 0; +}