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