]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Put into #ifdef DO_BLINK to avoid an unused static function. (#97965)
[~andy/gtk] / tests / testtext.c
1 #include <stdio.h>
2 #include <sys/stat.h>
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 #include <gtk/gtk.h>
8 #include <gdk/gdkkeysyms.h>
9
10 #include "prop-editor.h"
11
12 typedef struct _Buffer Buffer;
13 typedef struct _View View;
14
15 static gint untitled_serial = 1;
16
17 GSList *active_window_stack = NULL;
18
19 struct _Buffer
20 {
21   gint refcount;
22   GtkTextBuffer *buffer;
23   char *filename;
24   gint untitled_serial;
25   GtkTextTag *invisible_tag;
26   GtkTextTag *not_editable_tag;
27   GtkTextTag *found_text_tag;
28   GtkTextTag *custom_tabs_tag;
29   GSList *color_tags;
30   guint color_cycle_timeout;
31   gdouble start_hue;
32 };
33
34 struct _View
35 {
36   GtkWidget *window;
37   GtkWidget *text_view;
38   GtkAccelGroup *accel_group;
39   GtkItemFactory *item_factory;
40   Buffer *buffer;
41 };
42
43 static void push_active_window (GtkWindow *window);
44 static void pop_active_window (void);
45 static GtkWindow *get_active_window (void);
46
47 static Buffer * create_buffer      (void);
48 static gboolean check_buffer_saved (Buffer *buffer);
49 static gboolean save_buffer        (Buffer *buffer);
50 static gboolean save_as_buffer     (Buffer *buffer);
51 static char *   buffer_pretty_name (Buffer *buffer);
52 static void     buffer_filename_set (Buffer *buffer);
53 static void     buffer_search_forward (Buffer *buffer,
54                                        const char *str,
55                                        View *view);
56 static void     buffer_search_backward (Buffer *buffer,
57                                        const char *str,
58                                        View *view);
59 static void     buffer_set_colors      (Buffer  *buffer,
60                                         gboolean enabled);
61 static void     buffer_cycle_colors    (Buffer  *buffer);
62
63 static View *view_from_widget (GtkWidget *widget);
64
65 static View *create_view      (Buffer *buffer);
66 static void  check_close_view (View   *view);
67 static void  close_view       (View   *view);
68 static void  view_set_title   (View   *view);
69 static void  view_init_menus  (View   *view);
70 static void  view_add_example_widgets (View *view);
71
72 GSList *buffers = NULL;
73 GSList *views = NULL;
74
75 static void
76 push_active_window (GtkWindow *window)
77 {
78   g_object_ref (window);
79   active_window_stack = g_slist_prepend (active_window_stack, window);
80 }
81
82 static void
83 pop_active_window (void)
84 {
85   g_object_unref (active_window_stack->data);
86   active_window_stack = g_slist_delete_link (active_window_stack, active_window_stack);
87 }
88
89 static GtkWindow *
90 get_active_window (void)
91 {
92   if (active_window_stack)
93     return active_window_stack->data;
94   else
95     return NULL;
96 }
97
98 /*
99  * Filesel utility function
100  */
101
102 typedef gboolean (*FileselOKFunc) (const char *filename, gpointer data);
103
104 static void
105 filesel_ok_cb (GtkWidget *button, GtkWidget *filesel)
106 {
107   FileselOKFunc ok_func = (FileselOKFunc)g_object_get_data (G_OBJECT (filesel), "ok-func");
108   gpointer data = g_object_get_data (G_OBJECT (filesel), "ok-data");
109   gint *result = g_object_get_data (G_OBJECT (filesel), "ok-result");
110   
111   gtk_widget_hide (filesel);
112   
113   if ((*ok_func) (gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), data))
114     {
115       gtk_widget_destroy (filesel);
116       *result = TRUE;
117     }
118   else
119     gtk_widget_show (filesel);
120 }
121
122 gboolean
123 filesel_run (GtkWindow    *parent, 
124              const char   *title,
125              const char   *start_file,
126              FileselOKFunc func,
127              gpointer      data)
128 {
129   GtkWidget *filesel = gtk_file_selection_new (title);
130   gboolean result = FALSE;
131
132   if (!parent)
133     parent = get_active_window ();
134   
135   if (parent)
136     gtk_window_set_transient_for (GTK_WINDOW (filesel), parent);
137
138   if (start_file)
139     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), start_file);
140
141   
142   g_object_set_data (G_OBJECT (filesel), "ok-func", func);
143   g_object_set_data (G_OBJECT (filesel), "ok-data", data);
144   g_object_set_data (G_OBJECT (filesel), "ok-result", &result);
145
146   g_signal_connect (GTK_FILE_SELECTION (filesel)->ok_button,
147                     "clicked",
148                     G_CALLBACK (filesel_ok_cb), filesel);
149   g_signal_connect_swapped (GTK_FILE_SELECTION (filesel)->cancel_button,
150                             "clicked",
151                             G_CALLBACK (gtk_widget_destroy), filesel);
152
153   g_signal_connect (filesel, "destroy",
154                     G_CALLBACK (gtk_main_quit), NULL);
155   gtk_window_set_modal (GTK_WINDOW (filesel), TRUE);
156
157   gtk_widget_show (filesel);
158   gtk_main ();
159
160   return result;
161 }
162
163 /*
164  * MsgBox utility functions
165  */
166
167 static void
168 msgbox_yes_cb (GtkWidget *widget, gboolean *result)
169 {
170   *result = 0;
171   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
172 }
173
174 static void
175 msgbox_no_cb (GtkWidget *widget, gboolean *result)
176 {
177   *result = 1;
178   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
179 }
180
181 static gboolean
182 msgbox_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
183 {
184   if (event->keyval == GDK_Escape)
185     {
186       g_signal_stop_emission_by_name (widget, "key_press_event");
187       gtk_object_destroy (GTK_OBJECT (widget));
188       return TRUE;
189     }
190
191   return FALSE;
192 }
193
194 /* Don't copy this example, it's all crack-smoking - you can just use
195  * GtkMessageDialog now
196  */
197 gint
198 msgbox_run (GtkWindow  *parent,
199             const char *message,
200             const char *yes_button,
201             const char *no_button,
202             const char *cancel_button,
203             gint default_index)
204 {
205   gboolean result = -1;
206   GtkWidget *dialog;
207   GtkWidget *button;
208   GtkWidget *label;
209   GtkWidget *vbox;
210   GtkWidget *button_box;
211   GtkWidget *separator;
212
213   g_return_val_if_fail (message != NULL, FALSE);
214   g_return_val_if_fail (default_index >= 0 && default_index <= 1, FALSE);
215
216   if (!parent)
217     parent = get_active_window ();
218   
219   /* Create a dialog
220    */
221   dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
222   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
223   if (parent)
224     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
225   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
226
227   /* Quit our recursive main loop when the dialog is destroyed.
228    */
229   g_signal_connect (dialog, "destroy",
230                     G_CALLBACK (gtk_main_quit), NULL);
231
232   /* Catch Escape key presses and have them destroy the dialog
233    */
234   g_signal_connect (dialog, "key_press_event",
235                     G_CALLBACK (msgbox_key_press_cb), NULL);
236
237   /* Fill in the contents of the widget
238    */
239   vbox = gtk_vbox_new (FALSE, 0);
240   gtk_container_add (GTK_CONTAINER (dialog), vbox);
241   
242   label = gtk_label_new (message);
243   gtk_misc_set_padding (GTK_MISC (label), 12, 12);
244   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
245   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
246
247   separator = gtk_hseparator_new ();
248   gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
249
250   button_box = gtk_hbutton_box_new ();
251   gtk_box_pack_start (GTK_BOX (vbox), button_box, FALSE, FALSE, 0);
252   gtk_container_set_border_width (GTK_CONTAINER (button_box), 8);
253   
254
255   /* When Yes is clicked, call the msgbox_yes_cb
256    * This sets the result variable and destroys the dialog
257    */
258   if (yes_button)
259     {
260       button = gtk_button_new_with_label (yes_button);
261       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
262       gtk_container_add (GTK_CONTAINER (button_box), button);
263
264       if (default_index == 0)
265         gtk_widget_grab_default (button);
266       
267       g_signal_connect (button, "clicked",
268                         G_CALLBACK (msgbox_yes_cb), &result);
269     }
270
271   /* When No is clicked, call the msgbox_no_cb
272    * This sets the result variable and destroys the dialog
273    */
274   if (no_button)
275     {
276       button = gtk_button_new_with_label (no_button);
277       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
278       gtk_container_add (GTK_CONTAINER (button_box), button);
279
280       if (default_index == 0)
281         gtk_widget_grab_default (button);
282       
283       g_signal_connect (button, "clicked",
284                         G_CALLBACK (msgbox_no_cb), &result);
285     }
286
287   /* When Cancel is clicked, destroy the dialog
288    */
289   if (cancel_button)
290     {
291       button = gtk_button_new_with_label (cancel_button);
292       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
293       gtk_container_add (GTK_CONTAINER (button_box), button);
294       
295       if (default_index == 1)
296         gtk_widget_grab_default (button);
297       
298       g_signal_connect_swapped (button, "clicked",
299                                 G_CALLBACK (gtk_object_destroy), dialog);
300     }
301
302   gtk_widget_show_all (dialog);
303
304   /* Run a recursive main loop until a button is clicked
305    * or the user destroys the dialog through the window mananger */
306   gtk_main ();
307
308   return result;
309 }
310
311 #ifdef DO_BLINK
312 /*
313  * Example buffer filling code
314  */
315 static gint
316 blink_timeout (gpointer data)
317 {
318   GtkTextTag *tag;
319   static gboolean flip = FALSE;
320   
321   tag = GTK_TEXT_TAG (data);
322
323   g_object_set (G_OBJECT (tag),
324                  "foreground", flip ? "blue" : "purple",
325                  NULL);
326
327   flip = !flip;
328
329   return TRUE;
330 }
331 #endif
332
333 static gint
334 tag_event_handler (GtkTextTag *tag, GtkWidget *widget, GdkEvent *event,
335                   const GtkTextIter *iter, gpointer user_data)
336 {
337   gint char_index;
338
339   char_index = gtk_text_iter_get_offset (iter);
340   
341   switch (event->type)
342     {
343     case GDK_MOTION_NOTIFY:
344       printf ("Motion event at char %d tag `%s'\n",
345              char_index, tag->name);
346       break;
347         
348     case GDK_BUTTON_PRESS:
349       printf ("Button press at char %d tag `%s'\n",
350              char_index, tag->name);
351       break;
352         
353     case GDK_2BUTTON_PRESS:
354       printf ("Double click at char %d tag `%s'\n",
355              char_index, tag->name);
356       break;
357         
358     case GDK_3BUTTON_PRESS:
359       printf ("Triple click at char %d tag `%s'\n",
360              char_index, tag->name);
361       break;
362         
363     case GDK_BUTTON_RELEASE:
364       printf ("Button release at char %d tag `%s'\n",
365              char_index, tag->name);
366       break;
367         
368     case GDK_KEY_PRESS:
369     case GDK_KEY_RELEASE:
370       printf ("Key event at char %d tag `%s'\n",
371               char_index, tag->name);
372       break;
373       
374     case GDK_ENTER_NOTIFY:
375     case GDK_LEAVE_NOTIFY:
376     case GDK_PROPERTY_NOTIFY:
377     case GDK_SELECTION_CLEAR:
378     case GDK_SELECTION_REQUEST:
379     case GDK_SELECTION_NOTIFY:
380     case GDK_PROXIMITY_IN:
381     case GDK_PROXIMITY_OUT:
382     case GDK_DRAG_ENTER:
383     case GDK_DRAG_LEAVE:
384     case GDK_DRAG_MOTION:
385     case GDK_DRAG_STATUS:
386     case GDK_DROP_START:
387     case GDK_DROP_FINISHED:
388     default:
389       break;
390     }
391
392   return FALSE;
393 }
394
395 static void
396 setup_tag (GtkTextTag *tag)
397 {
398   g_signal_connect (tag,
399                     "event",
400                     G_CALLBACK (tag_event_handler),
401                     NULL);
402 }
403
404 static const char  *book_closed_xpm[] = {
405 "16 16 6 1",
406 "       c None s None",
407 ".      c black",
408 "X      c red",
409 "o      c yellow",
410 "O      c #808080",
411 "#      c white",
412 "                ",
413 "       ..       ",
414 "     ..XX.      ",
415 "   ..XXXXX.     ",
416 " ..XXXXXXXX.    ",
417 ".ooXXXXXXXXX.   ",
418 "..ooXXXXXXXXX.  ",
419 ".X.ooXXXXXXXXX. ",
420 ".XX.ooXXXXXX..  ",
421 " .XX.ooXXX..#O  ",
422 "  .XX.oo..##OO. ",
423 "   .XX..##OO..  ",
424 "    .X.#OO..    ",
425 "     ..O..      ",
426 "      ..        ",
427 "                "};
428
429 void
430 fill_example_buffer (GtkTextBuffer *buffer)
431 {
432   GtkTextIter iter, iter2;
433   GtkTextTag *tag;
434   GtkTextChildAnchor *anchor;
435   GdkColor color;
436   GdkColor color2;
437   GdkPixbuf *pixbuf;
438   int i;
439   char *str;
440   
441   /* FIXME this is broken if called twice on a buffer, since
442    * we try to create tags a second time.
443    */
444   
445   tag = gtk_text_buffer_create_tag (buffer, "fg_blue", NULL);
446
447 #ifdef DO_BLINK
448   gtk_timeout_add (1000, blink_timeout, tag);
449 #endif     
450  
451   setup_tag (tag);
452   
453   color.red = color.green = 0;
454   color.blue = 0xffff;
455   color2.red = 0xfff;
456   color2.blue = 0x0;
457   color2.green = 0;
458   g_object_set (G_OBJECT (tag),
459                 "foreground_gdk", &color,
460                 "background_gdk", &color2,
461                 "size_points", 24.0,
462                 NULL);
463
464   tag = gtk_text_buffer_create_tag (buffer, "fg_red", NULL);
465
466   setup_tag (tag);
467       
468   color.blue = color.green = 0;
469   color.red = 0xffff;
470   g_object_set (G_OBJECT (tag),
471                 "rise", -4 * PANGO_SCALE,
472                 "foreground_gdk", &color,
473                 NULL);
474
475   tag = gtk_text_buffer_create_tag (buffer, "bg_green", NULL);
476
477   setup_tag (tag);
478       
479   color.blue = color.red = 0;
480   color.green = 0xffff;
481   g_object_set (G_OBJECT (tag),
482                 "background_gdk", &color,
483                 "size_points", 10.0,
484                 NULL);
485
486   tag = gtk_text_buffer_create_tag (buffer, "strikethrough", NULL);
487
488   setup_tag (tag);
489       
490   g_object_set (G_OBJECT (tag),
491                 "strikethrough", TRUE,
492                 NULL);
493
494
495   tag = gtk_text_buffer_create_tag (buffer, "underline", NULL);
496
497   setup_tag (tag);
498       
499   g_object_set (G_OBJECT (tag),
500                 "underline", PANGO_UNDERLINE_SINGLE,
501                 NULL);
502
503   setup_tag (tag);
504       
505   g_object_set (G_OBJECT (tag),
506                 "underline", PANGO_UNDERLINE_SINGLE,
507                 NULL);
508
509   tag = gtk_text_buffer_create_tag (buffer, "centered", NULL);
510       
511   g_object_set (G_OBJECT (tag),
512                 "justification", GTK_JUSTIFY_CENTER,
513                 NULL);
514
515   tag = gtk_text_buffer_create_tag (buffer, "rtl_quote", NULL);
516       
517   g_object_set (G_OBJECT (tag),
518                 "wrap_mode", GTK_WRAP_WORD,
519                 "direction", GTK_TEXT_DIR_RTL,
520                 "indent", 30,
521                 "left_margin", 20,
522                 "right_margin", 20,
523                 NULL);
524
525
526   tag = gtk_text_buffer_create_tag (buffer, "negative_indent", NULL);
527       
528   g_object_set (G_OBJECT (tag),
529                 "indent", -25,
530                 NULL);
531   
532   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
533
534   anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
535
536   g_object_ref (anchor);
537   
538   g_object_set_data_full (G_OBJECT (buffer), "anchor", anchor,
539                           (GDestroyNotify) g_object_unref);
540   
541   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
542   
543   i = 0;
544   while (i < 100)
545     {
546       GtkTextMark * temp_mark;
547       
548       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
549           
550       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
551           
552       str = g_strdup_printf ("%d Hello World! blah blah blah blah blah blah blah blah blah blah blah blah\nwoo woo woo woo woo woo woo woo woo woo woo woo woo woo woo\n",
553                             i);
554       
555       gtk_text_buffer_insert (buffer, &iter, str, -1);
556
557       g_free (str);
558       
559       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 5);
560           
561       gtk_text_buffer_insert (buffer, &iter,
562                              "(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 with a significant quantity of text on it. This line really does contain some text. More text! More text! More text!\n"
563                              /* This is UTF8 stuff, Emacs doesn't
564                                 really know how to display it */
565                              "German (Deutsch Süd) Grüß Gott Greek (Ελληνικά) Γειά σας Hebrew(שלום) Hebrew punctuation(\xd6\xbfש\xd6\xbb\xd6\xbc\xd6\xbb\xd6\xbfל\xd6\xbcו\xd6\xbc\xd6\xbb\xd6\xbb\xd6\xbfם\xd6\xbc\xd6\xbb\xd6\xbf) Japanese (日本語) Thai (สวัสดีครับ) Thai wrong spelling (คำต่อไปนื่สะกดผิด พัั้ัั่งโกะ)\n", -1);
566
567       temp_mark =
568         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
569
570 #if 1
571       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 6);
572       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 13);
573
574       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
575
576       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 10);
577       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 16);
578
579       gtk_text_buffer_apply_tag_by_name (buffer, "underline", &iter, &iter2);
580
581       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 14);
582       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 24);
583
584       gtk_text_buffer_apply_tag_by_name (buffer, "strikethrough", &iter, &iter2);
585           
586       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 9);
587       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 16);
588
589       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
590   
591       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 2);
592       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 10);
593
594       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
595
596       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 8);
597       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 15);
598
599       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
600 #endif
601
602       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
603       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
604           
605       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
606       gtk_text_buffer_apply_tag_by_name (buffer, "centered", &iter2, &iter);
607
608       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
609       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
610       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
611       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
612       gtk_text_buffer_apply_tag_by_name (buffer, "rtl_quote", &iter2, &iter);
613
614       gtk_text_buffer_insert_with_tags (buffer, &iter,
615                                         "Paragraph with negative indentation. blah blah blah blah blah. The quick brown fox jumped over the lazy dog.\n",
616                                         -1,
617                                         gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
618                                                                    "negative_indent"),
619                                         NULL);
620       
621       ++i;
622     }
623
624   g_object_unref (pixbuf);
625   
626   printf ("%d lines %d chars\n",
627           gtk_text_buffer_get_line_count (buffer),
628           gtk_text_buffer_get_char_count (buffer));
629
630   /* Move cursor to start */
631   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
632   gtk_text_buffer_place_cursor (buffer, &iter);
633   
634   gtk_text_buffer_set_modified (buffer, FALSE);
635 }
636
637 gboolean
638 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
639 {
640   FILE* f;
641   gchar buf[2048];
642   gint remaining = 0;
643   GtkTextIter iter, end;
644
645   f = fopen (filename, "r");
646   
647   if (f == NULL)
648     {
649       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
650                                     filename, g_strerror (errno));
651       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
652       g_free (err);
653       return FALSE;
654     }
655   
656   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
657   while (!feof (f))
658     {
659       gint count;
660       const char *leftover;
661       int to_read = 2047  - remaining;
662
663       count = fread (buf + remaining, 1, to_read, f);
664       buf[count + remaining] = '\0';
665
666       g_utf8_validate (buf, count + remaining, &leftover);
667       
668       g_assert (g_utf8_validate (buf, leftover - buf, NULL));
669       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
670
671       remaining = (buf + remaining + count) - leftover;
672       g_memmove (buf, leftover, remaining);
673
674       if (remaining > 6 || count < to_read)
675           break;
676     }
677
678   if (remaining)
679     {
680       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
681       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
682       g_free (err);
683     }
684   
685   /* We had a newline in the buffer to begin with. (The buffer always contains
686    * a newline, so we delete to the end of the buffer to clean up.
687    */
688   gtk_text_buffer_get_end_iter (buffer, &end);
689   gtk_text_buffer_delete (buffer, &iter, &end);
690   
691   gtk_text_buffer_set_modified (buffer, FALSE);
692
693   return TRUE;
694 }
695
696 static gint
697 delete_event_cb (GtkWidget *window, GdkEventAny *event, gpointer data)
698 {
699   View *view = view_from_widget (window);
700
701   push_active_window (GTK_WINDOW (window));
702   check_close_view (view);
703   pop_active_window ();
704
705   return TRUE;
706 }
707
708 /*
709  * Menu callbacks
710  */
711
712 static View *
713 get_empty_view (View *view)
714 {
715   if (!view->buffer->filename &&
716       !gtk_text_buffer_get_modified (view->buffer->buffer))
717     return view;
718   else
719     return create_view (create_buffer ());
720 }
721
722 static View *
723 view_from_widget (GtkWidget *widget)
724 {
725   if (GTK_IS_MENU_ITEM (widget))
726     {
727       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
728       return g_object_get_data (G_OBJECT (item_factory), "view");      
729     }
730   else
731     {
732       GtkWidget *app = gtk_widget_get_toplevel (widget);
733       return g_object_get_data (G_OBJECT (app), "view");
734     }
735 }
736
737 static void
738 do_new (gpointer             callback_data,
739         guint                callback_action,
740         GtkWidget           *widget)
741 {
742   create_view (create_buffer ());
743 }
744
745 static void
746 do_new_view (gpointer             callback_data,
747              guint                callback_action,
748              GtkWidget           *widget)
749 {
750   View *view = view_from_widget (widget);
751   
752   create_view (view->buffer);
753 }
754
755 gboolean
756 open_ok_func (const char *filename, gpointer data)
757 {
758   View *view = data;
759   View *new_view = get_empty_view (view);
760
761   if (!fill_file_buffer (new_view->buffer->buffer, filename))
762     {
763       if (new_view != view)
764         close_view (new_view);
765       return FALSE;
766     }
767   else
768     {
769       g_free (new_view->buffer->filename);
770       new_view->buffer->filename = g_strdup (filename);
771       buffer_filename_set (new_view->buffer);
772       
773       return TRUE;
774     }
775 }
776
777 static void
778 do_open (gpointer             callback_data,
779          guint                callback_action,
780          GtkWidget           *widget)
781 {
782   View *view = view_from_widget (widget);
783
784   push_active_window (GTK_WINDOW (view->window));
785   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
786   pop_active_window ();
787 }
788
789 static void
790 do_save_as (gpointer             callback_data,
791             guint                callback_action,
792             GtkWidget           *widget)
793 {
794   View *view = view_from_widget (widget);  
795
796   push_active_window (GTK_WINDOW (view->window));
797   save_as_buffer (view->buffer);
798   pop_active_window ();
799 }
800
801 static void
802 do_save (gpointer             callback_data,
803          guint                callback_action,
804          GtkWidget           *widget)
805 {
806   View *view = view_from_widget (widget);
807
808   push_active_window (GTK_WINDOW (view->window));
809   if (!view->buffer->filename)
810     do_save_as (callback_data, callback_action, widget);
811   else
812     save_buffer (view->buffer);
813   pop_active_window ();
814 }
815
816 static void
817 do_close   (gpointer             callback_data,
818             guint                callback_action,
819             GtkWidget           *widget)
820 {
821   View *view = view_from_widget (widget);
822
823   push_active_window (GTK_WINDOW (view->window));
824   check_close_view (view);
825   pop_active_window ();
826 }
827
828 static void
829 do_exit    (gpointer             callback_data,
830             guint                callback_action,
831             GtkWidget           *widget)
832 {
833   View *view = view_from_widget (widget);
834
835   GSList *tmp_list = buffers;
836
837   push_active_window (GTK_WINDOW (view->window));
838   while (tmp_list)
839     {
840       if (!check_buffer_saved (tmp_list->data))
841         return;
842
843       tmp_list = tmp_list->next;
844     }
845
846   gtk_main_quit ();
847   pop_active_window ();
848 }
849
850 static void
851 do_example (gpointer             callback_data,
852             guint                callback_action,
853             GtkWidget           *widget)
854 {
855   View *view = view_from_widget (widget);
856   View *new_view;
857
858   new_view = get_empty_view (view);
859   
860   fill_example_buffer (new_view->buffer->buffer);
861
862   view_add_example_widgets (new_view);
863 }
864
865
866 static void
867 do_insert_and_scroll (gpointer             callback_data,
868                       guint                callback_action,
869                       GtkWidget           *widget)
870 {
871   View *view = view_from_widget (widget);
872   GtkTextBuffer *buffer;
873   GtkTextIter start, end;
874   GtkTextMark *mark;
875   
876   buffer = view->buffer->buffer;
877
878   gtk_text_buffer_get_bounds (buffer, &start, &end);
879   mark = gtk_text_buffer_create_mark (buffer, NULL, &end, /* right grav */ FALSE);
880
881   gtk_text_buffer_insert (buffer, &end,
882                           "Hello this is multiple lines of text\n"
883                           "Line 1\n"  "Line 2\n"
884                           "Line 3\n"  "Line 4\n"
885                           "Line 5\n",
886                           -1);
887
888   gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view->text_view), mark,
889                                 0, TRUE, 0.0, 1.0);
890   gtk_text_buffer_delete_mark (buffer, mark);
891 }
892
893 static void
894 do_wrap_changed (gpointer             callback_data,
895                  guint                callback_action,
896                  GtkWidget           *widget)
897 {
898   View *view = view_from_widget (widget);
899
900   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
901 }
902
903 static void
904 do_direction_changed (gpointer             callback_data,
905                       guint                callback_action,
906                       GtkWidget           *widget)
907 {
908   View *view = view_from_widget (widget);
909   
910   gtk_widget_set_direction (view->text_view, callback_action);
911   gtk_widget_queue_resize (view->text_view);
912 }
913
914
915 static void
916 do_spacing_changed (gpointer             callback_data,
917                     guint                callback_action,
918                     GtkWidget           *widget)
919 {
920   View *view = view_from_widget (widget);
921
922   if (callback_action)
923     {
924       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
925                                             23);
926       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
927                                             21);
928       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
929                                             9);
930     }
931   else
932     {
933       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
934                                             0);
935       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
936                                             0);
937       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
938                                             0);
939     }
940 }
941
942 static void
943 do_editable_changed (gpointer callback_data,
944                      guint callback_action,
945                      GtkWidget *widget)
946 {
947   View *view = view_from_widget (widget);
948
949   gtk_text_view_set_editable (GTK_TEXT_VIEW (view->text_view), callback_action);
950 }
951
952 static void
953 do_cursor_visible_changed (gpointer callback_data,
954                            guint callback_action,
955                            GtkWidget *widget)
956 {
957   View *view = view_from_widget (widget);
958
959   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view->text_view), callback_action);
960 }
961
962 static void
963 do_color_cycle_changed (gpointer callback_data,
964                         guint callback_action,
965                         GtkWidget *widget)
966 {
967   View *view = view_from_widget (widget);
968
969   buffer_set_colors (view->buffer, callback_action);
970 }
971
972 static void
973 do_apply_editable (gpointer callback_data,
974                    guint callback_action,
975                    GtkWidget *widget)
976 {
977   View *view = view_from_widget (widget);
978   GtkTextIter start;
979   GtkTextIter end;
980   
981   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
982                                             &start, &end))
983     {
984       if (callback_action)
985         {
986           gtk_text_buffer_remove_tag (view->buffer->buffer,
987                                       view->buffer->not_editable_tag,
988                                       &start, &end);
989         }
990       else
991         {
992           gtk_text_buffer_apply_tag (view->buffer->buffer,
993                                      view->buffer->not_editable_tag,
994                                      &start, &end);
995         }
996     }
997 }
998
999 static void
1000 do_apply_invisible (gpointer callback_data,
1001                     guint callback_action,
1002                     GtkWidget *widget)
1003 {
1004   View *view = view_from_widget (widget);
1005   GtkTextIter start;
1006   GtkTextIter end;
1007   
1008   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1009                                             &start, &end))
1010     {
1011       if (callback_action)
1012         {
1013           gtk_text_buffer_remove_tag (view->buffer->buffer,
1014                                       view->buffer->invisible_tag,
1015                                       &start, &end);
1016         }
1017       else
1018         {
1019           gtk_text_buffer_apply_tag (view->buffer->buffer,
1020                                      view->buffer->invisible_tag,
1021                                      &start, &end);
1022         }
1023     }
1024 }
1025
1026 static void
1027 do_apply_tabs (gpointer callback_data,
1028                guint callback_action,
1029                GtkWidget *widget)
1030 {
1031   View *view = view_from_widget (widget);
1032   GtkTextIter start;
1033   GtkTextIter end;
1034   
1035   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1036                                             &start, &end))
1037     {
1038       if (callback_action)
1039         {
1040           gtk_text_buffer_remove_tag (view->buffer->buffer,
1041                                       view->buffer->custom_tabs_tag,
1042                                       &start, &end);
1043         }
1044       else
1045         {
1046           gtk_text_buffer_apply_tag (view->buffer->buffer,
1047                                      view->buffer->custom_tabs_tag,
1048                                      &start, &end);
1049         }
1050     }
1051 }
1052
1053 static void
1054 do_apply_colors (gpointer callback_data,
1055                  guint callback_action,
1056                  GtkWidget *widget)
1057 {
1058   View *view = view_from_widget (widget);
1059   Buffer *buffer = view->buffer;
1060   GtkTextIter start;
1061   GtkTextIter end;
1062   
1063   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1064                                             &start, &end))
1065     {
1066       if (!callback_action)
1067         {
1068           GSList *tmp;
1069           
1070           tmp = buffer->color_tags;
1071           while (tmp != NULL)
1072             {
1073               gtk_text_buffer_remove_tag (view->buffer->buffer,
1074                                           tmp->data,
1075                                           &start, &end);              
1076               tmp = g_slist_next (tmp);
1077             }
1078         }
1079       else
1080         {
1081           GSList *tmp;
1082           
1083           tmp = buffer->color_tags;
1084           while (TRUE)
1085             {
1086               GtkTextIter next;
1087               gboolean done = FALSE;
1088               
1089               next = start;
1090               gtk_text_iter_forward_char (&next);
1091               gtk_text_iter_forward_char (&next);
1092
1093               if (gtk_text_iter_compare (&next, &end) >= 0)
1094                 {
1095                   next = end;
1096                   done = TRUE;
1097                 }
1098               
1099               gtk_text_buffer_apply_tag (view->buffer->buffer,
1100                                          tmp->data,
1101                                          &start, &next);
1102
1103               start = next;
1104
1105               if (done)
1106                 return;
1107               
1108               tmp = g_slist_next (tmp);
1109               if (tmp == NULL)
1110                 tmp = buffer->color_tags;
1111             } 
1112         }
1113     }
1114 }
1115
1116 static void
1117 do_remove_tags (gpointer callback_data,
1118                 guint callback_action,
1119                 GtkWidget *widget)
1120 {
1121   View *view = view_from_widget (widget);
1122   GtkTextIter start;
1123   GtkTextIter end;
1124   
1125   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1126                                             &start, &end))
1127     {
1128       gtk_text_buffer_remove_all_tags (view->buffer->buffer,
1129                                        &start, &end);
1130     }
1131 }
1132
1133 static void
1134 do_properties (gpointer callback_data,
1135                 guint callback_action,
1136                 GtkWidget *widget)
1137 {
1138   View *view = view_from_widget (widget);
1139
1140   create_prop_editor (G_OBJECT (view->text_view), 0);
1141 }
1142
1143 enum
1144 {
1145   RESPONSE_FORWARD,
1146   RESPONSE_BACKWARD
1147 };
1148
1149 static void
1150 dialog_response_callback (GtkWidget *dialog, gint response_id, gpointer data)
1151 {
1152   GtkTextBuffer *buffer;
1153   View *view = data;
1154   GtkTextIter start, end;
1155   gchar *search_string;
1156
1157   if (response_id != RESPONSE_FORWARD &&
1158       response_id != RESPONSE_BACKWARD)
1159     {
1160       gtk_widget_destroy (dialog);
1161       return;
1162     }
1163   
1164   buffer = g_object_get_data (G_OBJECT (dialog), "buffer");
1165
1166   gtk_text_buffer_get_bounds (buffer, &start, &end);
1167   
1168   search_string = gtk_text_iter_get_text (&start, &end);
1169
1170   g_print ("Searching for `%s'\n", search_string);
1171
1172   if (response_id == RESPONSE_FORWARD)
1173     buffer_search_forward (view->buffer, search_string, view);
1174   else if (response_id == RESPONSE_BACKWARD)
1175     buffer_search_backward (view->buffer, search_string, view);
1176     
1177   g_free (search_string);
1178   
1179   gtk_widget_destroy (dialog);
1180 }
1181
1182 static void
1183 do_search (gpointer callback_data,
1184            guint callback_action,
1185            GtkWidget *widget)
1186 {
1187   View *view = view_from_widget (widget);
1188   GtkWidget *dialog;
1189   GtkWidget *search_text;
1190   GtkTextBuffer *buffer;
1191
1192   dialog = gtk_dialog_new_with_buttons ("Search",
1193                                         GTK_WINDOW (view->window),
1194                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1195                                         "Forward", RESPONSE_FORWARD,
1196                                         "Backward", RESPONSE_BACKWARD,
1197                                         GTK_STOCK_CANCEL,
1198                                         GTK_RESPONSE_NONE, NULL);
1199
1200
1201   buffer = gtk_text_buffer_new (NULL);
1202
1203   search_text = gtk_text_view_new_with_buffer (buffer);
1204
1205   g_object_unref (buffer);
1206   
1207   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1208                     search_text,
1209                     TRUE, TRUE, 0);
1210
1211   g_object_set_data (G_OBJECT (dialog), "buffer", buffer);
1212   
1213   g_signal_connect (dialog,
1214                     "response",
1215                     G_CALLBACK (dialog_response_callback),
1216                     view);
1217
1218   gtk_widget_show (search_text);
1219
1220   gtk_widget_grab_focus (search_text);
1221   
1222   gtk_widget_show_all (dialog);
1223 }
1224
1225 typedef struct
1226 {
1227   /* position is in coordinate system of text_view_move_child */
1228   int click_x;
1229   int click_y;
1230   int start_x;
1231   int start_y;
1232   int button;
1233 } ChildMoveInfo;
1234
1235 static gboolean
1236 movable_child_callback (GtkWidget *child,
1237                         GdkEvent  *event,
1238                         gpointer   data)
1239 {
1240   ChildMoveInfo *info;
1241   GtkTextView *text_view;
1242
1243   text_view = GTK_TEXT_VIEW (data);
1244   
1245   g_return_val_if_fail (GTK_IS_EVENT_BOX (child), FALSE);
1246   g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (text_view), FALSE);  
1247   
1248   info = g_object_get_data (G_OBJECT (child),
1249                             "testtext-move-info");
1250
1251   if (info == NULL)
1252     {
1253       info = g_new (ChildMoveInfo, 1);      
1254       info->start_x = -1;
1255       info->start_y = -1;
1256       info->button = -1;
1257       g_object_set_data_full (G_OBJECT (child),
1258                               "testtext-move-info",
1259                               info,
1260                               g_free);
1261     }
1262   
1263   switch (event->type)
1264     {
1265     case GDK_BUTTON_PRESS:
1266       if (info->button < 0)
1267         {
1268           if (gdk_pointer_grab (event->button.window,
1269                                 FALSE,
1270                                 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1271                                 GDK_BUTTON_RELEASE_MASK,
1272                                 NULL,
1273                                 NULL,
1274                                 event->button.time) != GDK_GRAB_SUCCESS)
1275             return FALSE;
1276           
1277           info->button = event->button.button;
1278           
1279           info->start_x = child->allocation.x;
1280           info->start_y = child->allocation.y;
1281           info->click_x = child->allocation.x + event->button.x;
1282           info->click_y = child->allocation.y + event->button.y;
1283         }
1284       break;
1285
1286     case GDK_BUTTON_RELEASE:
1287       if (info->button < 0)
1288         return FALSE;
1289
1290       if (info->button == event->button.button)
1291         {
1292           int x, y;
1293           
1294           gdk_pointer_ungrab (event->button.time);
1295           info->button = -1;
1296
1297           /* convert to window coords from event box coords */
1298           x = info->start_x + (event->button.x + child->allocation.x - info->click_x);
1299           y = info->start_y + (event->button.y + child->allocation.y - info->click_y);
1300
1301           gtk_text_view_move_child (text_view,
1302                                     child,
1303                                     x, y);
1304         }
1305       break;
1306
1307     case GDK_MOTION_NOTIFY:
1308       {
1309         int x, y;
1310         
1311         if (info->button < 0)
1312           return FALSE;
1313         
1314         gdk_window_get_pointer (child->window, &x, &y, NULL); /* ensure more events */
1315
1316         /* to window coords from event box coords */
1317         x += child->allocation.x;
1318         y += child->allocation.y;
1319         
1320         x = info->start_x + (x - info->click_x);
1321         y = info->start_y + (y - info->click_y);
1322         
1323         gtk_text_view_move_child (text_view,
1324                                   child,
1325                                   x, y);
1326       }
1327       break;
1328
1329     default:
1330       break;
1331     }
1332
1333   return FALSE;
1334 }
1335
1336 static void
1337 add_movable_child (GtkTextView      *text_view,
1338                    GtkTextWindowType window)
1339 {
1340   GtkWidget *event_box;
1341   GtkWidget *label;
1342   GdkColor color;
1343   
1344   label = gtk_label_new ("Drag me around");  
1345   
1346   event_box = gtk_event_box_new ();
1347   gtk_widget_add_events (event_box,
1348                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1349                          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
1350
1351   color.red = 0xffff;
1352   color.green = color.blue = 0;
1353   gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color);
1354   
1355   gtk_container_add (GTK_CONTAINER (event_box), label);
1356
1357   gtk_widget_show_all (event_box);
1358
1359   g_signal_connect (event_box, "event",
1360                     G_CALLBACK (movable_child_callback),
1361                     text_view);
1362
1363   gtk_text_view_add_child_in_window (text_view,
1364                                      event_box,
1365                                      window,
1366                                      0, 0);
1367 }
1368
1369 static void
1370 do_add_children (gpointer callback_data,
1371                  guint callback_action,
1372                  GtkWidget *widget)
1373 {
1374   View *view = view_from_widget (widget);
1375
1376   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1377                      GTK_TEXT_WINDOW_WIDGET);
1378   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1379                      GTK_TEXT_WINDOW_LEFT);
1380   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1381                      GTK_TEXT_WINDOW_RIGHT);
1382 }
1383
1384 static void
1385 do_add_focus_children (gpointer callback_data,
1386                        guint callback_action,
1387                        GtkWidget *widget)
1388 {
1389   View *view = view_from_widget (widget);
1390   GtkWidget *child;
1391   GtkTextChildAnchor *anchor;
1392   GtkTextIter iter;
1393   GtkTextView *text_view;
1394
1395   text_view = GTK_TEXT_VIEW (view->text_view);
1396   
1397   child = gtk_button_new_with_mnemonic ("Button _A in widget->window");
1398
1399   gtk_text_view_add_child_in_window (text_view,
1400                                      child,
1401                                      GTK_TEXT_WINDOW_WIDGET,
1402                                      200, 200);
1403
1404   child = gtk_button_new_with_mnemonic ("Button _B in widget->window");
1405
1406   gtk_text_view_add_child_in_window (text_view,
1407                                      child,
1408                                      GTK_TEXT_WINDOW_WIDGET,
1409                                      350, 300);
1410
1411   child = gtk_button_new_with_mnemonic ("Button _C in left window");
1412
1413   gtk_text_view_add_child_in_window (text_view,
1414                                      child,
1415                                      GTK_TEXT_WINDOW_LEFT,
1416                                      0, 0);
1417
1418   child = gtk_button_new_with_mnemonic ("Button _D in right window");
1419   
1420   gtk_text_view_add_child_in_window (text_view,
1421                                      child,
1422                                      GTK_TEXT_WINDOW_RIGHT,
1423                                      0, 0);
1424
1425   gtk_text_buffer_get_start_iter (view->buffer->buffer, &iter);
1426   
1427   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1428
1429   child = gtk_button_new_with_mnemonic ("Button _E in buffer");
1430   
1431   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1432
1433   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1434
1435   child = gtk_button_new_with_mnemonic ("Button _F in buffer");
1436   
1437   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1438
1439   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1440
1441   child = gtk_button_new_with_mnemonic ("Button _G in buffer");
1442   
1443   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1444
1445   /* show all the buttons */
1446   gtk_widget_show_all (view->text_view);
1447 }
1448
1449 static void
1450 view_init_menus (View *view)
1451 {
1452   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
1453   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
1454   GtkWidget *menu_item = NULL;
1455
1456   switch (direction)
1457     {
1458     case GTK_TEXT_DIR_LTR:
1459       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1460       break;
1461     case GTK_TEXT_DIR_RTL:
1462       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1463       break;
1464     default:
1465       break;
1466     }
1467
1468   if (menu_item)
1469     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1470
1471   switch (wrap_mode)
1472     {
1473     case GTK_WRAP_NONE:
1474       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1475       break;
1476     case GTK_WRAP_WORD:
1477       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1478       break;
1479     case GTK_WRAP_CHAR:
1480       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Chars");
1481       break;
1482     default:
1483       break;
1484     }
1485   
1486   if (menu_item)
1487     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1488 }
1489
1490 static GtkItemFactoryEntry menu_items[] =
1491 {
1492   { "/_File",            NULL,         0,           0, "<Branch>" },
1493   { "/File/_New",        "<control>N", do_new,      0, NULL },
1494   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1495   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1496   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1497   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1498   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
1499   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1500   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1501
1502   { "/_Edit", NULL, 0, 0, "<Branch>" },
1503   { "/Edit/Find...", NULL, do_search, 0, NULL },
1504
1505   { "/_Settings",         NULL,         0,                0, "<Branch>" },
1506   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAP_NONE, "<RadioItem>" },
1507   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAP_WORD, "/Settings/Wrap Off" },
1508   { "/Settings/Wrap _Chars", NULL,      do_wrap_changed,  GTK_WRAP_CHAR, "/Settings/Wrap Off" },
1509   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1510   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1511   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1512   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1513
1514   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1515   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1516   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1517   
1518   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1519   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1520
1521   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1522   { "/Settings/Sane spacing", NULL,    do_spacing_changed,  FALSE, "<RadioItem>" },
1523   { "/Settings/Funky spacing", NULL,    do_spacing_changed,  TRUE, "/Settings/Sane spacing" },
1524   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1525   { "/Settings/Don't cycle color tags", NULL,    do_color_cycle_changed,  FALSE, "<RadioItem>" },
1526   { "/Settings/Cycle colors", NULL,    do_color_cycle_changed,  TRUE, "/Settings/Don't cycle color tags" },
1527   { "/_Attributes",       NULL,         0,                0, "<Branch>" },
1528   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1529   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1530   { "/Attributes/Invisible",      NULL,         do_apply_invisible, FALSE, NULL },
1531   { "/Attributes/Visible",        NULL,         do_apply_invisible, TRUE, NULL },
1532   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1533   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1534   { "/Attributes/Color cycles",           NULL,         do_apply_colors, TRUE, NULL },
1535   { "/Attributes/No colors",              NULL,         do_apply_colors, FALSE, NULL },
1536   { "/Attributes/Remove all tags",       NULL, do_remove_tags, 0, NULL },
1537   { "/Attributes/Properties",       NULL, do_properties, 0, NULL },
1538   { "/_Test",            NULL,         0,           0, "<Branch>" },
1539   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1540   { "/Test/_Insert and scroll", NULL,         do_insert_and_scroll,  0, NULL },
1541   { "/Test/_Add fixed children", NULL,         do_add_children,  0, NULL },
1542   { "/Test/A_dd focusable children", NULL,    do_add_focus_children,  0, NULL },
1543 };
1544
1545 static gboolean
1546 save_buffer (Buffer *buffer)
1547 {
1548   GtkTextIter start, end;
1549   gchar *chars;
1550   gboolean result = FALSE;
1551   gboolean have_backup = FALSE;
1552   gchar *bak_filename;
1553   FILE *file;
1554
1555   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1556
1557   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1558   
1559   if (rename (buffer->filename, bak_filename) != 0)
1560     {
1561       if (errno != ENOENT)
1562         {
1563           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1564                                         buffer->filename, bak_filename, g_strerror (errno));
1565           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1566           g_free (err);
1567           return FALSE;
1568         }
1569     }
1570   else
1571     have_backup = TRUE;
1572   
1573   file = fopen (buffer->filename, "w");
1574   if (!file)
1575     {
1576       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1577                                     buffer->filename, bak_filename, g_strerror (errno));
1578       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1579     }
1580   else
1581     {
1582       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1583       gtk_text_buffer_get_end_iter (buffer->buffer, &end);
1584   
1585       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1586
1587       if (fputs (chars, file) == EOF ||
1588           fclose (file) == EOF)
1589         {
1590           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1591                                         buffer->filename, g_strerror (errno));
1592           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1593           g_free (err);
1594         }
1595       else
1596         {
1597           /* Success
1598            */
1599           result = TRUE;
1600           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1601         }
1602         
1603       g_free (chars);
1604     }
1605
1606   if (!result && have_backup)
1607     {
1608       if (rename (bak_filename, buffer->filename) != 0)
1609         {
1610           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1611                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1612           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1613           g_free (err);
1614         }
1615     }
1616
1617   g_free (bak_filename);
1618   
1619   return result;
1620 }
1621
1622 static gboolean
1623 save_as_ok_func (const char *filename, gpointer data)
1624 {
1625   Buffer *buffer = data;
1626   char *old_filename = buffer->filename;
1627
1628   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1629     {
1630       struct stat statbuf;
1631
1632       if (stat (filename, &statbuf) == 0)
1633         {
1634           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1635           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1636           g_free (err);
1637
1638           if (result != 0)
1639             return FALSE;
1640         }
1641     }
1642   
1643   buffer->filename = g_strdup (filename);
1644
1645   if (save_buffer (buffer))
1646     {
1647       g_free (old_filename);
1648       buffer_filename_set (buffer);
1649       return TRUE;
1650     }
1651   else
1652     {
1653       g_free (buffer->filename);
1654       buffer->filename = old_filename;
1655       return FALSE;
1656     }
1657 }
1658
1659 static gboolean
1660 save_as_buffer (Buffer *buffer)
1661 {
1662   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1663 }
1664
1665 static gboolean
1666 check_buffer_saved (Buffer *buffer)
1667 {
1668   if (gtk_text_buffer_get_modified (buffer->buffer))
1669     {
1670       char *pretty_name = buffer_pretty_name (buffer);
1671       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1672       gint result;
1673       
1674       g_free (pretty_name);
1675       
1676       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1677       g_free (msg);
1678   
1679       if (result == 0)
1680         return save_as_buffer (buffer);
1681       else if (result == 1)
1682         return TRUE;
1683       else
1684         return FALSE;
1685     }
1686   else
1687     return TRUE;
1688 }
1689
1690 #define N_COLORS 16
1691
1692 static Buffer *
1693 create_buffer (void)
1694 {
1695   Buffer *buffer;
1696   PangoTabArray *tabs;
1697   gint i;
1698   
1699   buffer = g_new (Buffer, 1);
1700
1701   buffer->buffer = gtk_text_buffer_new (NULL);
1702   
1703   buffer->refcount = 1;
1704   buffer->filename = NULL;
1705   buffer->untitled_serial = -1;
1706
1707   buffer->color_tags = NULL;
1708   buffer->color_cycle_timeout = 0;
1709   buffer->start_hue = 0.0;
1710   
1711   i = 0;
1712   while (i < N_COLORS)
1713     {
1714       GtkTextTag *tag;
1715
1716       tag = gtk_text_buffer_create_tag (buffer->buffer, NULL, NULL);
1717       
1718       buffer->color_tags = g_slist_prepend (buffer->color_tags, tag);
1719       
1720       ++i;
1721     }
1722
1723 #if 0  
1724   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1725                                                       "invisible", TRUE, NULL);
1726 #endif  
1727   
1728   buffer->not_editable_tag =
1729     gtk_text_buffer_create_tag (buffer->buffer, NULL,
1730                                 "editable", FALSE,
1731                                 "foreground", "purple", NULL);
1732
1733   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1734                                                        "foreground", "red", NULL);
1735
1736   tabs = pango_tab_array_new_with_positions (4,
1737                                              TRUE,
1738                                              PANGO_TAB_LEFT, 10,
1739                                              PANGO_TAB_LEFT, 30,
1740                                              PANGO_TAB_LEFT, 60,
1741                                              PANGO_TAB_LEFT, 120);
1742   
1743   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1744                                                         "tabs", tabs,
1745                                                         "foreground", "green", NULL);
1746
1747   pango_tab_array_free (tabs);
1748   
1749   buffers = g_slist_prepend (buffers, buffer);
1750   
1751   return buffer;
1752 }
1753
1754 static char *
1755 buffer_pretty_name (Buffer *buffer)
1756 {
1757   if (buffer->filename)
1758     {
1759       char *p;
1760       char *result = g_path_get_basename (buffer->filename);
1761       p = strchr (result, '/');
1762       if (p)
1763         *p = '\0';
1764
1765       return result;
1766     }
1767   else
1768     {
1769       if (buffer->untitled_serial == -1)
1770         buffer->untitled_serial = untitled_serial++;
1771
1772       if (buffer->untitled_serial == 1)
1773         return g_strdup ("Untitled");
1774       else
1775         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1776     }
1777 }
1778
1779 static void
1780 buffer_filename_set (Buffer *buffer)
1781 {
1782   GSList *tmp_list = views;
1783
1784   while (tmp_list)
1785     {
1786       View *view = tmp_list->data;
1787
1788       if (view->buffer == buffer)
1789         view_set_title (view);
1790
1791       tmp_list = tmp_list->next;
1792     }
1793 }
1794
1795 static void
1796 buffer_search (Buffer     *buffer,
1797                const char *str,
1798                View       *view,
1799                gboolean forward)
1800 {
1801   GtkTextIter iter;
1802   GtkTextIter start, end;
1803   GtkWidget *dialog;
1804   int i;
1805   
1806   /* remove tag from whole buffer */
1807   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1808   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1809                               &start, &end );
1810   
1811   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1812                                     gtk_text_buffer_get_mark (buffer->buffer,
1813                                                               "insert"));
1814
1815   i = 0;
1816   if (*str != '\0')
1817     {
1818       GtkTextIter match_start, match_end;
1819
1820       if (forward)
1821         {
1822           while (gtk_text_iter_forward_search (&iter, str,
1823                                                GTK_TEXT_SEARCH_VISIBLE_ONLY |
1824                                                GTK_TEXT_SEARCH_TEXT_ONLY,
1825                                                &match_start, &match_end,
1826                                                NULL))
1827             {
1828               ++i;
1829               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1830                                          &match_start, &match_end);
1831               
1832               iter = match_end;
1833             }
1834         }
1835       else
1836         {
1837           while (gtk_text_iter_backward_search (&iter, str,
1838                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY |
1839                                                 GTK_TEXT_SEARCH_TEXT_ONLY,
1840                                                 &match_start, &match_end,
1841                                                 NULL))
1842             {
1843               ++i;
1844               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1845                                          &match_start, &match_end);
1846               
1847               iter = match_start;
1848             }
1849         }
1850     }
1851
1852   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1853                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1854                                    GTK_MESSAGE_INFO,
1855                                    GTK_BUTTONS_OK,
1856                                    "%d strings found and marked in red",
1857                                    i);
1858
1859   g_signal_connect_swapped (dialog,
1860                             "response",
1861                             G_CALLBACK (gtk_widget_destroy), dialog);
1862   
1863   gtk_widget_show (dialog);
1864 }
1865
1866 static void
1867 buffer_search_forward (Buffer *buffer, const char *str,
1868                        View *view)
1869 {
1870   buffer_search (buffer, str, view, TRUE);
1871 }
1872
1873 static void
1874 buffer_search_backward (Buffer *buffer, const char *str,
1875                         View *view)
1876 {
1877   buffer_search (buffer, str, view, FALSE);
1878 }
1879
1880 static void
1881 buffer_ref (Buffer *buffer)
1882 {
1883   buffer->refcount++;
1884 }
1885
1886 static void
1887 buffer_unref (Buffer *buffer)
1888 {
1889   buffer->refcount--;
1890   if (buffer->refcount == 0)
1891     {
1892       buffer_set_colors (buffer, FALSE);
1893       buffers = g_slist_remove (buffers, buffer);
1894       g_object_unref (buffer->buffer);
1895       g_free (buffer->filename);
1896       g_free (buffer);
1897     }
1898 }
1899
1900 static void
1901 hsv_to_rgb (gdouble *h,
1902             gdouble *s,
1903             gdouble *v)
1904 {
1905   gdouble hue, saturation, value;
1906   gdouble f, p, q, t;
1907
1908   if (*s == 0.0)
1909     {
1910       *h = *v;
1911       *s = *v;
1912       *v = *v; /* heh */
1913     }
1914   else
1915     {
1916       hue = *h * 6.0;
1917       saturation = *s;
1918       value = *v;
1919       
1920       if (hue >= 6.0)
1921         hue = 0.0;
1922       
1923       f = hue - (int) hue;
1924       p = value * (1.0 - saturation);
1925       q = value * (1.0 - saturation * f);
1926       t = value * (1.0 - saturation * (1.0 - f));
1927       
1928       switch ((int) hue)
1929         {
1930         case 0:
1931           *h = value;
1932           *s = t;
1933           *v = p;
1934           break;
1935           
1936         case 1:
1937           *h = q;
1938           *s = value;
1939           *v = p;
1940           break;
1941           
1942         case 2:
1943           *h = p;
1944           *s = value;
1945           *v = t;
1946           break;
1947           
1948         case 3:
1949           *h = p;
1950           *s = q;
1951           *v = value;
1952           break;
1953           
1954         case 4:
1955           *h = t;
1956           *s = p;
1957           *v = value;
1958           break;
1959           
1960         case 5:
1961           *h = value;
1962           *s = p;
1963           *v = q;
1964           break;
1965           
1966         default:
1967           g_assert_not_reached ();
1968         }
1969     }
1970 }
1971
1972 static void
1973 hue_to_color (gdouble   hue,
1974               GdkColor *color)
1975 {
1976   gdouble h, s, v;
1977
1978   h = hue;
1979   s = 1.0;
1980   v = 1.0;
1981
1982   g_return_if_fail (hue <= 1.0);
1983   
1984   hsv_to_rgb (&h, &s, &v);
1985
1986   color->red = h * 65535;
1987   color->green = s * 65535;
1988   color->blue = v * 65535;
1989 }
1990
1991
1992 static gint
1993 color_cycle_timeout (gpointer data)
1994 {
1995   Buffer *buffer = data;
1996
1997   buffer_cycle_colors (buffer);
1998
1999   return TRUE;
2000 }
2001
2002 static void
2003 buffer_set_colors (Buffer  *buffer,
2004                    gboolean enabled)
2005 {
2006   GSList *tmp;
2007   gdouble hue = 0.0;
2008
2009   if (enabled && buffer->color_cycle_timeout == 0)
2010     buffer->color_cycle_timeout = gtk_timeout_add (200, color_cycle_timeout, buffer);
2011   else if (!enabled && buffer->color_cycle_timeout != 0)
2012     {
2013       gtk_timeout_remove (buffer->color_cycle_timeout);
2014       buffer->color_cycle_timeout = 0;
2015     }
2016     
2017   tmp = buffer->color_tags;
2018   while (tmp != NULL)
2019     {
2020       if (enabled)
2021         {
2022           GdkColor color;
2023           
2024           hue_to_color (hue, &color);
2025
2026           g_object_set (G_OBJECT (tmp->data),
2027                           "foreground_gdk", &color,
2028                           NULL);
2029         }
2030       else
2031         g_object_set (G_OBJECT (tmp->data),
2032                         "foreground_set", FALSE,
2033                         NULL);
2034
2035       hue += 1.0 / N_COLORS;
2036       
2037       tmp = g_slist_next (tmp);
2038     }
2039 }
2040
2041 static void
2042 buffer_cycle_colors (Buffer *buffer)
2043 {
2044   GSList *tmp;
2045   gdouble hue = buffer->start_hue;
2046   
2047   tmp = buffer->color_tags;
2048   while (tmp != NULL)
2049     {
2050       GdkColor color;
2051       
2052       hue_to_color (hue, &color);
2053       
2054       g_object_set (G_OBJECT (tmp->data),
2055                     "foreground_gdk", &color,
2056                     NULL);
2057
2058       hue += 1.0 / N_COLORS;
2059       if (hue > 1.0)
2060         hue = 0.0;
2061       
2062       tmp = g_slist_next (tmp);
2063     }
2064
2065   buffer->start_hue += 1.0 / N_COLORS;
2066   if (buffer->start_hue > 1.0)
2067     buffer->start_hue = 0.0;
2068 }
2069
2070 static void
2071 close_view (View *view)
2072 {
2073   views = g_slist_remove (views, view);
2074   buffer_unref (view->buffer);
2075   gtk_widget_destroy (view->window);
2076   g_object_unref (view->item_factory);
2077   
2078   g_free (view);
2079   
2080   if (!views)
2081     gtk_main_quit ();
2082 }
2083
2084 static void
2085 check_close_view (View *view)
2086 {
2087   if (view->buffer->refcount > 1 ||
2088       check_buffer_saved (view->buffer))
2089     close_view (view);
2090 }
2091
2092 static void
2093 view_set_title (View *view)
2094 {
2095   char *pretty_name = buffer_pretty_name (view->buffer);
2096   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
2097
2098   gtk_window_set_title (GTK_WINDOW (view->window), title);
2099
2100   g_free (pretty_name);
2101   g_free (title);
2102 }
2103
2104 static void
2105 cursor_set_callback (GtkTextBuffer     *buffer,
2106                      const GtkTextIter *location,
2107                      GtkTextMark       *mark,
2108                      gpointer           user_data)
2109 {
2110   GtkTextView *text_view;
2111
2112   /* Redraw tab windows if the cursor moves
2113    * on the mapped widget (windows may not exist before realization...
2114    */
2115   
2116   text_view = GTK_TEXT_VIEW (user_data);
2117   
2118   if (GTK_WIDGET_MAPPED (text_view) &&
2119       mark == gtk_text_buffer_get_insert (buffer))
2120     {
2121       GdkWindow *tab_window;
2122
2123       tab_window = gtk_text_view_get_window (text_view,
2124                                              GTK_TEXT_WINDOW_TOP);
2125
2126       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2127       
2128       tab_window = gtk_text_view_get_window (text_view,
2129                                              GTK_TEXT_WINDOW_BOTTOM);
2130
2131       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2132     }
2133 }
2134
2135 static gint
2136 tab_stops_expose (GtkWidget      *widget,
2137                   GdkEventExpose *event,
2138                   gpointer        user_data)
2139 {
2140   gint first_x;
2141   gint last_x;
2142   gint i;
2143   GdkWindow *top_win;
2144   GdkWindow *bottom_win;
2145   GtkTextView *text_view;
2146   GtkTextWindowType type;
2147   GdkDrawable *target;
2148   gint *positions = NULL;
2149   gint size;
2150   GtkTextAttributes *attrs;
2151   GtkTextIter insert;
2152   GtkTextBuffer *buffer;
2153   gboolean in_pixels;
2154   
2155   text_view = GTK_TEXT_VIEW (widget);
2156   
2157   /* See if this expose is on the tab stop window */
2158   top_win = gtk_text_view_get_window (text_view,
2159                                       GTK_TEXT_WINDOW_TOP);
2160
2161   bottom_win = gtk_text_view_get_window (text_view,
2162                                          GTK_TEXT_WINDOW_BOTTOM);
2163
2164   if (event->window == top_win)
2165     {
2166       type = GTK_TEXT_WINDOW_TOP;
2167       target = top_win;
2168     }
2169   else if (event->window == bottom_win)
2170     {
2171       type = GTK_TEXT_WINDOW_BOTTOM;
2172       target = bottom_win;
2173     }
2174   else
2175     return FALSE;
2176   
2177   first_x = event->area.x;
2178   last_x = first_x + event->area.width;
2179
2180   gtk_text_view_window_to_buffer_coords (text_view,
2181                                          type,
2182                                          first_x,
2183                                          0,
2184                                          &first_x,
2185                                          NULL);
2186
2187   gtk_text_view_window_to_buffer_coords (text_view,
2188                                          type,
2189                                          last_x,
2190                                          0,
2191                                          &last_x,
2192                                          NULL);
2193
2194   buffer = gtk_text_view_get_buffer (text_view);
2195
2196   gtk_text_buffer_get_iter_at_mark (buffer,
2197                                     &insert,
2198                                     gtk_text_buffer_get_mark (buffer,
2199                                                               "insert"));
2200   
2201   attrs = gtk_text_attributes_new ();
2202
2203   gtk_text_iter_get_attributes (&insert, attrs);
2204
2205   if (attrs->tabs)
2206     {
2207       size = pango_tab_array_get_size (attrs->tabs);
2208       
2209       pango_tab_array_get_tabs (attrs->tabs,
2210                                 NULL,
2211                                 &positions);
2212
2213       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
2214     }
2215   else
2216     {
2217       size = 0;
2218       in_pixels = FALSE;
2219     }
2220       
2221   gtk_text_attributes_unref (attrs);
2222   
2223   i = 0;
2224   while (i < size)
2225     {
2226       gint pos;
2227
2228       if (!in_pixels)
2229         positions[i] = PANGO_PIXELS (positions[i]);
2230       
2231       gtk_text_view_buffer_to_window_coords (text_view,
2232                                              type,
2233                                              positions[i],
2234                                              0,
2235                                              &pos,
2236                                              NULL);
2237       
2238       gdk_draw_line (target, 
2239                      widget->style->fg_gc [widget->state],
2240                      pos, 0,
2241                      pos, 15); 
2242       
2243       ++i;
2244     }
2245
2246   g_free (positions);
2247
2248   return TRUE;
2249 }
2250
2251 static void
2252 get_lines (GtkTextView  *text_view,
2253            gint          first_y,
2254            gint          last_y,
2255            GArray       *buffer_coords,
2256            GArray       *numbers,
2257            gint         *countp)
2258 {
2259   GtkTextIter iter;
2260   gint count;
2261   gint size;  
2262
2263   g_array_set_size (buffer_coords, 0);
2264   g_array_set_size (numbers, 0);
2265   
2266   /* Get iter at first y */
2267   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
2268
2269   /* For each iter, get its location and add it to the arrays.
2270    * Stop when we pass last_y
2271    */
2272   count = 0;
2273   size = 0;
2274
2275   while (!gtk_text_iter_is_end (&iter))
2276     {
2277       gint y, height;
2278       gint line_num;
2279       
2280       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
2281
2282       g_array_append_val (buffer_coords, y);
2283       line_num = gtk_text_iter_get_line (&iter);
2284       g_array_append_val (numbers, line_num);
2285       
2286       ++count;
2287
2288       if ((y + height) >= last_y)
2289         break;
2290       
2291       gtk_text_iter_forward_line (&iter);
2292     }
2293
2294   *countp = count;
2295 }
2296
2297 static gint
2298 line_numbers_expose (GtkWidget      *widget,
2299                      GdkEventExpose *event,
2300                      gpointer        user_data)
2301 {
2302   gint count;
2303   GArray *numbers;
2304   GArray *pixels;
2305   gint first_y;
2306   gint last_y;
2307   gint i;
2308   GdkWindow *left_win;
2309   GdkWindow *right_win;
2310   PangoLayout *layout;
2311   GtkTextView *text_view;
2312   GtkTextWindowType type;
2313   GdkDrawable *target;
2314   
2315   text_view = GTK_TEXT_VIEW (widget);
2316   
2317   /* See if this expose is on the line numbers window */
2318   left_win = gtk_text_view_get_window (text_view,
2319                                        GTK_TEXT_WINDOW_LEFT);
2320
2321   right_win = gtk_text_view_get_window (text_view,
2322                                         GTK_TEXT_WINDOW_RIGHT);
2323
2324   if (event->window == left_win)
2325     {
2326       type = GTK_TEXT_WINDOW_LEFT;
2327       target = left_win;
2328     }
2329   else if (event->window == right_win)
2330     {
2331       type = GTK_TEXT_WINDOW_RIGHT;
2332       target = right_win;
2333     }
2334   else
2335     return FALSE;
2336   
2337   first_y = event->area.y;
2338   last_y = first_y + event->area.height;
2339
2340   gtk_text_view_window_to_buffer_coords (text_view,
2341                                          type,
2342                                          0,
2343                                          first_y,
2344                                          NULL,
2345                                          &first_y);
2346
2347   gtk_text_view_window_to_buffer_coords (text_view,
2348                                          type,
2349                                          0,
2350                                          last_y,
2351                                          NULL,
2352                                          &last_y);
2353
2354   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
2355   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
2356   
2357   get_lines (text_view,
2358              first_y,
2359              last_y,
2360              pixels,
2361              numbers,
2362              &count);
2363   
2364   /* Draw fully internationalized numbers! */
2365   
2366   layout = gtk_widget_create_pango_layout (widget, "");
2367   
2368   i = 0;
2369   while (i < count)
2370     {
2371       gint pos;
2372       gchar *str;
2373       
2374       gtk_text_view_buffer_to_window_coords (text_view,
2375                                              type,
2376                                              0,
2377                                              g_array_index (pixels, gint, i),
2378                                              NULL,
2379                                              &pos);
2380
2381       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
2382
2383       pango_layout_set_text (layout, str, -1);
2384
2385       gtk_paint_layout (widget->style,
2386                         target,
2387                         GTK_WIDGET_STATE (widget),
2388                         FALSE,
2389                         NULL,
2390                         widget,
2391                         NULL,
2392                         2, pos + 2,
2393                         layout);
2394
2395       g_free (str);
2396       
2397       ++i;
2398     }
2399
2400   g_array_free (pixels, TRUE);
2401   g_array_free (numbers, TRUE);
2402   
2403   g_object_unref (layout);
2404
2405   /* don't stop emission, need to draw children */
2406   return FALSE;
2407 }
2408
2409 static View *
2410 create_view (Buffer *buffer)
2411 {
2412   View *view;
2413   
2414   GtkWidget *sw;
2415   GtkWidget *vbox;
2416   
2417   view = g_new0 (View, 1);
2418   views = g_slist_prepend (views, view);
2419
2420   view->buffer = buffer;
2421   buffer_ref (buffer);
2422   
2423   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2424   g_object_set_data (G_OBJECT (view->window), "view", view);
2425   
2426   g_signal_connect (view->window, "delete_event",
2427                     G_CALLBACK (delete_event_cb), NULL);
2428
2429   view->accel_group = gtk_accel_group_new ();
2430   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
2431   g_object_set_data (G_OBJECT (view->item_factory), "view", view);
2432   
2433   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
2434
2435   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
2436
2437   vbox = gtk_vbox_new (FALSE, 0);
2438   gtk_container_add (GTK_CONTAINER (view->window), vbox);
2439
2440   gtk_box_pack_start (GTK_BOX (vbox),
2441                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
2442                       FALSE, FALSE, 0);
2443   
2444   sw = gtk_scrolled_window_new (NULL, NULL);
2445   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2446                                  GTK_POLICY_AUTOMATIC,
2447                                  GTK_POLICY_AUTOMATIC);
2448
2449   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
2450   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
2451                                GTK_WRAP_WORD);
2452
2453   /* Make sure border width works, no real reason to do this other than testing */
2454   gtk_container_set_border_width (GTK_CONTAINER (view->text_view),
2455                                   10);
2456   
2457   /* Draw tab stops in the top and bottom windows. */
2458   
2459   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2460                                         GTK_TEXT_WINDOW_TOP,
2461                                         15);
2462
2463   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2464                                         GTK_TEXT_WINDOW_BOTTOM,
2465                                         15);
2466
2467   g_signal_connect (view->text_view,
2468                     "expose_event",
2469                     G_CALLBACK (tab_stops_expose),
2470                     NULL);  
2471
2472   g_signal_connect (view->buffer->buffer,
2473                     "mark_set",
2474                     G_CALLBACK (cursor_set_callback),
2475                     view->text_view);
2476   
2477   /* Draw line numbers in the side windows; we should really be
2478    * more scientific about what width we set them to.
2479    */
2480   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2481                                         GTK_TEXT_WINDOW_RIGHT,
2482                                         30);
2483   
2484   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2485                                         GTK_TEXT_WINDOW_LEFT,
2486                                         30);
2487   
2488   g_signal_connect (view->text_view,
2489                     "expose_event",
2490                     G_CALLBACK (line_numbers_expose),
2491                     NULL);
2492   
2493   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
2494   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
2495
2496   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
2497
2498   gtk_widget_grab_focus (view->text_view);
2499
2500   view_set_title (view);
2501   view_init_menus (view);
2502
2503   view_add_example_widgets (view);
2504   
2505   gtk_widget_show_all (view->window);
2506   return view;
2507 }
2508
2509 static void
2510 view_add_example_widgets (View *view)
2511 {
2512   GtkTextChildAnchor *anchor;
2513   Buffer *buffer;
2514
2515   buffer = view->buffer;
2516   
2517   anchor = g_object_get_data (G_OBJECT (buffer->buffer),
2518                               "anchor");
2519
2520   if (anchor && !gtk_text_child_anchor_get_deleted (anchor))
2521     {
2522       GtkWidget *widget;
2523
2524       widget = gtk_button_new_with_label ("Foo");
2525       
2526       gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view->text_view),
2527                                          widget,
2528                                          anchor);
2529
2530       gtk_widget_show (widget);
2531     }
2532 }
2533
2534 void
2535 test_init ()
2536 {
2537   if (g_file_test ("../gdk-pixbuf/libpixbufloader-pnm.la",
2538                    G_FILE_TEST_EXISTS))
2539     {
2540       putenv ("GDK_PIXBUF_MODULE_FILE=../gdk-pixbuf/gdk-pixbuf.loaders");
2541       putenv ("GTK_IM_MODULE_FILE=../modules/input/gtk.immodules");
2542     }
2543 }
2544
2545 int
2546 main (int argc, char** argv)
2547 {
2548   Buffer *buffer;
2549   View *view;
2550   int i;
2551
2552   test_init ();
2553   gtk_init (&argc, &argv);
2554   
2555   buffer = create_buffer ();
2556   view = create_view (buffer);
2557   buffer_unref (buffer);
2558   
2559   push_active_window (GTK_WINDOW (view->window));
2560   for (i=1; i < argc; i++)
2561     {
2562       char *filename;
2563
2564       /* Quick and dirty canonicalization - better should be in GLib
2565        */
2566
2567       if (!g_path_is_absolute (argv[i]))
2568         {
2569           char *cwd = g_get_current_dir ();
2570           filename = g_strconcat (cwd, "/", argv[i], NULL);
2571           g_free (cwd);
2572         }
2573       else
2574         filename = argv[i];
2575
2576       open_ok_func (filename, view);
2577
2578       if (filename != argv[i])
2579         g_free (filename);
2580     }
2581   pop_active_window ();
2582   
2583   gtk_main ();
2584
2585   return 0;
2586 }
2587
2588