]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Fixes for uninstalled operation with module suffixes other than .so and in
[~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   tag = gtk_text_buffer_create_tag (buffer, "negative_indent", NULL);
523       
524   g_object_set (G_OBJECT (tag),
525                 "indent", -25,
526                 NULL);
527   
528   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
529
530   anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
531
532   g_object_ref (G_OBJECT (anchor));
533   
534   g_object_set_data_full (G_OBJECT (buffer), "anchor", anchor,
535                           (GDestroyNotify) g_object_unref);
536   
537   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
538   
539   i = 0;
540   while (i < 100)
541     {
542       GtkTextMark * temp_mark;
543       
544       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
545           
546       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
547           
548       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",
549                             i);
550       
551       gtk_text_buffer_insert (buffer, &iter, str, -1);
552
553       g_free (str);
554       
555       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 5);
556           
557       gtk_text_buffer_insert (buffer, &iter,
558                              "(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"
559                              /* This is UTF8 stuff, Emacs doesn't
560                                 really know how to display it */
561                              "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);
562
563       temp_mark =
564         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
565
566 #if 1
567       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 6);
568       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 13);
569
570       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
571
572       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 10);
573       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 16);
574
575       gtk_text_buffer_apply_tag_by_name (buffer, "underline", &iter, &iter2);
576
577       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 14);
578       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 24);
579
580       gtk_text_buffer_apply_tag_by_name (buffer, "strikethrough", &iter, &iter2);
581           
582       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 9);
583       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 16);
584
585       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
586   
587       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 2);
588       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 10);
589
590       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
591
592       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 8);
593       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 15);
594
595       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
596 #endif
597
598       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
599       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
600           
601       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
602       gtk_text_buffer_apply_tag_by_name (buffer, "centered", &iter2, &iter);
603
604       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
605       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
606       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
607       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
608       gtk_text_buffer_apply_tag_by_name (buffer, "rtl_quote", &iter2, &iter);
609
610       gtk_text_buffer_insert_with_tags (buffer, &iter,
611                                         "Paragraph with negative indentation. blah blah blah blah blah. The quick brown fox jumped over the lazy dog.\n",
612                                         -1,
613                                         gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
614                                                                    "negative_indent"),
615                                         NULL);
616       
617       ++i;
618     }
619
620   g_object_unref (G_OBJECT (pixbuf));
621   
622   printf ("%d lines %d chars\n",
623           gtk_text_buffer_get_line_count (buffer),
624           gtk_text_buffer_get_char_count (buffer));
625
626   /* Move cursor to start */
627   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
628   gtk_text_buffer_place_cursor (buffer, &iter);
629   
630   gtk_text_buffer_set_modified (buffer, FALSE);
631 }
632
633 gboolean
634 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
635 {
636   FILE* f;
637   gchar buf[2048];
638   gint remaining = 0;
639   GtkTextIter iter, end;
640
641   f = fopen (filename, "r");
642   
643   if (f == NULL)
644     {
645       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
646                                     filename, g_strerror (errno));
647       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
648       g_free (err);
649       return FALSE;
650     }
651   
652   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
653   while (!feof (f))
654     {
655       gint count;
656       const char *leftover;
657       int to_read = 2047  - remaining;
658
659       count = fread (buf + remaining, 1, to_read, f);
660       buf[count + remaining] = '\0';
661
662       g_utf8_validate (buf, count + remaining, &leftover);
663       
664       g_assert (g_utf8_validate (buf, leftover - buf, NULL));
665       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
666
667       remaining = (buf + remaining + count) - leftover;
668       g_memmove (buf, leftover, remaining);
669
670       if (remaining > 6 || count < to_read)
671           break;
672     }
673
674   if (remaining)
675     {
676       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
677       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
678       g_free (err);
679     }
680   
681   /* We had a newline in the buffer to begin with. (The buffer always contains
682    * a newline, so we delete to the end of the buffer to clean up.
683    */
684   gtk_text_buffer_get_end_iter (buffer, &end);
685   gtk_text_buffer_delete (buffer, &iter, &end);
686   
687   gtk_text_buffer_set_modified (buffer, FALSE);
688
689   return TRUE;
690 }
691
692 static gint
693 delete_event_cb (GtkWidget *window, GdkEventAny *event, gpointer data)
694 {
695   View *view = view_from_widget (window);
696
697   push_active_window (GTK_WINDOW (window));
698   check_close_view (view);
699   pop_active_window ();
700
701   return TRUE;
702 }
703
704 /*
705  * Menu callbacks
706  */
707
708 static View *
709 get_empty_view (View *view)
710 {
711   if (!view->buffer->filename &&
712       !gtk_text_buffer_get_modified (view->buffer->buffer))
713     return view;
714   else
715     return create_view (create_buffer ());
716 }
717
718 static View *
719 view_from_widget (GtkWidget *widget)
720 {
721   if (GTK_IS_MENU_ITEM (widget))
722     {
723       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
724       return g_object_get_data (G_OBJECT (item_factory), "view");      
725     }
726   else
727     {
728       GtkWidget *app = gtk_widget_get_toplevel (widget);
729       return g_object_get_data (G_OBJECT (app), "view");
730     }
731 }
732
733 static void
734 do_new (gpointer             callback_data,
735         guint                callback_action,
736         GtkWidget           *widget)
737 {
738   create_view (create_buffer ());
739 }
740
741 static void
742 do_new_view (gpointer             callback_data,
743              guint                callback_action,
744              GtkWidget           *widget)
745 {
746   View *view = view_from_widget (widget);
747   
748   create_view (view->buffer);
749 }
750
751 gboolean
752 open_ok_func (const char *filename, gpointer data)
753 {
754   View *view = data;
755   View *new_view = get_empty_view (view);
756
757   if (!fill_file_buffer (new_view->buffer->buffer, filename))
758     {
759       if (new_view != view)
760         close_view (new_view);
761       return FALSE;
762     }
763   else
764     {
765       g_free (new_view->buffer->filename);
766       new_view->buffer->filename = g_strdup (filename);
767       buffer_filename_set (new_view->buffer);
768       
769       return TRUE;
770     }
771 }
772
773 static void
774 do_open (gpointer             callback_data,
775          guint                callback_action,
776          GtkWidget           *widget)
777 {
778   View *view = view_from_widget (widget);
779
780   push_active_window (GTK_WINDOW (view->window));
781   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
782   pop_active_window ();
783 }
784
785 static void
786 do_save_as (gpointer             callback_data,
787             guint                callback_action,
788             GtkWidget           *widget)
789 {
790   View *view = view_from_widget (widget);  
791
792   push_active_window (GTK_WINDOW (view->window));
793   save_as_buffer (view->buffer);
794   pop_active_window ();
795 }
796
797 static void
798 do_save (gpointer             callback_data,
799          guint                callback_action,
800          GtkWidget           *widget)
801 {
802   View *view = view_from_widget (widget);
803
804   push_active_window (GTK_WINDOW (view->window));
805   if (!view->buffer->filename)
806     do_save_as (callback_data, callback_action, widget);
807   else
808     save_buffer (view->buffer);
809   pop_active_window ();
810 }
811
812 static void
813 do_close   (gpointer             callback_data,
814             guint                callback_action,
815             GtkWidget           *widget)
816 {
817   View *view = view_from_widget (widget);
818
819   push_active_window (GTK_WINDOW (view->window));
820   check_close_view (view);
821   pop_active_window ();
822 }
823
824 static void
825 do_exit    (gpointer             callback_data,
826             guint                callback_action,
827             GtkWidget           *widget)
828 {
829   View *view = view_from_widget (widget);
830
831   GSList *tmp_list = buffers;
832
833   push_active_window (GTK_WINDOW (view->window));
834   while (tmp_list)
835     {
836       if (!check_buffer_saved (tmp_list->data))
837         return;
838
839       tmp_list = tmp_list->next;
840     }
841
842   gtk_main_quit ();
843   pop_active_window ();
844 }
845
846 static void
847 do_example (gpointer             callback_data,
848             guint                callback_action,
849             GtkWidget           *widget)
850 {
851   View *view = view_from_widget (widget);
852   View *new_view;
853
854   new_view = get_empty_view (view);
855   
856   fill_example_buffer (new_view->buffer->buffer);
857
858   view_add_example_widgets (new_view);
859 }
860
861
862 static void
863 do_insert_and_scroll (gpointer             callback_data,
864                       guint                callback_action,
865                       GtkWidget           *widget)
866 {
867   View *view = view_from_widget (widget);
868   GtkTextBuffer *buffer;
869   GtkTextIter start, end;
870   GtkTextMark *mark;
871   
872   buffer = view->buffer->buffer;
873
874   gtk_text_buffer_get_bounds (buffer, &start, &end);
875   mark = gtk_text_buffer_create_mark (buffer, NULL, &end, /* right grav */ FALSE);
876
877   gtk_text_buffer_insert (buffer, &end,
878                           "Hello this is multiple lines of text\n"
879                           "Line 1\n"  "Line 2\n"
880                           "Line 3\n"  "Line 4\n"
881                           "Line 5\n",
882                           -1);
883
884   gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view->text_view), mark,
885                                 0, TRUE, 0.0, 1.0);
886   gtk_text_buffer_delete_mark (buffer, mark);
887 }
888
889 static void
890 do_wrap_changed (gpointer             callback_data,
891                  guint                callback_action,
892                  GtkWidget           *widget)
893 {
894   View *view = view_from_widget (widget);
895
896   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
897 }
898
899 static void
900 do_direction_changed (gpointer             callback_data,
901                       guint                callback_action,
902                       GtkWidget           *widget)
903 {
904   View *view = view_from_widget (widget);
905   
906   gtk_widget_set_direction (view->text_view, callback_action);
907   gtk_widget_queue_resize (view->text_view);
908 }
909
910
911 static void
912 do_spacing_changed (gpointer             callback_data,
913                     guint                callback_action,
914                     GtkWidget           *widget)
915 {
916   View *view = view_from_widget (widget);
917
918   if (callback_action)
919     {
920       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
921                                             23);
922       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
923                                             21);
924       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
925                                             9);
926     }
927   else
928     {
929       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
930                                             0);
931       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
932                                             0);
933       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
934                                             0);
935     }
936 }
937
938 static void
939 do_editable_changed (gpointer callback_data,
940                      guint callback_action,
941                      GtkWidget *widget)
942 {
943   View *view = view_from_widget (widget);
944
945   gtk_text_view_set_editable (GTK_TEXT_VIEW (view->text_view), callback_action);
946 }
947
948 static void
949 do_cursor_visible_changed (gpointer callback_data,
950                            guint callback_action,
951                            GtkWidget *widget)
952 {
953   View *view = view_from_widget (widget);
954
955   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view->text_view), callback_action);
956 }
957
958 static void
959 do_color_cycle_changed (gpointer callback_data,
960                         guint callback_action,
961                         GtkWidget *widget)
962 {
963   View *view = view_from_widget (widget);
964
965   buffer_set_colors (view->buffer, callback_action);
966 }
967
968 static void
969 do_apply_editable (gpointer callback_data,
970                    guint callback_action,
971                    GtkWidget *widget)
972 {
973   View *view = view_from_widget (widget);
974   GtkTextIter start;
975   GtkTextIter end;
976   
977   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
978                                             &start, &end))
979     {
980       if (callback_action)
981         {
982           gtk_text_buffer_remove_tag (view->buffer->buffer,
983                                       view->buffer->not_editable_tag,
984                                       &start, &end);
985         }
986       else
987         {
988           gtk_text_buffer_apply_tag (view->buffer->buffer,
989                                      view->buffer->not_editable_tag,
990                                      &start, &end);
991         }
992     }
993 }
994
995 static void
996 do_apply_invisible (gpointer callback_data,
997                     guint callback_action,
998                     GtkWidget *widget)
999 {
1000   View *view = view_from_widget (widget);
1001   GtkTextIter start;
1002   GtkTextIter end;
1003   
1004   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1005                                             &start, &end))
1006     {
1007       if (callback_action)
1008         {
1009           gtk_text_buffer_remove_tag (view->buffer->buffer,
1010                                       view->buffer->invisible_tag,
1011                                       &start, &end);
1012         }
1013       else
1014         {
1015           gtk_text_buffer_apply_tag (view->buffer->buffer,
1016                                      view->buffer->invisible_tag,
1017                                      &start, &end);
1018         }
1019     }
1020 }
1021
1022 static void
1023 do_apply_tabs (gpointer callback_data,
1024                guint callback_action,
1025                GtkWidget *widget)
1026 {
1027   View *view = view_from_widget (widget);
1028   GtkTextIter start;
1029   GtkTextIter end;
1030   
1031   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1032                                             &start, &end))
1033     {
1034       if (callback_action)
1035         {
1036           gtk_text_buffer_remove_tag (view->buffer->buffer,
1037                                       view->buffer->custom_tabs_tag,
1038                                       &start, &end);
1039         }
1040       else
1041         {
1042           gtk_text_buffer_apply_tag (view->buffer->buffer,
1043                                      view->buffer->custom_tabs_tag,
1044                                      &start, &end);
1045         }
1046     }
1047 }
1048
1049 static void
1050 do_apply_colors (gpointer callback_data,
1051                  guint callback_action,
1052                  GtkWidget *widget)
1053 {
1054   View *view = view_from_widget (widget);
1055   Buffer *buffer = view->buffer;
1056   GtkTextIter start;
1057   GtkTextIter end;
1058   
1059   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1060                                             &start, &end))
1061     {
1062       if (!callback_action)
1063         {
1064           GSList *tmp;
1065           
1066           tmp = buffer->color_tags;
1067           while (tmp != NULL)
1068             {
1069               gtk_text_buffer_remove_tag (view->buffer->buffer,
1070                                           tmp->data,
1071                                           &start, &end);              
1072               tmp = g_slist_next (tmp);
1073             }
1074         }
1075       else
1076         {
1077           GSList *tmp;
1078           
1079           tmp = buffer->color_tags;
1080           while (TRUE)
1081             {
1082               GtkTextIter next;
1083               gboolean done = FALSE;
1084               
1085               next = start;
1086               gtk_text_iter_forward_char (&next);
1087               gtk_text_iter_forward_char (&next);
1088
1089               if (gtk_text_iter_compare (&next, &end) >= 0)
1090                 {
1091                   next = end;
1092                   done = TRUE;
1093                 }
1094               
1095               gtk_text_buffer_apply_tag (view->buffer->buffer,
1096                                          tmp->data,
1097                                          &start, &next);
1098
1099               start = next;
1100
1101               if (done)
1102                 return;
1103               
1104               tmp = g_slist_next (tmp);
1105               if (tmp == NULL)
1106                 tmp = buffer->color_tags;
1107             } 
1108         }
1109     }
1110 }
1111
1112 static void
1113 do_remove_tags (gpointer callback_data,
1114                 guint callback_action,
1115                 GtkWidget *widget)
1116 {
1117   View *view = view_from_widget (widget);
1118   GtkTextIter start;
1119   GtkTextIter end;
1120   
1121   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1122                                             &start, &end))
1123     {
1124       gtk_text_buffer_remove_all_tags (view->buffer->buffer,
1125                                        &start, &end);
1126     }
1127 }
1128
1129 static void
1130 do_properties (gpointer callback_data,
1131                 guint callback_action,
1132                 GtkWidget *widget)
1133 {
1134   View *view = view_from_widget (widget);
1135
1136   create_prop_editor (G_OBJECT (view->text_view), 0);
1137 }
1138
1139 enum
1140 {
1141   RESPONSE_FORWARD,
1142   RESPONSE_BACKWARD
1143 };
1144
1145 static void
1146 dialog_response_callback (GtkWidget *dialog, gint response_id, gpointer data)
1147 {
1148   GtkTextBuffer *buffer;
1149   View *view = data;
1150   GtkTextIter start, end;
1151   gchar *search_string;
1152
1153   if (response_id != RESPONSE_FORWARD &&
1154       response_id != RESPONSE_BACKWARD)
1155     {
1156       gtk_widget_destroy (dialog);
1157       return;
1158     }
1159   
1160   buffer = g_object_get_data (G_OBJECT (dialog), "buffer");
1161
1162   gtk_text_buffer_get_bounds (buffer, &start, &end);
1163   
1164   search_string = gtk_text_iter_get_text (&start, &end);
1165
1166   g_print ("Searching for `%s'\n", search_string);
1167
1168   if (response_id == RESPONSE_FORWARD)
1169     buffer_search_forward (view->buffer, search_string, view);
1170   else if (response_id == RESPONSE_BACKWARD)
1171     buffer_search_backward (view->buffer, search_string, view);
1172     
1173   g_free (search_string);
1174   
1175   gtk_widget_destroy (dialog);
1176 }
1177
1178 static void
1179 do_search (gpointer callback_data,
1180            guint callback_action,
1181            GtkWidget *widget)
1182 {
1183   View *view = view_from_widget (widget);
1184   GtkWidget *dialog;
1185   GtkWidget *search_text;
1186   GtkTextBuffer *buffer;
1187
1188   dialog = gtk_dialog_new_with_buttons ("Search",
1189                                         GTK_WINDOW (view->window),
1190                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1191                                         "Forward", RESPONSE_FORWARD,
1192                                         "Backward", RESPONSE_BACKWARD,
1193                                         GTK_STOCK_CANCEL,
1194                                         GTK_RESPONSE_NONE, NULL);
1195
1196
1197   buffer = gtk_text_buffer_new (NULL);
1198
1199   search_text = gtk_text_view_new_with_buffer (buffer);
1200
1201   g_object_unref (G_OBJECT (buffer));
1202   
1203   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1204                     search_text,
1205                     TRUE, TRUE, 0);
1206
1207   g_object_set_data (G_OBJECT (dialog), "buffer", buffer);
1208   
1209   gtk_signal_connect (GTK_OBJECT (dialog),
1210                       "response",
1211                       GTK_SIGNAL_FUNC (dialog_response_callback),
1212                       view);
1213
1214   gtk_widget_show (search_text);
1215
1216   gtk_widget_grab_focus (search_text);
1217   
1218   gtk_widget_show_all (dialog);
1219 }
1220
1221 typedef struct
1222 {
1223   /* position is in coordinate system of text_view_move_child */
1224   int click_x;
1225   int click_y;
1226   int start_x;
1227   int start_y;
1228   int button;
1229 } ChildMoveInfo;
1230
1231 static gboolean
1232 movable_child_callback (GtkWidget *child,
1233                         GdkEvent  *event,
1234                         gpointer   data)
1235 {
1236   ChildMoveInfo *info;
1237   GtkTextView *text_view;
1238
1239   text_view = GTK_TEXT_VIEW (data);
1240   
1241   g_return_val_if_fail (GTK_IS_EVENT_BOX (child), FALSE);
1242   g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (text_view), FALSE);  
1243   
1244   info = g_object_get_data (G_OBJECT (child),
1245                             "testtext-move-info");
1246
1247   if (info == NULL)
1248     {
1249       info = g_new (ChildMoveInfo, 1);      
1250       info->start_x = -1;
1251       info->start_y = -1;
1252       info->button = -1;
1253       g_object_set_data_full (G_OBJECT (child),
1254                               "testtext-move-info",
1255                               info,
1256                               g_free);
1257     }
1258   
1259   switch (event->type)
1260     {
1261     case GDK_BUTTON_PRESS:
1262       if (info->button < 0)
1263         {
1264           if (gdk_pointer_grab (event->button.window,
1265                                 FALSE,
1266                                 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1267                                 GDK_BUTTON_RELEASE_MASK,
1268                                 NULL,
1269                                 NULL,
1270                                 event->button.time) != GDK_GRAB_SUCCESS)
1271             return FALSE;
1272           
1273           info->button = event->button.button;
1274           
1275           info->start_x = child->allocation.x;
1276           info->start_y = child->allocation.y;
1277           info->click_x = child->allocation.x + event->button.x;
1278           info->click_y = child->allocation.y + event->button.y;
1279         }
1280       break;
1281
1282     case GDK_BUTTON_RELEASE:
1283       if (info->button < 0)
1284         return FALSE;
1285
1286       if (info->button == event->button.button)
1287         {
1288           int x, y;
1289           
1290           gdk_pointer_ungrab (event->button.time);
1291           info->button = -1;
1292
1293           /* convert to window coords from event box coords */
1294           x = info->start_x + (event->button.x + child->allocation.x - info->click_x);
1295           y = info->start_y + (event->button.y + child->allocation.y - info->click_y);
1296
1297           gtk_text_view_move_child (text_view,
1298                                     child,
1299                                     x, y);
1300         }
1301       break;
1302
1303     case GDK_MOTION_NOTIFY:
1304       {
1305         int x, y;
1306         
1307         if (info->button < 0)
1308           return FALSE;
1309         
1310         gdk_window_get_pointer (child->window, &x, &y, NULL); /* ensure more events */
1311
1312         /* to window coords from event box coords */
1313         x += child->allocation.x;
1314         y += child->allocation.y;
1315         
1316         x = info->start_x + (x - info->click_x);
1317         y = info->start_y + (y - info->click_y);
1318         
1319         gtk_text_view_move_child (text_view,
1320                                   child,
1321                                   x, y);
1322       }
1323       break;
1324
1325     default:
1326       break;
1327     }
1328
1329   return FALSE;
1330 }
1331
1332 static void
1333 add_movable_child (GtkTextView      *text_view,
1334                    GtkTextWindowType window)
1335 {
1336   GtkWidget *event_box;
1337   GtkWidget *label;
1338   GdkColor color;
1339   
1340   label = gtk_label_new ("Drag me around");  
1341   
1342   event_box = gtk_event_box_new ();
1343   gtk_widget_add_events (event_box,
1344                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1345                          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
1346
1347   color.red = 0xffff;
1348   color.green = color.blue = 0;
1349   gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color);
1350   
1351   gtk_container_add (GTK_CONTAINER (event_box), label);
1352
1353   gtk_widget_show_all (event_box);
1354
1355   g_signal_connect (G_OBJECT (event_box), "event",
1356                     G_CALLBACK (movable_child_callback),
1357                     text_view);
1358
1359   gtk_text_view_add_child_in_window (text_view,
1360                                      event_box,
1361                                      window,
1362                                      0, 0);
1363 }
1364
1365 static void
1366 do_add_children (gpointer callback_data,
1367                  guint callback_action,
1368                  GtkWidget *widget)
1369 {
1370   View *view = view_from_widget (widget);
1371
1372   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1373                      GTK_TEXT_WINDOW_WIDGET);
1374   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1375                      GTK_TEXT_WINDOW_LEFT);
1376   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1377                      GTK_TEXT_WINDOW_RIGHT);
1378 }
1379
1380 static void
1381 do_add_focus_children (gpointer callback_data,
1382                        guint callback_action,
1383                        GtkWidget *widget)
1384 {
1385   View *view = view_from_widget (widget);
1386   GtkWidget *child;
1387   GtkTextChildAnchor *anchor;
1388   GtkTextIter iter;
1389   GtkTextView *text_view;
1390
1391   text_view = GTK_TEXT_VIEW (view->text_view);
1392   
1393   child = gtk_button_new_with_mnemonic ("Button _A in widget->window");
1394
1395   gtk_text_view_add_child_in_window (text_view,
1396                                      child,
1397                                      GTK_TEXT_WINDOW_WIDGET,
1398                                      200, 200);
1399
1400   child = gtk_button_new_with_mnemonic ("Button _B in widget->window");
1401
1402   gtk_text_view_add_child_in_window (text_view,
1403                                      child,
1404                                      GTK_TEXT_WINDOW_WIDGET,
1405                                      350, 300);
1406
1407   child = gtk_button_new_with_mnemonic ("Button _C in left window");
1408
1409   gtk_text_view_add_child_in_window (text_view,
1410                                      child,
1411                                      GTK_TEXT_WINDOW_LEFT,
1412                                      0, 0);
1413
1414   child = gtk_button_new_with_mnemonic ("Button _D in right window");
1415   
1416   gtk_text_view_add_child_in_window (text_view,
1417                                      child,
1418                                      GTK_TEXT_WINDOW_RIGHT,
1419                                      0, 0);
1420
1421   gtk_text_buffer_get_start_iter (view->buffer->buffer, &iter);
1422   
1423   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1424
1425   child = gtk_button_new_with_mnemonic ("Button _E in buffer");
1426   
1427   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1428
1429   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1430
1431   child = gtk_button_new_with_mnemonic ("Button _F in buffer");
1432   
1433   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1434
1435   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1436
1437   child = gtk_button_new_with_mnemonic ("Button _G in buffer");
1438   
1439   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1440
1441   /* show all the buttons */
1442   gtk_widget_show_all (view->text_view);
1443 }
1444
1445 static void
1446 view_init_menus (View *view)
1447 {
1448   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
1449   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
1450   GtkWidget *menu_item = NULL;
1451
1452   switch (direction)
1453     {
1454     case GTK_TEXT_DIR_LTR:
1455       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1456       break;
1457     case GTK_TEXT_DIR_RTL:
1458       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1459       break;
1460     default:
1461       break;
1462     }
1463
1464   if (menu_item)
1465     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1466
1467   switch (wrap_mode)
1468     {
1469     case GTK_WRAP_NONE:
1470       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1471       break;
1472     case GTK_WRAP_WORD:
1473       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1474       break;
1475     case GTK_WRAP_CHAR:
1476       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Chars");
1477       break;
1478     default:
1479       break;
1480     }
1481   
1482   if (menu_item)
1483     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1484 }
1485
1486 static GtkItemFactoryEntry menu_items[] =
1487 {
1488   { "/_File",            NULL,         0,           0, "<Branch>" },
1489   { "/File/_New",        "<control>N", do_new,      0, NULL },
1490   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1491   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1492   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1493   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1494   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
1495   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1496   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1497
1498   { "/_Edit", NULL, 0, 0, "<Branch>" },
1499   { "/Edit/Find...", NULL, do_search, 0, NULL },
1500
1501   { "/_Settings",         NULL,         0,                0, "<Branch>" },
1502   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAP_NONE, "<RadioItem>" },
1503   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAP_WORD, "/Settings/Wrap Off" },
1504   { "/Settings/Wrap _Chars", NULL,      do_wrap_changed,  GTK_WRAP_CHAR, "/Settings/Wrap Off" },
1505   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1506   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1507   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1508   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1509
1510   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1511   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1512   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1513   
1514   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1515   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1516
1517   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1518   { "/Settings/Sane spacing", NULL,    do_spacing_changed,  FALSE, "<RadioItem>" },
1519   { "/Settings/Funky spacing", NULL,    do_spacing_changed,  TRUE, "/Settings/Sane spacing" },
1520   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1521   { "/Settings/Don't cycle color tags", NULL,    do_color_cycle_changed,  FALSE, "<RadioItem>" },
1522   { "/Settings/Cycle colors", NULL,    do_color_cycle_changed,  TRUE, "/Settings/Don't cycle color tags" },
1523   { "/_Attributes",       NULL,         0,                0, "<Branch>" },
1524   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1525   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1526   { "/Attributes/Invisible",      NULL,         do_apply_invisible, FALSE, NULL },
1527   { "/Attributes/Visible",        NULL,         do_apply_invisible, TRUE, NULL },
1528   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1529   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1530   { "/Attributes/Color cycles",           NULL,         do_apply_colors, TRUE, NULL },
1531   { "/Attributes/No colors",              NULL,         do_apply_colors, FALSE, NULL },
1532   { "/Attributes/Remove all tags",       NULL, do_remove_tags, 0, NULL },
1533   { "/Attributes/Properties",       NULL, do_properties, 0, NULL },
1534   { "/_Test",            NULL,         0,           0, "<Branch>" },
1535   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1536   { "/Test/_Insert and scroll", NULL,         do_insert_and_scroll,  0, NULL },
1537   { "/Test/_Add fixed children", NULL,         do_add_children,  0, NULL },
1538   { "/Test/A_dd focusable children", NULL,    do_add_focus_children,  0, NULL },
1539 };
1540
1541 static gboolean
1542 save_buffer (Buffer *buffer)
1543 {
1544   GtkTextIter start, end;
1545   gchar *chars;
1546   gboolean result = FALSE;
1547   gboolean have_backup = FALSE;
1548   gchar *bak_filename;
1549   FILE *file;
1550
1551   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1552
1553   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1554   
1555   if (rename (buffer->filename, bak_filename) != 0)
1556     {
1557       if (errno != ENOENT)
1558         {
1559           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1560                                         buffer->filename, bak_filename, g_strerror (errno));
1561           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1562           g_free (err);
1563           return FALSE;
1564         }
1565     }
1566   else
1567     have_backup = TRUE;
1568   
1569   file = fopen (buffer->filename, "w");
1570   if (!file)
1571     {
1572       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1573                                     buffer->filename, bak_filename, g_strerror (errno));
1574       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1575     }
1576   else
1577     {
1578       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1579       gtk_text_buffer_get_end_iter (buffer->buffer, &end);
1580   
1581       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1582
1583       if (fputs (chars, file) == EOF ||
1584           fclose (file) == EOF)
1585         {
1586           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1587                                         buffer->filename, g_strerror (errno));
1588           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1589           g_free (err);
1590         }
1591       else
1592         {
1593           /* Success
1594            */
1595           result = TRUE;
1596           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1597         }
1598         
1599       g_free (chars);
1600     }
1601
1602   if (!result && have_backup)
1603     {
1604       if (rename (bak_filename, buffer->filename) != 0)
1605         {
1606           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1607                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1608           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1609           g_free (err);
1610         }
1611     }
1612
1613   g_free (bak_filename);
1614   
1615   return result;
1616 }
1617
1618 static gboolean
1619 save_as_ok_func (const char *filename, gpointer data)
1620 {
1621   Buffer *buffer = data;
1622   char *old_filename = buffer->filename;
1623
1624   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1625     {
1626       struct stat statbuf;
1627
1628       if (stat (filename, &statbuf) == 0)
1629         {
1630           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1631           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1632           g_free (err);
1633
1634           if (result != 0)
1635             return FALSE;
1636         }
1637     }
1638   
1639   buffer->filename = g_strdup (filename);
1640
1641   if (save_buffer (buffer))
1642     {
1643       g_free (old_filename);
1644       buffer_filename_set (buffer);
1645       return TRUE;
1646     }
1647   else
1648     {
1649       g_free (buffer->filename);
1650       buffer->filename = old_filename;
1651       return FALSE;
1652     }
1653 }
1654
1655 static gboolean
1656 save_as_buffer (Buffer *buffer)
1657 {
1658   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1659 }
1660
1661 static gboolean
1662 check_buffer_saved (Buffer *buffer)
1663 {
1664   if (gtk_text_buffer_get_modified (buffer->buffer))
1665     {
1666       char *pretty_name = buffer_pretty_name (buffer);
1667       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1668       gint result;
1669       
1670       g_free (pretty_name);
1671       
1672       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1673       g_free (msg);
1674   
1675       if (result == 0)
1676         return save_as_buffer (buffer);
1677       else if (result == 1)
1678         return TRUE;
1679       else
1680         return FALSE;
1681     }
1682   else
1683     return TRUE;
1684 }
1685
1686 #define N_COLORS 16
1687
1688 static Buffer *
1689 create_buffer (void)
1690 {
1691   Buffer *buffer;
1692   PangoTabArray *tabs;
1693   gint i;
1694   
1695   buffer = g_new (Buffer, 1);
1696
1697   buffer->buffer = gtk_text_buffer_new (NULL);
1698   
1699   buffer->refcount = 1;
1700   buffer->filename = NULL;
1701   buffer->untitled_serial = -1;
1702
1703   buffer->color_tags = NULL;
1704   buffer->color_cycle_timeout = 0;
1705   buffer->start_hue = 0.0;
1706   
1707   i = 0;
1708   while (i < N_COLORS)
1709     {
1710       GtkTextTag *tag;
1711
1712       tag = gtk_text_buffer_create_tag (buffer->buffer, NULL, NULL);
1713       
1714       buffer->color_tags = g_slist_prepend (buffer->color_tags, tag);
1715       
1716       ++i;
1717     }
1718   
1719   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1720                                                       "invisible", TRUE, NULL);
1721   
1722   buffer->not_editable_tag =
1723     gtk_text_buffer_create_tag (buffer->buffer, NULL,
1724                                 "editable", FALSE,
1725                                 "foreground", "purple", NULL);
1726
1727   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1728                                                        "foreground", "red", NULL);
1729
1730   tabs = pango_tab_array_new_with_positions (4,
1731                                              TRUE,
1732                                              PANGO_TAB_LEFT, 10,
1733                                              PANGO_TAB_LEFT, 30,
1734                                              PANGO_TAB_LEFT, 60,
1735                                              PANGO_TAB_LEFT, 120);
1736   
1737   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1738                                                         "tabs", tabs,
1739                                                         "foreground", "green", NULL);
1740
1741   pango_tab_array_free (tabs);
1742   
1743   buffers = g_slist_prepend (buffers, buffer);
1744   
1745   return buffer;
1746 }
1747
1748 static char *
1749 buffer_pretty_name (Buffer *buffer)
1750 {
1751   if (buffer->filename)
1752     {
1753       char *p;
1754       char *result = g_path_get_basename (buffer->filename);
1755       p = strchr (result, '/');
1756       if (p)
1757         *p = '\0';
1758
1759       return result;
1760     }
1761   else
1762     {
1763       if (buffer->untitled_serial == -1)
1764         buffer->untitled_serial = untitled_serial++;
1765
1766       if (buffer->untitled_serial == 1)
1767         return g_strdup ("Untitled");
1768       else
1769         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1770     }
1771 }
1772
1773 static void
1774 buffer_filename_set (Buffer *buffer)
1775 {
1776   GSList *tmp_list = views;
1777
1778   while (tmp_list)
1779     {
1780       View *view = tmp_list->data;
1781
1782       if (view->buffer == buffer)
1783         view_set_title (view);
1784
1785       tmp_list = tmp_list->next;
1786     }
1787 }
1788
1789 static void
1790 buffer_search (Buffer     *buffer,
1791                const char *str,
1792                View       *view,
1793                gboolean forward)
1794 {
1795   GtkTextIter iter;
1796   GtkTextIter start, end;
1797   GtkWidget *dialog;
1798   int i;
1799   
1800   /* remove tag from whole buffer */
1801   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1802   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1803                               &start, &end );
1804   
1805   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1806                                     gtk_text_buffer_get_mark (buffer->buffer,
1807                                                               "insert"));
1808
1809   i = 0;
1810   if (*str != '\0')
1811     {
1812       GtkTextIter match_start, match_end;
1813
1814       if (forward)
1815         {
1816           while (gtk_text_iter_forward_search (&iter, str,
1817                                                GTK_TEXT_SEARCH_VISIBLE_ONLY |
1818                                                GTK_TEXT_SEARCH_TEXT_ONLY,
1819                                                &match_start, &match_end,
1820                                                NULL))
1821             {
1822               ++i;
1823               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1824                                          &match_start, &match_end);
1825               
1826               iter = match_end;
1827             }
1828         }
1829       else
1830         {
1831           while (gtk_text_iter_backward_search (&iter, str,
1832                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY |
1833                                                 GTK_TEXT_SEARCH_TEXT_ONLY,
1834                                                 &match_start, &match_end,
1835                                                 NULL))
1836             {
1837               ++i;
1838               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1839                                          &match_start, &match_end);
1840               
1841               iter = match_start;
1842             }
1843         }
1844     }
1845
1846   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1847                                    GTK_MESSAGE_INFO,
1848                                    GTK_BUTTONS_OK,
1849                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1850                                    "%d strings found and marked in red",
1851                                    i);
1852
1853   gtk_signal_connect_object (GTK_OBJECT (dialog),
1854                              "response",
1855                              GTK_SIGNAL_FUNC (gtk_widget_destroy),
1856                              GTK_OBJECT (dialog));
1857   
1858   gtk_widget_show (dialog);
1859 }
1860
1861 static void
1862 buffer_search_forward (Buffer *buffer, const char *str,
1863                        View *view)
1864 {
1865   buffer_search (buffer, str, view, TRUE);
1866 }
1867
1868 static void
1869 buffer_search_backward (Buffer *buffer, const char *str,
1870                         View *view)
1871 {
1872   buffer_search (buffer, str, view, FALSE);
1873 }
1874
1875 static void
1876 buffer_ref (Buffer *buffer)
1877 {
1878   buffer->refcount++;
1879 }
1880
1881 static void
1882 buffer_unref (Buffer *buffer)
1883 {
1884   buffer->refcount--;
1885   if (buffer->refcount == 0)
1886     {
1887       buffer_set_colors (buffer, FALSE);
1888       buffers = g_slist_remove (buffers, buffer);
1889       g_object_unref (G_OBJECT (buffer->buffer));
1890       g_free (buffer->filename);
1891       g_free (buffer);
1892     }
1893 }
1894
1895 static void
1896 hsv_to_rgb (gdouble *h,
1897             gdouble *s,
1898             gdouble *v)
1899 {
1900   gdouble hue, saturation, value;
1901   gdouble f, p, q, t;
1902
1903   if (*s == 0.0)
1904     {
1905       *h = *v;
1906       *s = *v;
1907       *v = *v; /* heh */
1908     }
1909   else
1910     {
1911       hue = *h * 6.0;
1912       saturation = *s;
1913       value = *v;
1914       
1915       if (hue >= 6.0)
1916         hue = 0.0;
1917       
1918       f = hue - (int) hue;
1919       p = value * (1.0 - saturation);
1920       q = value * (1.0 - saturation * f);
1921       t = value * (1.0 - saturation * (1.0 - f));
1922       
1923       switch ((int) hue)
1924         {
1925         case 0:
1926           *h = value;
1927           *s = t;
1928           *v = p;
1929           break;
1930           
1931         case 1:
1932           *h = q;
1933           *s = value;
1934           *v = p;
1935           break;
1936           
1937         case 2:
1938           *h = p;
1939           *s = value;
1940           *v = t;
1941           break;
1942           
1943         case 3:
1944           *h = p;
1945           *s = q;
1946           *v = value;
1947           break;
1948           
1949         case 4:
1950           *h = t;
1951           *s = p;
1952           *v = value;
1953           break;
1954           
1955         case 5:
1956           *h = value;
1957           *s = p;
1958           *v = q;
1959           break;
1960           
1961         default:
1962           g_assert_not_reached ();
1963         }
1964     }
1965 }
1966
1967 static void
1968 hue_to_color (gdouble   hue,
1969               GdkColor *color)
1970 {
1971   gdouble h, s, v;
1972
1973   h = hue;
1974   s = 1.0;
1975   v = 1.0;
1976
1977   g_return_if_fail (hue <= 1.0);
1978   
1979   hsv_to_rgb (&h, &s, &v);
1980
1981   color->red = h * 65535;
1982   color->green = s * 65535;
1983   color->blue = v * 65535;
1984 }
1985
1986
1987 static gint
1988 color_cycle_timeout (gpointer data)
1989 {
1990   Buffer *buffer = data;
1991
1992   buffer_cycle_colors (buffer);
1993
1994   return TRUE;
1995 }
1996
1997 static void
1998 buffer_set_colors (Buffer  *buffer,
1999                    gboolean enabled)
2000 {
2001   GSList *tmp;
2002   gdouble hue = 0.0;
2003
2004   if (enabled && buffer->color_cycle_timeout == 0)
2005     buffer->color_cycle_timeout = gtk_timeout_add (200, color_cycle_timeout, buffer);
2006   else if (!enabled && buffer->color_cycle_timeout != 0)
2007     {
2008       gtk_timeout_remove (buffer->color_cycle_timeout);
2009       buffer->color_cycle_timeout = 0;
2010     }
2011     
2012   tmp = buffer->color_tags;
2013   while (tmp != NULL)
2014     {
2015       if (enabled)
2016         {
2017           GdkColor color;
2018           
2019           hue_to_color (hue, &color);
2020
2021           g_object_set (G_OBJECT (tmp->data),
2022                           "foreground_gdk", &color,
2023                           NULL);
2024         }
2025       else
2026         g_object_set (G_OBJECT (tmp->data),
2027                         "foreground_set", FALSE,
2028                         NULL);
2029
2030       hue += 1.0 / N_COLORS;
2031       
2032       tmp = g_slist_next (tmp);
2033     }
2034 }
2035
2036 static void
2037 buffer_cycle_colors (Buffer *buffer)
2038 {
2039   GSList *tmp;
2040   gdouble hue = buffer->start_hue;
2041   
2042   tmp = buffer->color_tags;
2043   while (tmp != NULL)
2044     {
2045       GdkColor color;
2046       
2047       hue_to_color (hue, &color);
2048       
2049       g_object_set (G_OBJECT (tmp->data),
2050                     "foreground_gdk", &color,
2051                     NULL);
2052
2053       hue += 1.0 / N_COLORS;
2054       if (hue > 1.0)
2055         hue = 0.0;
2056       
2057       tmp = g_slist_next (tmp);
2058     }
2059
2060   buffer->start_hue += 1.0 / N_COLORS;
2061   if (buffer->start_hue > 1.0)
2062     buffer->start_hue = 0.0;
2063 }
2064
2065 static void
2066 close_view (View *view)
2067 {
2068   views = g_slist_remove (views, view);
2069   buffer_unref (view->buffer);
2070   gtk_widget_destroy (view->window);
2071   g_object_unref (G_OBJECT (view->item_factory));
2072   
2073   g_free (view);
2074   
2075   if (!views)
2076     gtk_main_quit ();
2077 }
2078
2079 static void
2080 check_close_view (View *view)
2081 {
2082   if (view->buffer->refcount > 1 ||
2083       check_buffer_saved (view->buffer))
2084     close_view (view);
2085 }
2086
2087 static void
2088 view_set_title (View *view)
2089 {
2090   char *pretty_name = buffer_pretty_name (view->buffer);
2091   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
2092
2093   gtk_window_set_title (GTK_WINDOW (view->window), title);
2094
2095   g_free (pretty_name);
2096   g_free (title);
2097 }
2098
2099 static void
2100 cursor_set_callback (GtkTextBuffer     *buffer,
2101                      const GtkTextIter *location,
2102                      GtkTextMark       *mark,
2103                      gpointer           user_data)
2104 {
2105   GtkTextView *text_view;
2106
2107   /* Redraw tab windows if the cursor moves
2108    * on the mapped widget (windows may not exist before realization...
2109    */
2110   
2111   text_view = GTK_TEXT_VIEW (user_data);
2112   
2113   if (GTK_WIDGET_MAPPED (text_view) &&
2114       mark == gtk_text_buffer_get_insert (buffer))
2115     {
2116       GdkWindow *tab_window;
2117
2118       tab_window = gtk_text_view_get_window (text_view,
2119                                              GTK_TEXT_WINDOW_TOP);
2120
2121       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2122       
2123       tab_window = gtk_text_view_get_window (text_view,
2124                                              GTK_TEXT_WINDOW_BOTTOM);
2125
2126       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2127     }
2128 }
2129
2130 static gint
2131 tab_stops_expose (GtkWidget      *widget,
2132                   GdkEventExpose *event,
2133                   gpointer        user_data)
2134 {
2135   gint first_x;
2136   gint last_x;
2137   gint i;
2138   GdkWindow *top_win;
2139   GdkWindow *bottom_win;
2140   GtkTextView *text_view;
2141   GtkTextWindowType type;
2142   GdkDrawable *target;
2143   gint *positions = NULL;
2144   gint size;
2145   GtkTextAttributes *attrs;
2146   GtkTextIter insert;
2147   GtkTextBuffer *buffer;
2148   gboolean in_pixels;
2149   
2150   text_view = GTK_TEXT_VIEW (widget);
2151   
2152   /* See if this expose is on the tab stop window */
2153   top_win = gtk_text_view_get_window (text_view,
2154                                       GTK_TEXT_WINDOW_TOP);
2155
2156   bottom_win = gtk_text_view_get_window (text_view,
2157                                          GTK_TEXT_WINDOW_BOTTOM);
2158
2159   if (event->window == top_win)
2160     {
2161       type = GTK_TEXT_WINDOW_TOP;
2162       target = top_win;
2163     }
2164   else if (event->window == bottom_win)
2165     {
2166       type = GTK_TEXT_WINDOW_BOTTOM;
2167       target = bottom_win;
2168     }
2169   else
2170     return FALSE;
2171   
2172   first_x = event->area.x;
2173   last_x = first_x + event->area.width;
2174
2175   gtk_text_view_window_to_buffer_coords (text_view,
2176                                          type,
2177                                          first_x,
2178                                          0,
2179                                          &first_x,
2180                                          NULL);
2181
2182   gtk_text_view_window_to_buffer_coords (text_view,
2183                                          type,
2184                                          last_x,
2185                                          0,
2186                                          &last_x,
2187                                          NULL);
2188
2189   buffer = gtk_text_view_get_buffer (text_view);
2190
2191   gtk_text_buffer_get_iter_at_mark (buffer,
2192                                     &insert,
2193                                     gtk_text_buffer_get_mark (buffer,
2194                                                               "insert"));
2195   
2196   attrs = gtk_text_attributes_new ();
2197
2198   gtk_text_iter_get_attributes (&insert, attrs);
2199
2200   if (attrs->tabs)
2201     {
2202       size = pango_tab_array_get_size (attrs->tabs);
2203       
2204       pango_tab_array_get_tabs (attrs->tabs,
2205                                 NULL,
2206                                 &positions);
2207
2208       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
2209     }
2210   else
2211     {
2212       size = 0;
2213       in_pixels = FALSE;
2214     }
2215       
2216   gtk_text_attributes_unref (attrs);
2217   
2218   i = 0;
2219   while (i < size)
2220     {
2221       gint pos;
2222
2223       if (!in_pixels)
2224         positions[i] = PANGO_PIXELS (positions[i]);
2225       
2226       gtk_text_view_buffer_to_window_coords (text_view,
2227                                              type,
2228                                              positions[i],
2229                                              0,
2230                                              &pos,
2231                                              NULL);
2232       
2233       gdk_draw_line (target, 
2234                      widget->style->fg_gc [widget->state],
2235                      pos, 0,
2236                      pos, 15); 
2237       
2238       ++i;
2239     }
2240
2241   g_free (positions);
2242
2243   return TRUE;
2244 }
2245
2246 static void
2247 get_lines (GtkTextView  *text_view,
2248            gint          first_y,
2249            gint          last_y,
2250            GArray       *buffer_coords,
2251            GArray       *numbers,
2252            gint         *countp)
2253 {
2254   GtkTextIter iter;
2255   gint count;
2256   gint size;  
2257
2258   g_array_set_size (buffer_coords, 0);
2259   g_array_set_size (numbers, 0);
2260   
2261   /* Get iter at first y */
2262   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
2263
2264   /* For each iter, get its location and add it to the arrays.
2265    * Stop when we pass last_y
2266    */
2267   count = 0;
2268   size = 0;
2269
2270   while (!gtk_text_iter_is_end (&iter))
2271     {
2272       gint y, height;
2273       gint line_num;
2274       
2275       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
2276
2277       g_array_append_val (buffer_coords, y);
2278       line_num = gtk_text_iter_get_line (&iter);
2279       g_array_append_val (numbers, line_num);
2280       
2281       ++count;
2282
2283       if ((y + height) >= last_y)
2284         break;
2285       
2286       gtk_text_iter_forward_line (&iter);
2287     }
2288
2289   *countp = count;
2290 }
2291
2292 static gint
2293 line_numbers_expose (GtkWidget      *widget,
2294                      GdkEventExpose *event,
2295                      gpointer        user_data)
2296 {
2297   gint count;
2298   GArray *numbers;
2299   GArray *pixels;
2300   gint first_y;
2301   gint last_y;
2302   gint i;
2303   GdkWindow *left_win;
2304   GdkWindow *right_win;
2305   PangoLayout *layout;
2306   GtkTextView *text_view;
2307   GtkTextWindowType type;
2308   GdkDrawable *target;
2309   
2310   text_view = GTK_TEXT_VIEW (widget);
2311   
2312   /* See if this expose is on the line numbers window */
2313   left_win = gtk_text_view_get_window (text_view,
2314                                        GTK_TEXT_WINDOW_LEFT);
2315
2316   right_win = gtk_text_view_get_window (text_view,
2317                                         GTK_TEXT_WINDOW_RIGHT);
2318
2319   if (event->window == left_win)
2320     {
2321       type = GTK_TEXT_WINDOW_LEFT;
2322       target = left_win;
2323     }
2324   else if (event->window == right_win)
2325     {
2326       type = GTK_TEXT_WINDOW_RIGHT;
2327       target = right_win;
2328     }
2329   else
2330     return FALSE;
2331   
2332   first_y = event->area.y;
2333   last_y = first_y + event->area.height;
2334
2335   gtk_text_view_window_to_buffer_coords (text_view,
2336                                          type,
2337                                          0,
2338                                          first_y,
2339                                          NULL,
2340                                          &first_y);
2341
2342   gtk_text_view_window_to_buffer_coords (text_view,
2343                                          type,
2344                                          0,
2345                                          last_y,
2346                                          NULL,
2347                                          &last_y);
2348
2349   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
2350   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
2351   
2352   get_lines (text_view,
2353              first_y,
2354              last_y,
2355              pixels,
2356              numbers,
2357              &count);
2358   
2359   /* Draw fully internationalized numbers! */
2360   
2361   layout = gtk_widget_create_pango_layout (widget, "");
2362   
2363   i = 0;
2364   while (i < count)
2365     {
2366       gint pos;
2367       gchar *str;
2368       
2369       gtk_text_view_buffer_to_window_coords (text_view,
2370                                              type,
2371                                              0,
2372                                              g_array_index (pixels, gint, i),
2373                                              NULL,
2374                                              &pos);
2375
2376       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
2377
2378       pango_layout_set_text (layout, str, -1);
2379
2380       gtk_paint_layout (widget->style,
2381                         target,
2382                         GTK_WIDGET_STATE (widget),
2383                         FALSE,
2384                         NULL,
2385                         widget,
2386                         NULL,
2387                         2, pos + 2,
2388                         layout);
2389
2390       g_free (str);
2391       
2392       ++i;
2393     }
2394
2395   g_array_free (pixels, TRUE);
2396   g_array_free (numbers, TRUE);
2397   
2398   g_object_unref (G_OBJECT (layout));
2399
2400   /* don't stop emission, need to draw children */
2401   return FALSE;
2402 }
2403
2404 static View *
2405 create_view (Buffer *buffer)
2406 {
2407   View *view;
2408   
2409   GtkWidget *sw;
2410   GtkWidget *vbox;
2411   
2412   view = g_new0 (View, 1);
2413   views = g_slist_prepend (views, view);
2414
2415   view->buffer = buffer;
2416   buffer_ref (buffer);
2417   
2418   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2419   g_object_set_data (G_OBJECT (view->window), "view", view);
2420   
2421   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
2422                       GTK_SIGNAL_FUNC (delete_event_cb), NULL);
2423
2424   view->accel_group = gtk_accel_group_new ();
2425   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
2426   g_object_set_data (G_OBJECT (view->item_factory), "view", view);
2427   
2428   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
2429
2430   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
2431
2432   vbox = gtk_vbox_new (FALSE, 0);
2433   gtk_container_add (GTK_CONTAINER (view->window), vbox);
2434
2435   gtk_box_pack_start (GTK_BOX (vbox),
2436                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
2437                       FALSE, FALSE, 0);
2438   
2439   sw = gtk_scrolled_window_new (NULL, NULL);
2440   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2441                                  GTK_POLICY_AUTOMATIC,
2442                                  GTK_POLICY_AUTOMATIC);
2443
2444   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
2445   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
2446                                GTK_WRAP_WORD);
2447
2448   /* Make sure border width works, no real reason to do this other than testing */
2449   gtk_container_set_border_width (GTK_CONTAINER (view->text_view),
2450                                   10);
2451   
2452   /* Draw tab stops in the top and bottom windows. */
2453   
2454   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2455                                         GTK_TEXT_WINDOW_TOP,
2456                                         15);
2457
2458   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2459                                         GTK_TEXT_WINDOW_BOTTOM,
2460                                         15);
2461
2462   gtk_signal_connect (GTK_OBJECT (view->text_view),
2463                       "expose_event",
2464                       GTK_SIGNAL_FUNC (tab_stops_expose),
2465                       NULL);  
2466
2467   g_signal_connect (G_OBJECT (view->buffer->buffer),
2468                     "mark_set",
2469                     GTK_SIGNAL_FUNC (cursor_set_callback),
2470                     view->text_view);
2471   
2472   /* Draw line numbers in the side windows; we should really be
2473    * more scientific about what width we set them to.
2474    */
2475   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2476                                         GTK_TEXT_WINDOW_RIGHT,
2477                                         30);
2478   
2479   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2480                                         GTK_TEXT_WINDOW_LEFT,
2481                                         30);
2482   
2483   gtk_signal_connect (GTK_OBJECT (view->text_view),
2484                       "expose_event",
2485                       GTK_SIGNAL_FUNC (line_numbers_expose),
2486                       NULL);
2487   
2488   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
2489   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
2490
2491   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
2492
2493   gtk_widget_grab_focus (view->text_view);
2494
2495   view_set_title (view);
2496   view_init_menus (view);
2497
2498   view_add_example_widgets (view);
2499   
2500   gtk_widget_show_all (view->window);
2501   return view;
2502 }
2503
2504 static void
2505 view_add_example_widgets (View *view)
2506 {
2507   GtkTextChildAnchor *anchor;
2508   Buffer *buffer;
2509
2510   buffer = view->buffer;
2511   
2512   anchor = g_object_get_data (G_OBJECT (buffer->buffer),
2513                               "anchor");
2514
2515   if (anchor && !gtk_text_child_anchor_get_deleted (anchor))
2516     {
2517       GtkWidget *widget;
2518
2519       widget = gtk_button_new_with_label ("Foo");
2520       
2521       gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view->text_view),
2522                                          widget,
2523                                          anchor);
2524
2525       gtk_widget_show (widget);
2526     }
2527 }
2528
2529 static gboolean
2530 file_exists (const char *filename)
2531 {
2532   struct stat statbuf;
2533
2534   return stat (filename, &statbuf) == 0;
2535 }
2536 void
2537 test_init ()
2538 {
2539   if (file_exists ("../gdk-pixbuf/libpixbufloader-pnm.la"))
2540     {
2541       putenv ("GDK_PIXBUF_MODULEDIR=../gdk-pixbuf/.libs");
2542       putenv ("GTK_IM_MODULE_FILE=../modules/input/gtk.immodules");
2543     }
2544 }
2545
2546 int
2547 main (int argc, char** argv)
2548 {
2549   Buffer *buffer;
2550   View *view;
2551   int i;
2552
2553   test_init ();
2554   gtk_init (&argc, &argv);
2555   
2556   buffer = create_buffer ();
2557   view = create_view (buffer);
2558   buffer_unref (buffer);
2559   
2560   push_active_window (GTK_WINDOW (view->window));
2561   for (i=1; i < argc; i++)
2562     {
2563       char *filename;
2564
2565       /* Quick and dirty canonicalization - better should be in GLib
2566        */
2567
2568       if (!g_path_is_absolute (argv[i]))
2569         {
2570           char *cwd = g_get_current_dir ();
2571           filename = g_strconcat (cwd, "/", argv[i], NULL);
2572           g_free (cwd);
2573         }
2574       else
2575         filename = argv[i];
2576
2577       open_ok_func (filename, view);
2578
2579       if (filename != argv[i])
2580         g_free (filename);
2581     }
2582   pop_active_window ();
2583   
2584   gtk_main ();
2585
2586   return 0;
2587 }
2588
2589