]> Pileus Git - ~andy/gtk/blob - gtk/testtext.c
Remove all references to offscreen flag which was no longer used.
[~andy/gtk] / gtk / 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       gtk_text_buffer_get_iter_at_char(buffer, &iter, 0);
506           
507       gtk_text_buffer_insert_pixmap (buffer, &iter, pixmap, mask);
508           
509       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",
510                             i);
511       
512       gtk_text_buffer_insert(buffer, &iter, str, -1);
513
514       g_free(str);
515       
516       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 5);
517           
518       gtk_text_buffer_insert(buffer, &iter,
519                              "(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"
520                              /* This is UTF8 stuff, Emacs doesn't
521                                 really know how to display it */
522                              "German (Deutsch Süd) Grüß Gott Greek (Ελληνικά) Γειά σας Hebrew   שלום Japanese (日本語)\n", -1);
523
524       gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
525
526 #if 1
527       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 6);
528       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 0, 13);
529
530       gtk_text_buffer_apply_tag(buffer, "fg_blue", &iter, &iter2);
531
532       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 1, 10);
533       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 1, 16);
534
535       gtk_text_buffer_apply_tag(buffer, "underline", &iter, &iter2);
536
537       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 1, 14);
538       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 1, 24);
539
540       gtk_text_buffer_apply_tag(buffer, "overstrike", &iter, &iter2);
541           
542       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 0, 9);
543       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 0, 16);
544
545       gtk_text_buffer_apply_tag(buffer, "bg_green", &iter, &iter2);
546   
547       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 4, 2);
548       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 4, 10);
549
550       gtk_text_buffer_apply_tag(buffer, "bg_green", &iter, &iter2);
551
552       gtk_text_buffer_get_iter_at_line_char(buffer, &iter, 4, 8);
553       gtk_text_buffer_get_iter_at_line_char(buffer, &iter2, 4, 15);
554
555       gtk_text_buffer_apply_tag(buffer, "fg_red", &iter, &iter2);
556 #endif
557
558       gtk_text_buffer_get_iter_at_mark (buffer, &iter, "tmp_mark");
559       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
560           
561       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, "tmp_mark");
562       gtk_text_buffer_apply_tag (buffer, "centered", &iter2, &iter);
563
564       gtk_text_buffer_move_mark (buffer, "tmp_mark", &iter);
565       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
566       gtk_text_buffer_insert (buffer, &iter, "وقد بدأ ثلاث من أكثر المؤسسات تقدما في شبكة اكسيون برامجها كمنظمات لا تسعى للربح، ثم تحولت في السنوات الخمس الماضية إلى مؤسسات مالية منظمة، وباتت جزءا من النظام المالي في بلدانها، ولكنها تتخصص في خدمة قطاع المشروعات الصغيرة. وأحد أكثر هذه المؤسسات نجاحا هو »بانكوسول« في بوليفيا.\n", -1);
567       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, "tmp_mark");
568       gtk_text_buffer_apply_tag (buffer, "rtl_quote", &iter2, &iter);
569           
570       ++i;
571     }
572
573   gdk_pixmap_unref(pixmap);
574   if (mask)
575     gdk_bitmap_unref(mask);
576   
577   printf("%d lines %d chars\n",
578          gtk_text_buffer_get_line_count(buffer),
579          gtk_text_buffer_get_char_count(buffer));
580
581   gtk_text_buffer_set_modified (buffer, FALSE);
582 }
583
584 gboolean
585 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
586 {
587   FILE* f;
588   gchar buf[2048];
589   gint remaining = 0;
590   GtkTextIter iter, end;
591
592   f = fopen(filename, "r");
593   
594   if (f == NULL)
595     {
596       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
597                                     filename, g_strerror (errno));
598       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
599       g_free (err);
600       return FALSE;
601     }
602   
603   gtk_text_buffer_get_iter_at_char(buffer, &iter, 0);
604   while (!feof (f))
605     {
606       gint count;
607       char *leftover, *next;
608       unicode_char_t wc;
609       int to_read = 2047  - remaining;
610       
611       count = fread (buf + remaining, 1, to_read, f);
612       buf[count + remaining] = '\0';
613
614       leftover = next = buf;
615       while (next)
616         {
617           leftover = next;
618           if (!*leftover)
619             break;
620           
621           next = unicode_get_utf8 (next, &wc);
622         }
623
624       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
625
626       remaining = buf + remaining + count - leftover;
627       g_memmove (buf, leftover, remaining);
628
629       if (remaining > 6 || count < to_read)
630           break;
631     }
632
633   if (remaining)
634     {
635       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
636       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
637       g_free (err);
638     }
639   
640   /* We had a newline in the buffer to begin with. (The buffer always contains
641    * a newline, so we delete to the end of the buffer to clean up.
642    */
643   gtk_text_buffer_get_last_iter (buffer, &end);
644   gtk_text_buffer_delete (buffer, &iter, &end);
645   
646   gtk_text_buffer_set_modified (buffer, FALSE);
647
648   return TRUE;
649 }
650
651 static gint
652 delete_event_cb(GtkWidget *window, GdkEventAny *event, gpointer data)
653 {
654   View *view = view_from_widget (window);
655
656   push_active_window (GTK_WINDOW (window));
657   check_close_view (view);
658   pop_active_window ();
659
660   return TRUE;
661 }
662
663 /*
664  * Menu callbacks
665  */
666
667 static View *
668 get_empty_view (View *view)
669 {
670   if (!view->buffer->filename &&
671       !gtk_text_buffer_modified (view->buffer->buffer))
672     return view;
673   else
674     return create_view (create_buffer ());
675 }
676
677 static View *
678 view_from_widget (GtkWidget *widget)
679 {
680   GtkWidget *app;
681
682   if (GTK_IS_MENU_ITEM (widget))
683     {
684       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
685       return gtk_object_get_data (GTK_OBJECT (item_factory), "view");      
686     }
687   else
688     {
689       GtkWidget *app = gtk_widget_get_toplevel (widget);
690       return gtk_object_get_data (GTK_OBJECT (app), "view");
691     }
692 }
693
694 static void
695 do_new (gpointer             callback_data,
696         guint                callback_action,
697         GtkWidget           *widget)
698 {
699   create_view (create_buffer ());
700 }
701
702 static void
703 do_new_view (gpointer             callback_data,
704              guint                callback_action,
705              GtkWidget           *widget)
706 {
707   View *view = view_from_widget (widget);
708   
709   create_view (view->buffer);
710 }
711
712 gboolean
713 open_ok_func (const char *filename, gpointer data)
714 {
715   View *view = data;
716   View *new_view = get_empty_view (view);
717
718   if (!fill_file_buffer (new_view->buffer->buffer, filename))
719     {
720       if (new_view != view)
721         close_view (new_view);
722       return FALSE;
723     }
724   else
725     {
726       g_free (new_view->buffer->filename);
727       new_view->buffer->filename = g_strdup (filename);
728       buffer_filename_set (new_view->buffer);
729       
730       return TRUE;
731     }
732 }
733
734 static void
735 do_open (gpointer             callback_data,
736          guint                callback_action,
737          GtkWidget           *widget)
738 {
739   View *view = view_from_widget (widget);
740
741   push_active_window (GTK_WINDOW (view->window));
742   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
743   pop_active_window ();
744 }
745
746 static void
747 do_save_as (gpointer             callback_data,
748             guint                callback_action,
749             GtkWidget           *widget)
750 {
751   View *view = view_from_widget (widget);  
752
753   push_active_window (GTK_WINDOW (view->window));
754   save_as_buffer (view->buffer);
755   pop_active_window ();
756 }
757
758 static void
759 do_save (gpointer             callback_data,
760          guint                callback_action,
761          GtkWidget           *widget)
762 {
763   View *view = view_from_widget (widget);
764
765   push_active_window (GTK_WINDOW (view->window));
766   if (!view->buffer->filename)
767     do_save_as (callback_data, callback_action, widget);
768   else
769     save_buffer (view->buffer);
770   pop_active_window ();
771 }
772
773 static void
774 do_close   (gpointer             callback_data,
775             guint                callback_action,
776             GtkWidget           *widget)
777 {
778   View *view = view_from_widget (widget);
779
780   push_active_window (GTK_WINDOW (view->window));
781   check_close_view (view);
782   pop_active_window ();
783 }
784
785 static void
786 do_exit    (gpointer             callback_data,
787             guint                callback_action,
788             GtkWidget           *widget)
789 {
790   View *view = view_from_widget (widget);
791
792   GSList *tmp_list = buffers;
793
794   push_active_window (GTK_WINDOW (view->window));
795   while (tmp_list)
796     {
797       if (!check_buffer_saved (tmp_list->data))
798         return;
799
800       tmp_list = tmp_list->next;
801     }
802
803   gtk_main_quit();
804   pop_active_window ();
805 }
806
807 static void
808 do_example (gpointer             callback_data,
809             guint                callback_action,
810             GtkWidget           *widget)
811 {
812   View *view = view_from_widget (widget);
813   View *new_view;
814
815   new_view = get_empty_view (view);
816   
817   fill_example_buffer (new_view->buffer->buffer);
818 }
819
820 static void
821 do_wrap_changed (gpointer             callback_data,
822                  guint                callback_action,
823                  GtkWidget           *widget)
824 {
825   View *view = view_from_widget (widget);
826
827   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
828 }
829
830 static void
831 do_direction_changed (gpointer             callback_data,
832                       guint                callback_action,
833                       GtkWidget           *widget)
834 {
835   View *view = view_from_widget (widget);
836   
837   gtk_widget_set_direction (view->text_view, callback_action);
838   gtk_widget_queue_resize (view->text_view);
839 }
840
841 static void
842 view_init_menus (View *view)
843 {
844   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
845   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
846   GtkWidget *menu_item = NULL;
847
848   switch (direction)
849     {
850     case GTK_TEXT_DIR_LTR:
851       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
852       break;
853     case GTK_TEXT_DIR_RTL:
854       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
855       break;
856     default:
857       break;
858     }
859
860   if (menu_item)
861     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
862
863   switch (wrap_mode)
864     {
865     case GTK_WRAPMODE_NONE:
866       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
867       break;
868     case GTK_WRAPMODE_WORD:
869       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
870       break;
871     default:
872       break;
873     }
874   
875   if (menu_item)
876     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
877 }
878
879 static GtkItemFactoryEntry menu_items[] =
880 {
881   { "/_File",            NULL,         0,           0, "<Branch>" },
882   { "/File/_New",        "<control>N", do_new,      0, NULL },
883   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
884   { "/File/_Open",       "<control>O", do_open,     0, NULL },
885   { "/File/_Save",       "<control>S", do_save,     0, NULL },
886   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
887   { "/File/sep1",        NULL,         0,           0, "<Separator>" },
888   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
889   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
890
891   { "/_Settings",         NULL,         0,                0, "<Branch>" },
892   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAPMODE_NONE, "<RadioItem>" },
893   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAPMODE_WORD, "/Settings/Wrap Off" },
894   { "/Settings/sep1",        NULL,      0,                0, "<Separator>" },
895   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
896   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
897   
898   { "/_Test",            NULL,         0,           0, "<Branch>" },
899   { "/Test/_Example",    NULL,         do_example,  0, NULL },
900 };
901
902 static gboolean
903 save_buffer (Buffer *buffer)
904 {
905   GtkTextIter start, end;
906   gchar *chars;
907   gboolean result = FALSE;
908   gboolean have_backup = FALSE;
909   gchar *bak_filename;
910   FILE *file;
911
912   g_return_val_if_fail (buffer->filename != NULL, FALSE);
913
914   bak_filename = g_strconcat (buffer->filename, "~", NULL);
915   
916   if (rename (buffer->filename, bak_filename) != 0)
917     {
918       if (errno != ENOENT)
919         {
920           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
921                                         buffer->filename, bak_filename, g_strerror (errno));
922           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
923           g_free (err);
924         }
925       
926       return FALSE;
927     }
928   else
929     have_backup = TRUE;
930   
931   file = fopen (buffer->filename, "w");
932   if (!file)
933     {
934       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
935                                     buffer->filename, bak_filename, g_strerror (errno));
936       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
937     }
938   else
939     {
940       gtk_text_buffer_get_iter_at_char (buffer->buffer, &start, 0);
941       gtk_text_buffer_get_last_iter (buffer->buffer, &end);
942   
943       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
944
945       if (fputs (chars, file) == EOF ||
946           fclose (file) == EOF)
947         {
948           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
949                                         buffer->filename, g_strerror (errno));
950           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
951           g_free (err);
952         }
953       else
954         {
955           /* Success
956            */
957           result = TRUE;
958           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
959         }
960         
961       g_free (chars);
962     }
963
964   if (!result && have_backup)
965     {
966       if (rename (bak_filename, buffer->filename) != 0)
967         {
968           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
969                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
970           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
971           g_free (err);
972         }
973     }
974
975   g_free (bak_filename);
976   
977   return result;
978 }
979
980 static gboolean
981 save_as_ok_func (const char *filename, gpointer data)
982 {
983   Buffer *buffer = data;
984   char *old_filename = buffer->filename;
985
986   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
987     {
988       struct stat statbuf;
989
990       if (stat(filename, &statbuf) == 0)
991         {
992           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
993           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
994           g_free (err);
995
996           if (result != 0)
997             return FALSE;
998         }
999     }
1000   
1001   buffer->filename = g_strdup (filename);
1002
1003   if (save_buffer (buffer))
1004     {
1005       g_free (old_filename);
1006       buffer_filename_set (buffer);
1007       return TRUE;
1008     }
1009   else
1010     {
1011       g_free (buffer->filename);
1012       buffer->filename = old_filename;
1013       return FALSE;
1014     }
1015 }
1016
1017 static gboolean
1018 save_as_buffer (Buffer *buffer)
1019 {
1020   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1021 }
1022
1023 static gboolean
1024 check_buffer_saved (Buffer *buffer)
1025 {
1026   if (gtk_text_buffer_modified (buffer->buffer))
1027     {
1028       char *pretty_name = buffer_pretty_name (buffer);
1029       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1030       gint result;
1031       
1032       g_free (pretty_name);
1033       
1034       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1035       g_free (msg);
1036   
1037       if (result == 0)
1038         return save_as_buffer (buffer);
1039       else if (result == 1)
1040         return TRUE;
1041       else
1042         return FALSE;
1043     }
1044   else
1045     return TRUE;
1046 }
1047
1048 static Buffer *
1049 create_buffer (void)
1050 {
1051   Buffer *buffer;
1052
1053   buffer = g_new (Buffer, 1);
1054
1055   buffer->buffer = gtk_text_buffer_new (NULL);
1056   gtk_object_ref (GTK_OBJECT (buffer->buffer));
1057   gtk_object_sink (GTK_OBJECT (buffer->buffer));
1058   
1059   buffer->refcount = 1;
1060   buffer->filename = NULL;
1061   buffer->untitled_serial = -1;
1062
1063   buffers = g_slist_prepend (buffers, buffer);
1064   
1065   return buffer;
1066 }
1067
1068 static char *
1069 buffer_pretty_name (Buffer *buffer)
1070 {
1071   if (buffer->filename)
1072     {
1073       char *p;
1074       char *result = g_strdup (g_basename (buffer->filename));
1075       p = strchr (result, '/');
1076       if (p)
1077         *p = '\0';
1078
1079       return result;
1080     }
1081   else
1082     {
1083       if (buffer->untitled_serial == -1)
1084         buffer->untitled_serial = untitled_serial++;
1085
1086       if (buffer->untitled_serial == 1)
1087         return g_strdup ("Untitled");
1088       else
1089         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1090     }
1091 }
1092
1093 static void
1094 buffer_filename_set (Buffer *buffer)
1095 {
1096   GSList *tmp_list = views;
1097
1098   while (tmp_list)
1099     {
1100       View *view = tmp_list->data;
1101
1102       if (view->buffer == buffer)
1103         view_set_title (view);
1104
1105       tmp_list = tmp_list->next;
1106     }
1107 }
1108
1109 static void
1110 buffer_ref (Buffer *buffer)
1111 {
1112   buffer->refcount++;
1113 }
1114
1115 static void
1116 buffer_unref (Buffer *buffer)
1117 {
1118   buffer->refcount--;
1119   if (buffer->refcount == 0)
1120     {
1121       buffers = g_slist_remove (buffers, buffer);
1122       gtk_object_unref (GTK_OBJECT (buffer->buffer));
1123       g_free (buffer->filename);
1124       g_free (buffer);
1125     }
1126 }
1127
1128 static void
1129 close_view (View *view)
1130 {
1131   views = g_slist_remove (views, view);
1132   buffer_unref (view->buffer);
1133   gtk_widget_destroy (view->window);
1134   g_object_unref (G_OBJECT (view->item_factory));
1135   
1136   g_free (view);
1137   
1138   if (!views)
1139     gtk_main_quit();
1140 }
1141
1142 static void
1143 check_close_view (View *view)
1144 {
1145   if (view->buffer->refcount > 1 ||
1146       check_buffer_saved (view->buffer))
1147     close_view (view);
1148 }
1149
1150 static void
1151 view_set_title (View *view)
1152 {
1153   char *pretty_name = buffer_pretty_name (view->buffer);
1154   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
1155
1156   gtk_window_set_title (GTK_WINDOW (view->window), title);
1157
1158   g_free (pretty_name);
1159   g_free (title);
1160 }
1161
1162 static View *
1163 create_view (Buffer *buffer)
1164 {
1165   View *view;
1166   
1167   GtkWidget *sw;
1168   GtkWidget *vbox;
1169
1170   view = g_new0 (View, 1);
1171   views = g_slist_prepend (views, view);
1172
1173   view->buffer = buffer;
1174   buffer_ref (buffer);
1175   
1176   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1177   gtk_object_set_data (GTK_OBJECT (view->window), "view", view);
1178   
1179   gtk_signal_connect (GTK_OBJECT (view->window), "delete_event",
1180                       GTK_SIGNAL_FUNC(delete_event_cb), NULL);
1181
1182   view->accel_group = gtk_accel_group_new ();
1183   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
1184   gtk_object_set_data (GTK_OBJECT (view->item_factory), "view", view);
1185   
1186   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
1187
1188   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
1189
1190   vbox = gtk_vbox_new (FALSE, 0);
1191   gtk_container_add (GTK_CONTAINER (view->window), vbox);
1192
1193   gtk_box_pack_start (GTK_BOX (vbox),
1194                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
1195                       FALSE, FALSE, 0);
1196   
1197   sw = gtk_scrolled_window_new(NULL, NULL);
1198   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1199                                  GTK_POLICY_AUTOMATIC,
1200                                  GTK_POLICY_AUTOMATIC);
1201
1202   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
1203   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), GTK_WRAPMODE_WORD);
1204
1205   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
1206   gtk_container_add(GTK_CONTAINER(sw), view->text_view);
1207
1208   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
1209
1210   gtk_widget_grab_focus(view->text_view);
1211
1212   view_set_title (view);
1213   view_init_menus (view);
1214   
1215   gtk_widget_show_all (view->window);
1216   return view;
1217 }
1218
1219 int
1220 main(int argc, char** argv)
1221 {
1222   Buffer *buffer;
1223   View *view;
1224   int i;
1225   
1226   gtk_init(&argc, &argv);
1227
1228   buffer = create_buffer ();
1229   view = create_view (buffer);
1230   buffer_unref (buffer);
1231   
1232   push_active_window (GTK_WINDOW (view->window));
1233   for (i=1; i < argc; i++)
1234     {
1235       char *filename;
1236
1237       /* Quick and dirty canonicalization - better should be in GLib
1238        */
1239
1240       if (!g_path_is_absolute (argv[i]))
1241         {
1242           char *cwd = g_get_current_dir ();
1243           filename = g_strconcat (cwd, "/", argv[i], NULL);
1244           g_free (cwd);
1245         }
1246       else
1247         filename = argv[i];
1248
1249       open_ok_func (filename, view);
1250
1251       if (filename != argv[i])
1252         g_free (filename);
1253     }
1254   pop_active_window ();
1255   
1256   gtk_main();
1257
1258   return 0;
1259 }
1260
1261