]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Test program to make sure GtkTextBuffer is in working order.
[~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 #include <unicode.h>
7
8 #include <gtk/gtk.h>
9 #include <gdk/gdkkeysyms.h>
10
11 typedef struct _Buffer Buffer;
12 typedef struct _View View;
13
14 static gint untitled_serial = 1;
15
16 GSList *active_window_stack = NULL;
17
18 struct _Buffer
19 {
20   gint refcount;
21   GtkTextBuffer *buffer;
22   char *filename;
23   gint untitled_serial;
24 };
25
26 struct _View
27 {
28   GtkWidget *window;
29   GtkWidget *text_view;
30   GtkAccelGroup *accel_group;
31   GtkItemFactory *item_factory;
32   Buffer *buffer;
33 };
34
35 static void push_active_window (GtkWindow *window);
36 static void pop_active_window (void);
37 static GtkWindow *get_active_window (void);
38
39 static Buffer * create_buffer      (void);
40 static gboolean check_buffer_saved (Buffer *buffer);
41 static gboolean save_buffer        (Buffer *buffer);
42 static gboolean save_as_buffer     (Buffer *buffer);
43 static char *   buffer_pretty_name (Buffer *buffer);
44 static void     buffer_filename_set (Buffer *buffer);
45
46 static View *view_from_widget (GtkWidget *widget);
47
48 static View *create_view      (Buffer *buffer);
49 static void  check_close_view (View   *view);
50 static void  close_view       (View   *view);
51 static void  view_set_title   (View   *view);
52 static void  view_init_menus  (View   *view);
53
54 GSList *buffers = NULL;
55 GSList *views = NULL;
56
57 static void
58 push_active_window (GtkWindow *window)
59 {
60   gtk_object_ref (GTK_OBJECT (window));
61   active_window_stack = g_slist_prepend (active_window_stack, window);
62 }
63
64 static void
65 pop_active_window (void)
66 {
67   gtk_object_unref (active_window_stack->data);
68   active_window_stack = g_slist_delete_link (active_window_stack, active_window_stack);
69 }
70
71 static GtkWindow *
72 get_active_window (void)
73 {
74   if (active_window_stack)
75     return active_window_stack->data;
76   else
77     return NULL;
78 }
79
80 /*
81  * Filesel utility function
82  */
83
84 typedef gboolean (*FileselOKFunc) (const char *filename, gpointer data);
85
86 static void
87 filesel_ok_cb (GtkWidget *button, GtkWidget *filesel)
88 {
89   FileselOKFunc ok_func = gtk_object_get_data (GTK_OBJECT (filesel), "ok-func");
90   gpointer data = gtk_object_get_data (GTK_OBJECT (filesel), "ok-data");
91   gint *result = gtk_object_get_data (GTK_OBJECT (filesel), "ok-result");
92   
93   gtk_widget_hide (filesel);
94   
95   if ((*ok_func) (gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), data))
96     {
97       gtk_widget_destroy (filesel);
98       *result = TRUE;
99     }
100   else
101     gtk_widget_show (filesel);
102 }
103
104 gboolean
105 filesel_run (GtkWindow    *parent, 
106              const char   *title,
107              const char   *start_file,
108              FileselOKFunc func,
109              gpointer      data)
110 {
111   GtkWidget *filesel = gtk_file_selection_new (title);
112   gboolean result = FALSE;
113
114   if (!parent)
115     parent = get_active_window ();
116   
117   if (parent)
118     gtk_window_set_transient_for (GTK_WINDOW (filesel), parent);
119
120   if (start_file)
121     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), start_file);
122
123   
124   gtk_object_set_data (GTK_OBJECT (filesel), "ok-func", func);
125   gtk_object_set_data (GTK_OBJECT (filesel), "ok-data", data);
126   gtk_object_set_data (GTK_OBJECT (filesel), "ok-result", &result);
127
128   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
129                       "clicked",
130                       GTK_SIGNAL_FUNC (filesel_ok_cb), filesel);
131   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
132                              "clicked",
133                              GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (filesel));
134
135   gtk_signal_connect (GTK_OBJECT (filesel), "destroy",
136                       GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
137   gtk_window_set_modal (GTK_WINDOW (filesel), TRUE);
138
139   gtk_widget_show (filesel);
140   gtk_main ();
141
142   return result;
143 }
144
145 /*
146  * MsgBox utility functions
147  */
148
149 static void
150 msgbox_yes_cb (GtkWidget *widget, gboolean *result)
151 {
152   *result = 0;
153   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
154 }
155
156 static void
157 msgbox_no_cb (GtkWidget *widget, gboolean *result)
158 {
159   *result = 1;
160   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
161 }
162
163 static gboolean
164 msgbox_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
165 {
166   if (event->keyval == GDK_Escape)
167     {
168       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
169       gtk_object_destroy (GTK_OBJECT (widget));
170       return TRUE;
171     }
172
173   return FALSE;
174 }
175
176 gint
177 msgbox_run (GtkWindow  *parent,
178             const char *message,
179             const char *yes_button,
180             const char *no_button,
181             const char *cancel_button,
182             gint default_index)
183 {
184   gboolean result = -1;
185   GtkWidget *dialog;
186   GtkWidget *button;
187   GtkWidget *label;
188   GtkWidget *vbox;
189   GtkWidget *button_box;
190   GtkWidget *separator;
191
192   g_return_val_if_fail (message != NULL, FALSE);
193   g_return_val_if_fail (default_index >= 0 && default_index <= 1, FALSE);
194
195   if (!parent)
196     parent = get_active_window ();
197   
198   /* Create a dialog
199    */
200   dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
201   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
202   if (parent)
203     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
204   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
205
206   /* Quit our recursive main loop when the dialog is destroyed.
207    */
208   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
209                       GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
210
211   /* Catch Escape key presses and have them destroy the dialog
212    */
213   gtk_signal_connect (GTK_OBJECT (dialog), "key_press_event",
214                       GTK_SIGNAL_FUNC (msgbox_key_press_cb), NULL);
215
216   /* Fill in the contents of the widget
217    */
218   vbox = gtk_vbox_new (FALSE, 0);
219   gtk_container_add (GTK_CONTAINER (dialog), vbox);
220   
221   label = gtk_label_new (message);
222   gtk_misc_set_padding (GTK_MISC (label), 12, 12);
223   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
224   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
225
226   separator = gtk_hseparator_new ();
227   gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
228
229   button_box = gtk_hbutton_box_new ();
230   gtk_box_pack_start (GTK_BOX (vbox), button_box, FALSE, FALSE, 0);
231   gtk_container_set_border_width (GTK_CONTAINER (button_box), 8);
232   
233
234   /* When Yes is clicked, call the msgbox_yes_cb
235    * This sets the result variable and destroys the dialog
236    */
237   if (yes_button)
238     {
239       button = gtk_button_new_with_label (yes_button);
240       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
241       gtk_container_add (GTK_CONTAINER (button_box), button);
242
243       if (default_index == 0)
244         gtk_widget_grab_default (button);
245       
246       gtk_signal_connect (GTK_OBJECT (button), "clicked",
247                           GTK_SIGNAL_FUNC (msgbox_yes_cb), &result);
248     }
249
250   /* When No is clicked, call the msgbox_no_cb
251    * This sets the result variable and destroys the dialog
252    */
253   if (no_button)
254     {
255       button = gtk_button_new_with_label (no_button);
256       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
257       gtk_container_add (GTK_CONTAINER (button_box), button);
258
259       if (default_index == 0)
260         gtk_widget_grab_default (button);
261       
262       gtk_signal_connect (GTK_OBJECT (button), "clicked",
263                           GTK_SIGNAL_FUNC (msgbox_no_cb), &result);
264     }
265
266   /* When Cancel is clicked, destroy the dialog
267    */
268   if (cancel_button)
269     {
270       button = gtk_button_new_with_label (cancel_button);
271       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
272       gtk_container_add (GTK_CONTAINER (button_box), button);
273       
274       if (default_index == 1)
275         gtk_widget_grab_default (button);
276       
277       gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
278                                  GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (dialog));
279     }
280
281   gtk_widget_show_all (dialog);
282
283   /* Run a recursive main loop until a button is clicked
284    * or the user destroys the dialog through the window mananger */
285   gtk_main ();
286
287   return result;
288 }
289
290 /*
291  * Example buffer filling code
292  */
293 static gint
294 blink_timeout(gpointer data)
295 {
296   GtkTextTag *tag;
297   static gboolean flip = FALSE;
298   
299   tag = GTK_TEXT_TAG(data);
300
301   gtk_object_set(GTK_OBJECT(tag),
302                  "foreground", flip ? "blue" : "purple",
303                  NULL);
304
305   flip = !flip;
306
307   return TRUE;
308 }
309
310 static gint
311 tag_event_handler(GtkTextTag *tag, GtkWidget *widget, GdkEvent *event,
312                   const GtkTextIter *iter, gpointer user_data)
313 {
314   gint char_index;
315
316   char_index = gtk_text_iter_get_char_index(iter);
317   
318   switch (event->type)
319     {
320     case GDK_MOTION_NOTIFY:
321       printf("Motion event at char %d tag `%s'\n",
322              char_index, tag->name);
323       break;
324         
325     case GDK_BUTTON_PRESS:
326       printf("Button press at char %d tag `%s'\n",
327              char_index, tag->name);
328       break;
329         
330     case GDK_2BUTTON_PRESS:
331       printf("Double click at char %d tag `%s'\n",
332              char_index, tag->name);
333       break;
334         
335     case GDK_3BUTTON_PRESS:
336       printf("Triple click at char %d tag `%s'\n",
337              char_index, tag->name);
338       break;
339         
340     case GDK_BUTTON_RELEASE:
341       printf("Button release at char %d tag `%s'\n",
342              char_index, tag->name);
343       break;
344         
345     case GDK_KEY_PRESS:
346     case GDK_KEY_RELEASE:
347     case GDK_ENTER_NOTIFY:
348     case GDK_LEAVE_NOTIFY:
349     case GDK_PROPERTY_NOTIFY:
350     case GDK_SELECTION_CLEAR:
351     case GDK_SELECTION_REQUEST:
352     case GDK_SELECTION_NOTIFY:
353     case GDK_PROXIMITY_IN:
354     case GDK_PROXIMITY_OUT:
355     case GDK_DRAG_ENTER:
356     case GDK_DRAG_LEAVE:
357     case GDK_DRAG_MOTION:
358     case GDK_DRAG_STATUS:
359     case GDK_DROP_START:
360     case GDK_DROP_FINISHED:
361     default:
362       break;
363     }
364
365   return FALSE;
366 }
367
368 static void
369 setup_tag(GtkTextTag *tag)
370 {
371
372   gtk_signal_connect(GTK_OBJECT(tag),
373                      "event",
374                      GTK_SIGNAL_FUNC(tag_event_handler),
375                      NULL);
376 }
377
378 static char  *book_closed_xpm[] = {
379 "16 16 6 1",
380 "       c None s None",
381 ".      c black",
382 "X      c red",
383 "o      c yellow",
384 "O      c #808080",
385 "#      c white",
386 "                ",
387 "       ..       ",
388 "     ..XX.      ",
389 "   ..XXXXX.     ",
390 " ..XXXXXXXX.    ",
391 ".ooXXXXXXXXX.   ",
392 "..ooXXXXXXXXX.  ",
393 ".X.ooXXXXXXXXX. ",
394 ".XX.ooXXXXXX..  ",
395 " .XX.ooXXX..#O  ",
396 "  .XX.oo..##OO. ",
397 "   .XX..##OO..  ",
398 "    .X.#OO..    ",
399 "     ..O..      ",
400 "      ..        ",
401 "                "};
402
403
404
405 void
406 fill_example_buffer (GtkTextBuffer *buffer)
407 {
408   GtkTextIter iter, iter2;
409   GtkTextTag *tag;
410   GdkColor color;
411   GdkColor color2;
412   GdkPixmap *pixmap;
413   GdkBitmap *mask;
414   int i;
415   char *str;
416   
417   tag = gtk_text_buffer_create_tag(buffer, "fg_blue");
418
419   /*       gtk_timeout_add(1000, blink_timeout, tag); */
420       
421   setup_tag(tag);
422   
423   color.red = color.green = 0;
424   color.blue = 0xffff;
425   color2.red = 0xfff;
426   color2.blue = 0x0;
427   color2.green = 0;
428   gtk_object_set(GTK_OBJECT(tag),
429                  "foreground_gdk", &color,
430                  "background_gdk", &color2,
431                  "font", "Sans 24",
432                  NULL);
433
434   tag = gtk_text_buffer_create_tag(buffer, "fg_red");
435
436   setup_tag(tag);
437       
438   color.blue = color.green = 0;
439   color.red = 0xffff;
440   gtk_object_set(GTK_OBJECT(tag),
441                  "offset", -4,
442                  "foreground_gdk", &color,
443                  NULL);
444
445   tag = gtk_text_buffer_create_tag(buffer, "bg_green");
446
447   setup_tag(tag);
448       
449   color.blue = color.red = 0;
450   color.green = 0xffff;
451   gtk_object_set(GTK_OBJECT(tag),
452                  "background_gdk", &color,
453                  "font", "Sans 10",
454                  NULL);
455
456   tag = gtk_text_buffer_create_tag(buffer, "overstrike");
457
458   setup_tag(tag);
459       
460   gtk_object_set(GTK_OBJECT(tag),
461                  "overstrike", TRUE,
462                  NULL);
463
464
465   tag = gtk_text_buffer_create_tag(buffer, "underline");
466
467   setup_tag(tag);
468       
469   gtk_object_set(GTK_OBJECT(tag),
470                  "underline", PANGO_UNDERLINE_SINGLE,
471                  NULL);
472
473   setup_tag(tag);
474       
475   gtk_object_set(GTK_OBJECT(tag),
476                  "underline", PANGO_UNDERLINE_SINGLE,
477                  NULL);
478
479   tag = gtk_text_buffer_create_tag(buffer, "centered");
480       
481   gtk_object_set(GTK_OBJECT(tag),
482                  "justify", GTK_JUSTIFY_CENTER,
483                  NULL);
484
485   tag = gtk_text_buffer_create_tag(buffer, "rtl_quote");
486       
487   gtk_object_set(GTK_OBJECT(tag),
488                  "wrap_mode", GTK_WRAPMODE_WORD,
489                  "direction", GTK_TEXT_DIR_RTL,
490                  "left_wrapped_line_margin", 20,
491                  "left_margin", 20,
492                  "right_margin", 20,
493                  NULL);
494   
495   pixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL,
496                                                   gtk_widget_get_default_colormap(),
497                                                   &mask,
498                                                   NULL, book_closed_xpm);
499   
500   g_assert(pixmap != NULL);
501   
502   i = 0;
503   while (i < 100)
504     {
505       GtkTextMark * temp_mark;
506       
507       gtk_text_buffer_get_iter_at_char(buffer, &iter, 0);
508           
509       gtk_text_buffer_insert_pixmap (buffer, &iter, pixmap, mask);
510           
511       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",
512                             i);
513       
514       gtk_text_buffer_insert(buffer, &iter, str, -1);
515
516       g_free(str);
517       
518       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 5);
519           
520       gtk_text_buffer_insert(buffer, &iter,
521                              "(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"
522                              /* This is UTF8 stuff, Emacs doesn't
523                                 really know how to display it */
524                              "German (Deutsch Süd) Grüß Gott Greek (Ελληνικά) Γειά σας Hebrew   שלום Japanese (日本語)\n", -1);
525
526       temp_mark =
527         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
528
529 #if 1
530       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 6);
531       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 0, 13);
532
533       gtk_text_buffer_apply_tag(buffer, "fg_blue", &iter, &iter2);
534
535       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 1, 10);
536       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 1, 16);
537
538       gtk_text_buffer_apply_tag(buffer, "underline", &iter, &iter2);
539
540       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 1, 14);
541       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 1, 24);
542
543       gtk_text_buffer_apply_tag(buffer, "overstrike", &iter, &iter2);
544           
545       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 9);
546       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 0, 16);
547
548       gtk_text_buffer_apply_tag(buffer, "bg_green", &iter, &iter2);
549   
550       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 4, 2);
551       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 4, 10);
552
553       gtk_text_buffer_apply_tag(buffer, "bg_green", &iter, &iter2);
554
555       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 4, 8);
556       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 4, 15);
557
558       gtk_text_buffer_apply_tag(buffer, "fg_red", &iter, &iter2);
559 #endif
560
561       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
562       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
563           
564       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
565       gtk_text_buffer_apply_tag (buffer, "centered", &iter2, &iter);
566
567       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
568       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
569       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
570       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
571       gtk_text_buffer_apply_tag (buffer, "rtl_quote", &iter2, &iter);
572           
573       ++i;
574     }
575
576   gdk_pixmap_unref(pixmap);
577   if (mask)
578     gdk_bitmap_unref(mask);
579   
580   printf("%d lines %d chars\n",
581          gtk_text_buffer_get_line_count(buffer),
582          gtk_text_buffer_get_char_count(buffer));
583
584   gtk_text_buffer_set_modified (buffer, FALSE);
585 }
586
587 gboolean
588 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
589 {
590   FILE* f;
591   gchar buf[2048];
592   gint remaining = 0;
593   GtkTextIter iter, end;
594
595   f = fopen(filename, "r");
596   
597   if (f == NULL)
598     {
599       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
600                                     filename, g_strerror (errno));
601       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
602       g_free (err);
603       return FALSE;
604     }
605   
606   gtk_text_buffer_get_iter_at_char(buffer, &iter, 0);
607   while (!feof (f))
608     {
609       gint count;
610       char *leftover, *next;
611       unicode_char_t wc;
612       int to_read = 2047  - remaining;
613       
614       count = fread (buf + remaining, 1, to_read, f);
615       buf[count + remaining] = '\0';
616
617       leftover = next = buf;
618       while (next)
619         {
620           leftover = next;
621           if (!*leftover)
622             break;
623           
624           next = unicode_get_utf8 (next, &wc);
625         }
626
627       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
628
629       remaining = buf + remaining + count - leftover;
630       g_memmove (buf, leftover, remaining);
631
632       if (remaining > 6 || count < to_read)
633           break;
634     }
635
636   if (remaining)
637     {
638       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
639       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
640       g_free (err);
641     }
642   
643   /* We had a newline in the buffer to begin with. (The buffer always contains
644    * a newline, so we delete to the end of the buffer to clean up.
645    */
646   gtk_text_buffer_get_last_iter (buffer, &end);
647   gtk_text_buffer_delete (buffer, &iter, &end);
648   
649   gtk_text_buffer_set_modified (buffer, FALSE);
650
651   return TRUE;
652 }
653
654 static gint
655 delete_event_cb(GtkWidget *window, GdkEventAny *event, gpointer data)
656 {
657   View *view = view_from_widget (window);
658
659   push_active_window (GTK_WINDOW (window));
660   check_close_view (view);
661   pop_active_window ();
662
663   return TRUE;
664 }
665
666 /*
667  * Menu callbacks
668  */
669
670 static View *
671 get_empty_view (View *view)
672 {
673   if (!view->buffer->filename &&
674       !gtk_text_buffer_modified (view->buffer->buffer))
675     return view;
676   else
677     return create_view (create_buffer ());
678 }
679
680 static View *
681 view_from_widget (GtkWidget *widget)
682 {
683   GtkWidget *app;
684
685   if (GTK_IS_MENU_ITEM (widget))
686     {
687       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
688       return gtk_object_get_data (GTK_OBJECT (item_factory), "view");      
689     }
690   else
691     {
692       GtkWidget *app = gtk_widget_get_toplevel (widget);
693       return gtk_object_get_data (GTK_OBJECT (app), "view");
694     }
695 }
696
697 static void
698 do_new (gpointer             callback_data,
699         guint                callback_action,
700         GtkWidget           *widget)
701 {
702   create_view (create_buffer ());
703 }
704
705 static void
706 do_new_view (gpointer             callback_data,
707              guint                callback_action,
708              GtkWidget           *widget)
709 {
710   View *view = view_from_widget (widget);
711   
712   create_view (view->buffer);
713 }
714
715 gboolean
716 open_ok_func (const char *filename, gpointer data)
717 {
718   View *view = data;
719   View *new_view = get_empty_view (view);
720
721   if (!fill_file_buffer (new_view->buffer->buffer, filename))
722     {
723       if (new_view != view)
724         close_view (new_view);
725       return FALSE;
726     }
727   else
728     {
729       g_free (new_view->buffer->filename);
730       new_view->buffer->filename = g_strdup (filename);
731       buffer_filename_set (new_view->buffer);
732       
733       return TRUE;
734     }
735 }
736
737 static void
738 do_open (gpointer             callback_data,
739          guint                callback_action,
740          GtkWidget           *widget)
741 {
742   View *view = view_from_widget (widget);
743
744   push_active_window (GTK_WINDOW (view->window));
745   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
746   pop_active_window ();
747 }
748
749 static void
750 do_save_as (gpointer             callback_data,
751             guint                callback_action,
752             GtkWidget           *widget)
753 {
754   View *view = view_from_widget (widget);  
755
756   push_active_window (GTK_WINDOW (view->window));
757   save_as_buffer (view->buffer);
758   pop_active_window ();
759 }
760
761 static void
762 do_save (gpointer             callback_data,
763          guint                callback_action,
764          GtkWidget           *widget)
765 {
766   View *view = view_from_widget (widget);
767
768   push_active_window (GTK_WINDOW (view->window));
769   if (!view->buffer->filename)
770     do_save_as (callback_data, callback_action, widget);
771   else
772     save_buffer (view->buffer);
773   pop_active_window ();
774 }
775
776 static void
777 do_close   (gpointer             callback_data,
778             guint                callback_action,
779             GtkWidget           *widget)
780 {
781   View *view = view_from_widget (widget);
782
783   push_active_window (GTK_WINDOW (view->window));
784   check_close_view (view);
785   pop_active_window ();
786 }
787
788 static void
789 do_exit    (gpointer             callback_data,
790             guint                callback_action,
791             GtkWidget           *widget)
792 {
793   View *view = view_from_widget (widget);
794
795   GSList *tmp_list = buffers;
796
797   push_active_window (GTK_WINDOW (view->window));
798   while (tmp_list)
799     {
800       if (!check_buffer_saved (tmp_list->data))
801         return;
802
803       tmp_list = tmp_list->next;
804     }
805
806   gtk_main_quit();
807   pop_active_window ();
808 }
809
810 static void
811 do_example (gpointer             callback_data,
812             guint                callback_action,
813             GtkWidget           *widget)
814 {
815   View *view = view_from_widget (widget);
816   View *new_view;
817
818   new_view = get_empty_view (view);
819   
820   fill_example_buffer (new_view->buffer->buffer);
821 }
822
823 static void
824 do_wrap_changed (gpointer             callback_data,
825                  guint                callback_action,
826                  GtkWidget           *widget)
827 {
828   View *view = view_from_widget (widget);
829
830   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
831 }
832
833 static void
834 do_direction_changed (gpointer             callback_data,
835                       guint                callback_action,
836                       GtkWidget           *widget)
837 {
838   View *view = view_from_widget (widget);
839   
840   gtk_widget_set_direction (view->text_view, callback_action);
841   gtk_widget_queue_resize (view->text_view);
842 }
843
844 static void
845 view_init_menus (View *view)
846 {
847   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
848   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
849   GtkWidget *menu_item = NULL;
850
851   switch (direction)
852     {
853     case GTK_TEXT_DIR_LTR:
854       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
855       break;
856     case GTK_TEXT_DIR_RTL:
857       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
858       break;
859     default:
860       break;
861     }
862
863   if (menu_item)
864     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
865
866   switch (wrap_mode)
867     {
868     case GTK_WRAPMODE_NONE:
869       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
870       break;
871     case GTK_WRAPMODE_WORD:
872       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
873       break;
874     default:
875       break;
876     }
877   
878   if (menu_item)
879     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
880 }
881
882 static GtkItemFactoryEntry menu_items[] =
883 {
884   { "/_File",            NULL,         0,           0, "<Branch>" },
885   { "/File/_New",        "<control>N", do_new,      0, NULL },
886   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
887   { "/File/_Open",       "<control>O", do_open,     0, NULL },
888   { "/File/_Save",       "<control>S", do_save,     0, NULL },
889   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
890   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
891   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
892   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
893
894   { "/_Settings",         NULL,         0,                0, "<Branch>" },
895   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAPMODE_NONE, "<RadioItem>" },
896   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAPMODE_WORD, "/Settings/Wrap Off" },
897   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
898   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
899   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
900   
901   { "/_Test",            NULL,         0,           0, "<Branch>" },
902   { "/Test/_Example",    NULL,         do_example,  0, NULL },
903 };
904
905 static gboolean
906 save_buffer (Buffer *buffer)
907 {
908   GtkTextIter start, end;
909   gchar *chars;
910   gboolean result = FALSE;
911   gboolean have_backup = FALSE;
912   gchar *bak_filename;
913   FILE *file;
914
915   g_return_val_if_fail (buffer->filename != NULL, FALSE);
916
917   bak_filename = g_strconcat (buffer->filename, "~", NULL);
918   
919   if (rename (buffer->filename, bak_filename) != 0)
920     {
921       if (errno != ENOENT)
922         {
923           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
924                                         buffer->filename, bak_filename, g_strerror (errno));
925           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
926           g_free (err);
927         }
928       
929       return FALSE;
930     }
931   else
932     have_backup = TRUE;
933   
934   file = fopen (buffer->filename, "w");
935   if (!file)
936     {
937       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
938                                     buffer->filename, bak_filename, g_strerror (errno));
939       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
940     }
941   else
942     {
943       gtk_text_buffer_get_iter_at_char (buffer->buffer, &start, 0);
944       gtk_text_buffer_get_last_iter (buffer->buffer, &end);
945   
946       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
947
948       if (fputs (chars, file) == EOF ||
949           fclose (file) == EOF)
950         {
951           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
952                                         buffer->filename, g_strerror (errno));
953           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
954           g_free (err);
955         }
956       else
957         {
958           /* Success
959            */
960           result = TRUE;
961           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
962         }
963         
964       g_free (chars);
965     }
966
967   if (!result && have_backup)
968     {
969       if (rename (bak_filename, buffer->filename) != 0)
970         {
971           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
972                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
973           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
974           g_free (err);
975         }
976     }
977
978   g_free (bak_filename);
979   
980   return result;
981 }
982
983 static gboolean
984 save_as_ok_func (const char *filename, gpointer data)
985 {
986   Buffer *buffer = data;
987   char *old_filename = buffer->filename;
988
989   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
990     {
991       struct stat statbuf;
992
993       if (stat(filename, &statbuf) == 0)
994         {
995           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
996           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
997           g_free (err);
998
999           if (result != 0)
1000             return FALSE;
1001         }
1002     }
1003   
1004   buffer->filename = g_strdup (filename);
1005
1006   if (save_buffer (buffer))
1007     {
1008       g_free (old_filename);
1009       buffer_filename_set (buffer);
1010       return TRUE;
1011     }
1012   else
1013     {
1014       g_free (buffer->filename);
1015       buffer->filename = old_filename;
1016       return FALSE;
1017     }
1018 }
1019
1020 static gboolean
1021 save_as_buffer (Buffer *buffer)
1022 {
1023   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1024 }
1025
1026 static gboolean
1027 check_buffer_saved (Buffer *buffer)
1028 {
1029   if (gtk_text_buffer_modified (buffer->buffer))
1030     {
1031       char *pretty_name = buffer_pretty_name (buffer);
1032       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1033       gint result;
1034       
1035       g_free (pretty_name);
1036       
1037       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1038       g_free (msg);
1039   
1040       if (result == 0)
1041         return save_as_buffer (buffer);
1042       else if (result == 1)
1043         return TRUE;
1044       else
1045         return FALSE;
1046     }
1047   else
1048     return TRUE;
1049 }
1050
1051 static Buffer *
1052 create_buffer (void)
1053 {
1054   Buffer *buffer;
1055
1056   buffer = g_new (Buffer, 1);
1057
1058   buffer->buffer = gtk_text_buffer_new (NULL);
1059   gtk_object_ref (GTK_OBJECT (buffer->buffer));
1060   gtk_object_sink (GTK_OBJECT (buffer->buffer));
1061   
1062   buffer->refcount = 1;
1063   buffer->filename = NULL;
1064   buffer->untitled_serial = -1;
1065
1066   buffers = g_slist_prepend (buffers, buffer);
1067   
1068   return buffer;
1069 }
1070
1071 static char *
1072 buffer_pretty_name (Buffer *buffer)
1073 {
1074   if (buffer->filename)
1075     {
1076       char *p;
1077       char *result = g_strdup (g_basename (buffer->filename));
1078       p = strchr (result, '/');
1079       if (p)
1080         *p = '\0';
1081
1082       return result;
1083     }
1084   else
1085     {
1086       if (buffer->untitled_serial == -1)
1087         buffer->untitled_serial = untitled_serial++;
1088
1089       if (buffer->untitled_serial == 1)
1090         return g_strdup ("Untitled");
1091       else
1092         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1093     }
1094 }
1095
1096 static void
1097 buffer_filename_set (Buffer *buffer)
1098 {
1099   GSList *tmp_list = views;
1100
1101   while (tmp_list)
1102     {
1103       View *view = tmp_list->data;
1104
1105       if (view->buffer == buffer)
1106         view_set_title (view);
1107
1108       tmp_list = tmp_list->next;
1109     }
1110 }
1111
1112 static void
1113 buffer_ref (Buffer *buffer)
1114 {
1115   buffer->refcount++;
1116 }
1117
1118 static void
1119 buffer_unref (Buffer *buffer)
1120 {
1121   buffer->refcount--;
1122   if (buffer->refcount == 0)
1123     {
1124       buffers = g_slist_remove (buffers, buffer);
1125       gtk_object_unref (GTK_OBJECT (buffer->buffer));
1126       g_free (buffer->filename);
1127       g_free (buffer);
1128     }
1129 }
1130
1131 static void
1132 close_view (View *view)
1133 {
1134   views = g_slist_remove (views, view);
1135   buffer_unref (view->buffer);
1136   gtk_widget_destroy (view->window);
1137   g_object_unref (G_OBJECT (view->item_factory));
1138   
1139   g_free (view);
1140   
1141   if (!views)
1142     gtk_main_quit();
1143 }
1144
1145 static void
1146 check_close_view (View *view)
1147 {
1148   if (view->buffer->refcount > 1 ||
1149       check_buffer_saved (view->buffer))
1150     close_view (view);
1151 }
1152
1153 static void
1154 view_set_title (View *view)
1155 {
1156   char *pretty_name = buffer_pretty_name (view->buffer);
1157   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
1158
1159   gtk_window_set_title (GTK_WINDOW (view->window), title);
1160
1161   g_free (pretty_name);
1162   g_free (title);
1163 }
1164
1165 static View *
1166 create_view (Buffer *buffer)
1167 {
1168   View *view;
1169   
1170   GtkWidget *sw;
1171   GtkWidget *vbox;
1172
1173   view = g_new0 (View, 1);
1174   views = g_slist_prepend (views, view);
1175
1176   view->buffer = buffer;
1177   buffer_ref (buffer);
1178   
1179   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1180   gtk_object_set_data (GTK_OBJECT (view->window), "view", view);
1181   
1182   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
1183                       GTK_SIGNAL_FUNC(delete_event_cb), NULL);
1184
1185   view->accel_group = gtk_accel_group_new ();
1186   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
1187   gtk_object_set_data (GTK_OBJECT (view->item_factory), "view", view);
1188   
1189   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
1190
1191   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
1192
1193   vbox = gtk_vbox_new (FALSE, 0);
1194   gtk_container_add (GTK_CONTAINER (view->window), vbox);
1195
1196   gtk_box_pack_start (GTK_BOX (vbox),
1197                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
1198                       FALSE, FALSE, 0);
1199   
1200   sw = gtk_scrolled_window_new(NULL, NULL);
1201   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1202                                  GTK_POLICY_AUTOMATIC,
1203                                  GTK_POLICY_AUTOMATIC);
1204
1205   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
1206   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), GTK_WRAPMODE_WORD);
1207
1208   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
1209   gtk_container_add(GTK_CONTAINER(sw), view->text_view);
1210
1211   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
1212
1213   gtk_widget_grab_focus(view->text_view);
1214
1215   view_set_title (view);
1216   view_init_menus (view);
1217   
1218   gtk_widget_show_all (view->window);
1219   return view;
1220 }
1221
1222 int
1223 main(int argc, char** argv)
1224 {
1225   Buffer *buffer;
1226   View *view;
1227   int i;
1228   
1229   gtk_init(&argc, &argv);
1230
1231   buffer = create_buffer ();
1232   view = create_view (buffer);
1233   buffer_unref (buffer);
1234   
1235   push_active_window (GTK_WINDOW (view->window));
1236   for (i=1; i < argc; i++)
1237     {
1238       char *filename;
1239
1240       /* Quick and dirty canonicalization - better should be in GLib
1241        */
1242
1243       if (!g_path_is_absolute (argv[i]))
1244         {
1245           char *cwd = g_get_current_dir ();
1246           filename = g_strconcat (cwd, "/", argv[i], NULL);
1247           g_free (cwd);
1248         }
1249       else
1250         filename = argv[i];
1251
1252       open_ok_func (filename, view);
1253
1254       if (filename != argv[i])
1255         g_free (filename);
1256     }
1257   pop_active_window ();
1258   
1259   gtk_main();
1260
1261   return 0;
1262 }
1263
1264