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