]> Pileus Git - ~andy/gtk/blob - tests/textbuffertest.c
perf/widgets.h tests/print-editor.c tests/prop-editor.h
[~andy/gtk] / tests / textbuffertest.c
1 /* testtextbuffer.c -- Simplistic test suite
2  * Copyright (C) 2000 Red Hat, Inc
3  * Author: Havoc Pennington
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <gtk/gtk.h>
26 #include "gtk/gtktexttypes.h" /* Private header, for UNKNOWN_CHAR */
27
28 static void
29 gtk_text_iter_spew (const GtkTextIter *iter, const gchar *desc)
30 {
31   g_print (" %20s: line %d / char %d / line char %d / line byte %d\n",
32            desc,
33            gtk_text_iter_get_line (iter),
34            gtk_text_iter_get_offset (iter),
35            gtk_text_iter_get_line_offset (iter),
36            gtk_text_iter_get_line_index (iter));
37 }
38
39 static void
40 check_get_set_text (GtkTextBuffer *buffer,
41                     const char    *str)
42 {
43   GtkTextIter start, end, iter;
44   char *text;
45   int n;
46   
47   gtk_text_buffer_set_text (buffer, str, -1);
48   if (gtk_text_buffer_get_char_count (buffer) != g_utf8_strlen (str, -1))
49     g_error ("Wrong number of chars (%d not %d)",
50              gtk_text_buffer_get_char_count (buffer),
51              (int) g_utf8_strlen (str, -1));
52   gtk_text_buffer_get_bounds (buffer, &start, &end);
53   text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
54   if (strcmp (text, str) != 0)
55     g_error ("Got '%s' as buffer contents", text);
56   g_free (text);
57
58   /* line char counts */
59   iter = start;
60   n = 0;
61   do
62     {
63       n += gtk_text_iter_get_chars_in_line (&iter);
64     }
65   while (gtk_text_iter_forward_line (&iter));
66
67   if (n != gtk_text_buffer_get_char_count (buffer))
68     g_error ("Sum of chars in lines is %d but buffer char count is %d",
69              n, gtk_text_buffer_get_char_count (buffer));
70
71   /* line byte counts */
72   iter = start;
73   n = 0;
74   do
75     {
76       n += gtk_text_iter_get_bytes_in_line (&iter);
77     }
78   while (gtk_text_iter_forward_line (&iter));
79
80   if (n != strlen (str))
81     g_error ("Sum of chars in lines is %d but buffer byte count is %d",
82              n, (int) strlen (str));
83   
84   gtk_text_buffer_set_text (buffer, "", -1);
85
86   n = gtk_text_buffer_get_line_count (buffer);
87   if (n != 1)
88     g_error ("%d lines, expected 1", n);
89
90   n = gtk_text_buffer_get_char_count (buffer);
91   if (n != 0)
92     g_error ("%d chars, expected 0", n);
93 }
94
95 static gint
96 count_toggles_at_iter (GtkTextIter *iter,
97                        GtkTextTag  *of_tag)
98 {
99   GSList *tags;
100   GSList *tmp;
101   gint count = 0;
102   
103   /* get toggle-ons and toggle-offs */
104   tags = gtk_text_iter_get_toggled_tags (iter, TRUE);
105   tags = g_slist_concat (tags,
106                          gtk_text_iter_get_toggled_tags (iter, FALSE));
107   
108   tmp = tags;
109   while (tmp != NULL)
110     {
111       if (of_tag == NULL)
112         ++count;
113       else if (of_tag == tmp->data)
114         ++count;
115       
116       tmp = g_slist_next (tmp);
117     }
118   
119   g_slist_free (tags);
120
121   return count;
122 }
123
124 static gint
125 count_toggles_in_range_by_char (GtkTextBuffer     *buffer,
126                                 GtkTextTag        *of_tag,
127                                 const GtkTextIter *start,
128                                 const GtkTextIter *end)
129 {
130   GtkTextIter iter;
131   gint count = 0;
132   
133   iter = *start;
134   do
135     {
136       count += count_toggles_at_iter (&iter, of_tag);
137       if (!gtk_text_iter_forward_char (&iter))
138         {
139           /* end iterator */
140           count += count_toggles_at_iter (&iter, of_tag);
141           break;
142         }
143     }
144   while (gtk_text_iter_compare (&iter, end) <= 0);
145   
146   return count;
147 }
148
149 static gint
150 count_toggles_in_buffer (GtkTextBuffer *buffer,
151                          GtkTextTag    *of_tag)
152 {
153   GtkTextIter start, end;
154
155   gtk_text_buffer_get_bounds (buffer, &start, &end);
156
157   return count_toggles_in_range_by_char (buffer, of_tag, &start, &end);
158 }
159
160 static void
161 check_specific_tag_in_range (GtkTextBuffer     *buffer,
162                              const gchar       *tag_name,
163                              const GtkTextIter *start,
164                              const GtkTextIter *end)
165 {
166   GtkTextIter iter;
167   GtkTextTag *tag;
168   gboolean state;
169   gint count;
170   gint buffer_count;
171   gint last_offset;
172
173   if (gtk_text_iter_compare (start, end) > 0)
174     {
175       g_print ("  (inverted range for checking tags, skipping)\n");
176       return;
177     }
178   
179   tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
180                                    tag_name);
181
182   buffer_count = count_toggles_in_range_by_char (buffer, tag, start, end);
183   
184   state = FALSE;
185   count = 0;
186
187   last_offset = -1;
188   iter = *start;
189   if (gtk_text_iter_toggles_tag (&iter, tag) ||
190       gtk_text_iter_forward_to_tag_toggle (&iter, tag))
191     {
192       do
193         {
194           gint this_offset;
195           
196           ++count;
197
198           this_offset = gtk_text_iter_get_offset (&iter);
199
200           if (this_offset <= last_offset)
201             g_error ("forward_to_tag_toggle moved in wrong direction");
202
203           last_offset = this_offset;
204           
205           if (gtk_text_iter_begins_tag (&iter, tag))
206             {
207               if (state)
208                 g_error ("Tag %p is already on, and was toggled on?", tag);
209               state = TRUE;
210             }          
211           else if (gtk_text_iter_ends_tag (&iter, tag))
212             {
213               if (!state)
214                 g_error ("Tag %p toggled off, but wasn't toggled on?", tag);
215               state = FALSE;
216             }
217           else
218             g_error ("forward_to_tag_toggle went to a location without a toggle");
219         }
220       while (gtk_text_iter_forward_to_tag_toggle (&iter, tag) &&
221              gtk_text_iter_compare (&iter, end) <= 0);
222     }
223
224   if (count != buffer_count)
225     g_error ("Counted %d tags iterating by char, %d iterating forward by tag toggle\n",
226              buffer_count, count);
227   
228   state = FALSE;
229   count = 0;
230   
231   iter = *end;
232   last_offset = gtk_text_iter_get_offset (&iter);
233   if (gtk_text_iter_toggles_tag (&iter, tag) ||
234       gtk_text_iter_backward_to_tag_toggle (&iter, tag))
235     {
236       do
237         {
238           gint this_offset;
239           
240           ++count;
241
242           this_offset = gtk_text_iter_get_offset (&iter);
243           
244           if (this_offset >= last_offset)
245             g_error ("backward_to_tag_toggle moved in wrong direction");
246           
247           last_offset = this_offset;
248
249           if (gtk_text_iter_begins_tag (&iter, tag))
250             {
251               if (!state)
252                 g_error ("Tag %p wasn't on when we got to the on toggle going backward?", tag);
253               state = FALSE;
254             }
255           else if (gtk_text_iter_ends_tag (&iter, tag))
256             {
257               if (state)
258                 g_error ("Tag %p off toggle, but we were already inside a tag?", tag);
259               state = TRUE;
260             }
261           else
262             g_error ("backward_to_tag_toggle went to a location without a toggle");
263         }
264       while (gtk_text_iter_backward_to_tag_toggle (&iter, tag) &&
265              gtk_text_iter_compare (&iter, start) >= 0);
266     }
267
268   if (count != buffer_count)
269     g_error ("Counted %d tags iterating by char, %d iterating backward by tag toggle\n",
270              buffer_count, count);
271 }
272
273 static void
274 check_specific_tag (GtkTextBuffer *buffer,
275                     const gchar   *tag_name)
276 {
277   GtkTextIter start, end;
278
279   gtk_text_buffer_get_bounds (buffer, &start, &end);
280   check_specific_tag_in_range (buffer, tag_name, &start, &end);
281   gtk_text_iter_forward_chars (&start, 2);
282   gtk_text_iter_backward_chars (&end, 2);
283   if (gtk_text_iter_compare (&start, &end) < 0)
284     check_specific_tag_in_range (buffer, tag_name, &start, &end);
285 }
286
287 static void
288 run_tests (GtkTextBuffer *buffer)
289 {
290   GtkTextIter iter;
291   GtkTextIter start;
292   GtkTextIter end;
293   GtkTextIter mark;
294   gint i, j;
295   gint num_chars;
296   GtkTextMark *bar_mark;
297   GtkTextTag *tag;
298   GHashTable *tag_states;
299   gint count;
300   gint buffer_count;
301   
302   gtk_text_buffer_get_bounds (buffer, &start, &end);
303
304   /* Check that walking the tree via chars and via iterators produces
305    * the same number of indexable locations.
306    */
307   num_chars = gtk_text_buffer_get_char_count (buffer);
308   iter = start;
309   bar_mark = gtk_text_buffer_create_mark (buffer, "bar", &iter, FALSE);
310   i = 0;
311   while (i < num_chars)
312     {
313       GtkTextIter current;
314       GtkTextMark *foo_mark;
315
316       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
317
318       if (!gtk_text_iter_equal (&iter, &current))
319         {
320           g_error ("get_char_index didn't return current iter");
321         }
322
323       j = gtk_text_iter_get_offset (&iter);
324
325       if (i != j)
326         {
327           g_error ("iter converted to %d not %d", j, i);
328         }
329
330       /* get/set mark */
331       gtk_text_buffer_get_iter_at_mark (buffer, &mark, bar_mark);
332
333       if (!gtk_text_iter_equal (&iter, &mark))
334         {
335           gtk_text_iter_spew (&iter, "iter");
336           gtk_text_iter_spew (&mark, "mark");
337           g_error ("Mark not moved to the right place.");
338         }
339
340       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
341       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
342       gtk_text_buffer_delete_mark (buffer, foo_mark);
343
344       if (!gtk_text_iter_equal (&iter, &mark))
345         {
346           gtk_text_iter_spew (&iter, "iter");
347           gtk_text_iter_spew (&mark, "mark");
348           g_error ("Mark not created in the right place.");
349         }
350
351       if (gtk_text_iter_is_end (&iter))
352         g_error ("iterators ran out before chars (offset %d of %d)",
353                  i, num_chars);
354
355       gtk_text_iter_forward_char (&iter);
356
357       gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
358
359       ++i;
360     }
361
362   if (!gtk_text_iter_equal (&iter, &end))
363     g_error ("Iterating over all chars didn't end with the end iter");
364
365   /* Do the tree-walk backward
366    */
367   num_chars = gtk_text_buffer_get_char_count (buffer);
368   gtk_text_buffer_get_iter_at_offset (buffer, &iter, -1);
369
370   gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
371
372   i = num_chars;
373
374   if (!gtk_text_iter_equal (&iter, &end))
375     g_error ("iter at char -1 is not equal to the end iterator");
376
377   while (i >= 0)
378     {
379       GtkTextIter current;
380       GtkTextMark *foo_mark;
381
382       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
383
384       if (!gtk_text_iter_equal (&iter, &current))
385         {
386           g_error ("get_char_index didn't return current iter while going backward");
387         }
388       j = gtk_text_iter_get_offset (&iter);
389
390       if (i != j)
391         {
392           g_error ("going backward, iter converted to %d not %d", j, i);
393         }
394
395       /* get/set mark */
396       gtk_text_buffer_get_iter_at_mark (buffer, &mark, bar_mark);
397
398       if (!gtk_text_iter_equal (&iter, &mark))
399         {
400           gtk_text_iter_spew (&iter, "iter");
401           gtk_text_iter_spew (&mark, "mark");
402           g_error ("Mark not moved to the right place.");
403         }
404
405       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
406       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
407       gtk_text_buffer_delete_mark (buffer, foo_mark);
408
409       if (!gtk_text_iter_equal (&iter, &mark))
410         {
411           gtk_text_iter_spew (&iter, "iter");
412           gtk_text_iter_spew (&mark, "mark");
413           g_error ("Mark not created in the right place.");
414         }
415
416       if (i > 0)
417         {
418           if (!gtk_text_iter_backward_char (&iter))
419             g_error ("iterators ran out before char indexes");
420
421           gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
422         }
423       else
424         {
425           if (gtk_text_iter_backward_char (&iter))
426             g_error ("went backward from 0?");
427         }
428
429       --i;
430     }
431
432   if (!gtk_text_iter_equal (&iter, &start))
433     g_error ("Iterating backward over all chars didn't end with the start iter");
434
435   /*
436    * Check that get_line_count returns the same number of lines
437    * as walking the tree by line
438    */
439   i = 1; /* include current (first) line */
440   gtk_text_buffer_get_iter_at_line (buffer, &iter, 0);
441   while (gtk_text_iter_forward_line (&iter))
442     ++i;
443
444   if (i != gtk_text_buffer_get_line_count (buffer))
445     g_error ("Counted %d lines, buffer has %d", i,
446              gtk_text_buffer_get_line_count (buffer));
447
448   /*
449    * Check that moving over tag toggles thinks about working.
450    */
451
452   buffer_count = count_toggles_in_buffer (buffer, NULL);
453   
454   tag_states = g_hash_table_new (NULL, NULL);
455   count = 0;
456   
457   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
458   if (gtk_text_iter_toggles_tag (&iter, NULL) ||
459       gtk_text_iter_forward_to_tag_toggle (&iter, NULL))
460     {
461       do
462         {
463           GSList *tags;
464           GSList *tmp;
465           gboolean found_some = FALSE;
466           
467           /* get toggled-on tags */
468           tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
469
470           if (tags)
471             found_some = TRUE;
472           
473           tmp = tags;
474           while (tmp != NULL)
475             {
476               ++count;
477               
478               tag = tmp->data;
479               
480               if (g_hash_table_lookup (tag_states, tag))
481                 g_error ("Tag %p is already on, and was toggled on?", tag);
482
483               g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
484           
485               tmp = g_slist_next (tmp);
486             }
487
488           g_slist_free (tags);
489       
490           /* get toggled-off tags */
491           tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
492
493           if (tags)
494             found_some = TRUE;
495           
496           tmp = tags;
497           while (tmp != NULL)
498             {
499               ++count;
500               
501               tag = tmp->data;
502
503               if (!g_hash_table_lookup (tag_states, tag))
504                 g_error ("Tag %p is already off, and was toggled off?", tag);
505
506               g_hash_table_remove (tag_states, tag);
507           
508               tmp = g_slist_next (tmp);
509             }
510
511           g_slist_free (tags);
512
513           if (!found_some)
514             g_error ("No tags found going forward to tag toggle.");
515
516         }
517       while (gtk_text_iter_forward_to_tag_toggle (&iter, NULL));
518     }
519   
520   g_hash_table_destroy (tag_states);
521
522   if (count != buffer_count)
523     g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
524              buffer_count, count);
525   
526   /* Go backward; here TRUE in the hash means we saw
527    * an off toggle last.
528    */
529   
530   tag_states = g_hash_table_new (NULL, NULL);
531   count = 0;
532   
533   gtk_text_buffer_get_end_iter (buffer, &iter);
534   if (gtk_text_iter_toggles_tag (&iter, NULL) ||
535       gtk_text_iter_backward_to_tag_toggle (&iter, NULL))
536     {
537       do
538         {
539           GSList *tags;
540           GSList *tmp;
541           gboolean found_some = FALSE;
542           
543           /* get toggled-off tags */
544           tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
545
546           if (tags)
547             found_some = TRUE;
548           
549           tmp = tags;
550           while (tmp != NULL)
551             {
552               ++count;
553               
554               tag = tmp->data;
555
556               if (g_hash_table_lookup (tag_states, tag))
557                 g_error ("Tag %p has two off-toggles in a row?", tag);
558           
559               g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
560           
561               tmp = g_slist_next (tmp);
562             }
563
564           g_slist_free (tags);
565       
566           /* get toggled-on tags */
567           tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
568
569           if (tags)
570             found_some = TRUE;
571           
572           tmp = tags;
573           while (tmp != NULL)
574             {
575               ++count;
576               
577               tag = tmp->data;
578
579               if (!g_hash_table_lookup (tag_states, tag))
580                 g_error ("Tag %p was toggled on, but saw no off-toggle?", tag);
581
582               g_hash_table_remove (tag_states, tag);
583           
584               tmp = g_slist_next (tmp);
585             }
586
587           g_slist_free (tags);
588
589           if (!found_some)
590             g_error ("No tags found going backward to tag toggle.");
591         }
592       while (gtk_text_iter_backward_to_tag_toggle (&iter, NULL));
593     }
594   
595   g_hash_table_destroy (tag_states);
596
597   if (count != buffer_count)
598     g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
599              buffer_count, count);
600
601   check_specific_tag (buffer, "fg_red");
602   check_specific_tag (buffer, "bg_green");
603   check_specific_tag (buffer, "front_tag");
604   check_specific_tag (buffer, "center_tag");
605   check_specific_tag (buffer, "end_tag");
606 }
607
608
609 static const char  *book_closed_xpm[] = {
610 "16 16 6 1",
611 "       c None s None",
612 ".      c black",
613 "X      c red",
614 "o      c yellow",
615 "O      c #808080",
616 "#      c white",
617 "                ",
618 "       ..       ",
619 "     ..XX.      ",
620 "   ..XXXXX.     ",
621 " ..XXXXXXXX.    ",
622 ".ooXXXXXXXXX.   ",
623 "..ooXXXXXXXXX.  ",
624 ".X.ooXXXXXXXXX. ",
625 ".XX.ooXXXXXX..  ",
626 " .XX.ooXXX..#O  ",
627 "  .XX.oo..##OO. ",
628 "   .XX..##OO..  ",
629 "    .X.#OO..    ",
630 "     ..O..      ",
631 "      ..        ",
632 "                "};
633
634 static void
635 fill_buffer (GtkTextBuffer *buffer)
636 {
637   GtkTextTag *tag;
638   GdkColor color, color2;
639   GtkTextIter iter;
640   GtkTextIter iter2;
641   GdkPixbuf *pixbuf;
642   int i;
643
644   color.red = color.green = 0;
645   color.blue = 0xffff;
646   color2.red = 0xfff;
647   color2.blue = 0x0;
648   color2.green = 0;
649   
650   gtk_text_buffer_create_tag (buffer, "fg_blue",
651                               "foreground_gdk", &color,
652                               "background_gdk", &color2,
653                               "font", "-*-courier-bold-r-*-*-30-*-*-*-*-*-*-*",
654                               NULL);
655
656   color.blue = color.green = 0;
657   color.red = 0xffff;
658   
659   gtk_text_buffer_create_tag (buffer, "fg_red",
660                               "rise", -4,
661                               "foreground_gdk", &color,
662                               NULL);
663
664   color.blue = color.red = 0;
665   color.green = 0xffff;
666   
667   gtk_text_buffer_create_tag (buffer, "bg_green",
668                               "background_gdk", &color,
669                               "font", "-*-courier-bold-r-*-*-10-*-*-*-*-*-*-*",
670                               NULL);
671
672   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
673
674   g_assert (pixbuf != NULL);
675
676   i = 0;
677   while (i < 10)
678     {
679       gchar *str;
680
681       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
682
683       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
684
685       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 1);
686
687       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
688
689       str = g_strdup_printf ("%d Hello World!\nwoo woo woo woo woo woo woo woo\n",
690                             i);
691
692       gtk_text_buffer_insert (buffer, &iter, str, -1);
693
694       g_free (str);
695
696       gtk_text_buffer_insert (buffer, &iter,
697                               "(Hello World!)\nfoo foo Hello this is some text we are using to text word wrap. It has punctuation! gee; blah - hmm, great.\nnew line\n\n"
698                               /* This is UTF8 stuff, Emacs doesn't
699                                  really know how to display it */
700                               "Spanish (Espa\303\261ol) \302\241Hola! / French (Fran\303\247ais) Bonjour, Salut / German (Deutsch S\303\274d) Gr\303\274\303\237 Gott (testing Latin-1 chars encoded in UTF8)\nThai (we can't display this, just making sure we don't crash)  (\340\270\240\340\270\262\340\270\251\340\270\262\340\271\204\340\270\227\340\270\242)  \340\270\252\340\270\247\340\270\261\340\270\252\340\270\224\340\270\265\340\270\204\340\270\243\340\270\261\340\270\232, \340\270\252\340\270\247\340\270\261\340\270\252\340\270\224\340\270\265\340\270\204\340\271\210\340\270\260\n",
701                               -1);
702
703       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
704       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
705
706       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 4);
707
708       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
709
710       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 7);
711
712       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
713
714       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 8);
715
716       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
717
718       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 8);
719       iter2 = iter;
720       gtk_text_iter_forward_chars (&iter2, 10);
721
722       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
723
724       gtk_text_iter_forward_chars (&iter, 7);
725       gtk_text_iter_forward_chars (&iter2, 10);
726
727       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
728
729       gtk_text_iter_forward_chars (&iter, 12);
730       gtk_text_iter_forward_chars (&iter2, 10);
731
732       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
733
734       gtk_text_iter_forward_chars (&iter, 10);
735       gtk_text_iter_forward_chars (&iter2, 15);
736
737       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
738       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
739
740       gtk_text_iter_forward_chars (&iter, 20);
741       gtk_text_iter_forward_chars (&iter2, 20);
742
743       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
744       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
745
746       gtk_text_iter_backward_chars (&iter, 25);
747       gtk_text_iter_forward_chars (&iter2, 5);
748
749       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
750       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
751
752       gtk_text_iter_forward_chars (&iter, 15);
753       gtk_text_iter_backward_chars (&iter2, 10);
754
755       gtk_text_buffer_remove_tag_by_name (buffer, "fg_red", &iter, &iter2);
756       gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2);
757
758       ++i;
759     }
760
761   /* Put in tags that are just at the beginning, and just near the end,
762    * and just near the middle.
763    */
764   tag = gtk_text_buffer_create_tag (buffer, "front_tag", NULL);
765   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 3);
766   gtk_text_buffer_get_iter_at_offset (buffer, &iter2, 300);
767
768   gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
769   
770   tag = gtk_text_buffer_create_tag (buffer, "end_tag", NULL);
771   gtk_text_buffer_get_end_iter (buffer, &iter2);
772   gtk_text_iter_backward_chars (&iter2, 12);
773   iter = iter2;
774   gtk_text_iter_backward_chars (&iter, 157);
775
776   gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);
777   
778   tag = gtk_text_buffer_create_tag (buffer, "center_tag", NULL);
779   gtk_text_buffer_get_iter_at_offset (buffer, &iter,
780                                       gtk_text_buffer_get_char_count (buffer)/2);
781   gtk_text_iter_backward_chars (&iter, 37);
782   iter2 = iter;
783   gtk_text_iter_forward_chars (&iter2, 57);
784
785   gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
786
787   g_object_unref (pixbuf);
788 }
789
790
791 /*
792  * Line separator tests (initially to avoid regression on bugzilla #57428)
793  */
794
795 static void
796 test_line_separation (const char* str,
797                       gboolean    expect_next_line,
798                       gboolean    expect_end_iter,
799                       int         expected_line_count,
800                       int         expected_line_break,
801                       int         expected_next_line_start)
802 {
803   GtkTextIter iter;
804   GtkTextBuffer* buffer;
805   gboolean on_next_line;
806   gboolean on_end_iter;
807   gint new_pos;
808
809   buffer = gtk_text_buffer_new (NULL);
810
811   gtk_text_buffer_set_text (buffer, str, -1);
812   gtk_text_buffer_get_iter_at_offset (buffer, &iter, expected_line_break);
813
814   g_assert (gtk_text_iter_ends_line (&iter) || gtk_text_iter_is_end (&iter));
815
816   g_assert (gtk_text_buffer_get_line_count (buffer) == expected_line_count);
817   
818   on_next_line = gtk_text_iter_forward_line (&iter);
819
820   g_assert (expect_next_line == on_next_line);
821
822   on_end_iter = gtk_text_iter_is_end (&iter);
823
824   g_assert (on_end_iter == expect_end_iter);
825   
826   new_pos = gtk_text_iter_get_offset (&iter);
827     
828   if (on_next_line)
829     g_assert (expected_next_line_start == new_pos);
830
831   ++expected_line_break;
832   while (expected_line_break < expected_next_line_start)
833     {
834       gtk_text_buffer_get_iter_at_offset (buffer, &iter, expected_line_break);
835
836       g_assert (!gtk_text_iter_ends_line (&iter));
837
838       on_next_line = gtk_text_iter_forward_line (&iter);
839         
840       g_assert (expect_next_line == on_next_line);
841         
842       new_pos = gtk_text_iter_get_offset (&iter);
843         
844       if (on_next_line)
845         g_assert (expected_next_line_start == new_pos);
846         
847       ++expected_line_break;
848     }
849
850   /* FIXME tests for backward line */
851   
852   g_object_unref (buffer);
853 }
854
855 /* there are cases where \r and \n should not be treated like \r\n,
856  * originally bug #337022. */
857 static void
858 split_r_n_separators_test (void)
859 {
860   GtkTextBuffer *buffer;
861   GtkTextIter iter;
862
863   buffer = gtk_text_buffer_new (NULL);
864
865   gtk_text_buffer_set_text (buffer, "foo\ra\nbar\n", -1);
866
867   /* delete 'a' so that we have
868
869      1 foo\r
870      2 \n
871      3 bar\n
872
873    * and both \r and \n are line separators */
874
875   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 5);
876   gtk_text_buffer_backspace (buffer, &iter, TRUE, TRUE);
877
878   g_assert (gtk_text_iter_ends_line (&iter));
879
880   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 3);
881   g_assert (gtk_text_iter_ends_line (&iter));
882 }
883
884 static void
885 test_line_separator (void)
886 {
887   char *str;
888   char buf[7] = { '\0', };
889
890   /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in
891    * Unicode 3.0; update this if that changes.
892    */
893 #define PARAGRAPH_SEPARATOR 0x2029
894   
895   test_line_separation ("line", FALSE, TRUE, 1, 4, 4);
896   test_line_separation ("line\r\n", FALSE, TRUE, 2, 4, 6);
897   test_line_separation ("line\r", FALSE, TRUE, 2, 4, 5);
898   test_line_separation ("line\n", FALSE, TRUE, 2, 4, 5);
899   test_line_separation ("line\rqw", TRUE, FALSE, 2, 4, 5);
900   test_line_separation ("line\nqw", TRUE, FALSE, 2, 4, 5);
901   test_line_separation ("line\r\nqw", TRUE, FALSE, 2, 4, 6);
902   
903   g_unichar_to_utf8 (PARAGRAPH_SEPARATOR, buf);
904   
905   str = g_strdup_printf ("line%s", buf);
906   test_line_separation (str, FALSE, TRUE, 2, 4, 5);
907   g_free (str);
908   str = g_strdup_printf ("line%sqw", buf);
909   test_line_separation (str, TRUE, FALSE, 2, 4, 5);
910   g_free (str);
911
912   split_r_n_separators_test ();
913 }
914
915 static void
916 test_logical_motion (void)
917 {
918   char *str;
919   char buf1[7] = { '\0', };
920   char buf2[7] = { '\0', };
921   char buf3[7] = { '\0', };
922   int expected[30];
923   int expected_steps;
924   int i;
925   GtkTextBuffer *buffer;
926   GtkTextIter iter;
927   
928   buffer = gtk_text_buffer_new (NULL);
929   
930 #define LEADING_JAMO 0x1111
931 #define VOWEL_JAMO 0x1167
932 #define TRAILING_JAMO 0x11B9
933   
934   g_unichar_to_utf8 (LEADING_JAMO, buf1);
935   g_unichar_to_utf8 (VOWEL_JAMO, buf2);
936   g_unichar_to_utf8 (TRAILING_JAMO, buf3);
937
938   /* Build the string "abc<leading><vowel><trailing>def\r\nxyz" */
939   str = g_strconcat ("abc", buf1, buf2, buf3, "def\r\nxyz", NULL);
940   gtk_text_buffer_set_text (buffer, str, -1);
941   g_free (str);
942   
943   /* Check cursor positions */
944   memset (expected, 0, sizeof (expected));
945   expected[0] = 0;    /* before 'a' */
946   expected[1] = 1;    /* before 'b' */
947   expected[2] = 2;    /* before 'c' */
948   expected[3] = 3;    /* before jamo */
949   expected[4] = 6;    /* before 'd' */
950   expected[5] = 7;    /* before 'e' */
951   expected[6] = 8;    /* before 'f' */
952   expected[7] = 9;    /* before '\r' */
953   expected[8] = 11;   /* before 'x' */
954   expected[9] = 12;   /* before 'y' */
955   expected[10] = 13;  /* before 'z' */
956   expected[11] = 14;  /* after 'z' (only matters going backward) */
957   expected_steps = 11;
958   
959   gtk_text_buffer_get_start_iter (buffer, &iter);
960   i = 0;
961   do
962     {
963       int pos;
964
965       pos = gtk_text_iter_get_offset (&iter);
966       
967       if (pos != expected[i])
968         {
969           g_error ("Cursor position %d, expected %d",
970                    pos, expected[i]);
971         }
972
973       ++i;      
974     }
975   while (gtk_text_iter_forward_cursor_position (&iter));
976
977   if (!gtk_text_iter_is_end (&iter))
978     g_error ("Expected to stop at the end iterator\n");
979
980   if (!gtk_text_iter_is_cursor_position (&iter))
981     g_error ("Should be a cursor position before the end iterator");
982   
983   if (i != expected_steps)
984     g_error ("Expected %d steps, there were actually %d\n", expected_steps, i);
985
986   i = expected_steps;
987   do
988     {
989       int pos;
990
991       pos = gtk_text_iter_get_offset (&iter);
992       
993       if (pos != expected[i])
994         {
995           g_error ("Moving backward, cursor position %d, expected %d",
996                    pos, expected[i]);
997         }
998
999       /* g_print ("%d = %d\n", pos, expected[i]); */
1000       
1001       --i;
1002     }
1003   while (gtk_text_iter_backward_cursor_position (&iter));
1004
1005   if (i != -1)
1006     g_error ("Expected %d steps, there were actually %d\n", expected_steps - i, i);
1007
1008   if (!gtk_text_iter_is_start (&iter))
1009     g_error ("Expected to stop at the start iterator\n");
1010
1011
1012   /* Check sentence boundaries */
1013   
1014   gtk_text_buffer_set_text (buffer, "Hi.\nHi. \nHi! Hi. Hi? Hi.", -1);
1015
1016   memset (expected, 0, sizeof (expected));
1017
1018   expected[0] = 0;    /* before first Hi */
1019   expected[1] = 3;    /* After first . */
1020   expected[2] = 7;    /* After second . */
1021   expected[3] = 12;   /* After ! */
1022   expected[4] = 16;   /* After third . */
1023   expected[5] = 20;   /* After ? */
1024   
1025   expected_steps = 6;
1026   
1027   gtk_text_buffer_get_start_iter (buffer, &iter);
1028   i = 0;
1029   do
1030     {
1031       int pos;
1032
1033       pos = gtk_text_iter_get_offset (&iter);
1034
1035       if (pos != expected[i])
1036         {
1037           g_error ("Sentence position %d, expected %d",
1038                    pos, expected[i]);
1039         }
1040
1041       if (i != 0 &&
1042           !gtk_text_iter_is_end (&iter) &&
1043           !gtk_text_iter_ends_sentence (&iter))
1044         g_error ("Iterator at %d should end a sentence", pos);
1045       
1046       ++i;
1047     }
1048   while (gtk_text_iter_forward_sentence_end (&iter));
1049
1050   if (i != expected_steps)
1051     g_error ("Expected %d steps, there were actually %d\n", expected_steps, i);
1052
1053   if (!gtk_text_iter_is_end (&iter))
1054     g_error ("Expected to stop at the end iterator\n");
1055   
1056   gtk_text_buffer_set_text (buffer, "Hi.\nHi. \nHi! Hi. Hi? Hi.", -1);
1057
1058   memset (expected, 0, sizeof (expected));
1059
1060   expected[0] = 24;
1061   expected[1] = 21;
1062   expected[2] = 17;
1063   expected[3] = 13;
1064   expected[4] = 9;
1065   expected[5] = 4;
1066   expected[6] = 0;
1067   
1068   expected_steps = 7;
1069   
1070   gtk_text_buffer_get_end_iter (buffer, &iter);
1071   i = 0;
1072   do
1073     {
1074       int pos;
1075
1076       pos = gtk_text_iter_get_offset (&iter);
1077
1078       if (pos != expected[i])
1079         {
1080           g_error ("Sentence position %d, expected %d",
1081                    pos, expected[i]);
1082         }
1083
1084       if (pos != 0 &&
1085           !gtk_text_iter_is_end (&iter) &&
1086           !gtk_text_iter_starts_sentence (&iter))
1087         g_error ("Iterator at %d should start a sentence", pos);
1088       
1089       ++i;
1090     }
1091   while (gtk_text_iter_backward_sentence_start (&iter));
1092
1093   if (i != expected_steps)
1094     g_error ("Expected %d steps, there were actually %d\n", expected_steps, i);
1095
1096   if (gtk_text_iter_get_offset (&iter) != 0)
1097     g_error ("Expected to stop at the start iterator\n");
1098   
1099   g_object_unref (buffer);
1100 }
1101
1102 static void
1103 test_marks (void)
1104 {
1105   GtkTextBuffer *buf1, *buf2;
1106   GtkTextMark *mark;
1107   GtkTextIter iter;
1108
1109   buf1 = gtk_text_buffer_new (NULL);
1110   buf2 = gtk_text_buffer_new (NULL);
1111
1112   gtk_text_buffer_get_start_iter (buf1, &iter);
1113   mark = gtk_text_buffer_create_mark (buf1, "foo", &iter, TRUE);
1114   g_object_ref (mark);
1115   gtk_text_mark_set_visible (mark, TRUE);
1116   gtk_text_buffer_delete_mark (buf1, mark);
1117
1118   g_assert (gtk_text_mark_get_visible (mark));
1119   g_assert (gtk_text_mark_get_left_gravity (mark));
1120   g_assert (!strcmp ("foo", gtk_text_mark_get_name (mark)));
1121   g_assert (gtk_text_mark_get_buffer (mark) == NULL);
1122   g_assert (gtk_text_mark_get_deleted (mark));
1123   g_assert (gtk_text_buffer_get_mark (buf1, "foo") == NULL);
1124
1125   gtk_text_buffer_get_start_iter (buf2, &iter);
1126   gtk_text_buffer_add_mark (buf2, mark, &iter);
1127   gtk_text_buffer_insert (buf2, &iter, "ewfwefwefwe", -1);
1128   gtk_text_buffer_get_iter_at_mark (buf2, &iter, mark);
1129
1130   g_assert (gtk_text_mark_get_visible (mark));
1131   g_assert (gtk_text_iter_is_start (&iter));
1132   g_assert (gtk_text_mark_get_left_gravity (mark));
1133   g_assert (!strcmp ("foo", gtk_text_mark_get_name (mark)));
1134   g_assert (gtk_text_mark_get_buffer (mark) == buf2);
1135   g_assert (!gtk_text_mark_get_deleted (mark));
1136   g_assert (gtk_text_buffer_get_mark (buf2, "foo") == mark);
1137
1138   gtk_text_buffer_delete_mark (buf2, mark);
1139   gtk_text_mark_set_visible (mark, FALSE);
1140   g_object_unref (mark);
1141
1142   mark = gtk_text_mark_new ("blah", TRUE);
1143   gtk_text_buffer_get_start_iter (buf1, &iter);
1144   gtk_text_mark_set_visible (mark, TRUE);
1145   gtk_text_buffer_add_mark (buf1, mark, &iter);
1146
1147   g_assert (gtk_text_mark_get_visible (mark));
1148   g_assert (gtk_text_mark_get_buffer (mark) == buf1);
1149   g_assert (!gtk_text_mark_get_deleted (mark));
1150   g_assert (gtk_text_buffer_get_mark (buf1, "blah") == mark);
1151   g_assert (!strcmp ("blah", gtk_text_mark_get_name (mark)));
1152
1153   gtk_text_mark_set_visible (mark, FALSE);
1154   gtk_text_buffer_delete_mark (buf1, mark);
1155   g_assert (!gtk_text_mark_get_visible (mark));
1156   g_assert (gtk_text_buffer_get_mark (buf1, "blah") == NULL);
1157   g_assert (gtk_text_mark_get_buffer (mark) == NULL);
1158   g_assert (gtk_text_mark_get_deleted (mark));
1159
1160   gtk_text_buffer_get_start_iter (buf2, &iter);
1161   gtk_text_buffer_add_mark (buf2, mark, &iter);
1162   g_assert (gtk_text_mark_get_buffer (mark) == buf2);
1163   g_assert (!gtk_text_mark_get_deleted (mark));
1164   g_assert (gtk_text_buffer_get_mark (buf2, "blah") == mark);
1165   g_assert (!strcmp ("blah", gtk_text_mark_get_name (mark)));
1166
1167   g_object_unref (mark);
1168   g_object_unref (buf1);
1169   g_object_unref (buf2);
1170 }
1171
1172 static void
1173 test_utf8 (void)
1174 {
1175   gunichar ch;
1176
1177   /* Check UTF8 unknown char thing */
1178   g_assert (g_utf8_strlen (gtk_text_unknown_char_utf8, 3) == 1);
1179   ch = g_utf8_get_char (gtk_text_unknown_char_utf8);
1180   g_assert (ch == GTK_TEXT_UNKNOWN_CHAR);
1181 }
1182
1183 static void
1184 test_empty_buffer (void)
1185 {
1186   GtkTextBuffer *buffer;
1187   int n;
1188   GtkTextIter start;
1189
1190   buffer = gtk_text_buffer_new (NULL);
1191
1192   /* Check that buffer starts with one empty line and zero chars */
1193   n = gtk_text_buffer_get_line_count (buffer);
1194   if (n != 1)
1195     g_error ("%d lines, expected 1", n);
1196
1197   n = gtk_text_buffer_get_char_count (buffer);
1198   if (n != 0)
1199     g_error ("%d chars, expected 0", n);
1200
1201   /* empty first line contains 0 chars */
1202   gtk_text_buffer_get_start_iter (buffer, &start);
1203   n = gtk_text_iter_get_chars_in_line (&start);
1204   if (n != 0)
1205     g_error ("%d chars in first line, expected 0", n);
1206   n = gtk_text_iter_get_bytes_in_line (&start);
1207   if (n != 0)
1208     g_error ("%d bytes in first line, expected 0", n);
1209   
1210   /* Run gruesome alien test suite on buffer */
1211   run_tests (buffer);
1212
1213   g_object_unref (buffer);
1214 }
1215
1216 static void
1217 test_get_set(void)
1218 {
1219   GtkTextBuffer *buffer;
1220
1221   buffer = gtk_text_buffer_new (NULL);
1222
1223   check_get_set_text (buffer, "Hello");
1224   check_get_set_text (buffer, "Hello\n");
1225   check_get_set_text (buffer, "Hello\r\n");
1226   check_get_set_text (buffer, "Hello\r");
1227   check_get_set_text (buffer, "Hello\nBar\nFoo");
1228   check_get_set_text (buffer, "Hello\nBar\nFoo\n");
1229
1230   g_object_unref (buffer);
1231 }
1232
1233 static void
1234 test_fill_empty (void)
1235 {
1236   GtkTextBuffer *buffer;
1237   int n;
1238   GtkTextIter start, end;
1239   
1240   buffer = gtk_text_buffer_new (NULL);
1241
1242   /* Put stuff in the buffer */
1243   fill_buffer (buffer);
1244
1245   /* Subject stuff-bloated buffer to further torment */
1246   run_tests (buffer);
1247
1248   /* Delete all stuff from the buffer */
1249   gtk_text_buffer_get_bounds (buffer, &start, &end);
1250   gtk_text_buffer_delete (buffer, &start, &end);
1251
1252   /* Check buffer for emptiness (note that a single
1253      empty line always remains in the buffer) */
1254   n = gtk_text_buffer_get_line_count (buffer);
1255   if (n != 1)
1256     g_error ("%d lines, expected 1", n);
1257
1258   n = gtk_text_buffer_get_char_count (buffer);
1259   if (n != 0)
1260     g_error ("%d chars, expected 0", n);
1261
1262   run_tests (buffer);
1263
1264   g_object_unref (buffer);
1265 }
1266
1267 static void
1268 test_tag (void)
1269 {
1270   GtkTextBuffer *buffer;
1271   GtkTextIter start, end;
1272   
1273   buffer = gtk_text_buffer_new (NULL);
1274
1275   fill_buffer (buffer);
1276
1277   gtk_text_buffer_set_text (buffer, "adcdef", -1);
1278   gtk_text_buffer_get_iter_at_offset (buffer, &start, 1);
1279   gtk_text_buffer_get_iter_at_offset (buffer, &end, 3);
1280   gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &start, &end);
1281   
1282   run_tests (buffer);
1283   
1284   g_object_unref (buffer);
1285 }
1286
1287 extern void pixbuf_init (void);
1288
1289 int
1290 main (int argc, char** argv)
1291 {
1292   /* First, we turn on btree debugging. */
1293   gtk_debug_flags |= GTK_DEBUG_TEXT;
1294
1295   gtk_test_init (&argc, &argv);
1296   pixbuf_init ();
1297
1298   g_test_add_func ("/TextBuffer/UTF8 unknown char", test_utf8);
1299   g_test_add_func ("/TextBuffer/Line separator", test_line_separator);
1300   g_test_add_func ("/TextBuffer/Logical motion", test_logical_motion);
1301   g_test_add_func ("/TextBuffer/Marks", test_marks);
1302   g_test_add_func ("/TextBuffer/Empty buffer", test_empty_buffer);
1303   g_test_add_func ("/TextBuffer/Get and Set", test_get_set);
1304   g_test_add_func ("/TextBuffer/Fill and Empty", test_fill_empty);
1305   g_test_add_func ("/TextBuffer/Tag", test_tag);
1306   
1307   return g_test_run();
1308 }