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