]> Pileus Git - ~andy/gtk/blob - tests/testtext.c
Add a "Select All" menuitem. (#156792, Paolo Borelli)
[~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 static void
1236 do_select_all (gpointer callback_data,
1237                guint callback_action,
1238                GtkWidget *widget)
1239 {
1240   View *view = view_from_widget (widget);
1241   GtkTextBuffer *buffer;
1242   GtkTextIter start, end;
1243
1244   buffer = view->buffer->buffer;
1245
1246   gtk_text_buffer_get_bounds (buffer, &start, &end);
1247   gtk_text_buffer_select_range (buffer, &start, &end);
1248 }
1249
1250 typedef struct
1251 {
1252   /* position is in coordinate system of text_view_move_child */
1253   int click_x;
1254   int click_y;
1255   int start_x;
1256   int start_y;
1257   int button;
1258 } ChildMoveInfo;
1259
1260 static gboolean
1261 movable_child_callback (GtkWidget *child,
1262                         GdkEvent  *event,
1263                         gpointer   data)
1264 {
1265   ChildMoveInfo *info;
1266   GtkTextView *text_view;
1267
1268   text_view = GTK_TEXT_VIEW (data);
1269   
1270   g_return_val_if_fail (GTK_IS_EVENT_BOX (child), FALSE);
1271   g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (text_view), FALSE);  
1272   
1273   info = g_object_get_data (G_OBJECT (child),
1274                             "testtext-move-info");
1275
1276   if (info == NULL)
1277     {
1278       info = g_new (ChildMoveInfo, 1);      
1279       info->start_x = -1;
1280       info->start_y = -1;
1281       info->button = -1;
1282       g_object_set_data_full (G_OBJECT (child),
1283                               "testtext-move-info",
1284                               info,
1285                               g_free);
1286     }
1287   
1288   switch (event->type)
1289     {
1290     case GDK_BUTTON_PRESS:
1291       if (info->button < 0)
1292         {
1293           if (gdk_pointer_grab (event->button.window,
1294                                 FALSE,
1295                                 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1296                                 GDK_BUTTON_RELEASE_MASK,
1297                                 NULL,
1298                                 NULL,
1299                                 event->button.time) != GDK_GRAB_SUCCESS)
1300             return FALSE;
1301           
1302           info->button = event->button.button;
1303           
1304           info->start_x = child->allocation.x;
1305           info->start_y = child->allocation.y;
1306           info->click_x = child->allocation.x + event->button.x;
1307           info->click_y = child->allocation.y + event->button.y;
1308         }
1309       break;
1310
1311     case GDK_BUTTON_RELEASE:
1312       if (info->button < 0)
1313         return FALSE;
1314
1315       if (info->button == event->button.button)
1316         {
1317           int x, y;
1318           
1319           gdk_pointer_ungrab (event->button.time);
1320           info->button = -1;
1321
1322           /* convert to window coords from event box coords */
1323           x = info->start_x + (event->button.x + child->allocation.x - info->click_x);
1324           y = info->start_y + (event->button.y + child->allocation.y - info->click_y);
1325
1326           gtk_text_view_move_child (text_view,
1327                                     child,
1328                                     x, y);
1329         }
1330       break;
1331
1332     case GDK_MOTION_NOTIFY:
1333       {
1334         int x, y;
1335         
1336         if (info->button < 0)
1337           return FALSE;
1338         
1339         gdk_window_get_pointer (child->window, &x, &y, NULL); /* ensure more events */
1340
1341         /* to window coords from event box coords */
1342         x += child->allocation.x;
1343         y += child->allocation.y;
1344         
1345         x = info->start_x + (x - info->click_x);
1346         y = info->start_y + (y - info->click_y);
1347         
1348         gtk_text_view_move_child (text_view,
1349                                   child,
1350                                   x, y);
1351       }
1352       break;
1353
1354     default:
1355       break;
1356     }
1357
1358   return FALSE;
1359 }
1360
1361 static void
1362 add_movable_child (GtkTextView      *text_view,
1363                    GtkTextWindowType window)
1364 {
1365   GtkWidget *event_box;
1366   GtkWidget *label;
1367   GdkColor color;
1368   
1369   label = gtk_label_new ("Drag me around");  
1370   
1371   event_box = gtk_event_box_new ();
1372   gtk_widget_add_events (event_box,
1373                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1374                          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
1375
1376   color.red = 0xffff;
1377   color.green = color.blue = 0;
1378   gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color);
1379   
1380   gtk_container_add (GTK_CONTAINER (event_box), label);
1381
1382   gtk_widget_show_all (event_box);
1383
1384   g_signal_connect (event_box, "event",
1385                     G_CALLBACK (movable_child_callback),
1386                     text_view);
1387
1388   gtk_text_view_add_child_in_window (text_view,
1389                                      event_box,
1390                                      window,
1391                                      0, 0);
1392 }
1393
1394 static void
1395 do_add_children (gpointer callback_data,
1396                  guint callback_action,
1397                  GtkWidget *widget)
1398 {
1399   View *view = view_from_widget (widget);
1400
1401   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1402                      GTK_TEXT_WINDOW_WIDGET);
1403   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1404                      GTK_TEXT_WINDOW_LEFT);
1405   add_movable_child (GTK_TEXT_VIEW (view->text_view),
1406                      GTK_TEXT_WINDOW_RIGHT);
1407 }
1408
1409 static void
1410 do_add_focus_children (gpointer callback_data,
1411                        guint callback_action,
1412                        GtkWidget *widget)
1413 {
1414   View *view = view_from_widget (widget);
1415   GtkWidget *child;
1416   GtkTextChildAnchor *anchor;
1417   GtkTextIter iter;
1418   GtkTextView *text_view;
1419
1420   text_view = GTK_TEXT_VIEW (view->text_view);
1421   
1422   child = gtk_button_new_with_mnemonic ("Button _A in widget->window");
1423
1424   gtk_text_view_add_child_in_window (text_view,
1425                                      child,
1426                                      GTK_TEXT_WINDOW_WIDGET,
1427                                      200, 200);
1428
1429   child = gtk_button_new_with_mnemonic ("Button _B in widget->window");
1430
1431   gtk_text_view_add_child_in_window (text_view,
1432                                      child,
1433                                      GTK_TEXT_WINDOW_WIDGET,
1434                                      350, 300);
1435
1436   child = gtk_button_new_with_mnemonic ("Button _C in left window");
1437
1438   gtk_text_view_add_child_in_window (text_view,
1439                                      child,
1440                                      GTK_TEXT_WINDOW_LEFT,
1441                                      0, 0);
1442
1443   child = gtk_button_new_with_mnemonic ("Button _D in right window");
1444   
1445   gtk_text_view_add_child_in_window (text_view,
1446                                      child,
1447                                      GTK_TEXT_WINDOW_RIGHT,
1448                                      0, 0);
1449
1450   gtk_text_buffer_get_start_iter (view->buffer->buffer, &iter);
1451   
1452   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1453
1454   child = gtk_button_new_with_mnemonic ("Button _E in buffer");
1455   
1456   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1457
1458   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1459
1460   child = gtk_button_new_with_mnemonic ("Button _F in buffer");
1461   
1462   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1463
1464   anchor = gtk_text_buffer_create_child_anchor (view->buffer->buffer, &iter);
1465
1466   child = gtk_button_new_with_mnemonic ("Button _G in buffer");
1467   
1468   gtk_text_view_add_child_at_anchor (text_view, child, anchor);
1469
1470   /* show all the buttons */
1471   gtk_widget_show_all (view->text_view);
1472 }
1473
1474 static void
1475 view_init_menus (View *view)
1476 {
1477   GtkTextDirection direction = gtk_widget_get_direction (view->text_view);
1478   GtkWrapMode wrap_mode = gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view->text_view));
1479   GtkWidget *menu_item = NULL;
1480
1481   switch (direction)
1482     {
1483     case GTK_TEXT_DIR_LTR:
1484       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Left-to-Right");
1485       break;
1486     case GTK_TEXT_DIR_RTL:
1487       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Right-to-Left");
1488       break;
1489     default:
1490       break;
1491     }
1492
1493   if (menu_item)
1494     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1495
1496   switch (wrap_mode)
1497     {
1498     case GTK_WRAP_NONE:
1499       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Off");
1500       break;
1501     case GTK_WRAP_WORD:
1502       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Words");
1503       break;
1504     case GTK_WRAP_CHAR:
1505       menu_item = gtk_item_factory_get_widget (view->item_factory, "/Settings/Wrap Chars");
1506       break;
1507     default:
1508       break;
1509     }
1510   
1511   if (menu_item)
1512     gtk_menu_item_activate (GTK_MENU_ITEM (menu_item));
1513 }
1514
1515 static GtkItemFactoryEntry menu_items[] =
1516 {
1517   { "/_File",            NULL,         NULL,        0, "<Branch>" },
1518   { "/File/_New",        "<control>N", do_new,      0, NULL },
1519   { "/File/New _View",   NULL,         do_new_view, 0, NULL },
1520   { "/File/_Open",       "<control>O", do_open,     0, NULL },
1521   { "/File/_Save",       "<control>S", do_save,     0, NULL },
1522   { "/File/Save _As...", NULL,         do_save_as,  0, NULL },
1523   { "/File/sep1",        NULL,         NULL,        0, "<Separator>" },
1524   { "/File/_Close",     "<control>W" , do_close,    0, NULL },
1525   { "/File/E_xit",      "<control>Q" , do_exit,     0, NULL },
1526
1527   { "/_Edit", NULL, 0, 0, "<Branch>" },
1528   { "/Edit/Find...", NULL, do_search, 0, NULL },
1529   { "/Edit/Select All", "<control>A", do_select_all, 0, NULL }, 
1530
1531   { "/_Settings",         NULL,         NULL,             0, "<Branch>" },
1532   { "/Settings/Wrap _Off",   NULL,      do_wrap_changed,  GTK_WRAP_NONE, "<RadioItem>" },
1533   { "/Settings/Wrap _Words", NULL,      do_wrap_changed,  GTK_WRAP_WORD, "/Settings/Wrap Off" },
1534   { "/Settings/Wrap _Chars", NULL,      do_wrap_changed,  GTK_WRAP_CHAR, "/Settings/Wrap Off" },
1535   { "/Settings/sep1",        NULL,      NULL,             0, "<Separator>" },
1536   { "/Settings/Editable", NULL,      do_editable_changed,  TRUE, "<RadioItem>" },
1537   { "/Settings/Not editable",    NULL,      do_editable_changed,  FALSE, "/Settings/Editable" },
1538   { "/Settings/sep1",        NULL,      NULL,             0, "<Separator>" },
1539
1540   { "/Settings/Cursor visible",    NULL,      do_cursor_visible_changed,  TRUE, "<RadioItem>" },
1541   { "/Settings/Cursor not visible", NULL,      do_cursor_visible_changed,  FALSE, "/Settings/Cursor visible" },
1542   { "/Settings/sep1",        NULL,      NULL,          0, "<Separator>" },
1543   
1544   { "/Settings/Left-to-Right", NULL,    do_direction_changed,  GTK_TEXT_DIR_LTR, "<RadioItem>" },
1545   { "/Settings/Right-to-Left", NULL,    do_direction_changed,  GTK_TEXT_DIR_RTL, "/Settings/Left-to-Right" },
1546
1547   { "/Settings/sep1",        NULL,      NULL,                0, "<Separator>" },
1548   { "/Settings/Sane spacing", NULL,    do_spacing_changed,  FALSE, "<RadioItem>" },
1549   { "/Settings/Funky spacing", NULL,    do_spacing_changed,  TRUE, "/Settings/Sane spacing" },
1550   { "/Settings/sep1",        NULL,      NULL,                0, "<Separator>" },
1551   { "/Settings/Don't cycle color tags", NULL,    do_color_cycle_changed,  FALSE, "<RadioItem>" },
1552   { "/Settings/Cycle colors", NULL,    do_color_cycle_changed,  TRUE, "/Settings/Don't cycle color tags" },
1553   { "/_Attributes",       NULL,         NULL,                0, "<Branch>" },
1554   { "/Attributes/Editable",       NULL,         do_apply_editable, TRUE, NULL },
1555   { "/Attributes/Not editable",           NULL,         do_apply_editable, FALSE, NULL },
1556   { "/Attributes/Invisible",      NULL,         do_apply_invisible, FALSE, NULL },
1557   { "/Attributes/Visible",        NULL,         do_apply_invisible, TRUE, NULL },
1558   { "/Attributes/Custom tabs",            NULL,         do_apply_tabs, FALSE, NULL },
1559   { "/Attributes/Default tabs",           NULL,         do_apply_tabs, TRUE, NULL },
1560   { "/Attributes/Color cycles",           NULL,         do_apply_colors, TRUE, NULL },
1561   { "/Attributes/No colors",              NULL,         do_apply_colors, FALSE, NULL },
1562   { "/Attributes/Remove all tags",       NULL, do_remove_tags, 0, NULL },
1563   { "/Attributes/Properties",       NULL, do_properties, 0, NULL },
1564   { "/_Test",            NULL,         NULL,           0, "<Branch>" },
1565   { "/Test/_Example",    NULL,         do_example,  0, NULL },
1566   { "/Test/_Insert and scroll", NULL,         do_insert_and_scroll,  0, NULL },
1567   { "/Test/_Add fixed children", NULL,         do_add_children,  0, NULL },
1568   { "/Test/A_dd focusable children", NULL,    do_add_focus_children,  0, NULL },
1569 };
1570
1571 static gboolean
1572 save_buffer (Buffer *buffer)
1573 {
1574   GtkTextIter start, end;
1575   gchar *chars;
1576   gboolean result = FALSE;
1577   gboolean have_backup = FALSE;
1578   gchar *bak_filename;
1579   FILE *file;
1580
1581   g_return_val_if_fail (buffer->filename != NULL, FALSE);
1582
1583   bak_filename = g_strconcat (buffer->filename, "~", NULL);
1584   
1585   if (rename (buffer->filename, bak_filename) != 0)
1586     {
1587       if (errno != ENOENT)
1588         {
1589           gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1590                                         buffer->filename, bak_filename, g_strerror (errno));
1591           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1592           g_free (err);
1593           return FALSE;
1594         }
1595     }
1596   else
1597     have_backup = TRUE;
1598   
1599   file = fopen (buffer->filename, "w");
1600   if (!file)
1601     {
1602       gchar *err = g_strdup_printf ("Cannot back up '%s' to '%s': %s",
1603                                     buffer->filename, bak_filename, g_strerror (errno));
1604       msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1605     }
1606   else
1607     {
1608       gtk_text_buffer_get_iter_at_offset (buffer->buffer, &start, 0);
1609       gtk_text_buffer_get_end_iter (buffer->buffer, &end);
1610   
1611       chars = gtk_text_buffer_get_slice (buffer->buffer, &start, &end, FALSE);
1612
1613       if (fputs (chars, file) == EOF ||
1614           fclose (file) == EOF)
1615         {
1616           gchar *err = g_strdup_printf ("Error writing to '%s': %s",
1617                                         buffer->filename, g_strerror (errno));
1618           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1619           g_free (err);
1620         }
1621       else
1622         {
1623           /* Success
1624            */
1625           result = TRUE;
1626           gtk_text_buffer_set_modified (buffer->buffer, FALSE);   
1627         }
1628         
1629       g_free (chars);
1630     }
1631
1632   if (!result && have_backup)
1633     {
1634       if (rename (bak_filename, buffer->filename) != 0)
1635         {
1636           gchar *err = g_strdup_printf ("Error restoring backup file '%s' to '%s': %s\nBackup left as '%s'",
1637                                         buffer->filename, bak_filename, g_strerror (errno), bak_filename);
1638           msgbox_run (NULL, err, "OK", NULL, NULL, 0);
1639           g_free (err);
1640         }
1641     }
1642
1643   g_free (bak_filename);
1644   
1645   return result;
1646 }
1647
1648 static gboolean
1649 save_as_ok_func (const char *filename, gpointer data)
1650 {
1651   Buffer *buffer = data;
1652   char *old_filename = buffer->filename;
1653
1654   if (!buffer->filename || strcmp (filename, buffer->filename) != 0)
1655     {
1656       struct stat statbuf;
1657
1658       if (stat (filename, &statbuf) == 0)
1659         {
1660           gchar *err = g_strdup_printf ("Ovewrite existing file '%s'?", filename);
1661           gint result = msgbox_run (NULL, err, "Yes", "No", NULL, 1);
1662           g_free (err);
1663
1664           if (result != 0)
1665             return FALSE;
1666         }
1667     }
1668   
1669   buffer->filename = g_strdup (filename);
1670
1671   if (save_buffer (buffer))
1672     {
1673       g_free (old_filename);
1674       buffer_filename_set (buffer);
1675       return TRUE;
1676     }
1677   else
1678     {
1679       g_free (buffer->filename);
1680       buffer->filename = old_filename;
1681       return FALSE;
1682     }
1683 }
1684
1685 static gboolean
1686 save_as_buffer (Buffer *buffer)
1687 {
1688   return filesel_run (NULL, "Save File", NULL, save_as_ok_func, buffer);
1689 }
1690
1691 static gboolean
1692 check_buffer_saved (Buffer *buffer)
1693 {
1694   if (gtk_text_buffer_get_modified (buffer->buffer))
1695     {
1696       char *pretty_name = buffer_pretty_name (buffer);
1697       char *msg = g_strdup_printf ("Save changes to '%s'?", pretty_name);
1698       gint result;
1699       
1700       g_free (pretty_name);
1701       
1702       result = msgbox_run (NULL, msg, "Yes", "No", "Cancel", 0);
1703       g_free (msg);
1704   
1705       if (result == 0)
1706         return save_as_buffer (buffer);
1707       else if (result == 1)
1708         return TRUE;
1709       else
1710         return FALSE;
1711     }
1712   else
1713     return TRUE;
1714 }
1715
1716 #define N_COLORS 16
1717
1718 static Buffer *
1719 create_buffer (void)
1720 {
1721   Buffer *buffer;
1722   PangoTabArray *tabs;
1723   gint i;
1724   
1725   buffer = g_new (Buffer, 1);
1726
1727   buffer->buffer = gtk_text_buffer_new (NULL);
1728   
1729   buffer->refcount = 1;
1730   buffer->filename = NULL;
1731   buffer->untitled_serial = -1;
1732
1733   buffer->color_tags = NULL;
1734   buffer->color_cycle_timeout = 0;
1735   buffer->start_hue = 0.0;
1736   
1737   i = 0;
1738   while (i < N_COLORS)
1739     {
1740       GtkTextTag *tag;
1741
1742       tag = gtk_text_buffer_create_tag (buffer->buffer, NULL, NULL);
1743       
1744       buffer->color_tags = g_slist_prepend (buffer->color_tags, tag);
1745       
1746       ++i;
1747     }
1748
1749 #if 0  
1750   buffer->invisible_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1751                                                       "invisible", TRUE, NULL);
1752 #endif  
1753   
1754   buffer->not_editable_tag =
1755     gtk_text_buffer_create_tag (buffer->buffer, NULL,
1756                                 "editable", FALSE,
1757                                 "foreground", "purple", NULL);
1758
1759   buffer->found_text_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1760                                                        "foreground", "red", NULL);
1761
1762   tabs = pango_tab_array_new_with_positions (4,
1763                                              TRUE,
1764                                              PANGO_TAB_LEFT, 10,
1765                                              PANGO_TAB_LEFT, 30,
1766                                              PANGO_TAB_LEFT, 60,
1767                                              PANGO_TAB_LEFT, 120);
1768   
1769   buffer->custom_tabs_tag = gtk_text_buffer_create_tag (buffer->buffer, NULL,
1770                                                         "tabs", tabs,
1771                                                         "foreground", "green", NULL);
1772
1773   pango_tab_array_free (tabs);
1774   
1775   buffers = g_slist_prepend (buffers, buffer);
1776   
1777   return buffer;
1778 }
1779
1780 static char *
1781 buffer_pretty_name (Buffer *buffer)
1782 {
1783   if (buffer->filename)
1784     {
1785       char *p;
1786       char *result = g_path_get_basename (buffer->filename);
1787       p = strchr (result, '/');
1788       if (p)
1789         *p = '\0';
1790
1791       return result;
1792     }
1793   else
1794     {
1795       if (buffer->untitled_serial == -1)
1796         buffer->untitled_serial = untitled_serial++;
1797
1798       if (buffer->untitled_serial == 1)
1799         return g_strdup ("Untitled");
1800       else
1801         return g_strdup_printf ("Untitled #%d", buffer->untitled_serial);
1802     }
1803 }
1804
1805 static void
1806 buffer_filename_set (Buffer *buffer)
1807 {
1808   GSList *tmp_list = views;
1809
1810   while (tmp_list)
1811     {
1812       View *view = tmp_list->data;
1813
1814       if (view->buffer == buffer)
1815         view_set_title (view);
1816
1817       tmp_list = tmp_list->next;
1818     }
1819 }
1820
1821 static void
1822 buffer_search (Buffer     *buffer,
1823                const char *str,
1824                View       *view,
1825                gboolean forward)
1826 {
1827   GtkTextIter iter;
1828   GtkTextIter start, end;
1829   GtkWidget *dialog;
1830   int i;
1831   
1832   /* remove tag from whole buffer */
1833   gtk_text_buffer_get_bounds (buffer->buffer, &start, &end);
1834   gtk_text_buffer_remove_tag (buffer->buffer,  buffer->found_text_tag,
1835                               &start, &end );
1836   
1837   gtk_text_buffer_get_iter_at_mark (buffer->buffer, &iter,
1838                                     gtk_text_buffer_get_mark (buffer->buffer,
1839                                                               "insert"));
1840
1841   i = 0;
1842   if (*str != '\0')
1843     {
1844       GtkTextIter match_start, match_end;
1845
1846       if (forward)
1847         {
1848           while (gtk_text_iter_forward_search (&iter, str,
1849                                                GTK_TEXT_SEARCH_VISIBLE_ONLY |
1850                                                GTK_TEXT_SEARCH_TEXT_ONLY,
1851                                                &match_start, &match_end,
1852                                                NULL))
1853             {
1854               ++i;
1855               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1856                                          &match_start, &match_end);
1857               
1858               iter = match_end;
1859             }
1860         }
1861       else
1862         {
1863           while (gtk_text_iter_backward_search (&iter, str,
1864                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY |
1865                                                 GTK_TEXT_SEARCH_TEXT_ONLY,
1866                                                 &match_start, &match_end,
1867                                                 NULL))
1868             {
1869               ++i;
1870               gtk_text_buffer_apply_tag (buffer->buffer, buffer->found_text_tag,
1871                                          &match_start, &match_end);
1872               
1873               iter = match_start;
1874             }
1875         }
1876     }
1877
1878   dialog = gtk_message_dialog_new (GTK_WINDOW (view->window),
1879                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1880                                    GTK_MESSAGE_INFO,
1881                                    GTK_BUTTONS_OK,
1882                                    "%d strings found and marked in red",
1883                                    i);
1884
1885   g_signal_connect_swapped (dialog,
1886                             "response",
1887                             G_CALLBACK (gtk_widget_destroy), dialog);
1888   
1889   gtk_widget_show (dialog);
1890 }
1891
1892 static void
1893 buffer_search_forward (Buffer *buffer, const char *str,
1894                        View *view)
1895 {
1896   buffer_search (buffer, str, view, TRUE);
1897 }
1898
1899 static void
1900 buffer_search_backward (Buffer *buffer, const char *str,
1901                         View *view)
1902 {
1903   buffer_search (buffer, str, view, FALSE);
1904 }
1905
1906 static void
1907 buffer_ref (Buffer *buffer)
1908 {
1909   buffer->refcount++;
1910 }
1911
1912 static void
1913 buffer_unref (Buffer *buffer)
1914 {
1915   buffer->refcount--;
1916   if (buffer->refcount == 0)
1917     {
1918       buffer_set_colors (buffer, FALSE);
1919       buffers = g_slist_remove (buffers, buffer);
1920       g_object_unref (buffer->buffer);
1921       g_free (buffer->filename);
1922       g_free (buffer);
1923     }
1924 }
1925
1926 static void
1927 hsv_to_rgb (gdouble *h,
1928             gdouble *s,
1929             gdouble *v)
1930 {
1931   gdouble hue, saturation, value;
1932   gdouble f, p, q, t;
1933
1934   if (*s == 0.0)
1935     {
1936       *h = *v;
1937       *s = *v;
1938       *v = *v; /* heh */
1939     }
1940   else
1941     {
1942       hue = *h * 6.0;
1943       saturation = *s;
1944       value = *v;
1945       
1946       if (hue >= 6.0)
1947         hue = 0.0;
1948       
1949       f = hue - (int) hue;
1950       p = value * (1.0 - saturation);
1951       q = value * (1.0 - saturation * f);
1952       t = value * (1.0 - saturation * (1.0 - f));
1953       
1954       switch ((int) hue)
1955         {
1956         case 0:
1957           *h = value;
1958           *s = t;
1959           *v = p;
1960           break;
1961           
1962         case 1:
1963           *h = q;
1964           *s = value;
1965           *v = p;
1966           break;
1967           
1968         case 2:
1969           *h = p;
1970           *s = value;
1971           *v = t;
1972           break;
1973           
1974         case 3:
1975           *h = p;
1976           *s = q;
1977           *v = value;
1978           break;
1979           
1980         case 4:
1981           *h = t;
1982           *s = p;
1983           *v = value;
1984           break;
1985           
1986         case 5:
1987           *h = value;
1988           *s = p;
1989           *v = q;
1990           break;
1991           
1992         default:
1993           g_assert_not_reached ();
1994         }
1995     }
1996 }
1997
1998 static void
1999 hue_to_color (gdouble   hue,
2000               GdkColor *color)
2001 {
2002   gdouble h, s, v;
2003
2004   h = hue;
2005   s = 1.0;
2006   v = 1.0;
2007
2008   g_return_if_fail (hue <= 1.0);
2009   
2010   hsv_to_rgb (&h, &s, &v);
2011
2012   color->red = h * 65535;
2013   color->green = s * 65535;
2014   color->blue = v * 65535;
2015 }
2016
2017
2018 static gint
2019 color_cycle_timeout (gpointer data)
2020 {
2021   Buffer *buffer = data;
2022
2023   buffer_cycle_colors (buffer);
2024
2025   return TRUE;
2026 }
2027
2028 static void
2029 buffer_set_colors (Buffer  *buffer,
2030                    gboolean enabled)
2031 {
2032   GSList *tmp;
2033   gdouble hue = 0.0;
2034
2035   if (enabled && buffer->color_cycle_timeout == 0)
2036     buffer->color_cycle_timeout = g_timeout_add (200, color_cycle_timeout, buffer);
2037   else if (!enabled && buffer->color_cycle_timeout != 0)
2038     {
2039       g_source_remove (buffer->color_cycle_timeout);
2040       buffer->color_cycle_timeout = 0;
2041     }
2042     
2043   tmp = buffer->color_tags;
2044   while (tmp != NULL)
2045     {
2046       if (enabled)
2047         {
2048           GdkColor color;
2049           
2050           hue_to_color (hue, &color);
2051
2052           g_object_set (tmp->data,
2053                         "foreground_gdk", &color,
2054                         NULL);
2055         }
2056       else
2057         g_object_set (tmp->data,
2058                       "foreground_set", FALSE,
2059                       NULL);
2060
2061       hue += 1.0 / N_COLORS;
2062       
2063       tmp = g_slist_next (tmp);
2064     }
2065 }
2066
2067 static void
2068 buffer_cycle_colors (Buffer *buffer)
2069 {
2070   GSList *tmp;
2071   gdouble hue = buffer->start_hue;
2072   
2073   tmp = buffer->color_tags;
2074   while (tmp != NULL)
2075     {
2076       GdkColor color;
2077       
2078       hue_to_color (hue, &color);
2079       
2080       g_object_set (tmp->data,
2081                     "foreground_gdk", &color,
2082                     NULL);
2083
2084       hue += 1.0 / N_COLORS;
2085       if (hue > 1.0)
2086         hue = 0.0;
2087       
2088       tmp = g_slist_next (tmp);
2089     }
2090
2091   buffer->start_hue += 1.0 / N_COLORS;
2092   if (buffer->start_hue > 1.0)
2093     buffer->start_hue = 0.0;
2094 }
2095
2096 static void
2097 close_view (View *view)
2098 {
2099   views = g_slist_remove (views, view);
2100   buffer_unref (view->buffer);
2101   gtk_widget_destroy (view->window);
2102   g_object_unref (view->item_factory);
2103   
2104   g_free (view);
2105   
2106   if (!views)
2107     gtk_main_quit ();
2108 }
2109
2110 static void
2111 check_close_view (View *view)
2112 {
2113   if (view->buffer->refcount > 1 ||
2114       check_buffer_saved (view->buffer))
2115     close_view (view);
2116 }
2117
2118 static void
2119 view_set_title (View *view)
2120 {
2121   char *pretty_name = buffer_pretty_name (view->buffer);
2122   char *title = g_strconcat ("testtext - ", pretty_name, NULL);
2123
2124   gtk_window_set_title (GTK_WINDOW (view->window), title);
2125
2126   g_free (pretty_name);
2127   g_free (title);
2128 }
2129
2130 static void
2131 cursor_set_callback (GtkTextBuffer     *buffer,
2132                      const GtkTextIter *location,
2133                      GtkTextMark       *mark,
2134                      gpointer           user_data)
2135 {
2136   GtkTextView *text_view;
2137
2138   /* Redraw tab windows if the cursor moves
2139    * on the mapped widget (windows may not exist before realization...
2140    */
2141   
2142   text_view = GTK_TEXT_VIEW (user_data);
2143   
2144   if (GTK_WIDGET_MAPPED (text_view) &&
2145       mark == gtk_text_buffer_get_insert (buffer))
2146     {
2147       GdkWindow *tab_window;
2148
2149       tab_window = gtk_text_view_get_window (text_view,
2150                                              GTK_TEXT_WINDOW_TOP);
2151
2152       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2153       
2154       tab_window = gtk_text_view_get_window (text_view,
2155                                              GTK_TEXT_WINDOW_BOTTOM);
2156
2157       gdk_window_invalidate_rect (tab_window, NULL, FALSE);
2158     }
2159 }
2160
2161 static gint
2162 tab_stops_expose (GtkWidget      *widget,
2163                   GdkEventExpose *event,
2164                   gpointer        user_data)
2165 {
2166   gint first_x;
2167   gint last_x;
2168   gint i;
2169   GdkWindow *top_win;
2170   GdkWindow *bottom_win;
2171   GtkTextView *text_view;
2172   GtkTextWindowType type;
2173   GdkDrawable *target;
2174   gint *positions = NULL;
2175   gint size;
2176   GtkTextAttributes *attrs;
2177   GtkTextIter insert;
2178   GtkTextBuffer *buffer;
2179   gboolean in_pixels;
2180   
2181   text_view = GTK_TEXT_VIEW (widget);
2182   
2183   /* See if this expose is on the tab stop window */
2184   top_win = gtk_text_view_get_window (text_view,
2185                                       GTK_TEXT_WINDOW_TOP);
2186
2187   bottom_win = gtk_text_view_get_window (text_view,
2188                                          GTK_TEXT_WINDOW_BOTTOM);
2189
2190   if (event->window == top_win)
2191     {
2192       type = GTK_TEXT_WINDOW_TOP;
2193       target = top_win;
2194     }
2195   else if (event->window == bottom_win)
2196     {
2197       type = GTK_TEXT_WINDOW_BOTTOM;
2198       target = bottom_win;
2199     }
2200   else
2201     return FALSE;
2202   
2203   first_x = event->area.x;
2204   last_x = first_x + event->area.width;
2205
2206   gtk_text_view_window_to_buffer_coords (text_view,
2207                                          type,
2208                                          first_x,
2209                                          0,
2210                                          &first_x,
2211                                          NULL);
2212
2213   gtk_text_view_window_to_buffer_coords (text_view,
2214                                          type,
2215                                          last_x,
2216                                          0,
2217                                          &last_x,
2218                                          NULL);
2219
2220   buffer = gtk_text_view_get_buffer (text_view);
2221
2222   gtk_text_buffer_get_iter_at_mark (buffer,
2223                                     &insert,
2224                                     gtk_text_buffer_get_mark (buffer,
2225                                                               "insert"));
2226   
2227   attrs = gtk_text_attributes_new ();
2228
2229   gtk_text_iter_get_attributes (&insert, attrs);
2230
2231   if (attrs->tabs)
2232     {
2233       size = pango_tab_array_get_size (attrs->tabs);
2234       
2235       pango_tab_array_get_tabs (attrs->tabs,
2236                                 NULL,
2237                                 &positions);
2238
2239       in_pixels = pango_tab_array_get_positions_in_pixels (attrs->tabs);
2240     }
2241   else
2242     {
2243       size = 0;
2244       in_pixels = FALSE;
2245     }
2246       
2247   gtk_text_attributes_unref (attrs);
2248   
2249   i = 0;
2250   while (i < size)
2251     {
2252       gint pos;
2253
2254       if (!in_pixels)
2255         positions[i] = PANGO_PIXELS (positions[i]);
2256       
2257       gtk_text_view_buffer_to_window_coords (text_view,
2258                                              type,
2259                                              positions[i],
2260                                              0,
2261                                              &pos,
2262                                              NULL);
2263       
2264       gdk_draw_line (target, 
2265                      widget->style->fg_gc [widget->state],
2266                      pos, 0,
2267                      pos, 15); 
2268       
2269       ++i;
2270     }
2271
2272   g_free (positions);
2273
2274   return TRUE;
2275 }
2276
2277 static void
2278 get_lines (GtkTextView  *text_view,
2279            gint          first_y,
2280            gint          last_y,
2281            GArray       *buffer_coords,
2282            GArray       *numbers,
2283            gint         *countp)
2284 {
2285   GtkTextIter iter;
2286   gint count;
2287   gint size;  
2288
2289   g_array_set_size (buffer_coords, 0);
2290   g_array_set_size (numbers, 0);
2291   
2292   /* Get iter at first y */
2293   gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL);
2294
2295   /* For each iter, get its location and add it to the arrays.
2296    * Stop when we pass last_y
2297    */
2298   count = 0;
2299   size = 0;
2300
2301   while (!gtk_text_iter_is_end (&iter))
2302     {
2303       gint y, height;
2304       gint line_num;
2305       
2306       gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
2307
2308       g_array_append_val (buffer_coords, y);
2309       line_num = gtk_text_iter_get_line (&iter);
2310       g_array_append_val (numbers, line_num);
2311       
2312       ++count;
2313
2314       if ((y + height) >= last_y)
2315         break;
2316       
2317       gtk_text_iter_forward_line (&iter);
2318     }
2319
2320   *countp = count;
2321 }
2322
2323 static gint
2324 line_numbers_expose (GtkWidget      *widget,
2325                      GdkEventExpose *event,
2326                      gpointer        user_data)
2327 {
2328   gint count;
2329   GArray *numbers;
2330   GArray *pixels;
2331   gint first_y;
2332   gint last_y;
2333   gint i;
2334   GdkWindow *left_win;
2335   GdkWindow *right_win;
2336   PangoLayout *layout;
2337   GtkTextView *text_view;
2338   GtkTextWindowType type;
2339   GdkDrawable *target;
2340   
2341   text_view = GTK_TEXT_VIEW (widget);
2342   
2343   /* See if this expose is on the line numbers window */
2344   left_win = gtk_text_view_get_window (text_view,
2345                                        GTK_TEXT_WINDOW_LEFT);
2346
2347   right_win = gtk_text_view_get_window (text_view,
2348                                         GTK_TEXT_WINDOW_RIGHT);
2349
2350   if (event->window == left_win)
2351     {
2352       type = GTK_TEXT_WINDOW_LEFT;
2353       target = left_win;
2354     }
2355   else if (event->window == right_win)
2356     {
2357       type = GTK_TEXT_WINDOW_RIGHT;
2358       target = right_win;
2359     }
2360   else
2361     return FALSE;
2362   
2363   first_y = event->area.y;
2364   last_y = first_y + event->area.height;
2365
2366   gtk_text_view_window_to_buffer_coords (text_view,
2367                                          type,
2368                                          0,
2369                                          first_y,
2370                                          NULL,
2371                                          &first_y);
2372
2373   gtk_text_view_window_to_buffer_coords (text_view,
2374                                          type,
2375                                          0,
2376                                          last_y,
2377                                          NULL,
2378                                          &last_y);
2379
2380   numbers = g_array_new (FALSE, FALSE, sizeof (gint));
2381   pixels = g_array_new (FALSE, FALSE, sizeof (gint));
2382   
2383   get_lines (text_view,
2384              first_y,
2385              last_y,
2386              pixels,
2387              numbers,
2388              &count);
2389   
2390   /* Draw fully internationalized numbers! */
2391   
2392   layout = gtk_widget_create_pango_layout (widget, "");
2393   
2394   i = 0;
2395   while (i < count)
2396     {
2397       gint pos;
2398       gchar *str;
2399       
2400       gtk_text_view_buffer_to_window_coords (text_view,
2401                                              type,
2402                                              0,
2403                                              g_array_index (pixels, gint, i),
2404                                              NULL,
2405                                              &pos);
2406
2407       str = g_strdup_printf ("%d", g_array_index (numbers, gint, i));
2408
2409       pango_layout_set_text (layout, str, -1);
2410
2411       gtk_paint_layout (widget->style,
2412                         target,
2413                         GTK_WIDGET_STATE (widget),
2414                         FALSE,
2415                         NULL,
2416                         widget,
2417                         NULL,
2418                         2, pos + 2,
2419                         layout);
2420
2421       g_free (str);
2422       
2423       ++i;
2424     }
2425
2426   g_array_free (pixels, TRUE);
2427   g_array_free (numbers, TRUE);
2428   
2429   g_object_unref (layout);
2430
2431   /* don't stop emission, need to draw children */
2432   return FALSE;
2433 }
2434
2435 static View *
2436 create_view (Buffer *buffer)
2437 {
2438   View *view;
2439   
2440   GtkWidget *sw;
2441   GtkWidget *vbox;
2442   
2443   view = g_new0 (View, 1);
2444   views = g_slist_prepend (views, view);
2445
2446   view->buffer = buffer;
2447   buffer_ref (buffer);
2448   
2449   view->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2450   g_object_set_data (G_OBJECT (view->window), "view", view);
2451   
2452   g_signal_connect (view->window, "delete_event",
2453                     G_CALLBACK (delete_event_cb), NULL);
2454
2455   view->accel_group = gtk_accel_group_new ();
2456   view->item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", view->accel_group);
2457   g_object_set_data (G_OBJECT (view->item_factory), "view", view);
2458   
2459   gtk_item_factory_create_items (view->item_factory, G_N_ELEMENTS (menu_items), menu_items, view);
2460
2461   gtk_window_add_accel_group (GTK_WINDOW (view->window), view->accel_group);
2462
2463   vbox = gtk_vbox_new (FALSE, 0);
2464   gtk_container_add (GTK_CONTAINER (view->window), vbox);
2465
2466   gtk_box_pack_start (GTK_BOX (vbox),
2467                       gtk_item_factory_get_widget (view->item_factory, "<main>"),
2468                       FALSE, FALSE, 0);
2469   
2470   sw = gtk_scrolled_window_new (NULL, NULL);
2471   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2472                                  GTK_POLICY_AUTOMATIC,
2473                                  GTK_POLICY_AUTOMATIC);
2474
2475   view->text_view = gtk_text_view_new_with_buffer (buffer->buffer);
2476   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view->text_view),
2477                                GTK_WRAP_WORD);
2478
2479   /* Make sure border width works, no real reason to do this other than testing */
2480   gtk_container_set_border_width (GTK_CONTAINER (view->text_view),
2481                                   10);
2482   
2483   /* Draw tab stops in the top and bottom windows. */
2484   
2485   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2486                                         GTK_TEXT_WINDOW_TOP,
2487                                         15);
2488
2489   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2490                                         GTK_TEXT_WINDOW_BOTTOM,
2491                                         15);
2492
2493   g_signal_connect (view->text_view,
2494                     "expose_event",
2495                     G_CALLBACK (tab_stops_expose),
2496                     NULL);  
2497
2498   g_signal_connect (view->buffer->buffer,
2499                     "mark_set",
2500                     G_CALLBACK (cursor_set_callback),
2501                     view->text_view);
2502   
2503   /* Draw line numbers in the side windows; we should really be
2504    * more scientific about what width we set them to.
2505    */
2506   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2507                                         GTK_TEXT_WINDOW_RIGHT,
2508                                         30);
2509   
2510   gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view->text_view),
2511                                         GTK_TEXT_WINDOW_LEFT,
2512                                         30);
2513   
2514   g_signal_connect (view->text_view,
2515                     "expose_event",
2516                     G_CALLBACK (line_numbers_expose),
2517                     NULL);
2518   
2519   gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
2520   gtk_container_add (GTK_CONTAINER (sw), view->text_view);
2521
2522   gtk_window_set_default_size (GTK_WINDOW (view->window), 500, 500);
2523
2524   gtk_widget_grab_focus (view->text_view);
2525
2526   view_set_title (view);
2527   view_init_menus (view);
2528
2529   view_add_example_widgets (view);
2530   
2531   gtk_widget_show_all (view->window);
2532   return view;
2533 }
2534
2535 static void
2536 view_add_example_widgets (View *view)
2537 {
2538   GtkTextChildAnchor *anchor;
2539   Buffer *buffer;
2540
2541   buffer = view->buffer;
2542   
2543   anchor = g_object_get_data (G_OBJECT (buffer->buffer),
2544                               "anchor");
2545
2546   if (anchor && !gtk_text_child_anchor_get_deleted (anchor))
2547     {
2548       GtkWidget *widget;
2549
2550       widget = gtk_button_new_with_label ("Foo");
2551       
2552       gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view->text_view),
2553                                          widget,
2554                                          anchor);
2555
2556       gtk_widget_show (widget);
2557     }
2558 }
2559
2560 void
2561 test_init (void)
2562 {
2563   if (g_file_test ("../gdk-pixbuf/libpixbufloader-pnm.la",
2564                    G_FILE_TEST_EXISTS))
2565     {
2566       g_setenv ("GDK_PIXBUF_MODULE_FILE", "../gdk-pixbuf/gdk-pixbuf.loaders", TRUE);
2567       g_setenv ("GTK_IM_MODULE_FILE", "../modules/input/gtk.immodules", TRUE);
2568     }
2569 }
2570
2571 int
2572 main (int argc, char** argv)
2573 {
2574   Buffer *buffer;
2575   View *view;
2576   int i;
2577
2578   test_init ();
2579   gtk_init (&argc, &argv);
2580   
2581   buffer = create_buffer ();
2582   view = create_view (buffer);
2583   buffer_unref (buffer);
2584   
2585   push_active_window (GTK_WINDOW (view->window));
2586   for (i=1; i < argc; i++)
2587     {
2588       char *filename;
2589
2590       /* Quick and dirty canonicalization - better should be in GLib
2591        */
2592
2593       if (!g_path_is_absolute (argv[i]))
2594         {
2595           char *cwd = g_get_current_dir ();
2596           filename = g_strconcat (cwd, "/", argv[i], NULL);
2597           g_free (cwd);
2598         }
2599       else
2600         filename = argv[i];
2601
2602       open_ok_func (filename, view);
2603
2604       if (filename != argv[i])
2605         g_free (filename);
2606     }
2607   pop_active_window ();
2608   
2609   gtk_main ();
2610
2611   return 0;
2612 }
2613
2614