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