]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Some updates
[~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   GtkWidget *dialog;
1303   
1304   /* remove tag from whole buffer */
1305   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1306   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1307                               &start, &end );
1308   
1309   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1310                                     gtk_text_buffer_get_mark (buffer->buffer,
1311                                                               "insert"));
1312
1313
1314   if (*str != '\0')
1315     {
1316       GtkTextIter match_start, match_end;
1317       
1318       while (gtk_text_iter_forward_search (&iter, str, TRUE, FALSE,
1319                                            &match_start, &match_end))
1320         {
1321           gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1322                                      &match_start, &match_end);
1323
1324           iter = match_end;
1325         }
1326     }
1327
1328 #if 0  
1329   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1330                                    GTK_MESSAGE_INFO,
1331                                    GTK_BUTTONS_OK,
1332                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1333                                    "%d strings found and marked in red",
1334                                    i);
1335
1336   gtk_signal_connect_object (GTK_OBJECT (dialog),
1337                              "response",
1338                              GTK_SIGNAL_FUNC (gtk_widget_destroy),
1339                              GTK_OBJECT (dialog));
1340   
1341   gtk_widget_show (dialog);
1342 #endif
1343 }
1344
1345 static void
1346 buffer_ref (Buffer *buffer)
1347 {
1348   buffer->refcount++;
1349 }
1350
1351 static void
1352 buffer_unref (Buffer *buffer)
1353 {
1354   buffer->refcount--;
1355   if (buffer->refcount == 0)
1356     {
1357       buffers = g_slist_remove (buffers, buffer);
1358       gtk_object_unref (GTK_OBJECT (buffer->buffer));
1359       g_free (buffer->filename);
1360       g_free (buffer);
1361     }
1362 }
1363
1364 static void
1365 close_view (View *view)
1366 {
1367   views = g_slist_remove (views, view);
1368   buffer_unref (view->buffer);
1369   gtk_widget_destroy (view->window);
1370   g_object_unref (G_OBJECT (view->item_factory));
1371   
1372   g_free (view);
1373   
1374   if (!views)
1375     gtk_main_quit ();
1376 }
1377
1378 static void
1379 check_close_view (View *view)
1380 {
1381   if (view->buffer->refcount > 1 ||
1382       check_buffer_saved (view->buffer))
1383     close_view (view);
1384 }
1385
1386 static void
1387 view_set_title (View *view)
1388 {
1389   char *pretty_name = buffer_pretty_name (view->buffer);
1390   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
1391
1392   gtk_window_set_title (GTK_WINDOW (view->window), title);
1393
1394   g_free (pretty_name);
1395   g_free (title);
1396 }
1397
1398 static void
1399 cursor_set_callback (GtkTextBuffer     *buffer,
1400                      const GtkTextIter *location,
1401                      GtkTextMark       *mark,
1402                      gpointer           user_data)
1403 {
1404   GtkTextView *text_view;
1405
1406   /* Redraw tab windows if the cursor moves
1407    * on the mapped widget (windows may not exist before realization...
1408    */
1409   
1410   text_view = GTK_TEXT_VIEW (user_data);
1411   
1412   if (GTK_WIDGET_MAPPED (text_view) &&
1413       mark == gtk_text_buffer_get_insert (buffer))
1414     {
1415       GdkWindow *tab_window;
1416
1417       tab_window = gtk_text_view_get_window (text_view,
1418                                              GTK_TEXT_WINDOW_TOP);
1419
1420       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1421       
1422       tab_window = gtk_text_view_get_window (text_view,
1423                                              GTK_TEXT_WINDOW_BOTTOM);
1424
1425       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
1426     }
1427 }
1428
1429
1430 static void
1431 text_changed_callback (GtkTextBuffer     *buffer,
1432                        gpointer           user_data)
1433 {
1434   GtkTextView *text_view;
1435
1436   /* Redraw line number windows if the buffer changes
1437    * and the widget is mapped (windows may not exist otherwise)
1438    */
1439   
1440   text_view = GTK_TEXT_VIEW (user_data);
1441   
1442   if (GTK_WIDGET_MAPPED (text_view))
1443     {
1444       GdkWindow *line_window;
1445
1446       line_window = gtk_text_view_get_window (text_view,
1447                                               GTK_TEXT_WINDOW_LEFT);
1448       
1449       gdk_window_invalidate_rect (line_window, NULL, FALSE);
1450       
1451       line_window = gtk_text_view_get_window (text_view,
1452                                               GTK_TEXT_WINDOW_RIGHT);
1453
1454       gdk_window_invalidate_rect (line_window, NULL, FALSE);
1455     }
1456 }
1457
1458 static gint
1459 tab_stops_expose (GtkWidget      *widget,
1460                   GdkEventExpose *event,
1461                   gpointer        user_data)
1462 {
1463   gint first_x;
1464   gint last_x;
1465   gint i;
1466   GdkWindow *top_win;
1467   GdkWindow *bottom_win;
1468   GtkTextView *text_view;
1469   GtkTextWindowType type;
1470   GdkDrawable *target;
1471   gint *positions = NULL;
1472   gint size;
1473   GtkTextAttributes *attrs;
1474   GtkTextIter insert;
1475   GtkTextBuffer *buffer;
1476   gboolean in_pixels;
1477   
1478   text_view = GTK_TEXT_VIEW (widget);
1479   
1480   /* See if this expose is on the tab stop window */
1481   top_win = gtk_text_view_get_window (text_view,
1482                                       GTK_TEXT_WINDOW_TOP);
1483
1484   bottom_win = gtk_text_view_get_window (text_view,
1485                                          GTK_TEXT_WINDOW_BOTTOM);
1486
1487   if (event->window == top_win)
1488     {
1489       type = GTK_TEXT_WINDOW_TOP;
1490       target = top_win;
1491     }
1492   else if (event->window == bottom_win)
1493     {
1494       type = GTK_TEXT_WINDOW_BOTTOM;
1495       target = bottom_win;
1496     }
1497   else
1498     return FALSE;
1499   
1500   first_x = event->area.x;
1501   last_x = first_x + event->area.width;
1502
1503   gtk_text_view_window_to_buffer_coords (text_view,
1504                                          type,
1505                                          first_x,
1506                                          0,
1507                                          &first_x,
1508                                          NULL);
1509
1510   gtk_text_view_window_to_buffer_coords (text_view,
1511                                          type,
1512                                          last_x,
1513                                          0,
1514                                          &last_x,
1515                                          NULL);
1516
1517   buffer = gtk_text_view_get_buffer (text_view);
1518
1519   gtk_text_buffer_get_iter_at_mark (buffer,
1520                                     &insert,
1521                                     gtk_text_buffer_get_mark (buffer,
1522                                                               "insert"));
1523   
1524   attrs = gtk_text_attributes_new ();
1525
1526   gtk_text_iter_get_attributes (&insert, attrs);
1527
1528   if (attrs->tabs)
1529     {
1530       size = pango_tab_array_get_size (attrs->tabs);
1531       
1532       pango_tab_array_get_tabs (attrs->tabs,
1533                                 NULL,
1534                                 &positions);
1535
1536       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
1537     }
1538   else
1539     {
1540       size = 0;
1541       in_pixels = FALSE;
1542     }
1543       
1544   gtk_text_attributes_unref (attrs);
1545   
1546   i = 0;
1547   while (i < size)
1548     {
1549       gint pos;
1550
1551       if (!in_pixels)
1552         positions[i] = PANGO_PIXELS (positions[i]);
1553       
1554       gtk_text_view_buffer_to_window_coords (text_view,
1555                                              type,
1556                                              positions[i],
1557                                              0,
1558                                              &pos,
1559                                              NULL);
1560       
1561       gdk_draw_line (target, 
1562                      widget->style->fg_gc [widget->state],
1563                      pos, 0,
1564                      pos, 15); 
1565       
1566       ++i;
1567     }
1568
1569   g_free (positions);
1570
1571   return TRUE;
1572 }
1573
1574 static void
1575 get_lines (GtkTextView  *text_view,
1576            gint          first_y,
1577            gint          last_y,
1578            GArray       *buffer_coords,
1579            GArray       *numbers,
1580            gint         *countp)
1581 {
1582   GtkTextIter iter;
1583   gint count;
1584   gint size;  
1585
1586   g_array_set_size (buffer_coords, 0);
1587   g_array_set_size (numbers, 0);
1588   
1589   /* Get iter at first y */
1590   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
1591
1592   /* For each iter, get its location and add it to the arrays.
1593    * Stop when we pass last_y
1594    */
1595   count = 0;
1596   size = 0;
1597
1598   while (!gtk_text_iter_is_last (&iter))
1599     {
1600       gint y, height;
1601       gint line_num;
1602       
1603       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
1604
1605       g_array_append_val (buffer_coords, y);
1606       line_num = gtk_text_iter_get_line (&iter);
1607       g_array_append_val (numbers, line_num);
1608       
1609       ++count;
1610
1611       if ((y + height) >= last_y)
1612         break;
1613       
1614       gtk_text_iter_forward_line (&iter);
1615     }
1616
1617   *countp = count;
1618 }
1619
1620 static gint
1621 line_numbers_expose (GtkWidget      *widget,
1622                      GdkEventExpose *event,
1623                      gpointer        user_data)
1624 {
1625   gint count;
1626   GArray *numbers;
1627   GArray *pixels;
1628   gint first_y;
1629   gint last_y;
1630   gint i;
1631   GdkWindow *left_win;
1632   GdkWindow *right_win;
1633   PangoLayout *layout;
1634   GtkTextView *text_view;
1635   GtkTextWindowType type;
1636   GdkDrawable *target;
1637   
1638   text_view = GTK_TEXT_VIEW (widget);
1639   
1640   /* See if this expose is on the line numbers window */
1641   left_win = gtk_text_view_get_window (text_view,
1642                                        GTK_TEXT_WINDOW_LEFT);
1643
1644   right_win = gtk_text_view_get_window (text_view,
1645                                         GTK_TEXT_WINDOW_RIGHT);
1646
1647   if (event->window == left_win)
1648     {
1649       type = GTK_TEXT_WINDOW_LEFT;
1650       target = left_win;
1651     }
1652   else if (event->window == right_win)
1653     {
1654       type = GTK_TEXT_WINDOW_RIGHT;
1655       target = right_win;
1656     }
1657   else
1658     return FALSE;
1659   
1660   first_y = event->area.y;
1661   last_y = first_y + event->area.height;
1662
1663   gtk_text_view_window_to_buffer_coords (text_view,
1664                                          type,
1665                                          0,
1666                                          first_y,
1667                                          NULL,
1668                                          &first_y);
1669
1670   gtk_text_view_window_to_buffer_coords (text_view,
1671                                          type,
1672                                          0,
1673                                          last_y,
1674                                          NULL,
1675                                          &last_y);
1676
1677   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
1678   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
1679   
1680   get_lines (text_view,
1681              first_y,
1682              last_y,
1683              pixels,
1684              numbers,
1685              &count);
1686   
1687   /* Draw fully internationalized numbers! */
1688   
1689   layout = gtk_widget_create_pango_layout (widget, "");
1690   
1691   i = 0;
1692   while (i < count)
1693     {
1694       gint pos;
1695       gchar *str;
1696       
1697       gtk_text_view_buffer_to_window_coords (text_view,
1698                                              type,
1699                                              0,
1700                                              g_array_index (pixels, gint, i),
1701                                              NULL,
1702                                              &pos);
1703
1704       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
1705
1706       pango_layout_set_text (layout, str, -1);
1707
1708
1709       gdk_draw_layout (target,
1710                        widget->style->fg_gc [widget->state],
1711                        /* 2 is just a random padding */
1712                        2, pos + 2,
1713                        layout);
1714
1715       g_free (str);
1716       
1717       ++i;
1718     }
1719
1720   g_array_free (pixels, TRUE);
1721   g_array_free (numbers, TRUE);
1722   
1723   g_object_unref (G_OBJECT (layout));
1724
1725   return TRUE;
1726 }
1727
1728 static View *
1729 create_view (Buffer *buffer)
1730 {
1731   View *view;
1732   
1733   GtkWidget *sw;
1734   GtkWidget *vbox;
1735
1736   view = g_new0 (View, 1);
1737   views = g_slist_prepend (views, view);
1738
1739   view->buffer = buffer;
1740   buffer_ref (buffer);
1741   
1742   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1743   gtk_object_set_data (GTK_OBJECT (view->window), "view", view);
1744   
1745   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
1746                       GTK_SIGNAL_FUNC (delete_event_cb), NULL);
1747
1748   view->accel_group = gtk_accel_group_new ();
1749   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
1750   gtk_object_set_data (GTK_OBJECT (view->item_factory), "view", view);
1751   
1752   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
1753
1754   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
1755
1756   vbox = gtk_vbox_new (FALSE, 0);
1757   gtk_container_add (GTK_CONTAINER (view->window), vbox);
1758
1759   gtk_box_pack_start (GTK_BOX (vbox),
1760                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
1761                       FALSE, FALSE, 0);
1762   
1763   sw = gtk_scrolled_window_new (NULL, NULL);
1764   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1765                                  GTK_POLICY_AUTOMATIC,
1766                                  GTK_POLICY_AUTOMATIC);
1767
1768   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
1769   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
1770                                GTK_WRAPMODE_WORD);
1771
1772
1773   /* Draw tab stops in the top and bottom windows. */
1774   
1775   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1776                                         GTK_TEXT_WINDOW_TOP,
1777                                         15);
1778
1779   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1780                                         GTK_TEXT_WINDOW_BOTTOM,
1781                                         15);
1782
1783   gtk_signal_connect (GTK_OBJECT (view->text_view),
1784                       "expose_event",
1785                       GTK_SIGNAL_FUNC (tab_stops_expose),
1786                       NULL);  
1787
1788   gtk_signal_connect (GTK_OBJECT (view->buffer->buffer),
1789                       "mark_set",
1790                       GTK_SIGNAL_FUNC (cursor_set_callback),
1791                       view->text_view);
1792   
1793   /* Draw line numbers in the side windows; we should really be
1794    * more scientific about what width we set them to.
1795    */
1796   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1797                                         GTK_TEXT_WINDOW_RIGHT,
1798                                         30);
1799   
1800   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
1801                                         GTK_TEXT_WINDOW_LEFT,
1802                                         30);
1803   
1804   gtk_signal_connect (GTK_OBJECT (view->text_view),
1805                       "expose_event",
1806                       GTK_SIGNAL_FUNC (line_numbers_expose),
1807                       NULL);
1808
1809   gtk_signal_connect (GTK_OBJECT (view->buffer->buffer),
1810                       "changed",
1811                       GTK_SIGNAL_FUNC (text_changed_callback),
1812                       view->text_view);
1813   
1814   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
1815   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
1816
1817   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
1818
1819   gtk_widget_grab_focus (view->text_view);
1820
1821   view_set_title (view);
1822   view_init_menus (view);
1823   
1824   gtk_widget_show_all (view->window);
1825   return view;
1826 }
1827
1828 int
1829 main (int argc, char** argv)
1830 {
1831   Buffer *buffer;
1832   View *view;
1833   int i;
1834   
1835   gtk_init (&argc, &argv);
1836   
1837   buffer = create_buffer ();
1838   view = create_view (buffer);
1839   buffer_unref (buffer);
1840   
1841   push_active_window (GTK_WINDOW (view->window));
1842   for (i=1; i < argc; i++)
1843     {
1844       char *filename;
1845
1846       /* Quick and dirty canonicalization - better should be in GLib
1847        */
1848
1849       if (!g_path_is_absolute (argv[i]))
1850         {
1851           char *cwd = g_get_current_dir ();
1852           filename = g_strconcat (cwd, "/", argv[i], NULL);
1853           g_free (cwd);
1854         }
1855       else
1856         filename = argv[i];
1857
1858       open_ok_func (filename, view);
1859
1860       if (filename != argv[i])
1861         g_free (filename);
1862     }
1863   pop_active_window ();
1864   
1865   gtk_main ();
1866
1867   return 0;
1868 }
1869
1870