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