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