]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Fix many sparse warnings.
[~andy/gtk] / tests / testtext.c
1 #include <config.h>
2 #include <stdio.h>
3 #include <sys/stat.h>
4 #include <errno.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 #undef GTK_DISABLE_DEPRECATED
9
10 #include <gtk/gtk.h>
11 #include <gdk/gdkkeysyms.h>
12
13 #include "prop-editor.h"
14
15 typedef struct _Buffer Buffer;
16 typedef struct _View View;
17
18 static gint untitled_serial = 1;
19
20 GSList *active_window_stack = NULL;
21
22 struct _Buffer
23 {
24   gint refcount;
25   GtkTextBuffer *buffer;
26   char *filename;
27   gint untitled_serial;
28   GtkTextTag *invisible_tag;
29   GtkTextTag *not_editable_tag;
30   GtkTextTag *found_text_tag;
31   GtkTextTag *custom_tabs_tag;
32   GSList *color_tags;
33   guint color_cycle_timeout;
34   gdouble start_hue;
35 };
36
37 struct _View
38 {
39   GtkWidget *window;
40   GtkWidget *text_view;
41   GtkAccelGroup *accel_group;
42   GtkItemFactory *item_factory;
43   Buffer *buffer;
44 };
45
46 static void push_active_window (GtkWindow *window);
47 static void pop_active_window (void);
48 static GtkWindow *get_active_window (void);
49
50 static Buffer * create_buffer      (void);
51 static gboolean check_buffer_saved (Buffer *buffer);
52 static gboolean save_buffer        (Buffer *buffer);
53 static gboolean save_as_buffer     (Buffer *buffer);
54 static char *   buffer_pretty_name (Buffer *buffer);
55 static void     buffer_filename_set (Buffer *buffer);
56 static void     buffer_search_forward (Buffer *buffer,
57                                        const char *str,
58                                        View *view);
59 static void     buffer_search_backward (Buffer *buffer,
60                                        const char *str,
61                                        View *view);
62 static void     buffer_set_colors      (Buffer  *buffer,
63                                         gboolean enabled);
64 static void     buffer_cycle_colors    (Buffer  *buffer);
65
66 static View *view_from_widget (GtkWidget *widget);
67
68 static View *create_view      (Buffer *buffer);
69 static void  check_close_view (View   *view);
70 static void  close_view       (View   *view);
71 static void  view_set_title   (View   *view);
72 static void  view_init_menus  (View   *view);
73 static void  view_add_example_widgets (View *view);
74
75 GSList *buffers = NULL;
76 GSList *views = NULL;
77
78 static void
79 push_active_window (GtkWindow *window)
80 {
81   g_object_ref (window);
82   active_window_stack = g_slist_prepend (active_window_stack, window);
83 }
84
85 static void
86 pop_active_window (void)
87 {
88   g_object_unref (active_window_stack->data);
89   active_window_stack = g_slist_delete_link (active_window_stack, active_window_stack);
90 }
91
92 static GtkWindow *
93 get_active_window (void)
94 {
95   if (active_window_stack)
96     return active_window_stack->data;
97   else
98     return NULL;
99 }
100
101 /*
102  * Filesel utility function
103  */
104
105 typedef gboolean (*FileselOKFunc) (const char *filename, gpointer data);
106
107 static void
108 filesel_ok_cb (GtkWidget *button, GtkWidget *filesel)
109 {
110   FileselOKFunc ok_func = (FileselOKFunc)g_object_get_data (G_OBJECT (filesel), "ok-func");
111   gpointer data = g_object_get_data (G_OBJECT (filesel), "ok-data");
112   gint *result = g_object_get_data (G_OBJECT (filesel), "ok-result");
113   
114   gtk_widget_hide (filesel);
115   
116   if ((*ok_func) (gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), data))
117     {
118       gtk_widget_destroy (filesel);
119       *result = TRUE;
120     }
121   else
122     gtk_widget_show (filesel);
123 }
124
125 gboolean
126 filesel_run (GtkWindow    *parent, 
127              const char   *title,
128              const char   *start_file,
129              FileselOKFunc func,
130              gpointer      data)
131 {
132   GtkWidget *filesel = gtk_file_selection_new (title);
133   gboolean result = FALSE;
134
135   if (!parent)
136     parent = get_active_window ();
137   
138   if (parent)
139     gtk_window_set_transient_for (GTK_WINDOW (filesel), parent);
140
141   if (start_file)
142     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), start_file);
143
144   
145   g_object_set_data (G_OBJECT (filesel), "ok-func", func);
146   g_object_set_data (G_OBJECT (filesel), "ok-data", data);
147   g_object_set_data (G_OBJECT (filesel), "ok-result", &result);
148
149   g_signal_connect (GTK_FILE_SELECTION (filesel)->ok_button,
150                     "clicked",
151                     G_CALLBACK (filesel_ok_cb), filesel);
152   g_signal_connect_swapped (GTK_FILE_SELECTION (filesel)->cancel_button,
153                             "clicked",
154                             G_CALLBACK (gtk_widget_destroy), filesel);
155
156   g_signal_connect (filesel, "destroy",
157                     G_CALLBACK (gtk_main_quit), NULL);
158   gtk_window_set_modal (GTK_WINDOW (filesel), TRUE);
159
160   gtk_widget_show (filesel);
161   gtk_main ();
162
163   return result;
164 }
165
166 /*
167  * MsgBox utility functions
168  */
169
170 static void
171 msgbox_yes_cb (GtkWidget *widget, gboolean *result)
172 {
173   *result = 0;
174   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
175 }
176
177 static void
178 msgbox_no_cb (GtkWidget *widget, gboolean *result)
179 {
180   *result = 1;
181   gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget)));
182 }
183
184 static gboolean
185 msgbox_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
186 {
187   if (event->keyval == GDK_Escape)
188     {
189       g_signal_stop_emission_by_name (widget, "key_press_event");
190       gtk_object_destroy (GTK_OBJECT (widget));
191       return TRUE;
192     }
193
194   return FALSE;
195 }
196
197 /* Don't copy this example, it's all crack-smoking - you can just use
198  * GtkMessageDialog now
199  */
200 gint
201 msgbox_run (GtkWindow  *parent,
202             const char *message,
203             const char *yes_button,
204             const char *no_button,
205             const char *cancel_button,
206             gint default_index)
207 {
208   gboolean result = -1;
209   GtkWidget *dialog;
210   GtkWidget *button;
211   GtkWidget *label;
212   GtkWidget *vbox;
213   GtkWidget *button_box;
214   GtkWidget *separator;
215
216   g_return_val_if_fail (message != NULL, FALSE);
217   g_return_val_if_fail (default_index >= 0 && default_index <= 1, FALSE);
218
219   if (!parent)
220     parent = get_active_window ();
221   
222   /* Create a dialog
223    */
224   dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
225   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
226   if (parent)
227     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
228   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
229
230   /* Quit our recursive main loop when the dialog is destroyed.
231    */
232   g_signal_connect (dialog, "destroy",
233                     G_CALLBACK (gtk_main_quit), NULL);
234
235   /* Catch Escape key presses and have them destroy the dialog
236    */
237   g_signal_connect (dialog, "key_press_event",
238                     G_CALLBACK (msgbox_key_press_cb), NULL);
239
240   /* Fill in the contents of the widget
241    */
242   vbox = gtk_vbox_new (FALSE, 0);
243   gtk_container_add (GTK_CONTAINER (dialog), vbox);
244   
245   label = gtk_label_new (message);
246   gtk_misc_set_padding (GTK_MISC (label), 12, 12);
247   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
248   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
249
250   separator = gtk_hseparator_new ();
251   gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
252
253   button_box = gtk_hbutton_box_new ();
254   gtk_box_pack_start (GTK_BOX (vbox), button_box, FALSE, FALSE, 0);
255   gtk_container_set_border_width (GTK_CONTAINER (button_box), 8);
256   
257
258   /* When Yes is clicked, call the msgbox_yes_cb
259    * This sets the result variable and destroys the dialog
260    */
261   if (yes_button)
262     {
263       button = gtk_button_new_with_label (yes_button);
264       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
265       gtk_container_add (GTK_CONTAINER (button_box), button);
266
267       if (default_index == 0)
268         gtk_widget_grab_default (button);
269       
270       g_signal_connect (button, "clicked",
271                         G_CALLBACK (msgbox_yes_cb), &result);
272     }
273
274   /* When No is clicked, call the msgbox_no_cb
275    * This sets the result variable and destroys the dialog
276    */
277   if (no_button)
278     {
279       button = gtk_button_new_with_label (no_button);
280       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
281       gtk_container_add (GTK_CONTAINER (button_box), button);
282
283       if (default_index == 0)
284         gtk_widget_grab_default (button);
285       
286       g_signal_connect (button, "clicked",
287                         G_CALLBACK (msgbox_no_cb), &result);
288     }
289
290   /* When Cancel is clicked, destroy the dialog
291    */
292   if (cancel_button)
293     {
294       button = gtk_button_new_with_label (cancel_button);
295       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
296       gtk_container_add (GTK_CONTAINER (button_box), button);
297       
298       if (default_index == 1)
299         gtk_widget_grab_default (button);
300       
301       g_signal_connect_swapped (button, "clicked",
302                                 G_CALLBACK (gtk_object_destroy), dialog);
303     }
304
305   gtk_widget_show_all (dialog);
306
307   /* Run a recursive main loop until a button is clicked
308    * or the user destroys the dialog through the window mananger */
309   gtk_main ();
310
311   return result;
312 }
313
314 #ifdef DO_BLINK
315 /*
316  * Example buffer filling code
317  */
318 static gint
319 blink_timeout (gpointer data)
320 {
321   GtkTextTag *tag;
322   static gboolean flip = FALSE;
323   
324   tag = GTK_TEXT_TAG (data);
325
326   g_object_set (tag,
327                  "foreground", flip ? "blue" : "purple",
328                  NULL);
329
330   flip = !flip;
331
332   return TRUE;
333 }
334 #endif
335
336 static gint
337 tag_event_handler (GtkTextTag *tag, GtkWidget *widget, GdkEvent *event,
338                   const GtkTextIter *iter, gpointer user_data)
339 {
340   gint char_index;
341
342   char_index = gtk_text_iter_get_offset (iter);
343   
344   switch (event->type)
345     {
346     case GDK_MOTION_NOTIFY:
347       printf ("Motion event at char %d tag `%s'\n",
348              char_index, tag->name);
349       break;
350         
351     case GDK_BUTTON_PRESS:
352       printf ("Button press at char %d tag `%s'\n",
353              char_index, tag->name);
354       break;
355         
356     case GDK_2BUTTON_PRESS:
357       printf ("Double click at char %d tag `%s'\n",
358              char_index, tag->name);
359       break;
360         
361     case GDK_3BUTTON_PRESS:
362       printf ("Triple click at char %d tag `%s'\n",
363              char_index, tag->name);
364       break;
365         
366     case GDK_BUTTON_RELEASE:
367       printf ("Button release at char %d tag `%s'\n",
368              char_index, tag->name);
369       break;
370         
371     case GDK_KEY_PRESS:
372     case GDK_KEY_RELEASE:
373       printf ("Key event at char %d tag `%s'\n",
374               char_index, tag->name);
375       break;
376       
377     case GDK_ENTER_NOTIFY:
378     case GDK_LEAVE_NOTIFY:
379     case GDK_PROPERTY_NOTIFY:
380     case GDK_SELECTION_CLEAR:
381     case GDK_SELECTION_REQUEST:
382     case GDK_SELECTION_NOTIFY:
383     case GDK_PROXIMITY_IN:
384     case GDK_PROXIMITY_OUT:
385     case GDK_DRAG_ENTER:
386     case GDK_DRAG_LEAVE:
387     case GDK_DRAG_MOTION:
388     case GDK_DRAG_STATUS:
389     case GDK_DROP_START:
390     case GDK_DROP_FINISHED:
391     default:
392       break;
393     }
394
395   return FALSE;
396 }
397
398 static void
399 setup_tag (GtkTextTag *tag)
400 {
401   g_signal_connect (tag,
402                     "event",
403                     G_CALLBACK (tag_event_handler),
404                     NULL);
405 }
406
407 static const char  *book_closed_xpm[] = {
408 "16 16 6 1",
409 "       c None s None",
410 ".      c black",
411 "X      c red",
412 "o      c yellow",
413 "O      c #808080",
414 "#      c white",
415 "                ",
416 "       ..       ",
417 "     ..XX.      ",
418 "   ..XXXXX.     ",
419 " ..XXXXXXXX.    ",
420 ".ooXXXXXXXXX.   ",
421 "..ooXXXXXXXXX.  ",
422 ".X.ooXXXXXXXXX. ",
423 ".XX.ooXXXXXX..  ",
424 " .XX.ooXXX..#O  ",
425 "  .XX.oo..##OO. ",
426 "   .XX..##OO..  ",
427 "    .X.#OO..    ",
428 "     ..O..      ",
429 "      ..        ",
430 "                "};
431
432 void
433 fill_example_buffer (GtkTextBuffer *buffer)
434 {
435   GtkTextIter iter, iter2;
436   GtkTextTag *tag;
437   GtkTextChildAnchor *anchor;
438   GdkColor color;
439   GdkColor color2;
440   GdkPixbuf *pixbuf;
441   int i;
442   char *str;
443   
444   /* FIXME this is broken if called twice on a buffer, since
445    * we try to create tags a second time.
446    */
447   
448   tag = gtk_text_buffer_create_tag (buffer, "fg_blue", NULL);
449
450 #ifdef DO_BLINK
451   gtk_timeout_add (1000, blink_timeout, tag);
452 #endif     
453  
454   setup_tag (tag);
455   
456   color.red = color.green = 0;
457   color.blue = 0xffff;
458   color2.red = 0xfff;
459   color2.blue = 0x0;
460   color2.green = 0;
461   g_object_set (tag,
462                 "foreground_gdk", &color,
463                 "background_gdk", &color2,
464                 "size_points", 24.0,
465                 NULL);
466
467   tag = gtk_text_buffer_create_tag (buffer, "fg_red", NULL);
468
469   setup_tag (tag);
470       
471   color.blue = color.green = 0;
472   color.red = 0xffff;
473   g_object_set (tag,
474                 "rise", -4 * PANGO_SCALE,
475                 "foreground_gdk", &color,
476                 NULL);
477
478   tag = gtk_text_buffer_create_tag (buffer, "bg_green", NULL);
479
480   setup_tag (tag);
481       
482   color.blue = color.red = 0;
483   color.green = 0xffff;
484   g_object_set (tag,
485                 "background_gdk", &color,
486                 "size_points", 10.0,
487                 NULL);
488
489   tag = gtk_text_buffer_create_tag (buffer, "strikethrough", NULL);
490
491   setup_tag (tag);
492       
493   g_object_set (tag,
494                 "strikethrough", TRUE,
495                 NULL);
496
497
498   tag = gtk_text_buffer_create_tag (buffer, "underline", NULL);
499
500   setup_tag (tag);
501       
502   g_object_set (tag,
503                 "underline", PANGO_UNDERLINE_SINGLE,
504                 NULL);
505
506   tag = gtk_text_buffer_create_tag (buffer, "underline_error", NULL);
507
508   setup_tag (tag);
509       
510   g_object_set (tag,
511                 "underline", PANGO_UNDERLINE_ERROR,
512                 NULL);
513
514   tag = gtk_text_buffer_create_tag (buffer, "centered", NULL);
515       
516   g_object_set (tag,
517                 "justification", GTK_JUSTIFY_CENTER,
518                 NULL);
519
520   tag = gtk_text_buffer_create_tag (buffer, "rtl_quote", NULL);
521       
522   g_object_set (tag,
523                 "wrap_mode", GTK_WRAP_WORD,
524                 "direction", GTK_TEXT_DIR_RTL,
525                 "indent", 30,
526                 "left_margin", 20,
527                 "right_margin", 20,
528                 NULL);
529
530
531   tag = gtk_text_buffer_create_tag (buffer, "negative_indent", NULL);
532       
533   g_object_set (tag,
534                 "indent", -25,
535                 NULL);
536   
537   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
538
539   anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
540
541   g_object_ref (anchor);
542   
543   g_object_set_data_full (G_OBJECT (buffer), "anchor", anchor,
544                           (GDestroyNotify) g_object_unref);
545   
546   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
547   
548   i = 0;
549   while (i < 100)
550     {
551       GtkTextMark * temp_mark;
552       
553       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
554           
555       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
556           
557       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",
558                             i);
559       
560       gtk_text_buffer_insert (buffer, &iter, str, -1);
561
562       g_free (str);
563       
564       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 5);
565           
566       gtk_text_buffer_insert (buffer, &iter,
567                              "(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"
568                              /* This is UTF8 stuff, Emacs doesn't
569                                 really know how to display it */
570                              "German (Deutsch S\303\274d) Gr\303\274\303\237 Gott Greek (\316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254) \316\223\316\265\316\271\316\254 \317\203\316\261\317\202 Hebrew(\327\251\327\234\327\225\327\235) Hebrew punctuation(\xd6\xbf\327\251\xd6\xbb\xd6\xbc\xd6\xbb\xd6\xbf\327\234\xd6\xbc\327\225\xd6\xbc\xd6\xbb\xd6\xbb\xd6\xbf\327\235\xd6\xbc\xd6\xbb\xd6\xbf) Japanese (\346\227\245\346\234\254\350\252\236) Thai (\340\270\252\340\270\247\340\270\261\340\270\252\340\270\224\340\270\265\340\270\204\340\270\243\340\270\261\340\270\232) Thai wrong spelling (\340\270\204\340\270\263\340\270\225\340\271\210\340\270\255\340\271\204\340\270\233\340\270\231\340\270\267\340\271\210\340\270\252\340\270\260\340\270\201\340\270\224\340\270\234\340\270\264\340\270\224 \340\270\236\340\270\261\340\270\261\340\271\211\340\270\261\340\270\261\340\271\210\340\270\207\340\271\202\340\270\201\340\270\260)\n", -1);
571
572       temp_mark =
573         gtk_text_buffer_create_mark (buffer, "tmp_mark", &iter, TRUE);
574
575 #if 1
576       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 6);
577       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 13);
578
579       gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
580
581       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 10);
582       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 16);
583
584       gtk_text_buffer_apply_tag_by_name (buffer, "underline", &iter, &iter2);
585
586       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 4);
587       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 7);
588
589       gtk_text_buffer_apply_tag_by_name (buffer, "underline_error", &iter, &iter2);
590
591       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 14);
592       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 1, 24);
593
594       gtk_text_buffer_apply_tag_by_name (buffer, "strikethrough", &iter, &iter2);
595           
596       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 9);
597       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 0, 16);
598
599       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
600   
601       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 2);
602       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 10);
603
604       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
605
606       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 4, 8);
607       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter2, 4, 15);
608
609       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
610 #endif
611
612       gtk_text_buffer_get_iter_at_mark (buffer, &iter, temp_mark);
613       gtk_text_buffer_insert (buffer, &iter, "Centered text!\n", -1);
614           
615       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
616       gtk_text_buffer_apply_tag_by_name (buffer, "centered", &iter2, &iter);
617
618       gtk_text_buffer_move_mark (buffer, temp_mark, &iter);
619       gtk_text_buffer_insert (buffer, &iter, "Word wrapped, Right-to-left Quote\n", -1);
620       gtk_text_buffer_insert (buffer, &iter, "\331\210\331\202\330\257 \330\250\330\257\330\243 \330\253\331\204\330\247\330\253 \331\205\331\206 \330\243\331\203\330\253\330\261 \330\247\331\204\331\205\330\244\330\263\330\263\330\247\330\252 \330\252\331\202\330\257\331\205\330\247 \331\201\331\212 \330\264\330\250\331\203\330\251 \330\247\331\203\330\263\331\212\331\210\331\206 \330\250\330\261\330\247\331\205\330\254\331\207\330\247 \331\203\331\205\331\206\330\270\331\205\330\247\330\252 \331\204\330\247 \330\252\330\263\330\271\331\211 \331\204\331\204\330\261\330\250\330\255\330\214 \330\253\331\205 \330\252\330\255\331\210\331\204\330\252 \331\201\331\212 \330\247\331\204\330\263\331\206\331\210\330\247\330\252 \330\247\331\204\330\256\331\205\330\263 \330\247\331\204\331\205\330\247\330\266\331\212\330\251 \330\245\331\204\331\211 \331\205\330\244\330\263\330\263\330\247\330\252 \331\205\330\247\331\204\331\212\330\251 \331\205\331\206\330\270\331\205\330\251\330\214 \331\210\330\250\330\247\330\252\330\252 \330\254\330\262\330\241\330\247 \331\205\331\206 \330\247\331\204\331\206\330\270\330\247\331\205 \330\247\331\204\331\205\330\247\331\204\331\212 \331\201\331\212 \330\250\331\204\330\257\330\247\331\206\331\207\330\247\330\214 \331\210\331\204\331\203\331\206\331\207\330\247 \330\252\330\252\330\256\330\265\330\265 \331\201\331\212 \330\256\330\257\331\205\330\251 \331\202\330\267\330\247\330\271 \330\247\331\204\331\205\330\264\330\261\331\210\330\271\330\247\330\252 \330\247\331\204\330\265\330\272\331\212\330\261\330\251. \331\210\330\243\330\255\330\257 \330\243\331\203\330\253\330\261 \331\207\330\260\331\207 \330\247\331\204\331\205\330\244\330\263\330\263\330\247\330\252 \331\206\330\254\330\247\330\255\330\247 \331\207\331\210 \302\273\330\250\330\247\331\206\331\203\331\210\330\263\331\210\331\204\302\253 \331\201\331\212 \330\250\331\210\331\204\331\212\331\201\331\212\330\247.\n", -1);
621       gtk_text_buffer_get_iter_at_mark (buffer, &iter2, temp_mark);
622       gtk_text_buffer_apply_tag_by_name (buffer, "rtl_quote", &iter2, &iter);
623
624       gtk_text_buffer_insert_with_tags (buffer, &iter,
625                                         "Paragraph with negative indentation. blah blah blah blah blah. The quick brown fox jumped over the lazy dog.\n",
626                                         -1,
627                                         gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
628                                                                    "negative_indent"),
629                                         NULL);
630       
631       ++i;
632     }
633
634   g_object_unref (pixbuf);
635   
636   printf ("%d lines %d chars\n",
637           gtk_text_buffer_get_line_count (buffer),
638           gtk_text_buffer_get_char_count (buffer));
639
640   /* Move cursor to start */
641   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
642   gtk_text_buffer_place_cursor (buffer, &iter);
643   
644   gtk_text_buffer_set_modified (buffer, FALSE);
645 }
646
647 gboolean
648 fill_file_buffer (GtkTextBuffer *buffer, const char *filename)
649 {
650   FILE* f;
651   gchar buf[2048];
652   gint remaining = 0;
653   GtkTextIter iter, end;
654
655   f = fopen (filename, "r");
656   
657   if (f == NULL)
658     {
659       gchar *err = g_strdup_printf ("Cannot open file '%s': %s",
660                                     filename, g_strerror (errno));
661       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
662       g_free (err);
663       return FALSE;
664     }
665   
666   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
667   while (!feof (f))
668     {
669       gint count;
670       const char *leftover;
671       int to_read = 2047  - remaining;
672
673       count = fread (buf + remaining, 1, to_read, f);
674       buf[count + remaining] = '\0';
675
676       g_utf8_validate (buf, count + remaining, &leftover);
677       
678       g_assert (g_utf8_validate (buf, leftover - buf, NULL));
679       gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);
680
681       remaining = (buf + remaining + count) - leftover;
682       g_memmove (buf, leftover, remaining);
683
684       if (remaining > 6 || count < to_read)
685           break;
686     }
687
688   if (remaining)
689     {
690       gchar *err = g_strdup_printf ("Invalid UTF-8 data encountered reading file '%s'", filename);
691       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
692       g_free (err);
693     }
694   
695   /* We had a newline in the buffer to begin with. (The buffer always contains
696    * a newline, so we delete to the end of the buffer to clean up.
697    */
698   gtk_text_buffer_get_end_iter (buffer, &end);
699   gtk_text_buffer_delete (buffer, &iter, &end);
700   
701   gtk_text_buffer_set_modified (buffer, FALSE);
702
703   return TRUE;
704 }
705
706 static gint
707 delete_event_cb (GtkWidget *window, GdkEventAny *event, gpointer data)
708 {
709   View *view = view_from_widget (window);
710
711   push_active_window (GTK_WINDOW (window));
712   check_close_view (view);
713   pop_active_window ();
714
715   return TRUE;
716 }
717
718 /*
719  * Menu callbacks
720  */
721
722 static View *
723 get_empty_view (View *view)
724 {
725   if (!view->buffer->filename &&
726       !gtk_text_buffer_get_modified (view->buffer->buffer))
727     return view;
728   else
729     return create_view (create_buffer ());
730 }
731
732 static View *
733 view_from_widget (GtkWidget *widget)
734 {
735   if (GTK_IS_MENU_ITEM (widget))
736     {
737       GtkItemFactory *item_factory = gtk_item_factory_from_widget (widget);
738       return g_object_get_data (G_OBJECT (item_factory), "view");      
739     }
740   else
741     {
742       GtkWidget *app = gtk_widget_get_toplevel (widget);
743       return g_object_get_data (G_OBJECT (app), "view");
744     }
745 }
746
747 static void
748 do_new (gpointer             callback_data,
749         guint                callback_action,
750         GtkWidget           *widget)
751 {
752   create_view (create_buffer ());
753 }
754
755 static void
756 do_new_view (gpointer             callback_data,
757              guint                callback_action,
758              GtkWidget           *widget)
759 {
760   View *view = view_from_widget (widget);
761   
762   create_view (view->buffer);
763 }
764
765 gboolean
766 open_ok_func (const char *filename, gpointer data)
767 {
768   View *view = data;
769   View *new_view = get_empty_view (view);
770
771   if (!fill_file_buffer (new_view->buffer->buffer, filename))
772     {
773       if (new_view != view)
774         close_view (new_view);
775       return FALSE;
776     }
777   else
778     {
779       g_free (new_view->buffer->filename);
780       new_view->buffer->filename = g_strdup (filename);
781       buffer_filename_set (new_view->buffer);
782       
783       return TRUE;
784     }
785 }
786
787 static void
788 do_open (gpointer             callback_data,
789          guint                callback_action,
790          GtkWidget           *widget)
791 {
792   View *view = view_from_widget (widget);
793
794   push_active_window (GTK_WINDOW (view->window));
795   filesel_run (NULL, "Open File", NULL, open_ok_func, view);
796   pop_active_window ();
797 }
798
799 static void
800 do_save_as (gpointer             callback_data,
801             guint                callback_action,
802             GtkWidget           *widget)
803 {
804   View *view = view_from_widget (widget);  
805
806   push_active_window (GTK_WINDOW (view->window));
807   save_as_buffer (view->buffer);
808   pop_active_window ();
809 }
810
811 static void
812 do_save (gpointer             callback_data,
813          guint                callback_action,
814          GtkWidget           *widget)
815 {
816   View *view = view_from_widget (widget);
817
818   push_active_window (GTK_WINDOW (view->window));
819   if (!view->buffer->filename)
820     do_save_as (callback_data, callback_action, widget);
821   else
822     save_buffer (view->buffer);
823   pop_active_window ();
824 }
825
826 static void
827 do_close   (gpointer             callback_data,
828             guint                callback_action,
829             GtkWidget           *widget)
830 {
831   View *view = view_from_widget (widget);
832
833   push_active_window (GTK_WINDOW (view->window));
834   check_close_view (view);
835   pop_active_window ();
836 }
837
838 static void
839 do_exit    (gpointer             callback_data,
840             guint                callback_action,
841             GtkWidget           *widget)
842 {
843   View *view = view_from_widget (widget);
844
845   GSList *tmp_list = buffers;
846
847   push_active_window (GTK_WINDOW (view->window));
848   while (tmp_list)
849     {
850       if (!check_buffer_saved (tmp_list->data))
851         return;
852
853       tmp_list = tmp_list->next;
854     }
855
856   gtk_main_quit ();
857   pop_active_window ();
858 }
859
860 static void
861 do_example (gpointer             callback_data,
862             guint                callback_action,
863             GtkWidget           *widget)
864 {
865   View *view = view_from_widget (widget);
866   View *new_view;
867
868   new_view = get_empty_view (view);
869   
870   fill_example_buffer (new_view->buffer->buffer);
871
872   view_add_example_widgets (new_view);
873 }
874
875
876 static void
877 do_insert_and_scroll (gpointer             callback_data,
878                       guint                callback_action,
879                       GtkWidget           *widget)
880 {
881   View *view = view_from_widget (widget);
882   GtkTextBuffer *buffer;
883   GtkTextIter start, end;
884   GtkTextMark *mark;
885   
886   buffer = view->buffer->buffer;
887
888   gtk_text_buffer_get_bounds (buffer, &start, &end);
889   mark = gtk_text_buffer_create_mark (buffer, NULL, &end, /* right grav */ FALSE);
890
891   gtk_text_buffer_insert (buffer, &end,
892                           "Hello this is multiple lines of text\n"
893                           "Line 1\n"  "Line 2\n"
894                           "Line 3\n"  "Line 4\n"
895                           "Line 5\n",
896                           -1);
897
898   gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view->text_view), mark,
899                                 0, TRUE, 0.0, 1.0);
900   gtk_text_buffer_delete_mark (buffer, mark);
901 }
902
903 static void
904 do_wrap_changed (gpointer             callback_data,
905                  guint                callback_action,
906                  GtkWidget           *widget)
907 {
908   View *view = view_from_widget (widget);
909
910   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view), callback_action);
911 }
912
913 static void
914 do_direction_changed (gpointer             callback_data,
915                       guint                callback_action,
916                       GtkWidget           *widget)
917 {
918   View *view = view_from_widget (widget);
919   
920   gtk_widget_set_direction (view->text_view, callback_action);
921   gtk_widget_queue_resize (view->text_view);
922 }
923
924
925 static void
926 do_spacing_changed (gpointer             callback_data,
927                     guint                callback_action,
928                     GtkWidget           *widget)
929 {
930   View *view = view_from_widget (widget);
931
932   if (callback_action)
933     {
934       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
935                                             23);
936       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
937                                             21);
938       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
939                                             9);
940     }
941   else
942     {
943       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view->text_view),
944                                             0);
945       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (view->text_view),
946                                             0);
947       gtk_text_view_set_pixels_inside_wrap (GTK_TEXT_VIEW (view->text_view),
948                                             0);
949     }
950 }
951
952 static void
953 do_editable_changed (gpointer callback_data,
954                      guint callback_action,
955                      GtkWidget *widget)
956 {
957   View *view = view_from_widget (widget);
958
959   gtk_text_view_set_editable (GTK_TEXT_VIEW (view->text_view), callback_action);
960 }
961
962 static void
963 do_cursor_visible_changed (gpointer callback_data,
964                            guint callback_action,
965                            GtkWidget *widget)
966 {
967   View *view = view_from_widget (widget);
968
969   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view->text_view), callback_action);
970 }
971
972 static void
973 do_color_cycle_changed (gpointer callback_data,
974                         guint callback_action,
975                         GtkWidget *widget)
976 {
977   View *view = view_from_widget (widget);
978
979   buffer_set_colors (view->buffer, callback_action);
980 }
981
982 static void
983 do_apply_editable (gpointer callback_data,
984                    guint callback_action,
985                    GtkWidget *widget)
986 {
987   View *view = view_from_widget (widget);
988   GtkTextIter start;
989   GtkTextIter end;
990   
991   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
992                                             &start, &end))
993     {
994       if (callback_action)
995         {
996           gtk_text_buffer_remove_tag (view->buffer->buffer,
997                                       view->buffer->not_editable_tag,
998                                       &start, &end);
999         }
1000       else
1001         {
1002           gtk_text_buffer_apply_tag (view->buffer->buffer,
1003                                      view->buffer->not_editable_tag,
1004                                      &start, &end);
1005         }
1006     }
1007 }
1008
1009 static void
1010 do_apply_invisible (gpointer callback_data,
1011                     guint callback_action,
1012                     GtkWidget *widget)
1013 {
1014   View *view = view_from_widget (widget);
1015   GtkTextIter start;
1016   GtkTextIter end;
1017   
1018   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1019                                             &start, &end))
1020     {
1021       if (callback_action)
1022         {
1023           gtk_text_buffer_remove_tag (view->buffer->buffer,
1024                                       view->buffer->invisible_tag,
1025                                       &start, &end);
1026         }
1027       else
1028         {
1029           gtk_text_buffer_apply_tag (view->buffer->buffer,
1030                                      view->buffer->invisible_tag,
1031                                      &start, &end);
1032         }
1033     }
1034 }
1035
1036 static void
1037 do_apply_tabs (gpointer callback_data,
1038                guint callback_action,
1039                GtkWidget *widget)
1040 {
1041   View *view = view_from_widget (widget);
1042   GtkTextIter start;
1043   GtkTextIter end;
1044   
1045   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1046                                             &start, &end))
1047     {
1048       if (callback_action)
1049         {
1050           gtk_text_buffer_remove_tag (view->buffer->buffer,
1051                                       view->buffer->custom_tabs_tag,
1052                                       &start, &end);
1053         }
1054       else
1055         {
1056           gtk_text_buffer_apply_tag (view->buffer->buffer,
1057                                      view->buffer->custom_tabs_tag,
1058                                      &start, &end);
1059         }
1060     }
1061 }
1062
1063 static void
1064 do_apply_colors (gpointer callback_data,
1065                  guint callback_action,
1066                  GtkWidget *widget)
1067 {
1068   View *view = view_from_widget (widget);
1069   Buffer *buffer = view->buffer;
1070   GtkTextIter start;
1071   GtkTextIter end;
1072   
1073   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1074                                             &start, &end))
1075     {
1076       if (!callback_action)
1077         {
1078           GSList *tmp;
1079           
1080           tmp = buffer->color_tags;
1081           while (tmp != NULL)
1082             {
1083               gtk_text_buffer_remove_tag (view->buffer->buffer,
1084                                           tmp->data,
1085                                           &start, &end);              
1086               tmp = g_slist_next (tmp);
1087             }
1088         }
1089       else
1090         {
1091           GSList *tmp;
1092           
1093           tmp = buffer->color_tags;
1094           while (TRUE)
1095             {
1096               GtkTextIter next;
1097               gboolean done = FALSE;
1098               
1099               next = start;
1100               gtk_text_iter_forward_char (&next);
1101               gtk_text_iter_forward_char (&next);
1102
1103               if (gtk_text_iter_compare (&next, &end) >= 0)
1104                 {
1105                   next = end;
1106                   done = TRUE;
1107                 }
1108               
1109               gtk_text_buffer_apply_tag (view->buffer->buffer,
1110                                          tmp->data,
1111                                          &start, &next);
1112
1113               start = next;
1114
1115               if (done)
1116                 return;
1117               
1118               tmp = g_slist_next (tmp);
1119               if (tmp == NULL)
1120                 tmp = buffer->color_tags;
1121             } 
1122         }
1123     }
1124 }
1125
1126 static void
1127 do_remove_tags (gpointer callback_data,
1128                 guint callback_action,
1129                 GtkWidget *widget)
1130 {
1131   View *view = view_from_widget (widget);
1132   GtkTextIter start;
1133   GtkTextIter end;
1134   
1135   if (gtk_text_buffer_get_selection_bounds (view->buffer->buffer,
1136                                             &start, &end))
1137     {
1138       gtk_text_buffer_remove_all_tags (view->buffer->buffer,
1139                                        &start, &end);
1140     }
1141 }
1142
1143 static void
1144 do_properties (gpointer callback_data,
1145                 guint callback_action,
1146                 GtkWidget *widget)
1147 {
1148   View *view = view_from_widget (widget);
1149
1150   create_prop_editor (G_OBJECT (view->text_view), 0);
1151 }
1152
1153 enum
1154 {
1155   RESPONSE_FORWARD,
1156   RESPONSE_BACKWARD
1157 };
1158
1159 static void
1160 dialog_response_callback (GtkWidget *dialog, gint response_id, gpointer data)
1161 {
1162   GtkTextBuffer *buffer;
1163   View *view = data;
1164   GtkTextIter start, end;
1165   gchar *search_string;
1166
1167   if (response_id != RESPONSE_FORWARD &&
1168       response_id != RESPONSE_BACKWARD)
1169     {
1170       gtk_widget_destroy (dialog);
1171       return;
1172     }
1173   
1174   buffer = g_object_get_data (G_OBJECT (dialog), "buffer");
1175
1176   gtk_text_buffer_get_bounds (buffer, &start, &end);
1177   
1178   search_string = gtk_text_iter_get_text (&start, &end);
1179
1180   g_print ("Searching for `%s'\n", search_string);
1181
1182   if (response_id == RESPONSE_FORWARD)
1183     buffer_search_forward (view->buffer, search_string, view);
1184   else if (response_id == RESPONSE_BACKWARD)
1185     buffer_search_backward (view->buffer, search_string, view);
1186     
1187   g_free (search_string);
1188   
1189   gtk_widget_destroy (dialog);
1190 }
1191
1192 static void
1193 do_search (gpointer callback_data,
1194            guint callback_action,
1195            GtkWidget *widget)
1196 {
1197   View *view = view_from_widget (widget);
1198   GtkWidget *dialog;
1199   GtkWidget *search_text;
1200   GtkTextBuffer *buffer;
1201
1202   dialog = gtk_dialog_new_with_buttons ("Search",
1203                                         GTK_WINDOW (view->window),
1204                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1205                                         "Forward", RESPONSE_FORWARD,
1206                                         "Backward", RESPONSE_BACKWARD,
1207                                         GTK_STOCK_CANCEL,
1208                                         GTK_RESPONSE_NONE, NULL);
1209
1210
1211   buffer = gtk_text_buffer_new (NULL);
1212
1213   search_text = gtk_text_view_new_with_buffer (buffer);
1214
1215   g_object_unref (buffer);
1216   
1217   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1218                     search_text,
1219                     TRUE, TRUE, 0);
1220
1221   g_object_set_data (G_OBJECT (dialog), "buffer", buffer);
1222   
1223   g_signal_connect (dialog,
1224                     "response",
1225                     G_CALLBACK (dialog_response_callback),
1226                     view);
1227
1228   gtk_widget_show (search_text);
1229
1230   gtk_widget_grab_focus (search_text);
1231   
1232   gtk_widget_show_all (dialog);
1233 }
1234
1235 typedef struct
1236 {
1237   /* position is in coordinate system of text_view_move_child */
1238   int click_x;
1239   int click_y;
1240   int start_x;
1241   int start_y;
1242   int button;
1243 } ChildMoveInfo;
1244
1245 static gboolean
1246 movable_child_callback (GtkWidget *child,
1247                         GdkEvent  *event,
1248                         gpointer   data)
1249 {
1250   ChildMoveInfo *info;
1251   GtkTextView *text_view;
1252
1253   text_view = GTK_TEXT_VIEW (data);
1254   
1255   g_return_val_if_fail (GTK_IS_EVENT_BOX (child), FALSE);
1256   g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (text_view), FALSE);  
1257   
1258   info = g_object_get_data (G_OBJECT (child),
1259                             "testtext-move-info");
1260
1261   if (info == NULL)
1262     {
1263       info = g_new (ChildMoveInfo, 1);      
1264       info->start_x = -1;
1265       info->start_y = -1;
1266       info->button = -1;
1267       g_object_set_data_full (G_OBJECT (child),
1268                               "testtext-move-info",
1269                               info,
1270                               g_free);
1271     }
1272   
1273   switch (event->type)
1274     {
1275     case GDK_BUTTON_PRESS:
1276       if (info->button < 0)
1277         {
1278           if (gdk_pointer_grab (event->button.window,
1279                                 FALSE,
1280                                 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1281                                 GDK_BUTTON_RELEASE_MASK,
1282                                 NULL,
1283                                 NULL,
1284                                 event->button.time) != GDK_GRAB_SUCCESS)
1285             return FALSE;
1286           
1287           info->button = event->button.button;
1288           
1289           info->start_x = child->allocation.x;
1290           info->start_y = child->allocation.y;
1291           info->click_x = child->allocation.x + event->button.x;
1292           info->click_y = child->allocation.y + event->button.y;
1293         }
1294       break;
1295
1296     case GDK_BUTTON_RELEASE:
1297       if (info->button < 0)
1298         return FALSE;
1299
1300       if (info->button == event->button.button)
1301         {
1302           int x, y;
1303           
1304           gdk_pointer_ungrab (event->button.time);
1305           info->button = -1;
1306
1307           /* convert to window coords from event box coords */
1308           x = info->start_x + (event->button.x + child->allocation.x - info->click_x);
1309           y = info->start_y + (event->button.y + child->allocation.y - info->click_y);
1310
1311           gtk_text_view_move_child (text_view,
1312                                     child,
1313                                     x, y);
1314         }
1315       break;
1316
1317     case GDK_MOTION_NOTIFY:
1318       {
1319         int x, y;
1320         
1321         if (info->button < 0)
1322           return FALSE;
1323         
1324         gdk_window_get_pointer (child->window, &x, &y, NULL); /* ensure more events */
1325
1326         /* to window coords from event box coords */
1327         x += child->allocation.x;
1328         y += child->allocation.y;
1329         
1330         x = info->start_x + (x - info->click_x);
1331         y = info->start_y + (y - info->click_y);
1332         
1333         gtk_text_view_move_child (text_view,
1334                                   child,
1335                                   x, y);
1336       }
1337       break;
1338
1339     default:
1340       break;
1341     }
1342
1343   return FALSE;
1344 }
1345
1346 static void
1347 add_movable_child (GtkTextView      *text_view,
1348                    GtkTextWindowType window)
1349 {
1350   GtkWidget *event_box;
1351   GtkWidget *label;
1352   GdkColor color;
1353   
1354   label = gtk_label_new ("Drag me around");  
1355   
1356   event_box = gtk_event_box_new ();
1357   gtk_widget_add_events (event_box,
1358                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1359                          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
1360
1361   color.red = 0xffff;
1362   color.green = color.blue = 0;
1363   gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color);
1364   
1365   gtk_container_add (GTK_CONTAINER (event_box), label);
1366
1367   gtk_widget_show_all (event_box);
1368
1369   g_signal_connect (event_box, "event",
1370                     G_CALLBACK (movable_child_callback),
1371                     text_view);
1372
1373   gtk_text_view_add_child_in_window (text_view,
1374                                      event_box,
1375                                      window,
1376                                      0, 0);
1377 }
1378
1379 static void
1380 do_add_children (gpointer callback_data,
1381                  guint callback_action,
1382                  GtkWidget *widget)
1383 {
1384   View *view = view_from_widget (widget);
1385
1386   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1387                      GTK_TEXT_WINDOW_WIDGET);
1388   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1389                      GTK_TEXT_WINDOW_LEFT);
1390   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1391                      GTK_TEXT_WINDOW_RIGHT);
1392 }
1393
1394 static void
1395 do_add_focus_children (gpointer callback_data,
1396                        guint callback_action,
1397                        GtkWidget *widget)
1398 {
1399   View *view = view_from_widget (widget);
1400   GtkWidget *child;
1401   GtkTextChildAnchor *anchor;
1402   GtkTextIter iter;
1403   GtkTextView *text_view;
1404
1405   text_view = GTK_TEXT_VIEW (view->text_view);
1406   
1407   child = gtk_button_new_with_mnemonic ("Button _A in widget->window");
1408
1409   gtk_text_view_add_child_in_window (text_view,
1410                                      child,
1411                                      GTK_TEXT_WINDOW_WIDGET,
1412                                      200, 200);
1413
1414   child = gtk_button_new_with_mnemonic ("Button _B in widget->window");
1415
1416   gtk_text_view_add_child_in_window (text_view,
1417                                      child,
1418                                      GTK_TEXT_WINDOW_WIDGET,
1419                                      350, 300);
1420
1421   child = gtk_button_new_with_mnemonic ("Button _C in left window");
1422
1423   gtk_text_view_add_child_in_window (text_view,
1424                                      child,
1425                                      GTK_TEXT_WINDOW_LEFT,
1426                                      0, 0);
1427
1428   child = gtk_button_new_with_mnemonic ("Button _D in right window");
1429   
1430   gtk_text_view_add_child_in_window (text_view,
1431                                      child,
1432                                      GTK_TEXT_WINDOW_RIGHT,
1433                                      0, 0);
1434
1435   gtk_text_buffer_get_start_iter (view->buffer->buffer, &iter);
1436   
1437   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1438
1439   child = gtk_button_new_with_mnemonic ("Button _E in buffer");
1440   
1441   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1442
1443   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1444
1445   child = gtk_button_new_with_mnemonic ("Button _F in buffer");
1446   
1447   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1448
1449   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1450
1451   child = gtk_button_new_with_mnemonic ("Button _G in buffer");
1452   
1453   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1454
1455   /* show all the buttons */
1456   gtk_widget_show_all (view->text_view);
1457 }
1458
1459 static void
1460 view_init_menus (View *view)
1461 {
1462   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
1463   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
1464   GtkWidget *menu_item = NULL;
1465
1466   switch (direction)
1467     {
1468     case GTK_TEXT_DIR_LTR:
1469       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1470       break;
1471     case GTK_TEXT_DIR_RTL:
1472       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1473       break;
1474     default:
1475       break;
1476     }
1477
1478   if (menu_item)
1479     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1480
1481   switch (wrap_mode)
1482     {
1483     case GTK_WRAP_NONE:
1484       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1485       break;
1486     case GTK_WRAP_WORD:
1487       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1488       break;
1489     case GTK_WRAP_CHAR:
1490       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Chars");
1491       break;
1492     default:
1493       break;
1494     }
1495   
1496   if (menu_item)
1497     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1498 }
1499
1500 static GtkItemFactoryEntry menu_items[] =
1501 {
1502   { "/_File",            NULL,         NULL,        0, "<Branch>" },
1503   { "/File/_New",        "<control>N", do_new,      0, NULL },
1504   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1505   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1506   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1507   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1508   { "/File/sep1",        NULL,         NULL,        0, "<Separator>" },
1509   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1510   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1511
1512   { "/_Edit", NULL, 0, 0, "<Branch>" },
1513   { "/Edit/Find...", NULL, do_search, 0, NULL },
1514
1515   { "/_Settings",         NULL,         NULL,             0, "<Branch>" },
1516   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAP_NONE, "<RadioItem>" },
1517   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAP_WORD, "/Settings/Wrap Off" },
1518   { "/Settings/Wrap _Chars", NULL,      do_wrap_changed,  GTK_WRAP_CHAR, "/Settings/Wrap Off" },
1519   { "/Settings/sep1",        NULL,      NULL,             0, "<Separator>" },
1520   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1521   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1522   { "/Settings/sep1",        NULL,      NULL,             0, "<Separator>" },
1523
1524   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1525   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1526   { "/Settings/sep1",        NULL,      NULL,          0, "<Separator>" },
1527   
1528   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1529   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1530
1531   { "/Settings/sep1",        NULL,      NULL,                0, "<Separator>" },
1532   { "/Settings/Sane spacing", NULL,    do_spacing_changed,  FALSE, "<RadioItem>" },
1533   { "/Settings/Funky spacing", NULL,    do_spacing_changed,  TRUE, "/Settings/Sane spacing" },
1534   { "/Settings/sep1",        NULL,      NULL,                0, "<Separator>" },
1535   { "/Settings/Don't cycle color tags", NULL,    do_color_cycle_changed,  FALSE, "<RadioItem>" },
1536   { "/Settings/Cycle colors", NULL,    do_color_cycle_changed,  TRUE, "/Settings/Don't cycle color tags" },
1537   { "/_Attributes",       NULL,         NULL,                0, "<Branch>" },
1538   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1539   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1540   { "/Attributes/Invisible",      NULL,         do_apply_invisible, FALSE, NULL },
1541   { "/Attributes/Visible",        NULL,         do_apply_invisible, TRUE, NULL },
1542   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1543   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1544   { "/Attributes/Color cycles",           NULL,         do_apply_colors, TRUE, NULL },
1545   { "/Attributes/No colors",              NULL,         do_apply_colors, FALSE, NULL },
1546   { "/Attributes/Remove all tags",       NULL, do_remove_tags, 0, NULL },
1547   { "/Attributes/Properties",       NULL, do_properties, 0, NULL },
1548   { "/_Test",            NULL,         NULL,           0, "<Branch>" },
1549   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1550   { "/Test/_Insert and scroll", NULL,         do_insert_and_scroll,  0, NULL },
1551   { "/Test/_Add fixed children", NULL,         do_add_children,  0, NULL },
1552   { "/Test/A_dd focusable children", NULL,    do_add_focus_children,  0, NULL },
1553 };
1554
1555 static gboolean
1556 save_buffer (Buffer *buffer)
1557 {
1558   GtkTextIter start, end;
1559   gchar *chars;
1560   gboolean result = FALSE;
1561   gboolean have_backup = FALSE;
1562   gchar *bak_filename;
1563   FILE *file;
1564
1565   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1566
1567   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1568   
1569   if (rename (buffer->filename, bak_filename) != 0)
1570     {
1571       if (errno != ENOENT)
1572         {
1573           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1574                                         buffer->filename, bak_filename, g_strerror (errno));
1575           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1576           g_free (err);
1577           return FALSE;
1578         }
1579     }
1580   else
1581     have_backup = TRUE;
1582   
1583   file = fopen (buffer->filename, "w");
1584   if (!file)
1585     {
1586       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1587                                     buffer->filename, bak_filename, g_strerror (errno));
1588       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1589     }
1590   else
1591     {
1592       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1593       gtk_text_buffer_get_end_iter (buffer->buffer, &end);
1594   
1595       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1596
1597       if (fputs (chars, file) == EOF ||
1598           fclose (file) == EOF)
1599         {
1600           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1601                                         buffer->filename, g_strerror (errno));
1602           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1603           g_free (err);
1604         }
1605       else
1606         {
1607           /* Success
1608            */
1609           result = TRUE;
1610           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1611         }
1612         
1613       g_free (chars);
1614     }
1615
1616   if (!result && have_backup)
1617     {
1618       if (rename (bak_filename, buffer->filename) != 0)
1619         {
1620           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1621                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1622           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1623           g_free (err);
1624         }
1625     }
1626
1627   g_free (bak_filename);
1628   
1629   return result;
1630 }
1631
1632 static gboolean
1633 save_as_ok_func (const char *filename, gpointer data)
1634 {
1635   Buffer *buffer = data;
1636   char *old_filename = buffer->filename;
1637
1638   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1639     {
1640       struct stat statbuf;
1641
1642       if (stat (filename, &statbuf) == 0)
1643         {
1644           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1645           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1646           g_free (err);
1647
1648           if (result != 0)
1649             return FALSE;
1650         }
1651     }
1652   
1653   buffer->filename = g_strdup (filename);
1654
1655   if (save_buffer (buffer))
1656     {
1657       g_free (old_filename);
1658       buffer_filename_set (buffer);
1659       return TRUE;
1660     }
1661   else
1662     {
1663       g_free (buffer->filename);
1664       buffer->filename = old_filename;
1665       return FALSE;
1666     }
1667 }
1668
1669 static gboolean
1670 save_as_buffer (Buffer *buffer)
1671 {
1672   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1673 }
1674
1675 static gboolean
1676 check_buffer_saved (Buffer *buffer)
1677 {
1678   if (gtk_text_buffer_get_modified (buffer->buffer))
1679     {
1680       char *pretty_name = buffer_pretty_name (buffer);
1681       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1682       gint result;
1683       
1684       g_free (pretty_name);
1685       
1686       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1687       g_free (msg);
1688   
1689       if (result == 0)
1690         return save_as_buffer (buffer);
1691       else if (result == 1)
1692         return TRUE;
1693       else
1694         return FALSE;
1695     }
1696   else
1697     return TRUE;
1698 }
1699
1700 #define N_COLORS 16
1701
1702 static Buffer *
1703 create_buffer (void)
1704 {
1705   Buffer *buffer;
1706   PangoTabArray *tabs;
1707   gint i;
1708   
1709   buffer = g_new (Buffer, 1);
1710
1711   buffer->buffer = gtk_text_buffer_new (NULL);
1712   
1713   buffer->refcount = 1;
1714   buffer->filename = NULL;
1715   buffer->untitled_serial = -1;
1716
1717   buffer->color_tags = NULL;
1718   buffer->color_cycle_timeout = 0;
1719   buffer->start_hue = 0.0;
1720   
1721   i = 0;
1722   while (i < N_COLORS)
1723     {
1724       GtkTextTag *tag;
1725
1726       tag = gtk_text_buffer_create_tag (buffer->buffer, NULL, NULL);
1727       
1728       buffer->color_tags = g_slist_prepend (buffer->color_tags, tag);
1729       
1730       ++i;
1731     }
1732
1733 #if 0  
1734   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1735                                                       "invisible", TRUE, NULL);
1736 #endif  
1737   
1738   buffer->not_editable_tag =
1739     gtk_text_buffer_create_tag (buffer->buffer, NULL,
1740                                 "editable", FALSE,
1741                                 "foreground", "purple", NULL);
1742
1743   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1744                                                        "foreground", "red", NULL);
1745
1746   tabs = pango_tab_array_new_with_positions (4,
1747                                              TRUE,
1748                                              PANGO_TAB_LEFT, 10,
1749                                              PANGO_TAB_LEFT, 30,
1750                                              PANGO_TAB_LEFT, 60,
1751                                              PANGO_TAB_LEFT, 120);
1752   
1753   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1754                                                         "tabs", tabs,
1755                                                         "foreground", "green", NULL);
1756
1757   pango_tab_array_free (tabs);
1758   
1759   buffers = g_slist_prepend (buffers, buffer);
1760   
1761   return buffer;
1762 }
1763
1764 static char *
1765 buffer_pretty_name (Buffer *buffer)
1766 {
1767   if (buffer->filename)
1768     {
1769       char *p;
1770       char *result = g_path_get_basename (buffer->filename);
1771       p = strchr (result, '/');
1772       if (p)
1773         *p = '\0';
1774
1775       return result;
1776     }
1777   else
1778     {
1779       if (buffer->untitled_serial == -1)
1780         buffer->untitled_serial = untitled_serial++;
1781
1782       if (buffer->untitled_serial == 1)
1783         return g_strdup ("Untitled");
1784       else
1785         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1786     }
1787 }
1788
1789 static void
1790 buffer_filename_set (Buffer *buffer)
1791 {
1792   GSList *tmp_list = views;
1793
1794   while (tmp_list)
1795     {
1796       View *view = tmp_list->data;
1797
1798       if (view->buffer == buffer)
1799         view_set_title (view);
1800
1801       tmp_list = tmp_list->next;
1802     }
1803 }
1804
1805 static void
1806 buffer_search (Buffer     *buffer,
1807                const char *str,
1808                View       *view,
1809                gboolean forward)
1810 {
1811   GtkTextIter iter;
1812   GtkTextIter start, end;
1813   GtkWidget *dialog;
1814   int i;
1815   
1816   /* remove tag from whole buffer */
1817   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1818   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1819                               &start, &end );
1820   
1821   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1822                                     gtk_text_buffer_get_mark (buffer->buffer,
1823                                                               "insert"));
1824
1825   i = 0;
1826   if (*str != '\0')
1827     {
1828       GtkTextIter match_start, match_end;
1829
1830       if (forward)
1831         {
1832           while (gtk_text_iter_forward_search (&iter, str,
1833                                                GTK_TEXT_SEARCH_VISIBLE_ONLY |
1834                                                GTK_TEXT_SEARCH_TEXT_ONLY,
1835                                                &match_start, &match_end,
1836                                                NULL))
1837             {
1838               ++i;
1839               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1840                                          &match_start, &match_end);
1841               
1842               iter = match_end;
1843             }
1844         }
1845       else
1846         {
1847           while (gtk_text_iter_backward_search (&iter, str,
1848                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY |
1849                                                 GTK_TEXT_SEARCH_TEXT_ONLY,
1850                                                 &match_start, &match_end,
1851                                                 NULL))
1852             {
1853               ++i;
1854               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1855                                          &match_start, &match_end);
1856               
1857               iter = match_start;
1858             }
1859         }
1860     }
1861
1862   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1863                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1864                                    GTK_MESSAGE_INFO,
1865                                    GTK_BUTTONS_OK,
1866                                    "%d strings found and marked in red",
1867                                    i);
1868
1869   g_signal_connect_swapped (dialog,
1870                             "response",
1871                             G_CALLBACK (gtk_widget_destroy), dialog);
1872   
1873   gtk_widget_show (dialog);
1874 }
1875
1876 static void
1877 buffer_search_forward (Buffer *buffer, const char *str,
1878                        View *view)
1879 {
1880   buffer_search (buffer, str, view, TRUE);
1881 }
1882
1883 static void
1884 buffer_search_backward (Buffer *buffer, const char *str,
1885                         View *view)
1886 {
1887   buffer_search (buffer, str, view, FALSE);
1888 }
1889
1890 static void
1891 buffer_ref (Buffer *buffer)
1892 {
1893   buffer->refcount++;
1894 }
1895
1896 static void
1897 buffer_unref (Buffer *buffer)
1898 {
1899   buffer->refcount--;
1900   if (buffer->refcount == 0)
1901     {
1902       buffer_set_colors (buffer, FALSE);
1903       buffers = g_slist_remove (buffers, buffer);
1904       g_object_unref (buffer->buffer);
1905       g_free (buffer->filename);
1906       g_free (buffer);
1907     }
1908 }
1909
1910 static void
1911 hsv_to_rgb (gdouble *h,
1912             gdouble *s,
1913             gdouble *v)
1914 {
1915   gdouble hue, saturation, value;
1916   gdouble f, p, q, t;
1917
1918   if (*s == 0.0)
1919     {
1920       *h = *v;
1921       *s = *v;
1922       *v = *v; /* heh */
1923     }
1924   else
1925     {
1926       hue = *h * 6.0;
1927       saturation = *s;
1928       value = *v;
1929       
1930       if (hue >= 6.0)
1931         hue = 0.0;
1932       
1933       f = hue - (int) hue;
1934       p = value * (1.0 - saturation);
1935       q = value * (1.0 - saturation * f);
1936       t = value * (1.0 - saturation * (1.0 - f));
1937       
1938       switch ((int) hue)
1939         {
1940         case 0:
1941           *h = value;
1942           *s = t;
1943           *v = p;
1944           break;
1945           
1946         case 1:
1947           *h = q;
1948           *s = value;
1949           *v = p;
1950           break;
1951           
1952         case 2:
1953           *h = p;
1954           *s = value;
1955           *v = t;
1956           break;
1957           
1958         case 3:
1959           *h = p;
1960           *s = q;
1961           *v = value;
1962           break;
1963           
1964         case 4:
1965           *h = t;
1966           *s = p;
1967           *v = value;
1968           break;
1969           
1970         case 5:
1971           *h = value;
1972           *s = p;
1973           *v = q;
1974           break;
1975           
1976         default:
1977           g_assert_not_reached ();
1978         }
1979     }
1980 }
1981
1982 static void
1983 hue_to_color (gdouble   hue,
1984               GdkColor *color)
1985 {
1986   gdouble h, s, v;
1987
1988   h = hue;
1989   s = 1.0;
1990   v = 1.0;
1991
1992   g_return_if_fail (hue <= 1.0);
1993   
1994   hsv_to_rgb (&h, &s, &v);
1995
1996   color->red = h * 65535;
1997   color->green = s * 65535;
1998   color->blue = v * 65535;
1999 }
2000
2001
2002 static gint
2003 color_cycle_timeout (gpointer data)
2004 {
2005   Buffer *buffer = data;
2006
2007   buffer_cycle_colors (buffer);
2008
2009   return TRUE;
2010 }
2011
2012 static void
2013 buffer_set_colors (Buffer  *buffer,
2014                    gboolean enabled)
2015 {
2016   GSList *tmp;
2017   gdouble hue = 0.0;
2018
2019   if (enabled && buffer->color_cycle_timeout == 0)
2020     buffer->color_cycle_timeout = g_timeout_add (200, color_cycle_timeout, buffer);
2021   else if (!enabled && buffer->color_cycle_timeout != 0)
2022     {
2023       g_source_remove (buffer->color_cycle_timeout);
2024       buffer->color_cycle_timeout = 0;
2025     }
2026     
2027   tmp = buffer->color_tags;
2028   while (tmp != NULL)
2029     {
2030       if (enabled)
2031         {
2032           GdkColor color;
2033           
2034           hue_to_color (hue, &color);
2035
2036           g_object_set (tmp->data,
2037                         "foreground_gdk", &color,
2038                         NULL);
2039         }
2040       else
2041         g_object_set (tmp->data,
2042                       "foreground_set", FALSE,
2043                       NULL);
2044
2045       hue += 1.0 / N_COLORS;
2046       
2047       tmp = g_slist_next (tmp);
2048     }
2049 }
2050
2051 static void
2052 buffer_cycle_colors (Buffer *buffer)
2053 {
2054   GSList *tmp;
2055   gdouble hue = buffer->start_hue;
2056   
2057   tmp = buffer->color_tags;
2058   while (tmp != NULL)
2059     {
2060       GdkColor color;
2061       
2062       hue_to_color (hue, &color);
2063       
2064       g_object_set (tmp->data,
2065                     "foreground_gdk", &color,
2066                     NULL);
2067
2068       hue += 1.0 / N_COLORS;
2069       if (hue > 1.0)
2070         hue = 0.0;
2071       
2072       tmp = g_slist_next (tmp);
2073     }
2074
2075   buffer->start_hue += 1.0 / N_COLORS;
2076   if (buffer->start_hue > 1.0)
2077     buffer->start_hue = 0.0;
2078 }
2079
2080 static void
2081 close_view (View *view)
2082 {
2083   views = g_slist_remove (views, view);
2084   buffer_unref (view->buffer);
2085   gtk_widget_destroy (view->window);
2086   g_object_unref (view->item_factory);
2087   
2088   g_free (view);
2089   
2090   if (!views)
2091     gtk_main_quit ();
2092 }
2093
2094 static void
2095 check_close_view (View *view)
2096 {
2097   if (view->buffer->refcount > 1 ||
2098       check_buffer_saved (view->buffer))
2099     close_view (view);
2100 }
2101
2102 static void
2103 view_set_title (View *view)
2104 {
2105   char *pretty_name = buffer_pretty_name (view->buffer);
2106   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
2107
2108   gtk_window_set_title (GTK_WINDOW (view->window), title);
2109
2110   g_free (pretty_name);
2111   g_free (title);
2112 }
2113
2114 static void
2115 cursor_set_callback (GtkTextBuffer     *buffer,
2116                      const GtkTextIter *location,
2117                      GtkTextMark       *mark,
2118                      gpointer           user_data)
2119 {
2120   GtkTextView *text_view;
2121
2122   /* Redraw tab windows if the cursor moves
2123    * on the mapped widget (windows may not exist before realization...
2124    */
2125   
2126   text_view = GTK_TEXT_VIEW (user_data);
2127   
2128   if (GTK_WIDGET_MAPPED (text_view) &&
2129       mark == gtk_text_buffer_get_insert (buffer))
2130     {
2131       GdkWindow *tab_window;
2132
2133       tab_window = gtk_text_view_get_window (text_view,
2134                                              GTK_TEXT_WINDOW_TOP);
2135
2136       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2137       
2138       tab_window = gtk_text_view_get_window (text_view,
2139                                              GTK_TEXT_WINDOW_BOTTOM);
2140
2141       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2142     }
2143 }
2144
2145 static gint
2146 tab_stops_expose (GtkWidget      *widget,
2147                   GdkEventExpose *event,
2148                   gpointer        user_data)
2149 {
2150   gint first_x;
2151   gint last_x;
2152   gint i;
2153   GdkWindow *top_win;
2154   GdkWindow *bottom_win;
2155   GtkTextView *text_view;
2156   GtkTextWindowType type;
2157   GdkDrawable *target;
2158   gint *positions = NULL;
2159   gint size;
2160   GtkTextAttributes *attrs;
2161   GtkTextIter insert;
2162   GtkTextBuffer *buffer;
2163   gboolean in_pixels;
2164   
2165   text_view = GTK_TEXT_VIEW (widget);
2166   
2167   /* See if this expose is on the tab stop window */
2168   top_win = gtk_text_view_get_window (text_view,
2169                                       GTK_TEXT_WINDOW_TOP);
2170
2171   bottom_win = gtk_text_view_get_window (text_view,
2172                                          GTK_TEXT_WINDOW_BOTTOM);
2173
2174   if (event->window == top_win)
2175     {
2176       type = GTK_TEXT_WINDOW_TOP;
2177       target = top_win;
2178     }
2179   else if (event->window == bottom_win)
2180     {
2181       type = GTK_TEXT_WINDOW_BOTTOM;
2182       target = bottom_win;
2183     }
2184   else
2185     return FALSE;
2186   
2187   first_x = event->area.x;
2188   last_x = first_x + event->area.width;
2189
2190   gtk_text_view_window_to_buffer_coords (text_view,
2191                                          type,
2192                                          first_x,
2193                                          0,
2194                                          &first_x,
2195                                          NULL);
2196
2197   gtk_text_view_window_to_buffer_coords (text_view,
2198                                          type,
2199                                          last_x,
2200                                          0,
2201                                          &last_x,
2202                                          NULL);
2203
2204   buffer = gtk_text_view_get_buffer (text_view);
2205
2206   gtk_text_buffer_get_iter_at_mark (buffer,
2207                                     &insert,
2208                                     gtk_text_buffer_get_mark (buffer,
2209                                                               "insert"));
2210   
2211   attrs = gtk_text_attributes_new ();
2212
2213   gtk_text_iter_get_attributes (&insert, attrs);
2214
2215   if (attrs->tabs)
2216     {
2217       size = pango_tab_array_get_size (attrs->tabs);
2218       
2219       pango_tab_array_get_tabs (attrs->tabs,
2220                                 NULL,
2221                                 &positions);
2222
2223       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
2224     }
2225   else
2226     {
2227       size = 0;
2228       in_pixels = FALSE;
2229     }
2230       
2231   gtk_text_attributes_unref (attrs);
2232   
2233   i = 0;
2234   while (i < size)
2235     {
2236       gint pos;
2237
2238       if (!in_pixels)
2239         positions[i] = PANGO_PIXELS (positions[i]);
2240       
2241       gtk_text_view_buffer_to_window_coords (text_view,
2242                                              type,
2243                                              positions[i],
2244                                              0,
2245                                              &pos,
2246                                              NULL);
2247       
2248       gdk_draw_line (target, 
2249                      widget->style->fg_gc [widget->state],
2250                      pos, 0,
2251                      pos, 15); 
2252       
2253       ++i;
2254     }
2255
2256   g_free (positions);
2257
2258   return TRUE;
2259 }
2260
2261 static void
2262 get_lines (GtkTextView  *text_view,
2263            gint          first_y,
2264            gint          last_y,
2265            GArray       *buffer_coords,
2266            GArray       *numbers,
2267            gint         *countp)
2268 {
2269   GtkTextIter iter;
2270   gint count;
2271   gint size;  
2272
2273   g_array_set_size (buffer_coords, 0);
2274   g_array_set_size (numbers, 0);
2275   
2276   /* Get iter at first y */
2277   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
2278
2279   /* For each iter, get its location and add it to the arrays.
2280    * Stop when we pass last_y
2281    */
2282   count = 0;
2283   size = 0;
2284
2285   while (!gtk_text_iter_is_end (&iter))
2286     {
2287       gint y, height;
2288       gint line_num;
2289       
2290       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
2291
2292       g_array_append_val (buffer_coords, y);
2293       line_num = gtk_text_iter_get_line (&iter);
2294       g_array_append_val (numbers, line_num);
2295       
2296       ++count;
2297
2298       if ((y + height) >= last_y)
2299         break;
2300       
2301       gtk_text_iter_forward_line (&iter);
2302     }
2303
2304   *countp = count;
2305 }
2306
2307 static gint
2308 line_numbers_expose (GtkWidget      *widget,
2309                      GdkEventExpose *event,
2310                      gpointer        user_data)
2311 {
2312   gint count;
2313   GArray *numbers;
2314   GArray *pixels;
2315   gint first_y;
2316   gint last_y;
2317   gint i;
2318   GdkWindow *left_win;
2319   GdkWindow *right_win;
2320   PangoLayout *layout;
2321   GtkTextView *text_view;
2322   GtkTextWindowType type;
2323   GdkDrawable *target;
2324   
2325   text_view = GTK_TEXT_VIEW (widget);
2326   
2327   /* See if this expose is on the line numbers window */
2328   left_win = gtk_text_view_get_window (text_view,
2329                                        GTK_TEXT_WINDOW_LEFT);
2330
2331   right_win = gtk_text_view_get_window (text_view,
2332                                         GTK_TEXT_WINDOW_RIGHT);
2333
2334   if (event->window == left_win)
2335     {
2336       type = GTK_TEXT_WINDOW_LEFT;
2337       target = left_win;
2338     }
2339   else if (event->window == right_win)
2340     {
2341       type = GTK_TEXT_WINDOW_RIGHT;
2342       target = right_win;
2343     }
2344   else
2345     return FALSE;
2346   
2347   first_y = event->area.y;
2348   last_y = first_y + event->area.height;
2349
2350   gtk_text_view_window_to_buffer_coords (text_view,
2351                                          type,
2352                                          0,
2353                                          first_y,
2354                                          NULL,
2355                                          &first_y);
2356
2357   gtk_text_view_window_to_buffer_coords (text_view,
2358                                          type,
2359                                          0,
2360                                          last_y,
2361                                          NULL,
2362                                          &last_y);
2363
2364   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
2365   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
2366   
2367   get_lines (text_view,
2368              first_y,
2369              last_y,
2370              pixels,
2371              numbers,
2372              &count);
2373   
2374   /* Draw fully internationalized numbers! */
2375   
2376   layout = gtk_widget_create_pango_layout (widget, "");
2377   
2378   i = 0;
2379   while (i < count)
2380     {
2381       gint pos;
2382       gchar *str;
2383       
2384       gtk_text_view_buffer_to_window_coords (text_view,
2385                                              type,
2386                                              0,
2387                                              g_array_index (pixels, gint, i),
2388                                              NULL,
2389                                              &pos);
2390
2391       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
2392
2393       pango_layout_set_text (layout, str, -1);
2394
2395       gtk_paint_layout (widget->style,
2396                         target,
2397                         GTK_WIDGET_STATE (widget),
2398                         FALSE,
2399                         NULL,
2400                         widget,
2401                         NULL,
2402                         2, pos + 2,
2403                         layout);
2404
2405       g_free (str);
2406       
2407       ++i;
2408     }
2409
2410   g_array_free (pixels, TRUE);
2411   g_array_free (numbers, TRUE);
2412   
2413   g_object_unref (layout);
2414
2415   /* don't stop emission, need to draw children */
2416   return FALSE;
2417 }
2418
2419 static View *
2420 create_view (Buffer *buffer)
2421 {
2422   View *view;
2423   
2424   GtkWidget *sw;
2425   GtkWidget *vbox;
2426   
2427   view = g_new0 (View, 1);
2428   views = g_slist_prepend (views, view);
2429
2430   view->buffer = buffer;
2431   buffer_ref (buffer);
2432   
2433   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2434   g_object_set_data (G_OBJECT (view->window), "view", view);
2435   
2436   g_signal_connect (view->window, "delete_event",
2437                     G_CALLBACK (delete_event_cb), NULL);
2438
2439   view->accel_group = gtk_accel_group_new ();
2440   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
2441   g_object_set_data (G_OBJECT (view->item_factory), "view", view);
2442   
2443   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
2444
2445   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
2446
2447   vbox = gtk_vbox_new (FALSE, 0);
2448   gtk_container_add (GTK_CONTAINER (view->window), vbox);
2449
2450   gtk_box_pack_start (GTK_BOX (vbox),
2451                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
2452                       FALSE, FALSE, 0);
2453   
2454   sw = gtk_scrolled_window_new (NULL, NULL);
2455   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2456                                  GTK_POLICY_AUTOMATIC,
2457                                  GTK_POLICY_AUTOMATIC);
2458
2459   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
2460   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
2461                                GTK_WRAP_WORD);
2462
2463   /* Make sure border width works, no real reason to do this other than testing */
2464   gtk_container_set_border_width (GTK_CONTAINER (view->text_view),
2465                                   10);
2466   
2467   /* Draw tab stops in the top and bottom windows. */
2468   
2469   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2470                                         GTK_TEXT_WINDOW_TOP,
2471                                         15);
2472
2473   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2474                                         GTK_TEXT_WINDOW_BOTTOM,
2475                                         15);
2476
2477   g_signal_connect (view->text_view,
2478                     "expose_event",
2479                     G_CALLBACK (tab_stops_expose),
2480                     NULL);  
2481
2482   g_signal_connect (view->buffer->buffer,
2483                     "mark_set",
2484                     G_CALLBACK (cursor_set_callback),
2485                     view->text_view);
2486   
2487   /* Draw line numbers in the side windows; we should really be
2488    * more scientific about what width we set them to.
2489    */
2490   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2491                                         GTK_TEXT_WINDOW_RIGHT,
2492                                         30);
2493   
2494   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2495                                         GTK_TEXT_WINDOW_LEFT,
2496                                         30);
2497   
2498   g_signal_connect (view->text_view,
2499                     "expose_event",
2500                     G_CALLBACK (line_numbers_expose),
2501                     NULL);
2502   
2503   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
2504   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
2505
2506   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
2507
2508   gtk_widget_grab_focus (view->text_view);
2509
2510   view_set_title (view);
2511   view_init_menus (view);
2512
2513   view_add_example_widgets (view);
2514   
2515   gtk_widget_show_all (view->window);
2516   return view;
2517 }
2518
2519 static void
2520 view_add_example_widgets (View *view)
2521 {
2522   GtkTextChildAnchor *anchor;
2523   Buffer *buffer;
2524
2525   buffer = view->buffer;
2526   
2527   anchor = g_object_get_data (G_OBJECT (buffer->buffer),
2528                               "anchor");
2529
2530   if (anchor && !gtk_text_child_anchor_get_deleted (anchor))
2531     {
2532       GtkWidget *widget;
2533
2534       widget = gtk_button_new_with_label ("Foo");
2535       
2536       gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view->text_view),
2537                                          widget,
2538                                          anchor);
2539
2540       gtk_widget_show (widget);
2541     }
2542 }
2543
2544 void
2545 test_init (void)
2546 {
2547   if (g_file_test ("../gdk-pixbuf/libpixbufloader-pnm.la",
2548                    G_FILE_TEST_EXISTS))
2549     {
2550       g_setenv ("GDK_PIXBUF_MODULE_FILE", "../gdk-pixbuf/gdk-pixbuf.loaders", TRUE);
2551       g_setenv ("GTK_IM_MODULE_FILE", "../modules/input/gtk.immodules", TRUE);
2552     }
2553 }
2554
2555 int
2556 main (int argc, char** argv)
2557 {
2558   Buffer *buffer;
2559   View *view;
2560   int i;
2561
2562   test_init ();
2563   gtk_init (&argc, &argv);
2564   
2565   buffer = create_buffer ();
2566   view = create_view (buffer);
2567   buffer_unref (buffer);
2568   
2569   push_active_window (GTK_WINDOW (view->window));
2570   for (i=1; i < argc; i++)
2571     {
2572       char *filename;
2573
2574       /* Quick and dirty canonicalization - better should be in GLib
2575        */
2576
2577       if (!g_path_is_absolute (argv[i]))
2578         {
2579           char *cwd = g_get_current_dir ();
2580           filename = g_strconcat (cwd, "/", argv[i], NULL);
2581           g_free (cwd);
2582         }
2583       else
2584         filename = argv[i];
2585
2586       open_ok_func (filename, view);
2587
2588       if (filename != argv[i])
2589         g_free (filename);
2590     }
2591   pop_active_window ();
2592   
2593   gtk_main ();
2594
2595   return 0;
2596 }
2597
2598