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