]> Pileus Git - ~andy/gtk/commitdiff
don't write out </apply_tag> for tags that have already been closed by the
authorMichael Natterer <mitch@imendio.com>
Mon, 28 Aug 2006 16:14:42 +0000 (16:14 +0000)
committerMichael Natterer <mitch@src.gnome.org>
Mon, 28 Aug 2006 16:14:42 +0000 (16:14 +0000)
2006-08-28  Michael Natterer  <mitch@imendio.com>

* gtk/gtktextbufferserialize.c (serialize_text): don't write out
</apply_tag> 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.

ChangeLog
gtk/gtktextbufferserialize.c
tests/Makefile.am
tests/testrichtext.c [new file with mode: 0644]

index 7d14086ea78faeafc42d7291c9579fb92134eb12..17114f390a010d7ba9db058e146ae1731bf1e853 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2006-08-28  Michael Natterer  <mitch@imendio.com>
+
+       * gtk/gtktextbufferserialize.c (serialize_text): don't write out
+       </apply_tag> 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  <mclasen@redhat.com>
 
        * gtk/gtkstatusicon.c (gtk_status_icon_reset_image_data): 
index 66eb18d63ea959a53e0b67ef32f2a22893aad600..cacf6c61d102ac37bd61d4f531a49df4a9785f18 100644 (file)
@@ -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, "<text>");
 
   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, "</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>");
-           }
+          /* 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, "</apply_tag>");
 
-         g_queue_pop_head (active_tags);
+              /* 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, "</apply_tag>");
+                }
 
-         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, "<apply_tag id=\"%d\">", 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, "</apply_tag>");
-  }
-  g_queue_free (active_tags);
+
+  g_slist_free (active_tags);
   g_string_append (context->text_str, "</text>\n</text_view_markup>\n");
 }
 
index 32bac901e4aa36c186616a88c47518767f159f39..d9458dfd953dfa882318386c734904e9d7aac95a 100644 (file)
@@ -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 (file)
index 0000000..2a64cf7
--- /dev/null
@@ -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 <string.h>
+#include <gtk/gtk.h>
+
+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;
+}