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