]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Fix error in computing length and a memory leak. (Fixes #94072, reported
[~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 = (FileselOKFunc)g_object_get_data (G_OBJECT (filesel), "ok-func");
108   gpointer data = g_object_get_data (G_OBJECT (filesel), "ok-data");
109   gint *result = g_object_get_data (G_OBJECT (filesel), "ok-result");
110   
111   gtk_widget_hide (filesel);
112   
113   if ((*ok_func) (gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), data))
114     {
115       gtk_widget_destroy (filesel);
116       *result = TRUE;
117     }
118   else
119     gtk_widget_show (filesel);
120 }
121
122 gboolean
123 filesel_run (GtkWindow    *parent, 
124              const char   *title,
125              const char   *start_file,
126              FileselOKFunc func,
127              gpointer      data)
128 {
129   GtkWidget *filesel = gtk_file_selection_new (title);
130   gboolean result = FALSE;
131
132   if (!parent)
133     parent = get_active_window ();
134   
135   if (parent)
136     gtk_window_set_transient_for (GTK_WINDOW (filesel), parent);
137
138   if (start_file)
139     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), start_file);
140
141   
142   g_object_set_data (G_OBJECT (filesel), "ok-func", (gpointer)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 #if 0  
1720   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1721                                                       "invisible", TRUE, NULL);
1722 #endif  
1723   
1724   buffer->not_editable_tag =
1725     gtk_text_buffer_create_tag (buffer->buffer, NULL,
1726                                 "editable", FALSE,
1727                                 "foreground", "purple", NULL);
1728
1729   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1730                                                        "foreground", "red", NULL);
1731
1732   tabs = pango_tab_array_new_with_positions (4,
1733                                              TRUE,
1734                                              PANGO_TAB_LEFT, 10,
1735                                              PANGO_TAB_LEFT, 30,
1736                                              PANGO_TAB_LEFT, 60,
1737                                              PANGO_TAB_LEFT, 120);
1738   
1739   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1740                                                         "tabs", tabs,
1741                                                         "foreground", "green", NULL);
1742
1743   pango_tab_array_free (tabs);
1744   
1745   buffers = g_slist_prepend (buffers, buffer);
1746   
1747   return buffer;
1748 }
1749
1750 static char *
1751 buffer_pretty_name (Buffer *buffer)
1752 {
1753   if (buffer->filename)
1754     {
1755       char *p;
1756       char *result = g_path_get_basename (buffer->filename);
1757       p = strchr (result, '/');
1758       if (p)
1759         *p = '\0';
1760
1761       return result;
1762     }
1763   else
1764     {
1765       if (buffer->untitled_serial == -1)
1766         buffer->untitled_serial = untitled_serial++;
1767
1768       if (buffer->untitled_serial == 1)
1769         return g_strdup ("Untitled");
1770       else
1771         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1772     }
1773 }
1774
1775 static void
1776 buffer_filename_set (Buffer *buffer)
1777 {
1778   GSList *tmp_list = views;
1779
1780   while (tmp_list)
1781     {
1782       View *view = tmp_list->data;
1783
1784       if (view->buffer == buffer)
1785         view_set_title (view);
1786
1787       tmp_list = tmp_list->next;
1788     }
1789 }
1790
1791 static void
1792 buffer_search (Buffer     *buffer,
1793                const char *str,
1794                View       *view,
1795                gboolean forward)
1796 {
1797   GtkTextIter iter;
1798   GtkTextIter start, end;
1799   GtkWidget *dialog;
1800   int i;
1801   
1802   /* remove tag from whole buffer */
1803   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1804   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1805                               &start, &end );
1806   
1807   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1808                                     gtk_text_buffer_get_mark (buffer->buffer,
1809                                                               "insert"));
1810
1811   i = 0;
1812   if (*str != '\0')
1813     {
1814       GtkTextIter match_start, match_end;
1815
1816       if (forward)
1817         {
1818           while (gtk_text_iter_forward_search (&iter, str,
1819                                                GTK_TEXT_SEARCH_VISIBLE_ONLY |
1820                                                GTK_TEXT_SEARCH_TEXT_ONLY,
1821                                                &match_start, &match_end,
1822                                                NULL))
1823             {
1824               ++i;
1825               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1826                                          &match_start, &match_end);
1827               
1828               iter = match_end;
1829             }
1830         }
1831       else
1832         {
1833           while (gtk_text_iter_backward_search (&iter, str,
1834                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY |
1835                                                 GTK_TEXT_SEARCH_TEXT_ONLY,
1836                                                 &match_start, &match_end,
1837                                                 NULL))
1838             {
1839               ++i;
1840               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1841                                          &match_start, &match_end);
1842               
1843               iter = match_start;
1844             }
1845         }
1846     }
1847
1848   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1849                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1850                                    GTK_MESSAGE_INFO,
1851                                    GTK_BUTTONS_OK,
1852                                    "%d strings found and marked in red",
1853                                    i);
1854
1855   gtk_signal_connect_object (GTK_OBJECT (dialog),
1856                              "response",
1857                              GTK_SIGNAL_FUNC (gtk_widget_destroy),
1858                              GTK_OBJECT (dialog));
1859   
1860   gtk_widget_show (dialog);
1861 }
1862
1863 static void
1864 buffer_search_forward (Buffer *buffer, const char *str,
1865                        View *view)
1866 {
1867   buffer_search (buffer, str, view, TRUE);
1868 }
1869
1870 static void
1871 buffer_search_backward (Buffer *buffer, const char *str,
1872                         View *view)
1873 {
1874   buffer_search (buffer, str, view, FALSE);
1875 }
1876
1877 static void
1878 buffer_ref (Buffer *buffer)
1879 {
1880   buffer->refcount++;
1881 }
1882
1883 static void
1884 buffer_unref (Buffer *buffer)
1885 {
1886   buffer->refcount--;
1887   if (buffer->refcount == 0)
1888     {
1889       buffer_set_colors (buffer, FALSE);
1890       buffers = g_slist_remove (buffers, buffer);
1891       g_object_unref (G_OBJECT (buffer->buffer));
1892       g_free (buffer->filename);
1893       g_free (buffer);
1894     }
1895 }
1896
1897 static void
1898 hsv_to_rgb (gdouble *h,
1899             gdouble *s,
1900             gdouble *v)
1901 {
1902   gdouble hue, saturation, value;
1903   gdouble f, p, q, t;
1904
1905   if (*s == 0.0)
1906     {
1907       *h = *v;
1908       *s = *v;
1909       *v = *v; /* heh */
1910     }
1911   else
1912     {
1913       hue = *h * 6.0;
1914       saturation = *s;
1915       value = *v;
1916       
1917       if (hue >= 6.0)
1918         hue = 0.0;
1919       
1920       f = hue - (int) hue;
1921       p = value * (1.0 - saturation);
1922       q = value * (1.0 - saturation * f);
1923       t = value * (1.0 - saturation * (1.0 - f));
1924       
1925       switch ((int) hue)
1926         {
1927         case 0:
1928           *h = value;
1929           *s = t;
1930           *v = p;
1931           break;
1932           
1933         case 1:
1934           *h = q;
1935           *s = value;
1936           *v = p;
1937           break;
1938           
1939         case 2:
1940           *h = p;
1941           *s = value;
1942           *v = t;
1943           break;
1944           
1945         case 3:
1946           *h = p;
1947           *s = q;
1948           *v = value;
1949           break;
1950           
1951         case 4:
1952           *h = t;
1953           *s = p;
1954           *v = value;
1955           break;
1956           
1957         case 5:
1958           *h = value;
1959           *s = p;
1960           *v = q;
1961           break;
1962           
1963         default:
1964           g_assert_not_reached ();
1965         }
1966     }
1967 }
1968
1969 static void
1970 hue_to_color (gdouble   hue,
1971               GdkColor *color)
1972 {
1973   gdouble h, s, v;
1974
1975   h = hue;
1976   s = 1.0;
1977   v = 1.0;
1978
1979   g_return_if_fail (hue <= 1.0);
1980   
1981   hsv_to_rgb (&h, &s, &v);
1982
1983   color->red = h * 65535;
1984   color->green = s * 65535;
1985   color->blue = v * 65535;
1986 }
1987
1988
1989 static gint
1990 color_cycle_timeout (gpointer data)
1991 {
1992   Buffer *buffer = data;
1993
1994   buffer_cycle_colors (buffer);
1995
1996   return TRUE;
1997 }
1998
1999 static void
2000 buffer_set_colors (Buffer  *buffer,
2001                    gboolean enabled)
2002 {
2003   GSList *tmp;
2004   gdouble hue = 0.0;
2005
2006   if (enabled && buffer->color_cycle_timeout == 0)
2007     buffer->color_cycle_timeout = gtk_timeout_add (200, color_cycle_timeout, buffer);
2008   else if (!enabled && buffer->color_cycle_timeout != 0)
2009     {
2010       gtk_timeout_remove (buffer->color_cycle_timeout);
2011       buffer->color_cycle_timeout = 0;
2012     }
2013     
2014   tmp = buffer->color_tags;
2015   while (tmp != NULL)
2016     {
2017       if (enabled)
2018         {
2019           GdkColor color;
2020           
2021           hue_to_color (hue, &color);
2022
2023           g_object_set (G_OBJECT (tmp->data),
2024                           "foreground_gdk", &color,
2025                           NULL);
2026         }
2027       else
2028         g_object_set (G_OBJECT (tmp->data),
2029                         "foreground_set", FALSE,
2030                         NULL);
2031
2032       hue += 1.0 / N_COLORS;
2033       
2034       tmp = g_slist_next (tmp);
2035     }
2036 }
2037
2038 static void
2039 buffer_cycle_colors (Buffer *buffer)
2040 {
2041   GSList *tmp;
2042   gdouble hue = buffer->start_hue;
2043   
2044   tmp = buffer->color_tags;
2045   while (tmp != NULL)
2046     {
2047       GdkColor color;
2048       
2049       hue_to_color (hue, &color);
2050       
2051       g_object_set (G_OBJECT (tmp->data),
2052                     "foreground_gdk", &color,
2053                     NULL);
2054
2055       hue += 1.0 / N_COLORS;
2056       if (hue > 1.0)
2057         hue = 0.0;
2058       
2059       tmp = g_slist_next (tmp);
2060     }
2061
2062   buffer->start_hue += 1.0 / N_COLORS;
2063   if (buffer->start_hue > 1.0)
2064     buffer->start_hue = 0.0;
2065 }
2066
2067 static void
2068 close_view (View *view)
2069 {
2070   views = g_slist_remove (views, view);
2071   buffer_unref (view->buffer);
2072   gtk_widget_destroy (view->window);
2073   g_object_unref (G_OBJECT (view->item_factory));
2074   
2075   g_free (view);
2076   
2077   if (!views)
2078     gtk_main_quit ();
2079 }
2080
2081 static void
2082 check_close_view (View *view)
2083 {
2084   if (view->buffer->refcount > 1 ||
2085       check_buffer_saved (view->buffer))
2086     close_view (view);
2087 }
2088
2089 static void
2090 view_set_title (View *view)
2091 {
2092   char *pretty_name = buffer_pretty_name (view->buffer);
2093   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
2094
2095   gtk_window_set_title (GTK_WINDOW (view->window), title);
2096
2097   g_free (pretty_name);
2098   g_free (title);
2099 }
2100
2101 static void
2102 cursor_set_callback (GtkTextBuffer     *buffer,
2103                      const GtkTextIter *location,
2104                      GtkTextMark       *mark,
2105                      gpointer           user_data)
2106 {
2107   GtkTextView *text_view;
2108
2109   /* Redraw tab windows if the cursor moves
2110    * on the mapped widget (windows may not exist before realization...
2111    */
2112   
2113   text_view = GTK_TEXT_VIEW (user_data);
2114   
2115   if (GTK_WIDGET_MAPPED (text_view) &&
2116       mark == gtk_text_buffer_get_insert (buffer))
2117     {
2118       GdkWindow *tab_window;
2119
2120       tab_window = gtk_text_view_get_window (text_view,
2121                                              GTK_TEXT_WINDOW_TOP);
2122
2123       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2124       
2125       tab_window = gtk_text_view_get_window (text_view,
2126                                              GTK_TEXT_WINDOW_BOTTOM);
2127
2128       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2129     }
2130 }
2131
2132 static gint
2133 tab_stops_expose (GtkWidget      *widget,
2134                   GdkEventExpose *event,
2135                   gpointer        user_data)
2136 {
2137   gint first_x;
2138   gint last_x;
2139   gint i;
2140   GdkWindow *top_win;
2141   GdkWindow *bottom_win;
2142   GtkTextView *text_view;
2143   GtkTextWindowType type;
2144   GdkDrawable *target;
2145   gint *positions = NULL;
2146   gint size;
2147   GtkTextAttributes *attrs;
2148   GtkTextIter insert;
2149   GtkTextBuffer *buffer;
2150   gboolean in_pixels;
2151   
2152   text_view = GTK_TEXT_VIEW (widget);
2153   
2154   /* See if this expose is on the tab stop window */
2155   top_win = gtk_text_view_get_window (text_view,
2156                                       GTK_TEXT_WINDOW_TOP);
2157
2158   bottom_win = gtk_text_view_get_window (text_view,
2159                                          GTK_TEXT_WINDOW_BOTTOM);
2160
2161   if (event->window == top_win)
2162     {
2163       type = GTK_TEXT_WINDOW_TOP;
2164       target = top_win;
2165     }
2166   else if (event->window == bottom_win)
2167     {
2168       type = GTK_TEXT_WINDOW_BOTTOM;
2169       target = bottom_win;
2170     }
2171   else
2172     return FALSE;
2173   
2174   first_x = event->area.x;
2175   last_x = first_x + event->area.width;
2176
2177   gtk_text_view_window_to_buffer_coords (text_view,
2178                                          type,
2179                                          first_x,
2180                                          0,
2181                                          &first_x,
2182                                          NULL);
2183
2184   gtk_text_view_window_to_buffer_coords (text_view,
2185                                          type,
2186                                          last_x,
2187                                          0,
2188                                          &last_x,
2189                                          NULL);
2190
2191   buffer = gtk_text_view_get_buffer (text_view);
2192
2193   gtk_text_buffer_get_iter_at_mark (buffer,
2194                                     &insert,
2195                                     gtk_text_buffer_get_mark (buffer,
2196                                                               "insert"));
2197   
2198   attrs = gtk_text_attributes_new ();
2199
2200   gtk_text_iter_get_attributes (&insert, attrs);
2201
2202   if (attrs->tabs)
2203     {
2204       size = pango_tab_array_get_size (attrs->tabs);
2205       
2206       pango_tab_array_get_tabs (attrs->tabs,
2207                                 NULL,
2208                                 &positions);
2209
2210       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
2211     }
2212   else
2213     {
2214       size = 0;
2215       in_pixels = FALSE;
2216     }
2217       
2218   gtk_text_attributes_unref (attrs);
2219   
2220   i = 0;
2221   while (i < size)
2222     {
2223       gint pos;
2224
2225       if (!in_pixels)
2226         positions[i] = PANGO_PIXELS (positions[i]);
2227       
2228       gtk_text_view_buffer_to_window_coords (text_view,
2229                                              type,
2230                                              positions[i],
2231                                              0,
2232                                              &pos,
2233                                              NULL);
2234       
2235       gdk_draw_line (target, 
2236                      widget->style->fg_gc [widget->state],
2237                      pos, 0,
2238                      pos, 15); 
2239       
2240       ++i;
2241     }
2242
2243   g_free (positions);
2244
2245   return TRUE;
2246 }
2247
2248 static void
2249 get_lines (GtkTextView  *text_view,
2250            gint          first_y,
2251            gint          last_y,
2252            GArray       *buffer_coords,
2253            GArray       *numbers,
2254            gint         *countp)
2255 {
2256   GtkTextIter iter;
2257   gint count;
2258   gint size;  
2259
2260   g_array_set_size (buffer_coords, 0);
2261   g_array_set_size (numbers, 0);
2262   
2263   /* Get iter at first y */
2264   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
2265
2266   /* For each iter, get its location and add it to the arrays.
2267    * Stop when we pass last_y
2268    */
2269   count = 0;
2270   size = 0;
2271
2272   while (!gtk_text_iter_is_end (&iter))
2273     {
2274       gint y, height;
2275       gint line_num;
2276       
2277       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
2278
2279       g_array_append_val (buffer_coords, y);
2280       line_num = gtk_text_iter_get_line (&iter);
2281       g_array_append_val (numbers, line_num);
2282       
2283       ++count;
2284
2285       if ((y + height) >= last_y)
2286         break;
2287       
2288       gtk_text_iter_forward_line (&iter);
2289     }
2290
2291   *countp = count;
2292 }
2293
2294 static gint
2295 line_numbers_expose (GtkWidget      *widget,
2296                      GdkEventExpose *event,
2297                      gpointer        user_data)
2298 {
2299   gint count;
2300   GArray *numbers;
2301   GArray *pixels;
2302   gint first_y;
2303   gint last_y;
2304   gint i;
2305   GdkWindow *left_win;
2306   GdkWindow *right_win;
2307   PangoLayout *layout;
2308   GtkTextView *text_view;
2309   GtkTextWindowType type;
2310   GdkDrawable *target;
2311   
2312   text_view = GTK_TEXT_VIEW (widget);
2313   
2314   /* See if this expose is on the line numbers window */
2315   left_win = gtk_text_view_get_window (text_view,
2316                                        GTK_TEXT_WINDOW_LEFT);
2317
2318   right_win = gtk_text_view_get_window (text_view,
2319                                         GTK_TEXT_WINDOW_RIGHT);
2320
2321   if (event->window == left_win)
2322     {
2323       type = GTK_TEXT_WINDOW_LEFT;
2324       target = left_win;
2325     }
2326   else if (event->window == right_win)
2327     {
2328       type = GTK_TEXT_WINDOW_RIGHT;
2329       target = right_win;
2330     }
2331   else
2332     return FALSE;
2333   
2334   first_y = event->area.y;
2335   last_y = first_y + event->area.height;
2336
2337   gtk_text_view_window_to_buffer_coords (text_view,
2338                                          type,
2339                                          0,
2340                                          first_y,
2341                                          NULL,
2342                                          &first_y);
2343
2344   gtk_text_view_window_to_buffer_coords (text_view,
2345                                          type,
2346                                          0,
2347                                          last_y,
2348                                          NULL,
2349                                          &last_y);
2350
2351   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
2352   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
2353   
2354   get_lines (text_view,
2355              first_y,
2356              last_y,
2357              pixels,
2358              numbers,
2359              &count);
2360   
2361   /* Draw fully internationalized numbers! */
2362   
2363   layout = gtk_widget_create_pango_layout (widget, "");
2364   
2365   i = 0;
2366   while (i < count)
2367     {
2368       gint pos;
2369       gchar *str;
2370       
2371       gtk_text_view_buffer_to_window_coords (text_view,
2372                                              type,
2373                                              0,
2374                                              g_array_index (pixels, gint, i),
2375                                              NULL,
2376                                              &pos);
2377
2378       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
2379
2380       pango_layout_set_text (layout, str, -1);
2381
2382       gtk_paint_layout (widget->style,
2383                         target,
2384                         GTK_WIDGET_STATE (widget),
2385                         FALSE,
2386                         NULL,
2387                         widget,
2388                         NULL,
2389                         2, pos + 2,
2390                         layout);
2391
2392       g_free (str);
2393       
2394       ++i;
2395     }
2396
2397   g_array_free (pixels, TRUE);
2398   g_array_free (numbers, TRUE);
2399   
2400   g_object_unref (G_OBJECT (layout));
2401
2402   /* don't stop emission, need to draw children */
2403   return FALSE;
2404 }
2405
2406 static View *
2407 create_view (Buffer *buffer)
2408 {
2409   View *view;
2410   
2411   GtkWidget *sw;
2412   GtkWidget *vbox;
2413   
2414   view = g_new0 (View, 1);
2415   views = g_slist_prepend (views, view);
2416
2417   view->buffer = buffer;
2418   buffer_ref (buffer);
2419   
2420   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2421   g_object_set_data (G_OBJECT (view->window), "view", view);
2422   
2423   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
2424                       GTK_SIGNAL_FUNC (delete_event_cb), NULL);
2425
2426   view->accel_group = gtk_accel_group_new ();
2427   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
2428   g_object_set_data (G_OBJECT (view->item_factory), "view", view);
2429   
2430   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
2431
2432   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
2433
2434   vbox = gtk_vbox_new (FALSE, 0);
2435   gtk_container_add (GTK_CONTAINER (view->window), vbox);
2436
2437   gtk_box_pack_start (GTK_BOX (vbox),
2438                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
2439                       FALSE, FALSE, 0);
2440   
2441   sw = gtk_scrolled_window_new (NULL, NULL);
2442   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2443                                  GTK_POLICY_AUTOMATIC,
2444                                  GTK_POLICY_AUTOMATIC);
2445
2446   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
2447   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
2448                                GTK_WRAP_WORD);
2449
2450   /* Make sure border width works, no real reason to do this other than testing */
2451   gtk_container_set_border_width (GTK_CONTAINER (view->text_view),
2452                                   10);
2453   
2454   /* Draw tab stops in the top and bottom windows. */
2455   
2456   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2457                                         GTK_TEXT_WINDOW_TOP,
2458                                         15);
2459
2460   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2461                                         GTK_TEXT_WINDOW_BOTTOM,
2462                                         15);
2463
2464   gtk_signal_connect (GTK_OBJECT (view->text_view),
2465                       "expose_event",
2466                       GTK_SIGNAL_FUNC (tab_stops_expose),
2467                       NULL);  
2468
2469   g_signal_connect (G_OBJECT (view->buffer->buffer),
2470                     "mark_set",
2471                     GTK_SIGNAL_FUNC (cursor_set_callback),
2472                     view->text_view);
2473   
2474   /* Draw line numbers in the side windows; we should really be
2475    * more scientific about what width we set them to.
2476    */
2477   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2478                                         GTK_TEXT_WINDOW_RIGHT,
2479                                         30);
2480   
2481   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2482                                         GTK_TEXT_WINDOW_LEFT,
2483                                         30);
2484   
2485   gtk_signal_connect (GTK_OBJECT (view->text_view),
2486                       "expose_event",
2487                       GTK_SIGNAL_FUNC (line_numbers_expose),
2488                       NULL);
2489   
2490   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
2491   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
2492
2493   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
2494
2495   gtk_widget_grab_focus (view->text_view);
2496
2497   view_set_title (view);
2498   view_init_menus (view);
2499
2500   view_add_example_widgets (view);
2501   
2502   gtk_widget_show_all (view->window);
2503   return view;
2504 }
2505
2506 static void
2507 view_add_example_widgets (View *view)
2508 {
2509   GtkTextChildAnchor *anchor;
2510   Buffer *buffer;
2511
2512   buffer = view->buffer;
2513   
2514   anchor = g_object_get_data (G_OBJECT (buffer->buffer),
2515                               "anchor");
2516
2517   if (anchor && !gtk_text_child_anchor_get_deleted (anchor))
2518     {
2519       GtkWidget *widget;
2520
2521       widget = gtk_button_new_with_label ("Foo");
2522       
2523       gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view->text_view),
2524                                          widget,
2525                                          anchor);
2526
2527       gtk_widget_show (widget);
2528     }
2529 }
2530
2531 static gboolean
2532 file_exists (const char *filename)
2533 {
2534   struct stat statbuf;
2535
2536   return stat (filename, &statbuf) == 0;
2537 }
2538 void
2539 test_init ()
2540 {
2541   if (file_exists ("../gdk-pixbuf/libpixbufloader-pnm.la"))
2542     {
2543       putenv ("GDK_PIXBUF_MODULEDIR=../gdk-pixbuf/.libs");
2544       putenv ("GTK_IM_MODULE_FILE=../modules/input/gtk.immodules");
2545     }
2546 }
2547
2548 int
2549 main (int argc, char** argv)
2550 {
2551   Buffer *buffer;
2552   View *view;
2553   int i;
2554
2555   test_init ();
2556   gtk_init (&argc, &argv);
2557   
2558   buffer = create_buffer ();
2559   view = create_view (buffer);
2560   buffer_unref (buffer);
2561   
2562   push_active_window (GTK_WINDOW (view->window));
2563   for (i=1; i < argc; i++)
2564     {
2565       char *filename;
2566
2567       /* Quick and dirty canonicalization - better should be in GLib
2568        */
2569
2570       if (!g_path_is_absolute (argv[i]))
2571         {
2572           char *cwd = g_get_current_dir ();
2573           filename = g_strconcat (cwd, "/", argv[i], NULL);
2574           g_free (cwd);
2575         }
2576       else
2577         filename = argv[i];
2578
2579       open_ok_func (filename, view);
2580
2581       if (filename != argv[i])
2582         g_free (filename);
2583     }
2584   pop_active_window ();
2585   
2586   gtk_main ();
2587
2588   return 0;
2589 }
2590
2591