]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
change demo install dir to datadir/gtk+-2.0/demo
[~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 typedef struct _Buffer Buffer;
11 typedef struct _View View;
12
13 static gint untitled_serial = 1;
14
15 GSList *active_window_stack = NULL;
16
17 struct _Buffer
18 {
19   gint refcount;
20   GtkTextBuffer *buffer;
21   char *filename;
22   gint untitled_serial;
23   GtkTextTag *invisible_tag;
24   GtkTextTag *not_editable_tag;
25   GtkTextTag *found_text_tag;
26   GtkTextTag *custom_tabs_tag;
27 };
28
29 struct _View
30 {
31   GtkWidget *window;
32   GtkWidget *text_view;
33   GtkAccelGroup *accel_group;
34   GtkItemFactory *item_factory;
35   Buffer *buffer;
36 };
37
38 static void push_active_window (GtkWindow *window);
39 static void pop_active_window (void);
40 static GtkWindow *get_active_window (void);
41
42 static Buffer * create_buffer      (void);
43 static gboolean check_buffer_saved (Buffer *buffer);
44 static gboolean save_buffer        (Buffer *buffer);
45 static gboolean save_as_buffer     (Buffer *buffer);
46 static char *   buffer_pretty_name (Buffer *buffer);
47 static void     buffer_filename_set (Buffer *buffer);
48 static void     buffer_search_forward (Buffer *buffer,
49                                        const char *str,
50                                        View *view);
51 static void     buffer_search_backward (Buffer *buffer,
52                                        const char *str,
53                                        View *view);
54
55 static View *view_from_widget (GtkWidget *widget);
56
57 static View *create_view      (Buffer *buffer);
58 static void  check_close_view (View   *view);
59 static void  close_view       (View   *view);
60 static void  view_set_title   (View   *view);
61 static void  view_init_menus  (View   *view);
62
63 GSList *buffers = NULL;
64 GSList *views = NULL;
65
66 static void
67 push_active_window (GtkWindow *window)
68 {
69   gtk_object_ref (GTK_OBJECT (window));
70   active_window_stack = g_slist_prepend (active_window_stack, window);
71 }
72
73 static void
74 pop_active_window (void)
75 {
76   gtk_object_unref (active_window_stack->data);
77   active_window_stack = g_slist_delete_link (active_window_stack, active_window_stack);
78 }
79
80 static GtkWindow *
81 get_active_window (void)
82 {
83   if (active_window_stack)
84     return active_window_stack->data;
85   else
86     return NULL;
87 }
88
89 /*
90  * Filesel utility function
91  */
92
93 typedef gboolean (*FileselOKFunc) (const char *filename, gpointer data);
94
95 static void
96 filesel_ok_cb (GtkWidget *button, GtkWidget *filesel)
97 {
98   FileselOKFunc ok_func = gtk_object_get_data (GTK_OBJECT (filesel), "ok-func");
99   gpointer data = gtk_object_get_data (GTK_OBJECT (filesel), "ok-data");
100   gint *result = gtk_object_get_data (GTK_OBJECT (filesel), "ok-result");
101   
102   gtk_widget_hide (filesel);
103   
104   if ((*ok_func) (gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), data))
105     {
106       gtk_widget_destroy (filesel);
107       *result = TRUE;
108     }
109   else
110     gtk_widget_show (filesel);
111 }
112
113 gboolean
114 filesel_run (GtkWindow    *parent, 
115              const char   *title,
116              const char   *start_file,
117              FileselOKFunc func,
118              gpointer      data)
119 {
120   GtkWidget *filesel = gtk_file_selection_new (title);
121   gboolean result = FALSE;
122
123   if (!parent)
124     parent = get_active_window ();
125   
126   if (parent)
127     gtk_window_set_transient_for (GTK_WINDOW (filesel), parent);
128
129   if (start_file)
130     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), start_file);
131
132   
133   gtk_object_set_data (GTK_OBJECT (filesel), "ok-func", func);
134   gtk_object_set_data (GTK_OBJECT (filesel), "ok-data", data);
135   gtk_object_set_data (GTK_OBJECT (filesel), "ok-result", &result);
136
137   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
138                       "clicked",
139                       GTK_SIGNAL_FUNC (filesel_ok_cb), filesel);
140   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
141                              "clicked",
142                              GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (filesel));
143
144   gtk_signal_connect (GTK_OBJECT (filesel), "destroy",
145                       GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
146   gtk_window_set_modal (GTK_WINDOW (filesel), TRUE);
147
148   gtk_widget_show (filesel);
149   gtk_main ();
150
151   return result;
152 }
153
154 /*
155  * MsgBox utility functions
156  */
157
158 static void
159 msgbox_yes_cb (GtkWidget *widget, gboolean *result)
160 {
161   *result = 0;
162   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
163 }
164
165 static void
166 msgbox_no_cb (GtkWidget *widget, gboolean *result)
167 {
168   *result = 1;
169   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
170 }
171
172 static gboolean
173 msgbox_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
174 {
175   if (event->keyval == GDK_Escape)
176     {
177       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
178       gtk_object_destroy (GTK_OBJECT (widget));
179       return TRUE;
180     }
181
182   return FALSE;
183 }
184
185 gint
186 msgbox_run (GtkWindow  *parent,
187             const char *message,
188             const char *yes_button,
189             const char *no_button,
190             const char *cancel_button,
191             gint default_index)
192 {
193   gboolean result = -1;
194   GtkWidget *dialog;
195   GtkWidget *button;
196   GtkWidget *label;
197   GtkWidget *vbox;
198   GtkWidget *button_box;
199   GtkWidget *separator;
200
201   g_return_val_if_fail (message != NULL, FALSE);
202   g_return_val_if_fail (default_index >= 0 && default_index <= 1, FALSE);
203
204   if (!parent)
205     parent = get_active_window ();
206   
207   /* Create a dialog
208    */
209   dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
210   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
211   if (parent)
212     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
213   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
214
215   /* Quit our recursive main loop when the dialog is destroyed.
216    */
217   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
218                       GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
219
220   /* Catch Escape key presses and have them destroy the dialog
221    */
222   gtk_signal_connect (GTK_OBJECT (dialog), "key_press_event",
223                       GTK_SIGNAL_FUNC (msgbox_key_press_cb), NULL);
224
225   /* Fill in the contents of the widget
226    */
227   vbox = gtk_vbox_new (FALSE, 0);
228   gtk_container_add (GTK_CONTAINER (dialog), vbox);
229   
230   label = gtk_label_new (message);
231   gtk_misc_set_padding (GTK_MISC (label), 12, 12);
232   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
233   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
234
235   separator = gtk_hseparator_new ();
236   gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
237
238   button_box = gtk_hbutton_box_new ();
239   gtk_box_pack_start (GTK_BOX (vbox), button_box, FALSE, FALSE, 0);
240   gtk_container_set_border_width (GTK_CONTAINER (button_box), 8);
241   
242
243   /* When Yes is clicked, call the msgbox_yes_cb
244    * This sets the result variable and destroys the dialog
245    */
246   if (yes_button)
247     {
248       button = gtk_button_new_with_label (yes_button);
249       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
250       gtk_container_add (GTK_CONTAINER (button_box), button);
251
252       if (default_index == 0)
253         gtk_widget_grab_default (button);
254       
255       gtk_signal_connect (GTK_OBJECT (button), "clicked",
256                           GTK_SIGNAL_FUNC (msgbox_yes_cb), &result);
257     }
258
259   /* When No is clicked, call the msgbox_no_cb
260    * This sets the result variable and destroys the dialog
261    */
262   if (no_button)
263     {
264       button = gtk_button_new_with_label (no_button);
265       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
266       gtk_container_add (GTK_CONTAINER (button_box), button);
267
268       if (default_index == 0)
269         gtk_widget_grab_default (button);
270       
271       gtk_signal_connect (GTK_OBJECT (button), "clicked",
272                           GTK_SIGNAL_FUNC (msgbox_no_cb), &result);
273     }
274
275   /* When Cancel is clicked, destroy the dialog
276    */
277   if (cancel_button)
278     {
279       button = gtk_button_new_with_label (cancel_button);
280       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
281       gtk_container_add (GTK_CONTAINER (button_box), button);
282       
283       if (default_index == 1)
284         gtk_widget_grab_default (button);
285       
286       gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
287                                  GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (dialog));
288     }
289
290   gtk_widget_show_all (dialog);
291
292   /* Run a recursive main loop until a button is clicked
293    * or the user destroys the dialog through the window mananger */
294   gtk_main ();
295
296   return result;
297 }
298
299 /*
300  * Example buffer filling code
301  */
302 static gint
303 blink_timeout (gpointer data)
304 {
305   GtkTextTag *tag;
306   static gboolean flip = FALSE;
307   
308   tag = GTK_TEXT_TAG (data);
309
310   gtk_object_set (GTK_OBJECT (tag),
311                  "foreground", flip ? "blue" : "purple",
312                  NULL);
313
314   flip = !flip;
315
316   return TRUE;
317 }
318
319 static gint
320 tag_event_handler (GtkTextTag *tag, GtkWidget *widget, GdkEvent *event,
321                   const GtkTextIter *iter, gpointer user_data)
322 {
323   gint char_index;
324
325   char_index = gtk_text_iter_get_offset (iter);
326   
327   switch (event->type)
328     {
329     case GDK_MOTION_NOTIFY:
330       printf ("Motion event at char %d tag `%s'\n",
331              char_index, tag->name);
332       break;
333         
334     case GDK_BUTTON_PRESS:
335       printf ("Button press at char %d tag `%s'\n",
336              char_index, tag->name);
337       break;
338         
339     case GDK_2BUTTON_PRESS:
340       printf ("Double click at char %d tag `%s'\n",
341              char_index, tag->name);
342       break;
343         
344     case GDK_3BUTTON_PRESS:
345       printf ("Triple click at char %d tag `%s'\n",
346              char_index, tag->name);
347       break;
348         
349     case GDK_BUTTON_RELEASE:
350       printf ("Button release at char %d tag `%s'\n",
351              char_index, tag->name);
352       break;
353         
354     case GDK_KEY_PRESS:
355     case GDK_KEY_RELEASE:
356       printf ("Key event at char %d tag `%s'\n",
357               char_index, tag->name);
358       break;
359       
360     case GDK_ENTER_NOTIFY:
361     case GDK_LEAVE_NOTIFY:
362     case GDK_PROPERTY_NOTIFY:
363     case GDK_SELECTION_CLEAR:
364     case GDK_SELECTION_REQUEST:
365     case GDK_SELECTION_NOTIFY:
366     case GDK_PROXIMITY_IN:
367     case GDK_PROXIMITY_OUT:
368     case GDK_DRAG_ENTER:
369     case GDK_DRAG_LEAVE:
370     case GDK_DRAG_MOTION:
371     case GDK_DRAG_STATUS:
372     case GDK_DROP_START:
373     case GDK_DROP_FINISHED:
374     default:
375       break;
376     }
377
378   return FALSE;
379 }
380
381 static void
382 setup_tag (GtkTextTag *tag)
383 {
384
385   gtk_signal_connect (GTK_OBJECT (tag),
386                      "event",
387                      GTK_SIGNAL_FUNC (tag_event_handler),
388                      NULL);
389 }
390
391 static char  *book_closed_xpm[] = {
392 "16 16 6 1",
393 "       c None s None",
394 ".      c black",
395 "X      c red",
396 "o      c yellow",
397 "O      c #808080",
398 "#      c white",
399 "                ",
400 "       ..       ",
401 "     ..XX.      ",
402 "   ..XXXXX.     ",
403 " ..XXXXXXXX.    ",
404 ".ooXXXXXXXXX.   ",
405 "..ooXXXXXXXXX.  ",
406 ".X.ooXXXXXXXXX. ",
407 ".XX.ooXXXXXX..  ",
408 " .XX.ooXXX..#O  ",
409 "  .XX.oo..##OO. ",
410 "   .XX..##OO..  ",
411 "    .X.#OO..    ",
412 "     ..O..      ",
413 "      ..        ",
414 "                "};
415
416
417
418 void
419 fill_example_buffer (GtkTextBuffer *buffer)
420 {
421   GtkTextIter iter, iter2;
422   GtkTextTag *tag;
423   GdkColor color;
424   GdkColor color2;
425   GdkPixbuf *pixbuf;
426   int i;
427   char *str;
428
429   /* FIXME this is broken if called twice on a buffer, since
430    * we try to create tags a second time.
431    */
432   
433   tag = gtk_text_buffer_create_tag (buffer, "fg_blue");
434
435   /*       gtk_timeout_add (1000, blink_timeout, tag); */
436       
437   setup_tag (tag);
438   
439   color.red = color.green = 0;
440   color.blue = 0xffff;
441   color2.red = 0xfff;
442   color2.blue = 0x0;
443   color2.green = 0;
444   gtk_object_set (GTK_OBJECT (tag),
445                  "foreground_gdk", &color,
446                  "background_gdk", &color2,
447                  "font", "Sans 24",
448                  NULL);
449
450   tag = gtk_text_buffer_create_tag (buffer, "fg_red");
451
452   setup_tag (tag);
453       
454   color.blue = color.green = 0;
455   color.red = 0xffff;
456   gtk_object_set (GTK_OBJECT (tag),
457                  "offset", -4,
458                  "foreground_gdk", &color,
459                  NULL);
460
461   tag = gtk_text_buffer_create_tag (buffer, "bg_green");
462
463   setup_tag (tag);
464       
465   color.blue = color.red = 0;
466   color.green = 0xffff;
467   gtk_object_set (GTK_OBJECT (tag),
468                  "background_gdk", &color,
469                  "font", "Sans 10",
470                  NULL);
471
472   tag = gtk_text_buffer_create_tag (buffer, "strikethrough");
473
474   setup_tag (tag);
475       
476   gtk_object_set (GTK_OBJECT (tag),
477                  "strikethrough", TRUE,
478                  NULL);
479
480
481   tag = gtk_text_buffer_create_tag (buffer, "underline");
482
483   setup_tag (tag);
484       
485   gtk_object_set (GTK_OBJECT (tag),
486                  "underline", PANGO_UNDERLINE_SINGLE,
487                  NULL);
488
489   setup_tag (tag);
490       
491   gtk_object_set (GTK_OBJECT (tag),
492                  "underline", PANGO_UNDERLINE_SINGLE,
493                  NULL);
494
495   tag = gtk_text_buffer_create_tag (buffer, "centered");
496       
497   gtk_object_set (GTK_OBJECT (tag),
498                  "justify", GTK_JUSTIFY_CENTER,
499                  NULL);
500
501   tag = gtk_text_buffer_create_tag (buffer, "rtl_quote");
502       
503   gtk_object_set (GTK_OBJECT (tag),
504                  "wrap_mode", GTK_WRAPMODE_WORD,
505                  "direction", GTK_TEXT_DIR_RTL,
506                  "left_wrapped_line_margin", 20,
507                  "left_margin", 20,
508                  "right_margin", 20,
509                  NULL);
510
511
512   
513   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
514   
515   i = 0;
516   while (i < 100)
517     {
518       GtkTextMark * temp_mark;
519       
520       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
521           
522       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
523           
524       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",
525                             i);
526       
527       gtk_text_buffer_insert (buffer, &iter, str, -1);
528
529       g_free (str);
530       
531       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 5);
532           
533       gtk_text_buffer_insert (buffer, &iter,
534                              "(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"
535                              /* This is UTF8 stuff, Emacs doesn't
536                                 really know how to display it */
537                              "German (Deutsch Süd) Grüß Gott Greek (Ελληνικά) Γειά σας Hebrew   שלום Japanese (日本語)\n", -1);
538
539       temp_mark =
540         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
541
542 #if 1
543       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 6);
544       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 13);
545
546       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
547
548       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 10);
549       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 16);
550
551       gtk_text_buffer_apply_tag_by_name (buffer, "underline", &iter, &iter2);
552
553       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 14);
554       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 24);
555
556       gtk_text_buffer_apply_tag_by_name (buffer, "strikethrough", &iter, &iter2);
557           
558       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 9);
559       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 16);
560
561       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
562   
563       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 2);
564       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 10);
565
566       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
567
568       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 8);
569       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 15);
570
571       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
572 #endif
573
574       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
575       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
576           
577       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
578       gtk_text_buffer_apply_tag_by_name (buffer, "centered", &iter2, &iter);
579
580       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
581       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
582       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
583       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
584       gtk_text_buffer_apply_tag_by_name (buffer, "rtl_quote", &iter2, &iter);
585           
586       ++i;
587     }
588
589   g_object_unref (G_OBJECT (pixbuf));
590   
591   printf ("%d lines %d chars\n",
592           gtk_text_buffer_get_line_count (buffer),
593           gtk_text_buffer_get_char_count (buffer));
594
595   /* Move cursor to start */
596   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
597   gtk_text_buffer_place_cursor (buffer, &iter);
598   
599   gtk_text_buffer_set_modified (buffer, FALSE);
600 }
601
602 gboolean
603 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
604 {
605   FILE* f;
606   gchar buf[2048];
607   gint remaining = 0;
608   GtkTextIter iter, end;
609
610   f = fopen (filename, "r");
611   
612   if (f == NULL)
613     {
614       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
615                                     filename, g_strerror (errno));
616       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
617       g_free (err);
618       return FALSE;
619     }
620   
621   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
622   while (!feof (f))
623     {
624       gint count;
625       char *leftover, *next;
626       int to_read = 2047  - remaining;
627
628       count = fread (buf + remaining, 1, to_read, f);
629       buf[count + remaining] = '\0';
630
631       leftover = next = buf;
632       while (next)
633         {
634           leftover = next;
635           if (!*leftover)
636             break;
637           
638           next = g_utf8_next_char (next);
639           if (next > buf+count+remaining) {
640             next = NULL;
641             break;
642           }
643         }
644
645       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
646
647       remaining = buf + remaining + count - leftover;
648       g_memmove (buf, leftover, remaining);
649
650       if (remaining > 6 || count < to_read)
651           break;
652     }
653
654   if (remaining)
655     {
656       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
657       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
658       g_free (err);
659     }
660   
661   /* We had a newline in the buffer to begin with. (The buffer always contains
662    * a newline, so we delete to the end of the buffer to clean up.
663    */
664   gtk_text_buffer_get_last_iter (buffer, &end);
665   gtk_text_buffer_delete (buffer, &iter, &end);
666   
667   gtk_text_buffer_set_modified (buffer, FALSE);
668
669   return TRUE;
670 }
671
672 static gint
673 delete_event_cb (GtkWidget *window, GdkEventAny *event, gpointer data)
674 {
675   View *view = view_from_widget (window);
676
677   push_active_window (GTK_WINDOW (window));
678   check_close_view (view);
679   pop_active_window ();
680
681   return TRUE;
682 }
683
684 /*
685  * Menu callbacks
686  */
687
688 static View *
689 get_empty_view (View *view)
690 {
691   if (!view->buffer->filename &&
692       !gtk_text_buffer_modified (view->buffer->buffer))
693     return view;
694   else
695     return create_view (create_buffer ());
696 }
697
698 static View *
699 view_from_widget (GtkWidget *widget)
700 {
701   GtkWidget *app;
702
703   if (GTK_IS_MENU_ITEM (widget))
704     {
705       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
706       return gtk_object_get_data (GTK_OBJECT (item_factory), "view");      
707     }
708   else
709     {
710       GtkWidget *app = gtk_widget_get_toplevel (widget);
711       return gtk_object_get_data (GTK_OBJECT (app), "view");
712     }
713 }
714
715 static void
716 do_new (gpointer             callback_data,
717         guint                callback_action,
718         GtkWidget           *widget)
719 {
720   create_view (create_buffer ());
721 }
722
723 static void
724 do_new_view (gpointer             callback_data,
725              guint                callback_action,
726              GtkWidget           *widget)
727 {
728   View *view = view_from_widget (widget);
729   
730   create_view (view->buffer);
731 }
732
733 gboolean
734 open_ok_func (const char *filename, gpointer data)
735 {
736   View *view = data;
737   View *new_view = get_empty_view (view);
738
739   if (!fill_file_buffer (new_view->buffer->buffer, filename))
740     {
741       if (new_view != view)
742         close_view (new_view);
743       return FALSE;
744     }
745   else
746     {
747       g_free (new_view->buffer->filename);
748       new_view->buffer->filename = g_strdup (filename);
749       buffer_filename_set (new_view->buffer);
750       
751       return TRUE;
752     }
753 }
754
755 static void
756 do_open (gpointer             callback_data,
757          guint                callback_action,
758          GtkWidget           *widget)
759 {
760   View *view = view_from_widget (widget);
761
762   push_active_window (GTK_WINDOW (view->window));
763   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
764   pop_active_window ();
765 }
766
767 static void
768 do_save_as (gpointer             callback_data,
769             guint                callback_action,
770             GtkWidget           *widget)
771 {
772   View *view = view_from_widget (widget);  
773
774   push_active_window (GTK_WINDOW (view->window));
775   save_as_buffer (view->buffer);
776   pop_active_window ();
777 }
778
779 static void
780 do_save (gpointer             callback_data,
781          guint                callback_action,
782          GtkWidget           *widget)
783 {
784   View *view = view_from_widget (widget);
785
786   push_active_window (GTK_WINDOW (view->window));
787   if (!view->buffer->filename)
788     do_save_as (callback_data, callback_action, widget);
789   else
790     save_buffer (view->buffer);
791   pop_active_window ();
792 }
793
794 static void
795 do_close   (gpointer             callback_data,
796             guint                callback_action,
797             GtkWidget           *widget)
798 {
799   View *view = view_from_widget (widget);
800
801   push_active_window (GTK_WINDOW (view->window));
802   check_close_view (view);
803   pop_active_window ();
804 }
805
806 static void
807 do_exit    (gpointer             callback_data,
808             guint                callback_action,
809             GtkWidget           *widget)
810 {
811   View *view = view_from_widget (widget);
812
813   GSList *tmp_list = buffers;
814
815   push_active_window (GTK_WINDOW (view->window));
816   while (tmp_list)
817     {
818       if (!check_buffer_saved (tmp_list->data))
819         return;
820
821       tmp_list = tmp_list->next;
822     }
823
824   gtk_main_quit ();
825   pop_active_window ();
826 }
827
828 static void
829 do_example (gpointer             callback_data,
830             guint                callback_action,
831             GtkWidget           *widget)
832 {
833   View *view = view_from_widget (widget);
834   View *new_view;
835
836   new_view = get_empty_view (view);
837   
838   fill_example_buffer (new_view->buffer->buffer);
839 }
840
841 static void
842 do_wrap_changed (gpointer             callback_data,
843                  guint                callback_action,
844                  GtkWidget           *widget)
845 {
846   View *view = view_from_widget (widget);
847
848   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
849 }
850
851 static void
852 do_direction_changed (gpointer             callback_data,
853                       guint                callback_action,
854                       GtkWidget           *widget)
855 {
856   View *view = view_from_widget (widget);
857   
858   gtk_widget_set_direction (view->text_view, callback_action);
859   gtk_widget_queue_resize (view->text_view);
860 }
861
862 static void
863 do_editable_changed (gpointer callback_data,
864                      guint callback_action,
865                      GtkWidget *widget)
866 {
867   View *view = view_from_widget (widget);
868
869   gtk_text_view_set_editable (GTK_TEXT_VIEW (view->text_view), callback_action);
870 }
871
872 static void
873 do_cursor_visible_changed (gpointer callback_data,
874                            guint callback_action,
875                            GtkWidget *widget)
876 {
877   View *view = view_from_widget (widget);
878
879   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view->text_view), callback_action);
880 }
881
882 static void
883 do_apply_editable (gpointer callback_data,
884                    guint callback_action,
885                    GtkWidget *widget)
886 {
887   View *view = view_from_widget (widget);
888   GtkTextIter start;
889   GtkTextIter end;
890   
891   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
892                                             &start, &end))
893     {
894       if (callback_action)
895         {
896           gtk_text_buffer_remove_tag (view->buffer->buffer,
897                                       view->buffer->not_editable_tag,
898                                       &start, &end);
899         }
900       else
901         {
902           gtk_text_buffer_apply_tag (view->buffer->buffer,
903                                      view->buffer->not_editable_tag,
904                                      &start, &end);
905         }
906     }
907 }
908
909 static void
910 do_apply_invisible (gpointer callback_data,
911                     guint callback_action,
912                     GtkWidget *widget)
913 {
914   View *view = view_from_widget (widget);
915   GtkTextIter start;
916   GtkTextIter end;
917   
918   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
919                                             &start, &end))
920     {
921       if (callback_action)
922         {
923           gtk_text_buffer_remove_tag (view->buffer->buffer,
924                                       view->buffer->invisible_tag,
925                                       &start, &end);
926         }
927       else
928         {
929           gtk_text_buffer_apply_tag (view->buffer->buffer,
930                                      view->buffer->invisible_tag,
931                                      &start, &end);
932         }
933     }
934 }
935
936 static void
937 do_apply_tabs (gpointer callback_data,
938                guint callback_action,
939                GtkWidget *widget)
940 {
941   View *view = view_from_widget (widget);
942   GtkTextIter start;
943   GtkTextIter end;
944   
945   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
946                                             &start, &end))
947     {
948       if (callback_action)
949         {
950           gtk_text_buffer_remove_tag (view->buffer->buffer,
951                                       view->buffer->custom_tabs_tag,
952                                       &start, &end);
953         }
954       else
955         {
956           gtk_text_buffer_apply_tag (view->buffer->buffer,
957                                      view->buffer->custom_tabs_tag,
958                                      &start, &end);
959         }
960     }
961 }
962
963 enum
964 {
965   RESPONSE_FORWARD,
966   RESPONSE_BACKWARD
967 };
968
969 static void
970 dialog_response_callback (GtkWidget *dialog, gint response_id, gpointer data)
971 {
972   GtkTextBuffer *buffer;
973   View *view = data;
974   GtkTextIter start, end;
975   gchar *search_string;
976
977   if (response_id != RESPONSE_FORWARD &&
978       response_id != RESPONSE_BACKWARD)
979     {
980       gtk_widget_destroy (dialog);
981       return;
982     }
983   
984   buffer = gtk_object_get_data (GTK_OBJECT (dialog), "buffer");
985
986   gtk_text_buffer_get_bounds (buffer, &start, &end);
987
988   /* Remove trailing newline */
989   gtk_text_iter_prev_char (&end);
990   
991   search_string = gtk_text_iter_get_text (&start, &end);
992
993   printf ("Searching for `%s'\n", search_string);
994
995   if (response_id == RESPONSE_FORWARD)
996     buffer_search_forward (view->buffer, search_string, view);
997   else if (response_id == RESPONSE_BACKWARD)
998     buffer_search_backward (view->buffer, search_string, view);
999     
1000   g_free (search_string);
1001   
1002   gtk_widget_destroy (dialog);
1003 }
1004
1005 static void
1006 do_search (gpointer callback_data,
1007            guint callback_action,
1008            GtkWidget *widget)
1009 {
1010   View *view = view_from_widget (widget);
1011   GtkWidget *dialog;
1012   GtkWidget *search_text;
1013   GtkTextBuffer *buffer;
1014
1015   dialog = gtk_dialog_new_with_buttons ("Search",
1016                                         GTK_WINDOW (view->window),
1017                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1018                                         "Forward", RESPONSE_FORWARD,
1019                                         "Backward", RESPONSE_BACKWARD,
1020                                         GTK_STOCK_BUTTON_CANCEL,
1021                                         GTK_RESPONSE_NONE, NULL);
1022
1023
1024   buffer = gtk_text_buffer_new (NULL);
1025
1026   search_text = gtk_text_view_new_with_buffer (buffer);
1027
1028   g_object_unref (G_OBJECT (buffer));
1029   
1030   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1031                     search_text,
1032                     TRUE, TRUE, 0);
1033
1034   gtk_object_set_data (GTK_OBJECT (dialog), "buffer", buffer);
1035   
1036   gtk_signal_connect (GTK_OBJECT (dialog),
1037                       "response",
1038                       GTK_SIGNAL_FUNC (dialog_response_callback),
1039                       view);
1040
1041   gtk_widget_show (search_text);
1042
1043   gtk_widget_grab_focus (search_text);
1044   
1045   gtk_widget_show_all (dialog);
1046 }
1047
1048 static void
1049 view_init_menus (View *view)
1050 {
1051   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
1052   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
1053   GtkWidget *menu_item = NULL;
1054
1055   switch (direction)
1056     {
1057     case GTK_TEXT_DIR_LTR:
1058       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1059       break;
1060     case GTK_TEXT_DIR_RTL:
1061       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1062       break;
1063     default:
1064       break;
1065     }
1066
1067   if (menu_item)
1068     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1069
1070   switch (wrap_mode)
1071     {
1072     case GTK_WRAPMODE_NONE:
1073       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1074       break;
1075     case GTK_WRAPMODE_WORD:
1076       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1077       break;
1078     default:
1079       break;
1080     }
1081   
1082   if (menu_item)
1083     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1084 }
1085
1086 static GtkItemFactoryEntry menu_items[] =
1087 {
1088   { "/_File",            NULL,         0,           0, "<Branch>" },
1089   { "/File/_New",        "<control>N", do_new,      0, NULL },
1090   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1091   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1092   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1093   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1094   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
1095   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1096   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1097
1098   { "/_Edit", NULL, 0, 0, "<Branch>" },
1099   { "/Edit/Find...", NULL, do_search, 0, NULL },
1100
1101   { "/_Settings",         NULL,         0,                0, "<Branch>" },
1102   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAPMODE_NONE, "<RadioItem>" },
1103   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAPMODE_WORD, "/Settings/Wrap Off" },
1104   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1105   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1106   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1107   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1108
1109   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1110   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1111   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1112   
1113   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1114   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1115   { "/_Attributes",       NULL,         0,                0, "<Branch>" },
1116   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1117   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1118   { "/Attributes/Invisible",      NULL,         do_apply_invisible, FALSE, NULL },
1119   { "/Attributes/Visible",        NULL,         do_apply_invisible, TRUE, NULL },
1120   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1121   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1122   { "/_Test",            NULL,         0,           0, "<Branch>" },
1123   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1124 };
1125
1126 static gboolean
1127 save_buffer (Buffer *buffer)
1128 {
1129   GtkTextIter start, end;
1130   gchar *chars;
1131   gboolean result = FALSE;
1132   gboolean have_backup = FALSE;
1133   gchar *bak_filename;
1134   FILE *file;
1135
1136   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1137
1138   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1139   
1140   if (rename (buffer->filename, bak_filename) != 0)
1141     {
1142       if (errno != ENOENT)
1143         {
1144           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1145                                         buffer->filename, bak_filename, g_strerror (errno));
1146           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1147           g_free (err);
1148           return FALSE;
1149         }
1150     }
1151   else
1152     have_backup = TRUE;
1153   
1154   file = fopen (buffer->filename, "w");
1155   if (!file)
1156     {
1157       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1158                                     buffer->filename, bak_filename, g_strerror (errno));
1159       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1160     }
1161   else
1162     {
1163       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1164       gtk_text_buffer_get_last_iter (buffer->buffer, &end);
1165   
1166       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1167
1168       if (fputs (chars, file) == EOF ||
1169           fclose (file) == EOF)
1170         {
1171           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1172                                         buffer->filename, g_strerror (errno));
1173           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1174           g_free (err);
1175         }
1176       else
1177         {
1178           /* Success
1179            */
1180           result = TRUE;
1181           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1182         }
1183         
1184       g_free (chars);
1185     }
1186
1187   if (!result && have_backup)
1188     {
1189       if (rename (bak_filename, buffer->filename) != 0)
1190         {
1191           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1192                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1193           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1194           g_free (err);
1195         }
1196     }
1197
1198   g_free (bak_filename);
1199   
1200   return result;
1201 }
1202
1203 static gboolean
1204 save_as_ok_func (const char *filename, gpointer data)
1205 {
1206   Buffer *buffer = data;
1207   char *old_filename = buffer->filename;
1208
1209   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1210     {
1211       struct stat statbuf;
1212
1213       if (stat (filename, &statbuf) == 0)
1214         {
1215           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1216           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1217           g_free (err);
1218
1219           if (result != 0)
1220             return FALSE;
1221         }
1222     }
1223   
1224   buffer->filename = g_strdup (filename);
1225
1226   if (save_buffer (buffer))
1227     {
1228       g_free (old_filename);
1229       buffer_filename_set (buffer);
1230       return TRUE;
1231     }
1232   else
1233     {
1234       g_free (buffer->filename);
1235       buffer->filename = old_filename;
1236       return FALSE;
1237     }
1238 }
1239
1240 static gboolean
1241 save_as_buffer (Buffer *buffer)
1242 {
1243   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1244 }
1245
1246 static gboolean
1247 check_buffer_saved (Buffer *buffer)
1248 {
1249   if (gtk_text_buffer_modified (buffer->buffer))
1250     {
1251       char *pretty_name = buffer_pretty_name (buffer);
1252       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1253       gint result;
1254       
1255       g_free (pretty_name);
1256       
1257       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1258       g_free (msg);
1259   
1260       if (result == 0)
1261         return save_as_buffer (buffer);
1262       else if (result == 1)
1263         return TRUE;
1264       else
1265         return FALSE;
1266     }
1267   else
1268     return TRUE;
1269 }
1270
1271 static Buffer *
1272 create_buffer (void)
1273 {
1274   Buffer *buffer;
1275   PangoTabArray *tabs;
1276   
1277   buffer = g_new (Buffer, 1);
1278
1279   buffer->buffer = gtk_text_buffer_new (NULL);
1280   
1281   buffer->refcount = 1;
1282   buffer->filename = NULL;
1283   buffer->untitled_serial = -1;
1284
1285   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1286   gtk_object_set (GTK_OBJECT (buffer->invisible_tag),
1287                   "invisible", TRUE,
1288                   NULL);
1289   
1290   buffer->not_editable_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1291   gtk_object_set (GTK_OBJECT (buffer->not_editable_tag),
1292                   "editable", FALSE,
1293                   "foreground", "purple", NULL);
1294
1295   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1296   gtk_object_set (GTK_OBJECT (buffer->found_text_tag),
1297                   "foreground", "red", NULL);
1298
1299   tabs = pango_tab_array_new_with_positions (4,
1300                                              TRUE,
1301                                              PANGO_TAB_LEFT, 10,
1302                                              PANGO_TAB_LEFT, 30,
1303                                              PANGO_TAB_LEFT, 60,
1304                                              PANGO_TAB_LEFT, 120);
1305   
1306   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1307   gtk_object_set (GTK_OBJECT (buffer->custom_tabs_tag),
1308                   "tabs", tabs,
1309                   "foreground", "green", NULL);
1310
1311   pango_tab_array_free (tabs);
1312   
1313   buffers = g_slist_prepend (buffers, buffer);
1314   
1315   return buffer;
1316 }
1317
1318 static char *
1319 buffer_pretty_name (Buffer *buffer)
1320 {
1321   if (buffer->filename)
1322     {
1323       char *p;
1324       char *result = g_path_get_basename (buffer->filename);
1325       p = strchr (result, '/');
1326       if (p)
1327         *p = '\0';
1328
1329       return result;
1330     }
1331   else
1332     {
1333       if (buffer->untitled_serial == -1)
1334         buffer->untitled_serial = untitled_serial++;
1335
1336       if (buffer->untitled_serial == 1)
1337         return g_strdup ("Untitled");
1338       else
1339         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1340     }
1341 }
1342
1343 static void
1344 buffer_filename_set (Buffer *buffer)
1345 {
1346   GSList *tmp_list = views;
1347
1348   while (tmp_list)
1349     {
1350       View *view = tmp_list->data;
1351
1352       if (view->buffer == buffer)
1353         view_set_title (view);
1354
1355       tmp_list = tmp_list->next;
1356     }
1357 }
1358
1359 static void
1360 buffer_search (Buffer     *buffer,
1361                const char *str,
1362                View       *view,
1363                gboolean forward)
1364 {
1365   GtkTextIter iter;
1366   GtkTextIter start, end;
1367   GtkWidget *dialog;
1368   int i;
1369   
1370   /* remove tag from whole buffer */
1371   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1372   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1373                               &start, &end );
1374   
1375   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1376                                     gtk_text_buffer_get_mark (buffer->buffer,
1377                                                               "insert"));
1378
1379   i = 0;
1380   if (*str != '\0')
1381     {
1382       GtkTextIter match_start, match_end;
1383
1384       if (forward)
1385         {
1386           while (gtk_text_iter_forward_search (&iter, str, TRUE, FALSE,
1387                                                &match_start, &match_end,
1388                                                NULL))
1389             {
1390               ++i;
1391               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1392                                          &match_start, &match_end);
1393               
1394               iter = match_end;
1395             }
1396         }
1397       else
1398         {
1399           while (gtk_text_iter_backward_search (&iter, str, TRUE, FALSE,
1400                                                 &match_start, &match_end,
1401                                                 NULL))
1402             {
1403               ++i;
1404               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1405                                          &match_start, &match_end);
1406               
1407               iter = match_start;
1408             }
1409         }
1410     }
1411
1412   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1413                                    GTK_MESSAGE_INFO,
1414                                    GTK_BUTTONS_OK,
1415                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1416                                    "%d strings found and marked in red",
1417                                    i);
1418
1419   gtk_signal_connect_object (GTK_OBJECT (dialog),
1420                              "response",
1421                              GTK_SIGNAL_FUNC (gtk_widget_destroy),
1422                              GTK_OBJECT (dialog));
1423   
1424   gtk_widget_show (dialog);
1425 }
1426
1427 static void
1428 buffer_search_forward (Buffer *buffer, const char *str,
1429                        View *view)
1430 {
1431   buffer_search (buffer, str, view, TRUE);
1432 }
1433
1434 static void
1435 buffer_search_backward (Buffer *buffer, const char *str,
1436                         View *view)
1437 {
1438   buffer_search (buffer, str, view, FALSE);
1439 }
1440
1441 static void
1442 buffer_ref (Buffer *buffer)
1443 {
1444   buffer->refcount++;
1445 }
1446
1447 static void
1448 buffer_unref (Buffer *buffer)
1449 {
1450   buffer->refcount--;
1451   if (buffer->refcount == 0)
1452     {
1453       buffers = g_slist_remove (buffers, buffer);
1454       gtk_object_unref (GTK_OBJECT (buffer->buffer));
1455       g_free (buffer->filename);
1456       g_free (buffer);
1457     }
1458 }
1459
1460 static void
1461 close_view (View *view)
1462 {
1463   views = g_slist_remove (views, view);
1464   buffer_unref (view->buffer);
1465   gtk_widget_destroy (view->window);
1466   g_object_unref (G_OBJECT (view->item_factory));
1467   
1468   g_free (view);
1469   
1470   if (!views)
1471     gtk_main_quit ();
1472 }
1473
1474 static void
1475 check_close_view (View *view)
1476 {
1477   if (view->buffer->refcount > 1 ||
1478       check_buffer_saved (view->buffer))
1479     close_view (view);
1480 }
1481
1482 static void
1483 view_set_title (View *view)
1484 {
1485   char *pretty_name = buffer_pretty_name (view->buffer);
1486   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
1487
1488   gtk_window_set_title (GTK_WINDOW (view->window), title);
1489
1490   g_free (pretty_name);
1491   g_free (title);
1492 }
1493
1494 static void
1495 cursor_set_callback (GtkTextBuffer     *buffer,
1496                      const GtkTextIter *location,
1497                      GtkTextMark       *mark,
1498                      gpointer           user_data)
1499 {
1500   GtkTextView *text_view;
1501
1502   /* Redraw tab windows if the cursor moves
1503    * on the mapped widget (windows may not exist before realization...
1504    */
1505   
1506   text_view = GTK_TEXT_VIEW (user_data);
1507   
1508   if (GTK_WIDGET_MAPPED (text_view) &&
1509       mark == gtk_text_buffer_get_insert (buffer))
1510     {
1511       GdkWindow *tab_window;
1512
1513       tab_window = gtk_text_view_get_window (text_view,
1514                                              GTK_TEXT_WINDOW_TOP);
1515
1516       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1517       
1518       tab_window = gtk_text_view_get_window (text_view,
1519                                              GTK_TEXT_WINDOW_BOTTOM);
1520
1521       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1522     }
1523 }
1524
1525
1526 static void
1527 text_changed_callback (GtkTextBuffer     *buffer,
1528                        gpointer           user_data)
1529 {
1530   GtkTextView *text_view;
1531
1532   /* Redraw line number windows if the buffer changes
1533    * and the widget is mapped (windows may not exist otherwise)
1534    */
1535   
1536   text_view = GTK_TEXT_VIEW (user_data);
1537   
1538   if (GTK_WIDGET_MAPPED (text_view))
1539     {
1540       GdkWindow *line_window;
1541
1542       line_window = gtk_text_view_get_window (text_view,
1543                                               GTK_TEXT_WINDOW_LEFT);
1544       
1545       gdk_window_invalidate_rect (line_window, NULL, FALSE);
1546       
1547       line_window = gtk_text_view_get_window (text_view,
1548                                               GTK_TEXT_WINDOW_RIGHT);
1549
1550       gdk_window_invalidate_rect (line_window, NULL, FALSE);
1551     }
1552 }
1553
1554 static gint
1555 tab_stops_expose (GtkWidget      *widget,
1556                   GdkEventExpose *event,
1557                   gpointer        user_data)
1558 {
1559   gint first_x;
1560   gint last_x;
1561   gint i;
1562   GdkWindow *top_win;
1563   GdkWindow *bottom_win;
1564   GtkTextView *text_view;
1565   GtkTextWindowType type;
1566   GdkDrawable *target;
1567   gint *positions = NULL;
1568   gint size;
1569   GtkTextAttributes *attrs;
1570   GtkTextIter insert;
1571   GtkTextBuffer *buffer;
1572   gboolean in_pixels;
1573   
1574   text_view = GTK_TEXT_VIEW (widget);
1575   
1576   /* See if this expose is on the tab stop window */
1577   top_win = gtk_text_view_get_window (text_view,
1578                                       GTK_TEXT_WINDOW_TOP);
1579
1580   bottom_win = gtk_text_view_get_window (text_view,
1581                                          GTK_TEXT_WINDOW_BOTTOM);
1582
1583   if (event->window == top_win)
1584     {
1585       type = GTK_TEXT_WINDOW_TOP;
1586       target = top_win;
1587     }
1588   else if (event->window == bottom_win)
1589     {
1590       type = GTK_TEXT_WINDOW_BOTTOM;
1591       target = bottom_win;
1592     }
1593   else
1594     return FALSE;
1595   
1596   first_x = event->area.x;
1597   last_x = first_x + event->area.width;
1598
1599   gtk_text_view_window_to_buffer_coords (text_view,
1600                                          type,
1601                                          first_x,
1602                                          0,
1603                                          &first_x,
1604                                          NULL);
1605
1606   gtk_text_view_window_to_buffer_coords (text_view,
1607                                          type,
1608                                          last_x,
1609                                          0,
1610                                          &last_x,
1611                                          NULL);
1612
1613   buffer = gtk_text_view_get_buffer (text_view);
1614
1615   gtk_text_buffer_get_iter_at_mark (buffer,
1616                                     &insert,
1617                                     gtk_text_buffer_get_mark (buffer,
1618                                                               "insert"));
1619   
1620   attrs = gtk_text_attributes_new ();
1621
1622   gtk_text_iter_get_attributes (&insert, attrs);
1623
1624   if (attrs->tabs)
1625     {
1626       size = pango_tab_array_get_size (attrs->tabs);
1627       
1628       pango_tab_array_get_tabs (attrs->tabs,
1629                                 NULL,
1630                                 &positions);
1631
1632       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
1633     }
1634   else
1635     {
1636       size = 0;
1637       in_pixels = FALSE;
1638     }
1639       
1640   gtk_text_attributes_unref (attrs);
1641   
1642   i = 0;
1643   while (i < size)
1644     {
1645       gint pos;
1646
1647       if (!in_pixels)
1648         positions[i] = PANGO_PIXELS (positions[i]);
1649       
1650       gtk_text_view_buffer_to_window_coords (text_view,
1651                                              type,
1652                                              positions[i],
1653                                              0,
1654                                              &pos,
1655                                              NULL);
1656       
1657       gdk_draw_line (target, 
1658                      widget->style->fg_gc [widget->state],
1659                      pos, 0,
1660                      pos, 15); 
1661       
1662       ++i;
1663     }
1664
1665   g_free (positions);
1666
1667   return TRUE;
1668 }
1669
1670 static void
1671 get_lines (GtkTextView  *text_view,
1672            gint          first_y,
1673            gint          last_y,
1674            GArray       *buffer_coords,
1675            GArray       *numbers,
1676            gint         *countp)
1677 {
1678   GtkTextIter iter;
1679   gint count;
1680   gint size;  
1681
1682   g_array_set_size (buffer_coords, 0);
1683   g_array_set_size (numbers, 0);
1684   
1685   /* Get iter at first y */
1686   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
1687
1688   /* For each iter, get its location and add it to the arrays.
1689    * Stop when we pass last_y
1690    */
1691   count = 0;
1692   size = 0;
1693
1694   while (!gtk_text_iter_is_last (&iter))
1695     {
1696       gint y, height;
1697       gint line_num;
1698       
1699       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
1700
1701       g_array_append_val (buffer_coords, y);
1702       line_num = gtk_text_iter_get_line (&iter);
1703       g_array_append_val (numbers, line_num);
1704       
1705       ++count;
1706
1707       if ((y + height) >= last_y)
1708         break;
1709       
1710       gtk_text_iter_forward_line (&iter);
1711     }
1712
1713   *countp = count;
1714 }
1715
1716 static gint
1717 line_numbers_expose (GtkWidget      *widget,
1718                      GdkEventExpose *event,
1719                      gpointer        user_data)
1720 {
1721   gint count;
1722   GArray *numbers;
1723   GArray *pixels;
1724   gint first_y;
1725   gint last_y;
1726   gint i;
1727   GdkWindow *left_win;
1728   GdkWindow *right_win;
1729   PangoLayout *layout;
1730   GtkTextView *text_view;
1731   GtkTextWindowType type;
1732   GdkDrawable *target;
1733   
1734   text_view = GTK_TEXT_VIEW (widget);
1735   
1736   /* See if this expose is on the line numbers window */
1737   left_win = gtk_text_view_get_window (text_view,
1738                                        GTK_TEXT_WINDOW_LEFT);
1739
1740   right_win = gtk_text_view_get_window (text_view,
1741                                         GTK_TEXT_WINDOW_RIGHT);
1742
1743   if (event->window == left_win)
1744     {
1745       type = GTK_TEXT_WINDOW_LEFT;
1746       target = left_win;
1747     }
1748   else if (event->window == right_win)
1749     {
1750       type = GTK_TEXT_WINDOW_RIGHT;
1751       target = right_win;
1752     }
1753   else
1754     return FALSE;
1755   
1756   first_y = event->area.y;
1757   last_y = first_y + event->area.height;
1758
1759   gtk_text_view_window_to_buffer_coords (text_view,
1760                                          type,
1761                                          0,
1762                                          first_y,
1763                                          NULL,
1764                                          &first_y);
1765
1766   gtk_text_view_window_to_buffer_coords (text_view,
1767                                          type,
1768                                          0,
1769                                          last_y,
1770                                          NULL,
1771                                          &last_y);
1772
1773   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
1774   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
1775   
1776   get_lines (text_view,
1777              first_y,
1778              last_y,
1779              pixels,
1780              numbers,
1781              &count);
1782   
1783   /* Draw fully internationalized numbers! */
1784   
1785   layout = gtk_widget_create_pango_layout (widget, "");
1786   
1787   i = 0;
1788   while (i < count)
1789     {
1790       gint pos;
1791       gchar *str;
1792       
1793       gtk_text_view_buffer_to_window_coords (text_view,
1794                                              type,
1795                                              0,
1796                                              g_array_index (pixels, gint, i),
1797                                              NULL,
1798                                              &pos);
1799
1800       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
1801
1802       pango_layout_set_text (layout, str, -1);
1803
1804
1805       gdk_draw_layout (target,
1806                        widget->style->fg_gc [widget->state],
1807                        /* 2 is just a random padding */
1808                        2, pos + 2,
1809                        layout);
1810
1811       g_free (str);
1812       
1813       ++i;
1814     }
1815
1816   g_array_free (pixels, TRUE);
1817   g_array_free (numbers, TRUE);
1818   
1819   g_object_unref (G_OBJECT (layout));
1820
1821   return TRUE;
1822 }
1823
1824 static View *
1825 create_view (Buffer *buffer)
1826 {
1827   View *view;
1828   
1829   GtkWidget *sw;
1830   GtkWidget *vbox;
1831
1832   view = g_new0 (View, 1);
1833   views = g_slist_prepend (views, view);
1834
1835   view->buffer = buffer;
1836   buffer_ref (buffer);
1837   
1838   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1839   gtk_object_set_data (GTK_OBJECT (view->window), "view", view);
1840   
1841   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
1842                       GTK_SIGNAL_FUNC (delete_event_cb), NULL);
1843
1844   view->accel_group = gtk_accel_group_new ();
1845   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
1846   gtk_object_set_data (GTK_OBJECT (view->item_factory), "view", view);
1847   
1848   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
1849
1850   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
1851
1852   vbox = gtk_vbox_new (FALSE, 0);
1853   gtk_container_add (GTK_CONTAINER (view->window), vbox);
1854
1855   gtk_box_pack_start (GTK_BOX (vbox),
1856                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
1857                       FALSE, FALSE, 0);
1858   
1859   sw = gtk_scrolled_window_new (NULL, NULL);
1860   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1861                                  GTK_POLICY_AUTOMATIC,
1862                                  GTK_POLICY_AUTOMATIC);
1863
1864   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
1865   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
1866                                GTK_WRAPMODE_WORD);
1867
1868
1869   /* Draw tab stops in the top and bottom windows. */
1870   
1871   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1872                                         GTK_TEXT_WINDOW_TOP,
1873                                         15);
1874
1875   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1876                                         GTK_TEXT_WINDOW_BOTTOM,
1877                                         15);
1878
1879   gtk_signal_connect (GTK_OBJECT (view->text_view),
1880                       "expose_event",
1881                       GTK_SIGNAL_FUNC (tab_stops_expose),
1882                       NULL);  
1883
1884   gtk_signal_connect (GTK_OBJECT (view->buffer->buffer),
1885                       "mark_set",
1886                       GTK_SIGNAL_FUNC (cursor_set_callback),
1887                       view->text_view);
1888   
1889   /* Draw line numbers in the side windows; we should really be
1890    * more scientific about what width we set them to.
1891    */
1892   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1893                                         GTK_TEXT_WINDOW_RIGHT,
1894                                         30);
1895   
1896   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1897                                         GTK_TEXT_WINDOW_LEFT,
1898                                         30);
1899   
1900   gtk_signal_connect (GTK_OBJECT (view->text_view),
1901                       "expose_event",
1902                       GTK_SIGNAL_FUNC (line_numbers_expose),
1903                       NULL);
1904
1905   gtk_signal_connect (GTK_OBJECT (view->buffer->buffer),
1906                       "changed",
1907                       GTK_SIGNAL_FUNC (text_changed_callback),
1908                       view->text_view);
1909   
1910   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
1911   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
1912
1913   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
1914
1915   gtk_widget_grab_focus (view->text_view);
1916
1917   view_set_title (view);
1918   view_init_menus (view);
1919   
1920   gtk_widget_show_all (view->window);
1921   return view;
1922 }
1923
1924 static gboolean
1925 file_exists (const char *filename)
1926 {
1927   struct stat statbuf;
1928
1929   return stat (filename, &statbuf) == 0;
1930 }
1931 void
1932 test_init ()
1933 {
1934   if (file_exists ("../gdk-pixbuf/.libs/libpixbufloader-pnm.so"))
1935     {
1936       putenv ("GDK_PIXBUF_MODULEDIR=../gdk-pixbuf/.libs");
1937       putenv ("GTK_IM_MODULE_FILE=./gtk.immodules");
1938     }
1939 }
1940
1941 int
1942 main (int argc, char** argv)
1943 {
1944   Buffer *buffer;
1945   View *view;
1946   int i;
1947
1948   test_init ();
1949   gtk_set_locale ();
1950   gtk_init (&argc, &argv);
1951   
1952   buffer = create_buffer ();
1953   view = create_view (buffer);
1954   buffer_unref (buffer);
1955   
1956   push_active_window (GTK_WINDOW (view->window));
1957   for (i=1; i < argc; i++)
1958     {
1959       char *filename;
1960
1961       /* Quick and dirty canonicalization - better should be in GLib
1962        */
1963
1964       if (!g_path_is_absolute (argv[i]))
1965         {
1966           char *cwd = g_get_current_dir ();
1967           filename = g_strconcat (cwd, "/", argv[i], NULL);
1968           g_free (cwd);
1969         }
1970       else
1971         filename = argv[i];
1972
1973       open_ok_func (filename, view);
1974
1975       if (filename != argv[i])
1976         g_free (filename);
1977     }
1978   pop_active_window ();
1979   
1980   gtk_main ();
1981
1982   return 0;
1983 }
1984
1985