]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
fix indentation
[~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   tag = gtk_text_buffer_create_tag (buffer, "fg_blue");
426
427   /*       gtk_timeout_add (1000, blink_timeout, tag); */
428       
429   setup_tag (tag);
430   
431   color.red = color.green = 0;
432   color.blue = 0xffff;
433   color2.red = 0xfff;
434   color2.blue = 0x0;
435   color2.green = 0;
436   gtk_object_set (GTK_OBJECT (tag),
437                  "foreground_gdk", &color,
438                  "background_gdk", &color2,
439                  "font", "Sans 24",
440                  NULL);
441
442   tag = gtk_text_buffer_create_tag (buffer, "fg_red");
443
444   setup_tag (tag);
445       
446   color.blue = color.green = 0;
447   color.red = 0xffff;
448   gtk_object_set (GTK_OBJECT (tag),
449                  "offset", -4,
450                  "foreground_gdk", &color,
451                  NULL);
452
453   tag = gtk_text_buffer_create_tag (buffer, "bg_green");
454
455   setup_tag (tag);
456       
457   color.blue = color.red = 0;
458   color.green = 0xffff;
459   gtk_object_set (GTK_OBJECT (tag),
460                  "background_gdk", &color,
461                  "font", "Sans 10",
462                  NULL);
463
464   tag = gtk_text_buffer_create_tag (buffer, "strikethrough");
465
466   setup_tag (tag);
467       
468   gtk_object_set (GTK_OBJECT (tag),
469                  "strikethrough", TRUE,
470                  NULL);
471
472
473   tag = gtk_text_buffer_create_tag (buffer, "underline");
474
475   setup_tag (tag);
476       
477   gtk_object_set (GTK_OBJECT (tag),
478                  "underline", PANGO_UNDERLINE_SINGLE,
479                  NULL);
480
481   setup_tag (tag);
482       
483   gtk_object_set (GTK_OBJECT (tag),
484                  "underline", PANGO_UNDERLINE_SINGLE,
485                  NULL);
486
487   tag = gtk_text_buffer_create_tag (buffer, "centered");
488       
489   gtk_object_set (GTK_OBJECT (tag),
490                  "justify", GTK_JUSTIFY_CENTER,
491                  NULL);
492
493   tag = gtk_text_buffer_create_tag (buffer, "rtl_quote");
494       
495   gtk_object_set (GTK_OBJECT (tag),
496                  "wrap_mode", GTK_WRAPMODE_WORD,
497                  "direction", GTK_TEXT_DIR_RTL,
498                  "left_wrapped_line_margin", 20,
499                  "left_margin", 20,
500                  "right_margin", 20,
501                  NULL);
502
503
504   
505   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
506   
507   i = 0;
508   while (i < 100)
509     {
510       GtkTextMark * temp_mark;
511       
512       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
513           
514       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
515           
516       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",
517                             i);
518       
519       gtk_text_buffer_insert (buffer, &iter, str, -1);
520
521       g_free (str);
522       
523       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 5);
524           
525       gtk_text_buffer_insert (buffer, &iter,
526                              "(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"
527                              /* This is UTF8 stuff, Emacs doesn't
528                                 really know how to display it */
529                              "German (Deutsch Süd) Grüß Gott Greek (Ελληνικά) Γειά σας Hebrew   שלום Japanese (日本語)\n", -1);
530
531       temp_mark =
532         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
533
534 #if 1
535       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 6);
536       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 13);
537
538       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
539
540       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 10);
541       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 16);
542
543       gtk_text_buffer_apply_tag_by_name (buffer, "underline", &iter, &iter2);
544
545       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 14);
546       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 24);
547
548       gtk_text_buffer_apply_tag_by_name (buffer, "strikethrough", &iter, &iter2);
549           
550       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 9);
551       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 16);
552
553       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
554   
555       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 2);
556       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 10);
557
558       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
559
560       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 8);
561       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 15);
562
563       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
564 #endif
565
566       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
567       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
568           
569       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
570       gtk_text_buffer_apply_tag_by_name (buffer, "centered", &iter2, &iter);
571
572       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
573       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
574       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
575       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
576       gtk_text_buffer_apply_tag_by_name (buffer, "rtl_quote", &iter2, &iter);
577           
578       ++i;
579     }
580
581   g_object_unref (G_OBJECT (pixbuf));
582   
583   printf ("%d lines %d chars\n",
584          gtk_text_buffer_get_line_count (buffer),
585          gtk_text_buffer_get_char_count (buffer));
586
587   gtk_text_buffer_set_modified (buffer, FALSE);
588 }
589
590 gboolean
591 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
592 {
593   FILE* f;
594   gchar buf[2048];
595   gint remaining = 0;
596   GtkTextIter iter, end;
597
598   f = fopen (filename, "r");
599   
600   if (f == NULL)
601     {
602       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
603                                     filename, g_strerror (errno));
604       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
605       g_free (err);
606       return FALSE;
607     }
608   
609   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
610   while (!feof (f))
611     {
612       gint count;
613       char *leftover, *next;
614       int to_read = 2047  - remaining;
615       
616       count = fread (buf + remaining, 1, to_read, f);
617       buf[count + remaining] = '\0';
618
619       leftover = next = buf;
620       while (next)
621         {
622           leftover = next;
623           if (!*leftover)
624             break;
625           
626           next = g_utf8_next_char (next);
627         }
628
629       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
630
631       remaining = buf + remaining + count - leftover;
632       g_memmove (buf, leftover, remaining);
633
634       if (remaining > 6 || count < to_read)
635           break;
636     }
637
638   if (remaining)
639     {
640       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
641       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
642       g_free (err);
643     }
644   
645   /* We had a newline in the buffer to begin with. (The buffer always contains
646    * a newline, so we delete to the end of the buffer to clean up.
647    */
648   gtk_text_buffer_get_last_iter (buffer, &end);
649   gtk_text_buffer_delete (buffer, &iter, &end);
650   
651   gtk_text_buffer_set_modified (buffer, FALSE);
652
653   return TRUE;
654 }
655
656 static gint
657 delete_event_cb (GtkWidget *window, GdkEventAny *event, gpointer data)
658 {
659   View *view = view_from_widget (window);
660
661   push_active_window (GTK_WINDOW (window));
662   check_close_view (view);
663   pop_active_window ();
664
665   return TRUE;
666 }
667
668 /*
669  * Menu callbacks
670  */
671
672 static View *
673 get_empty_view (View *view)
674 {
675   if (!view->buffer->filename &&
676       !gtk_text_buffer_modified (view->buffer->buffer))
677     return view;
678   else
679     return create_view (create_buffer ());
680 }
681
682 static View *
683 view_from_widget (GtkWidget *widget)
684 {
685   GtkWidget *app;
686
687   if (GTK_IS_MENU_ITEM (widget))
688     {
689       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
690       return gtk_object_get_data (GTK_OBJECT (item_factory), "view");      
691     }
692   else
693     {
694       GtkWidget *app = gtk_widget_get_toplevel (widget);
695       return gtk_object_get_data (GTK_OBJECT (app), "view");
696     }
697 }
698
699 static void
700 do_new (gpointer             callback_data,
701         guint                callback_action,
702         GtkWidget           *widget)
703 {
704   create_view (create_buffer ());
705 }
706
707 static void
708 do_new_view (gpointer             callback_data,
709              guint                callback_action,
710              GtkWidget           *widget)
711 {
712   View *view = view_from_widget (widget);
713   
714   create_view (view->buffer);
715 }
716
717 gboolean
718 open_ok_func (const char *filename, gpointer data)
719 {
720   View *view = data;
721   View *new_view = get_empty_view (view);
722
723   if (!fill_file_buffer (new_view->buffer->buffer, filename))
724     {
725       if (new_view != view)
726         close_view (new_view);
727       return FALSE;
728     }
729   else
730     {
731       g_free (new_view->buffer->filename);
732       new_view->buffer->filename = g_strdup (filename);
733       buffer_filename_set (new_view->buffer);
734       
735       return TRUE;
736     }
737 }
738
739 static void
740 do_open (gpointer             callback_data,
741          guint                callback_action,
742          GtkWidget           *widget)
743 {
744   View *view = view_from_widget (widget);
745
746   push_active_window (GTK_WINDOW (view->window));
747   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
748   pop_active_window ();
749 }
750
751 static void
752 do_save_as (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   save_as_buffer (view->buffer);
760   pop_active_window ();
761 }
762
763 static void
764 do_save (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   if (!view->buffer->filename)
772     do_save_as (callback_data, callback_action, widget);
773   else
774     save_buffer (view->buffer);
775   pop_active_window ();
776 }
777
778 static void
779 do_close   (gpointer             callback_data,
780             guint                callback_action,
781             GtkWidget           *widget)
782 {
783   View *view = view_from_widget (widget);
784
785   push_active_window (GTK_WINDOW (view->window));
786   check_close_view (view);
787   pop_active_window ();
788 }
789
790 static void
791 do_exit    (gpointer             callback_data,
792             guint                callback_action,
793             GtkWidget           *widget)
794 {
795   View *view = view_from_widget (widget);
796
797   GSList *tmp_list = buffers;
798
799   push_active_window (GTK_WINDOW (view->window));
800   while (tmp_list)
801     {
802       if (!check_buffer_saved (tmp_list->data))
803         return;
804
805       tmp_list = tmp_list->next;
806     }
807
808   gtk_main_quit ();
809   pop_active_window ();
810 }
811
812 static void
813 do_example (gpointer             callback_data,
814             guint                callback_action,
815             GtkWidget           *widget)
816 {
817   View *view = view_from_widget (widget);
818   View *new_view;
819
820   new_view = get_empty_view (view);
821   
822   fill_example_buffer (new_view->buffer->buffer);
823 }
824
825 static void
826 do_wrap_changed (gpointer             callback_data,
827                  guint                callback_action,
828                  GtkWidget           *widget)
829 {
830   View *view = view_from_widget (widget);
831
832   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
833 }
834
835 static void
836 do_direction_changed (gpointer             callback_data,
837                       guint                callback_action,
838                       GtkWidget           *widget)
839 {
840   View *view = view_from_widget (widget);
841   
842   gtk_widget_set_direction (view->text_view, callback_action);
843   gtk_widget_queue_resize (view->text_view);
844 }
845
846 static void
847 do_editable_changed (gpointer callback_data,
848                      guint callback_action,
849                      GtkWidget *widget)
850 {
851   View *view = view_from_widget (widget);
852
853   gtk_text_view_set_editable (GTK_TEXT_VIEW (view->text_view), callback_action);
854 }
855
856 static void
857 do_cursor_visible_changed (gpointer callback_data,
858                            guint callback_action,
859                            GtkWidget *widget)
860 {
861   View *view = view_from_widget (widget);
862
863   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view->text_view), callback_action);
864 }
865
866 static void
867 do_apply_editable (gpointer callback_data,
868                    guint callback_action,
869                    GtkWidget *widget)
870 {
871   View *view = view_from_widget (widget);
872   GtkTextIter start;
873   GtkTextIter end;
874   
875   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
876                                             &start, &end))
877     {
878       if (callback_action)
879         {
880           gtk_text_buffer_remove_tag (view->buffer->buffer,
881                                       view->buffer->not_editable_tag,
882                                       &start, &end);
883         }
884       else
885         {
886           gtk_text_buffer_apply_tag (view->buffer->buffer,
887                                      view->buffer->not_editable_tag,
888                                      &start, &end);
889         }
890     }
891 }
892
893
894 static void
895 do_apply_tabs (gpointer callback_data,
896                guint callback_action,
897                GtkWidget *widget)
898 {
899   View *view = view_from_widget (widget);
900   GtkTextIter start;
901   GtkTextIter end;
902   
903   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
904                                             &start, &end))
905     {
906       if (callback_action)
907         {
908           gtk_text_buffer_remove_tag (view->buffer->buffer,
909                                       view->buffer->custom_tabs_tag,
910                                       &start, &end);
911         }
912       else
913         {
914           gtk_text_buffer_apply_tag (view->buffer->buffer,
915                                      view->buffer->custom_tabs_tag,
916                                      &start, &end);
917         }
918     }
919 }
920
921 static void
922 dialog_response_callback (GtkWidget *dialog, gint response_id, gpointer data)
923 {
924   GtkTextBuffer *buffer;
925   View *view = data;
926   GtkTextIter start, end;
927   gchar *search_string;
928   
929   buffer = gtk_object_get_data (GTK_OBJECT (dialog), "buffer");
930
931   gtk_text_buffer_get_bounds (buffer, &start, &end);
932
933   /* Remove trailing newline */
934   gtk_text_iter_prev_char (&end);
935   
936   search_string = gtk_text_iter_get_text (&start, &end);
937
938   printf ("Searching for `%s'\n", search_string);
939   
940   buffer_search_forward (view->buffer, search_string, view);
941
942   g_free (search_string);
943   
944   gtk_widget_destroy (dialog);
945 }
946
947 static void
948 do_search (gpointer callback_data,
949            guint callback_action,
950            GtkWidget *widget)
951 {
952   View *view = view_from_widget (widget);
953   GtkWidget *dialog;
954   GtkWidget *search_text;
955   GtkTextBuffer *buffer;
956
957 #if 0
958   
959   dialog = gtk_dialog_new_with_buttons ("Search",
960                                         GTK_WINDOW (view->window),
961                                         GTK_DIALOG_DESTROY_WITH_PARENT,
962                                         GTK_STOCK_BUTTON_CLOSE,
963                                         GTK_RESPONSE_NONE, NULL);
964
965   buffer = gtk_text_buffer_new (NULL);
966
967   /* FIXME memory leak once buffer is a GObject */
968   search_text = gtk_text_view_new_with_buffer (buffer);
969
970   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
971                     search_text,
972                     TRUE, TRUE, 0);
973
974   gtk_object_set_data (GTK_OBJECT (dialog), "buffer", buffer);
975   
976   gtk_signal_connect (GTK_OBJECT (dialog),
977                       "response",
978                       GTK_SIGNAL_FUNC (dialog_response_callback),
979                       view);
980
981   gtk_widget_show (search_text);
982
983   gtk_widget_grab_focus (search_text);
984   
985   gtk_widget_show_all (dialog);
986 #endif
987 }
988
989 static void
990 view_init_menus (View *view)
991 {
992   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
993   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
994   GtkWidget *menu_item = NULL;
995
996   switch (direction)
997     {
998     case GTK_TEXT_DIR_LTR:
999       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1000       break;
1001     case GTK_TEXT_DIR_RTL:
1002       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1003       break;
1004     default:
1005       break;
1006     }
1007
1008   if (menu_item)
1009     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1010
1011   switch (wrap_mode)
1012     {
1013     case GTK_WRAPMODE_NONE:
1014       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1015       break;
1016     case GTK_WRAPMODE_WORD:
1017       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1018       break;
1019     default:
1020       break;
1021     }
1022   
1023   if (menu_item)
1024     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1025 }
1026
1027 static GtkItemFactoryEntry menu_items[] =
1028 {
1029   { "/_File",            NULL,         0,           0, "<Branch>" },
1030   { "/File/_New",        "<control>N", do_new,      0, NULL },
1031   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1032   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1033   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1034   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1035   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
1036   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1037   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1038
1039   { "/_Edit", NULL, 0, 0, "<Branch>" },
1040   { "/Edit/Find...", NULL, do_search, 0, NULL },
1041
1042   { "/_Settings",         NULL,         0,                0, "<Branch>" },
1043   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAPMODE_NONE, "<RadioItem>" },
1044   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAPMODE_WORD, "/Settings/Wrap Off" },
1045   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1046   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1047   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1048   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1049
1050   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1051   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1052   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
1053   
1054   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1055   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1056   { "/_Attributes",       NULL,         0,                0, "<Branch>" },
1057   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1058   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1059   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1060   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1061   { "/_Test",            NULL,         0,           0, "<Branch>" },
1062   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1063 };
1064
1065 static gboolean
1066 save_buffer (Buffer *buffer)
1067 {
1068   GtkTextIter start, end;
1069   gchar *chars;
1070   gboolean result = FALSE;
1071   gboolean have_backup = FALSE;
1072   gchar *bak_filename;
1073   FILE *file;
1074
1075   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1076
1077   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1078   
1079   if (rename (buffer->filename, bak_filename) != 0)
1080     {
1081       if (errno != ENOENT)
1082         {
1083           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1084                                         buffer->filename, bak_filename, g_strerror (errno));
1085           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1086           g_free (err);
1087         }
1088       
1089       return FALSE;
1090     }
1091   else
1092     have_backup = TRUE;
1093   
1094   file = fopen (buffer->filename, "w");
1095   if (!file)
1096     {
1097       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1098                                     buffer->filename, bak_filename, g_strerror (errno));
1099       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1100     }
1101   else
1102     {
1103       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1104       gtk_text_buffer_get_last_iter (buffer->buffer, &end);
1105   
1106       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1107
1108       if (fputs (chars, file) == EOF ||
1109           fclose (file) == EOF)
1110         {
1111           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1112                                         buffer->filename, g_strerror (errno));
1113           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1114           g_free (err);
1115         }
1116       else
1117         {
1118           /* Success
1119            */
1120           result = TRUE;
1121           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1122         }
1123         
1124       g_free (chars);
1125     }
1126
1127   if (!result && have_backup)
1128     {
1129       if (rename (bak_filename, buffer->filename) != 0)
1130         {
1131           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1132                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1133           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1134           g_free (err);
1135         }
1136     }
1137
1138   g_free (bak_filename);
1139   
1140   return result;
1141 }
1142
1143 static gboolean
1144 save_as_ok_func (const char *filename, gpointer data)
1145 {
1146   Buffer *buffer = data;
1147   char *old_filename = buffer->filename;
1148
1149   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1150     {
1151       struct stat statbuf;
1152
1153       if (stat (filename, &statbuf) == 0)
1154         {
1155           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1156           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1157           g_free (err);
1158
1159           if (result != 0)
1160             return FALSE;
1161         }
1162     }
1163   
1164   buffer->filename = g_strdup (filename);
1165
1166   if (save_buffer (buffer))
1167     {
1168       g_free (old_filename);
1169       buffer_filename_set (buffer);
1170       return TRUE;
1171     }
1172   else
1173     {
1174       g_free (buffer->filename);
1175       buffer->filename = old_filename;
1176       return FALSE;
1177     }
1178 }
1179
1180 static gboolean
1181 save_as_buffer (Buffer *buffer)
1182 {
1183   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1184 }
1185
1186 static gboolean
1187 check_buffer_saved (Buffer *buffer)
1188 {
1189   if (gtk_text_buffer_modified (buffer->buffer))
1190     {
1191       char *pretty_name = buffer_pretty_name (buffer);
1192       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1193       gint result;
1194       
1195       g_free (pretty_name);
1196       
1197       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1198       g_free (msg);
1199   
1200       if (result == 0)
1201         return save_as_buffer (buffer);
1202       else if (result == 1)
1203         return TRUE;
1204       else
1205         return FALSE;
1206     }
1207   else
1208     return TRUE;
1209 }
1210
1211 static Buffer *
1212 create_buffer (void)
1213 {
1214   Buffer *buffer;
1215   PangoTabArray *tabs;
1216   
1217   buffer = g_new (Buffer, 1);
1218
1219   buffer->buffer = gtk_text_buffer_new (NULL);
1220   gtk_object_ref (GTK_OBJECT (buffer->buffer));
1221   gtk_object_sink (GTK_OBJECT (buffer->buffer));
1222   
1223   buffer->refcount = 1;
1224   buffer->filename = NULL;
1225   buffer->untitled_serial = -1;
1226
1227   buffer->not_editable_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1228   gtk_object_set (GTK_OBJECT (buffer->not_editable_tag),
1229                   "editable", FALSE,
1230                   "foreground", "purple", NULL);
1231
1232   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1233   gtk_object_set (GTK_OBJECT (buffer->found_text_tag),
1234                   "foreground", "red", NULL);
1235
1236   tabs = pango_tab_array_new_with_positions (4,
1237                                              TRUE,
1238                                              PANGO_TAB_LEFT, 10,
1239                                              PANGO_TAB_LEFT, 30,
1240                                              PANGO_TAB_LEFT, 60,
1241                                              PANGO_TAB_LEFT, 120);
1242   
1243   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL);
1244   gtk_object_set (GTK_OBJECT (buffer->custom_tabs_tag),
1245                   "tabs", tabs,
1246                   "foreground", "green", NULL);
1247
1248   pango_tab_array_free (tabs);
1249   
1250   buffers = g_slist_prepend (buffers, buffer);
1251   
1252   return buffer;
1253 }
1254
1255 static char *
1256 buffer_pretty_name (Buffer *buffer)
1257 {
1258   if (buffer->filename)
1259     {
1260       char *p;
1261       char *result = g_path_get_basename (buffer->filename);
1262       p = strchr (result, '/');
1263       if (p)
1264         *p = '\0';
1265
1266       return result;
1267     }
1268   else
1269     {
1270       if (buffer->untitled_serial == -1)
1271         buffer->untitled_serial = untitled_serial++;
1272
1273       if (buffer->untitled_serial == 1)
1274         return g_strdup ("Untitled");
1275       else
1276         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1277     }
1278 }
1279
1280 static void
1281 buffer_filename_set (Buffer *buffer)
1282 {
1283   GSList *tmp_list = views;
1284
1285   while (tmp_list)
1286     {
1287       View *view = tmp_list->data;
1288
1289       if (view->buffer == buffer)
1290         view_set_title (view);
1291
1292       tmp_list = tmp_list->next;
1293     }
1294 }
1295
1296 static void
1297 buffer_search_forward (Buffer *buffer, const char *str,
1298                        View *view)
1299 {
1300   GtkTextIter iter;
1301   GtkTextIter start, end;
1302   gint char_len;
1303   int i = 0;
1304   GtkWidget *dialog;
1305   
1306   /* remove tag from whole buffer */
1307   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1308   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1309                               &start, &end );
1310   
1311   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1312                                     gtk_text_buffer_get_mark (buffer->buffer,
1313                                                               "insert"));
1314
1315
1316   char_len = g_utf8_strlen (str, -1);
1317
1318   if (char_len > 0)
1319     {
1320       while (gtk_text_iter_forward_search (&iter, str, TRUE, FALSE))
1321         {
1322           GtkTextIter end = iter;
1323           
1324           gtk_text_iter_forward_chars (&end, char_len);
1325           
1326           gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1327                                      &iter, &end);
1328
1329           iter = end;
1330           
1331           ++i;
1332         }
1333     }
1334
1335 #if 0  
1336   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1337                                    GTK_MESSAGE_INFO,
1338                                    GTK_BUTTONS_OK,
1339                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1340                                    "%d strings found and marked in red",
1341                                    i);
1342
1343   gtk_signal_connect_object (GTK_OBJECT (dialog),
1344                              "response",
1345                              GTK_SIGNAL_FUNC (gtk_widget_destroy),
1346                              GTK_OBJECT (dialog));
1347   
1348   gtk_widget_show (dialog);
1349 #endif
1350 }
1351
1352 static void
1353 buffer_ref (Buffer *buffer)
1354 {
1355   buffer->refcount++;
1356 }
1357
1358 static void
1359 buffer_unref (Buffer *buffer)
1360 {
1361   buffer->refcount--;
1362   if (buffer->refcount == 0)
1363     {
1364       buffers = g_slist_remove (buffers, buffer);
1365       gtk_object_unref (GTK_OBJECT (buffer->buffer));
1366       g_free (buffer->filename);
1367       g_free (buffer);
1368     }
1369 }
1370
1371 static void
1372 close_view (View *view)
1373 {
1374   views = g_slist_remove (views, view);
1375   buffer_unref (view->buffer);
1376   gtk_widget_destroy (view->window);
1377   g_object_unref (G_OBJECT (view->item_factory));
1378   
1379   g_free (view);
1380   
1381   if (!views)
1382     gtk_main_quit ();
1383 }
1384
1385 static void
1386 check_close_view (View *view)
1387 {
1388   if (view->buffer->refcount > 1 ||
1389       check_buffer_saved (view->buffer))
1390     close_view (view);
1391 }
1392
1393 static void
1394 view_set_title (View *view)
1395 {
1396   char *pretty_name = buffer_pretty_name (view->buffer);
1397   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
1398
1399   gtk_window_set_title (GTK_WINDOW (view->window), title);
1400
1401   g_free (pretty_name);
1402   g_free (title);
1403 }
1404
1405 static void
1406 cursor_set_callback (GtkTextBuffer     *buffer,
1407                      const GtkTextIter *location,
1408                      GtkTextMark       *mark,
1409                      gpointer           user_data)
1410 {
1411   GtkTextView *text_view;
1412
1413   /* Redraw tab windows if the cursor moves
1414    * on the mapped widget (windows may not exist before realization...
1415    */
1416   
1417   text_view = GTK_TEXT_VIEW (user_data);
1418   
1419   if (GTK_WIDGET_MAPPED (text_view) &&
1420       mark == gtk_text_buffer_get_insert (buffer))
1421     {
1422       GdkWindow *tab_window;
1423
1424       tab_window = gtk_text_view_get_window (text_view,
1425                                              GTK_TEXT_WINDOW_TOP);
1426
1427       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1428       
1429       tab_window = gtk_text_view_get_window (text_view,
1430                                              GTK_TEXT_WINDOW_BOTTOM);
1431
1432       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1433     }
1434 }
1435
1436 static gint
1437 tab_stops_expose (GtkWidget      *widget,
1438                   GdkEventExpose *event,
1439                   gpointer        user_data)
1440 {
1441   gint first_x;
1442   gint last_x;
1443   gint i;
1444   GdkWindow *top_win;
1445   GdkWindow *bottom_win;
1446   GtkTextView *text_view;
1447   GtkTextWindowType type;
1448   GdkDrawable *target;
1449   gint *positions = NULL;
1450   gint size;
1451   GtkTextAttributes *attrs;
1452   GtkTextIter insert;
1453   GtkTextBuffer *buffer;
1454   gboolean in_pixels;
1455   
1456   text_view = GTK_TEXT_VIEW (widget);
1457   
1458   /* See if this expose is on the tab stop window */
1459   top_win = gtk_text_view_get_window (text_view,
1460                                       GTK_TEXT_WINDOW_TOP);
1461
1462   bottom_win = gtk_text_view_get_window (text_view,
1463                                          GTK_TEXT_WINDOW_BOTTOM);
1464
1465   if (event->window == top_win)
1466     {
1467       type = GTK_TEXT_WINDOW_TOP;
1468       target = top_win;
1469     }
1470   else if (event->window == bottom_win)
1471     {
1472       type = GTK_TEXT_WINDOW_BOTTOM;
1473       target = bottom_win;
1474     }
1475   else
1476     return FALSE;
1477   
1478   first_x = event->area.x;
1479   last_x = first_x + event->area.width;
1480
1481   gtk_text_view_window_to_buffer_coords (text_view,
1482                                          type,
1483                                          first_x,
1484                                          0,
1485                                          &first_x,
1486                                          NULL);
1487
1488   gtk_text_view_window_to_buffer_coords (text_view,
1489                                          type,
1490                                          last_x,
1491                                          0,
1492                                          &last_x,
1493                                          NULL);
1494
1495   buffer = gtk_text_view_get_buffer (text_view);
1496
1497   gtk_text_buffer_get_iter_at_mark (buffer,
1498                                     &insert,
1499                                     gtk_text_buffer_get_mark (buffer,
1500                                                               "insert"));
1501   
1502   attrs = gtk_text_attributes_new ();
1503
1504   gtk_text_iter_get_attributes (&insert, attrs);
1505
1506   if (attrs->tabs)
1507     {
1508       size = pango_tab_array_get_size (attrs->tabs);
1509       
1510       pango_tab_array_get_tabs (attrs->tabs,
1511                                 NULL,
1512                                 &positions);
1513
1514       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
1515     }
1516   else
1517     {
1518       size = 0;
1519       in_pixels = FALSE;
1520     }
1521       
1522   gtk_text_attributes_unref (attrs);
1523   
1524   i = 0;
1525   while (i < size)
1526     {
1527       gint pos;
1528
1529       if (!in_pixels)
1530         positions[i] = PANGO_PIXELS (positions[i]);
1531       
1532       gtk_text_view_buffer_to_window_coords (text_view,
1533                                              type,
1534                                              positions[i],
1535                                              0,
1536                                              &pos,
1537                                              NULL);
1538       
1539       gdk_draw_line (target, 
1540                      widget->style->fg_gc [widget->state],
1541                      pos, 0,
1542                      pos, 15); 
1543       
1544       ++i;
1545     }
1546
1547   g_free (positions);
1548
1549   return TRUE;
1550 }
1551
1552 static void
1553 get_lines (GtkTextView  *text_view,
1554            gint          first_y,
1555            gint          last_y,
1556            GArray       *buffer_coords,
1557            GArray       *numbers,
1558            gint         *countp)
1559 {
1560   GtkTextIter iter;
1561   gint count;
1562   gint size;  
1563
1564   g_array_set_size (buffer_coords, 0);
1565   g_array_set_size (numbers, 0);
1566   
1567   /* Get iter at first y */
1568   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
1569
1570   /* For each iter, get its location and add it to the arrays.
1571    * Stop when we pass last_y
1572    */
1573   count = 0;
1574   size = 0;
1575
1576   while (!gtk_text_iter_is_last (&iter))
1577     {
1578       gint y, height;
1579       gint line_num;
1580       
1581       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
1582
1583       g_array_append_val (buffer_coords, y);
1584       line_num = gtk_text_iter_get_line (&iter);
1585       g_array_append_val (numbers, line_num);
1586       
1587       ++count;
1588
1589       if ((y + height) >= last_y)
1590         break;
1591       
1592       gtk_text_iter_forward_line (&iter);
1593     }
1594
1595   *countp = count;
1596 }
1597
1598 static gint
1599 line_numbers_expose (GtkWidget      *widget,
1600                      GdkEventExpose *event,
1601                      gpointer        user_data)
1602 {
1603   gint count;
1604   GArray *numbers;
1605   GArray *pixels;
1606   gint first_y;
1607   gint last_y;
1608   gint i;
1609   GdkWindow *left_win;
1610   GdkWindow *right_win;
1611   PangoLayout *layout;
1612   GtkTextView *text_view;
1613   GtkTextWindowType type;
1614   GdkDrawable *target;
1615   
1616   text_view = GTK_TEXT_VIEW (widget);
1617   
1618   /* See if this expose is on the line numbers window */
1619   left_win = gtk_text_view_get_window (text_view,
1620                                        GTK_TEXT_WINDOW_LEFT);
1621
1622   right_win = gtk_text_view_get_window (text_view,
1623                                         GTK_TEXT_WINDOW_RIGHT);
1624
1625   if (event->window == left_win)
1626     {
1627       type = GTK_TEXT_WINDOW_LEFT;
1628       target = left_win;
1629     }
1630   else if (event->window == right_win)
1631     {
1632       type = GTK_TEXT_WINDOW_RIGHT;
1633       target = right_win;
1634     }
1635   else
1636     return FALSE;
1637   
1638   first_y = event->area.y;
1639   last_y = first_y + event->area.height;
1640
1641   gtk_text_view_window_to_buffer_coords (text_view,
1642                                          type,
1643                                          0,
1644                                          first_y,
1645                                          NULL,
1646                                          &first_y);
1647
1648   gtk_text_view_window_to_buffer_coords (text_view,
1649                                          type,
1650                                          0,
1651                                          last_y,
1652                                          NULL,
1653                                          &last_y);
1654
1655   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
1656   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
1657   
1658   get_lines (text_view,
1659              first_y,
1660              last_y,
1661              pixels,
1662              numbers,
1663              &count);
1664   
1665   /* Draw fully internationalized numbers! */
1666   
1667   layout = gtk_widget_create_pango_layout (widget, "");
1668   
1669   i = 0;
1670   while (i < count)
1671     {
1672       gint pos;
1673       gchar *str;
1674       
1675       gtk_text_view_buffer_to_window_coords (text_view,
1676                                              type,
1677                                              0,
1678                                              g_array_index (pixels, gint, i),
1679                                              NULL,
1680                                              &pos);
1681
1682       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
1683
1684       pango_layout_set_text (layout, str, -1);
1685
1686
1687       gdk_draw_layout (target,
1688                        widget->style->fg_gc [widget->state],
1689                        /* 2 is just a random padding */
1690                        2, pos + 2,
1691                        layout);
1692
1693       g_free (str);
1694       
1695       ++i;
1696     }
1697
1698   g_array_free (pixels, TRUE);
1699   g_array_free (numbers, TRUE);
1700   
1701   g_object_unref (G_OBJECT (layout));
1702
1703   return TRUE;
1704 }
1705
1706 static View *
1707 create_view (Buffer *buffer)
1708 {
1709   View *view;
1710   
1711   GtkWidget *sw;
1712   GtkWidget *vbox;
1713
1714   view = g_new0 (View, 1);
1715   views = g_slist_prepend (views, view);
1716
1717   view->buffer = buffer;
1718   buffer_ref (buffer);
1719   
1720   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1721   gtk_object_set_data (GTK_OBJECT (view->window), "view", view);
1722   
1723   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
1724                       GTK_SIGNAL_FUNC (delete_event_cb), NULL);
1725
1726   view->accel_group = gtk_accel_group_new ();
1727   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
1728   gtk_object_set_data (GTK_OBJECT (view->item_factory), "view", view);
1729   
1730   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
1731
1732   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
1733
1734   vbox = gtk_vbox_new (FALSE, 0);
1735   gtk_container_add (GTK_CONTAINER (view->window), vbox);
1736
1737   gtk_box_pack_start (GTK_BOX (vbox),
1738                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
1739                       FALSE, FALSE, 0);
1740   
1741   sw = gtk_scrolled_window_new (NULL, NULL);
1742   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1743                                  GTK_POLICY_AUTOMATIC,
1744                                  GTK_POLICY_AUTOMATIC);
1745
1746   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
1747   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
1748                                GTK_WRAPMODE_WORD);
1749
1750
1751   /* Draw tab stops in the top and bottom windows. */
1752   
1753   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1754                                         GTK_TEXT_WINDOW_TOP,
1755                                         15);
1756
1757   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1758                                         GTK_TEXT_WINDOW_BOTTOM,
1759                                         15);
1760
1761   gtk_signal_connect (GTK_OBJECT (view->text_view),
1762                       "expose_event",
1763                       GTK_SIGNAL_FUNC (tab_stops_expose),
1764                       NULL);  
1765
1766   gtk_signal_connect (GTK_OBJECT (view->buffer->buffer),
1767                       "mark_set",
1768                       GTK_SIGNAL_FUNC (cursor_set_callback),
1769                       view->text_view);
1770   
1771   /* Draw line numbers in the side windows; we should really be
1772    * more scientific about what width we set them to.
1773    */
1774   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1775                                         GTK_TEXT_WINDOW_RIGHT,
1776                                         30);
1777   
1778   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1779                                         GTK_TEXT_WINDOW_LEFT,
1780                                         30);
1781   
1782   gtk_signal_connect (GTK_OBJECT (view->text_view),
1783                       "expose_event",
1784                       GTK_SIGNAL_FUNC (line_numbers_expose),
1785                       NULL);
1786   
1787   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
1788   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
1789
1790   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
1791
1792   gtk_widget_grab_focus (view->text_view);
1793
1794   view_set_title (view);
1795   view_init_menus (view);
1796   
1797   gtk_widget_show_all (view->window);
1798   return view;
1799 }
1800
1801 int
1802 main (int argc, char** argv)
1803 {
1804   Buffer *buffer;
1805   View *view;
1806   int i;
1807   
1808   gtk_init (&argc, &argv);
1809   
1810   buffer = create_buffer ();
1811   view = create_view (buffer);
1812   buffer_unref (buffer);
1813   
1814   push_active_window (GTK_WINDOW (view->window));
1815   for (i=1; i < argc; i++)
1816     {
1817       char *filename;
1818
1819       /* Quick and dirty canonicalization - better should be in GLib
1820        */
1821
1822       if (!g_path_is_absolute (argv[i]))
1823         {
1824           char *cwd = g_get_current_dir ();
1825           filename = g_strconcat (cwd, "/", argv[i], NULL);
1826           g_free (cwd);
1827         }
1828       else
1829         filename = argv[i];
1830
1831       open_ok_func (filename, view);
1832
1833       if (filename != argv[i])
1834         g_free (filename);
1835     }
1836   pop_active_window ();
1837   
1838   gtk_main ();
1839
1840   return 0;
1841 }
1842
1843