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