]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/main.c
Protect getc_unlocked() by HAVE_FLOCKFILE in all cases. (#134205, Julio M.
[~andy/gtk] / demos / gtk-demo / main.c
1 #include <errno.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 #include <gtk/gtk.h>
7
8 #include <demos.h>
9
10 static GtkTextBuffer *info_buffer;
11 static GtkTextBuffer *source_buffer;
12
13 static gchar *current_file = NULL;
14
15
16 enum {
17   TITLE_COLUMN,
18   FILENAME_COLUMN,
19   FUNC_COLUMN,
20   ITALIC_COLUMN,
21   NUM_COLUMNS
22 };
23
24 typedef struct _CallbackData CallbackData;
25 struct _CallbackData
26 {
27   GtkTreeModel *model;
28   GtkTreePath *path;
29 };
30
31 #ifdef G_OS_WIN32
32
33 #undef DEMOCODEDIR
34
35 static char *
36 get_democodedir (void)
37 {
38   static char *result = NULL;
39
40   if (result == NULL)
41     {
42       result = g_win32_get_package_installation_directory (NULL, NULL);
43       if (result == NULL)
44         result = "unknown-location";
45
46       result = g_strconcat (result, "\\share\\gtk-2.0\\demo", NULL);
47     }
48
49   return result;
50 }
51
52 #define DEMOCODEDIR get_democodedir ()
53
54 #endif
55
56 /**
57  * demo_find_file:
58  * @base: base filename
59  * @err:  location to store error, or %NULL.
60  * 
61  * Looks for @base first in the current directory, then in the
62  * location GTK+ where it will be installed on make install,
63  * returns the first file found.
64  * 
65  * Return value: the filename, if found or %NULL
66  **/
67 gchar *
68 demo_find_file (const char *base,
69                 GError    **err)
70 {
71   g_return_val_if_fail (err == NULL || *err == NULL, NULL);
72   
73   if (g_file_test ("gtk-logo-rgb.gif", G_FILE_TEST_EXISTS) &&
74       g_file_test (base, G_FILE_TEST_EXISTS))
75     return g_strdup (base);
76   else
77     {
78       char *filename = g_build_filename (DEMOCODEDIR, base, NULL);
79       if (!g_file_test (filename, G_FILE_TEST_EXISTS))
80         {
81           g_set_error (err, G_FILE_ERROR, G_FILE_ERROR_NOENT,
82                        "Cannot find demo data file \"%s\"", base);
83           g_free (filename);
84           return NULL;
85         }
86       return filename;
87     }
88 }
89
90 static void
91 window_closed_cb (GtkWidget *window, gpointer data)
92 {
93   CallbackData *cbdata = data;
94   GtkTreeIter iter;
95   gboolean italic;
96
97   gtk_tree_model_get_iter (cbdata->model, &iter, cbdata->path);
98   gtk_tree_model_get (GTK_TREE_MODEL (cbdata->model), &iter,
99                       ITALIC_COLUMN, &italic,
100                       -1);
101   if (italic)
102     gtk_tree_store_set (GTK_TREE_STORE (cbdata->model), &iter,
103                         ITALIC_COLUMN, !italic,
104                         -1);
105
106   gtk_tree_path_free (cbdata->path);
107   g_free (cbdata);
108 }
109
110 gboolean
111 read_line (FILE *stream, GString *str)
112 {
113   int n_read = 0;
114   
115 #ifdef HAVE_FLOCKFILE
116   flockfile (stream);
117 #endif
118
119   g_string_truncate (str, 0);
120   
121   while (1)
122     {
123       int c;
124       
125 #ifndef HAVE_FLOCKFILE
126       c = getc_unlocked (stream);
127 #else
128       c = getc (stream);
129 #endif
130
131       if (c == EOF)
132         goto done;
133       else
134         n_read++;
135
136       switch (c)
137         {
138         case '\r':
139         case '\n':
140           {
141 #ifdef HAVE_FLOCKFILE
142             int next_c = getc_unlocked (stream);
143 #else
144             int next_c = getc (stream);
145 #endif
146             
147             if (!(next_c == EOF ||
148                   (c == '\r' && next_c == '\n') ||
149                   (c == '\n' && next_c == '\r')))
150               ungetc (next_c, stream);
151             
152             goto done;
153           }
154         default:
155           g_string_append_c (str, c);
156         }
157     }
158
159  done:
160
161 #ifdef HAVE_FLOCKFILE
162   funlockfile (stream);
163 #endif
164
165   return n_read > 0;
166 }
167
168
169 /* Stupid syntax highlighting.
170  *
171  * No regex was used in the making of this highlighting.
172  * It should only work for simple cases.  This is good, as
173  * that's all we should have in the demos.
174  */
175 /* This code should not be used elsewhere, except perhaps as an example of how
176  * to iterate through a text buffer.
177  */
178 enum {
179   STATE_NORMAL,
180   STATE_IN_COMMENT
181 };
182
183 static gchar *tokens[] =
184 {
185   "/*",
186   "\"",
187   NULL
188 };
189
190 static gchar *types[] =
191 {
192   "static",
193   "const ",
194   "void",
195   "gint",
196   "int ",
197   "char ",
198   "gchar ",
199   "gfloat",
200   "float",
201   "gint8",
202   "gint16",
203   "gint32",
204   "guint",
205   "guint8",
206   "guint16",
207   "guint32",
208   "guchar",
209   "glong",
210   "gboolean" ,
211   "gshort",
212   "gushort",
213   "gulong",
214   "gdouble",
215   "gldouble",
216   "gpointer",
217   "NULL",
218   "GList",
219   "GSList",
220   "FALSE",
221   "TRUE",
222   "FILE ",
223   "GtkObject ",
224   "GtkColorSelection ",
225   "GtkWidget ",
226   "GtkButton ",
227   "GdkColor ",
228   "GdkRectangle ",
229   "GdkEventExpose ",
230   "GdkGC ",
231   "GdkPixbufLoader ",
232   "GdkPixbuf ",
233   "GError",
234   "size_t",
235   NULL
236 };
237
238 static gchar *control[] =
239 {
240   " if ",
241   " while ",
242   " else",
243   " do ",
244   " for ",
245   "?",
246   ":",
247   "return ",
248   "goto ",
249   NULL
250 };
251 void
252 parse_chars (gchar     *text,
253              gchar    **end_ptr,
254              gint      *state,
255              gchar    **tag,
256              gboolean   start)
257 {
258   gint i;
259   gchar *next_token;
260
261   /* Handle comments first */
262   if (*state == STATE_IN_COMMENT)
263     {
264       *end_ptr = strstr (text, "*/");
265       if (*end_ptr)
266         {
267           *end_ptr += 2;
268           *state = STATE_NORMAL;
269           *tag = "comment";
270         }
271       return;
272     }
273
274   *tag = NULL;
275   *end_ptr = NULL;
276
277   /* check for comment */
278   if (!strncmp (text, "/*", 2))
279     {
280       *end_ptr = strstr (text, "*/");
281       if (*end_ptr)
282         *end_ptr += 2;
283       else
284         *state = STATE_IN_COMMENT;
285       *tag = "comment";
286       return;
287     }
288
289   /* check for preprocessor defines */
290   if (*text == '#' && start)
291     {
292       *end_ptr = NULL;
293       *tag = "preprocessor";
294       return;
295     }
296
297   /* functions */
298   if (start && * text != '\t' && *text != ' ' && *text != '{' && *text != '}')
299     {
300       if (strstr (text, "("))
301         {
302           *end_ptr = strstr (text, "(");
303           *tag = "function";
304           return;
305         }
306     }
307   /* check for types */
308   for (i = 0; types[i] != NULL; i++)
309     if (!strncmp (text, types[i], strlen (types[i])))
310       {
311         *end_ptr = text + strlen (types[i]);
312         *tag = "type";
313         return;
314       }
315
316   /* check for control */
317   for (i = 0; control[i] != NULL; i++)
318     if (!strncmp (text, control[i], strlen (control[i])))
319       {
320         *end_ptr = text + strlen (control[i]);
321         *tag = "control";
322         return;
323       }
324
325   /* check for string */
326   if (text[0] == '"')
327     {
328       gint maybe_escape = FALSE;
329
330       *end_ptr = text + 1;
331       *tag = "string";
332       while (**end_ptr != '\000')
333         {
334           if (**end_ptr == '\"' && !maybe_escape)
335             {
336               *end_ptr += 1;
337               return;
338             }
339           if (**end_ptr == '\\')
340             maybe_escape = TRUE;
341           else
342             maybe_escape = FALSE;
343           *end_ptr += 1;
344         }
345       return;
346     }
347
348   /* not at the start of a tag.  Find the next one. */
349   for (i = 0; tokens[i] != NULL; i++)
350     {
351       next_token = strstr (text, tokens[i]);
352       if (next_token)
353         {
354           if (*end_ptr)
355             *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
356           else
357             *end_ptr = next_token;
358         }
359     }
360
361   for (i = 0; types[i] != NULL; i++)
362     {
363       next_token = strstr (text, types[i]);
364       if (next_token)
365         {
366           if (*end_ptr)
367             *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
368           else
369             *end_ptr = next_token;
370         }
371     }
372
373   for (i = 0; control[i] != NULL; i++)
374     {
375       next_token = strstr (text, control[i]);
376       if (next_token)
377         {
378           if (*end_ptr)
379             *end_ptr = (*end_ptr<next_token)?*end_ptr:next_token;
380           else
381             *end_ptr = next_token;
382         }
383     }
384 }
385
386 /* While not as cool as c-mode, this will do as a quick attempt at highlighting */
387 static void
388 fontify ()
389 {
390   GtkTextIter start_iter, next_iter, tmp_iter;
391   gint state;
392   gchar *text;
393   gchar *start_ptr, *end_ptr;
394   gchar *tag;
395
396   state = STATE_NORMAL;
397
398   gtk_text_buffer_get_iter_at_offset (source_buffer, &start_iter, 0);
399
400   next_iter = start_iter;
401   while (gtk_text_iter_forward_line (&next_iter))
402     {
403       gboolean start = TRUE;
404       start_ptr = text = gtk_text_iter_get_text (&start_iter, &next_iter);
405
406       do
407         {
408           parse_chars (start_ptr, &end_ptr, &state, &tag, start);
409
410           start = FALSE;
411           if (end_ptr)
412             {
413               tmp_iter = start_iter;
414               gtk_text_iter_forward_chars (&tmp_iter, end_ptr - start_ptr);
415             }
416           else
417             {
418               tmp_iter = next_iter;
419             }
420           if (tag)
421             gtk_text_buffer_apply_tag_by_name (source_buffer, tag, &start_iter, &tmp_iter);
422
423           start_iter = tmp_iter;
424           start_ptr = end_ptr;
425         }
426       while (end_ptr);
427
428       g_free (text);
429       start_iter = next_iter;
430     }
431 }
432
433 void
434 load_file (const gchar *filename)
435 {
436   FILE *file;
437   GtkTextIter start, end;
438   char *full_filename;
439   GError *err = NULL;
440   GString *buffer = g_string_new (NULL);
441   int state = 0;
442   gboolean in_para = 0;
443
444   if (current_file && !strcmp (current_file, filename))
445     {
446       g_string_free (buffer, TRUE);
447       return;
448     }
449
450   g_free (current_file);
451   current_file = g_strdup (filename);
452   
453   gtk_text_buffer_get_bounds (info_buffer, &start, &end);
454   gtk_text_buffer_delete (info_buffer, &start, &end);
455
456   gtk_text_buffer_get_bounds (source_buffer, &start, &end);
457   gtk_text_buffer_delete (source_buffer, &start, &end);
458
459   full_filename = demo_find_file (filename, &err);
460   if (!full_filename)
461     {
462       g_warning ("%s", err->message);
463       g_error_free (err);
464       return;
465     }
466
467   file = fopen (full_filename, "r");
468
469   if (!file)
470     g_warning ("Cannot open %s: %s\n", full_filename, g_strerror (errno));
471
472   g_free (full_filename);
473
474   if (!file)
475     return;
476
477   gtk_text_buffer_get_iter_at_offset (info_buffer, &start, 0);
478   while (read_line (file, buffer))
479     {
480       gchar *p = buffer->str;
481       gchar *q;
482       gchar *r;
483       
484       switch (state)
485         {
486         case 0:
487           /* Reading title */
488           while (*p == '/' || *p == '*' || g_ascii_isspace (*p))
489             p++;
490           r = p;
491           while (*r != '/' && strlen (r))
492             r++;
493           if (strlen (r) > 0)
494             p = r + 1;
495           q = p + strlen (p);
496           while (q > p && g_ascii_isspace (*(q - 1)))
497             q--;
498
499           if (q > p)
500             {
501               int len_chars = g_utf8_pointer_to_offset (p, q);
502
503               end = start;
504
505               g_assert (strlen (p) >= q - p);
506               gtk_text_buffer_insert (info_buffer, &end, p, q - p);
507               start = end;
508
509               gtk_text_iter_backward_chars (&start, len_chars);
510               gtk_text_buffer_apply_tag_by_name (info_buffer, "title", &start, &end);
511
512               start = end;
513               
514               state++;
515             }
516           break;
517             
518         case 1:
519           /* Reading body of info section */
520           while (g_ascii_isspace (*p))
521             p++;
522           if (*p == '*' && *(p + 1) == '/')
523             {
524               gtk_text_buffer_get_iter_at_offset (source_buffer, &start, 0);
525               state++;
526             }
527           else
528             {
529               int len;
530               
531               while (*p == '*' || g_ascii_isspace (*p))
532                 p++;
533
534               len = strlen (p);
535               while (g_ascii_isspace (*(p + len - 1)))
536                 len--;
537               
538               if (len > 0)
539                 {
540                   if (in_para)
541                     gtk_text_buffer_insert (info_buffer, &start, " ", 1);
542
543                   g_assert (strlen (p) >= len);
544                   gtk_text_buffer_insert (info_buffer, &start, p, len);
545                   in_para = 1;
546                 }
547               else
548                 {
549                   gtk_text_buffer_insert (info_buffer, &start, "\n", 1);
550                   in_para = 0;
551                 }
552             }
553           break;
554
555         case 2:
556           /* Skipping blank lines */
557           while (g_ascii_isspace (*p))
558             p++;
559           if (*p)
560             {
561               p = buffer->str;
562               state++;
563               /* Fall through */
564             }
565           else
566             break;
567           
568         case 3:
569           /* Reading program body */
570           gtk_text_buffer_insert (source_buffer, &start, p, -1);
571           gtk_text_buffer_insert (source_buffer, &start, "\n", 1);
572           break;
573         }
574     }
575
576   fontify ();
577
578   g_string_free (buffer, TRUE);
579 }
580
581 void
582 row_activated_cb (GtkTreeView       *tree_view,
583                   GtkTreePath       *path,
584                   GtkTreeViewColumn *column)
585 {
586   GtkTreeIter iter;
587   gboolean italic;
588   GDoDemoFunc func;
589   GtkWidget *window;
590   GtkTreeModel *model;
591
592   model = gtk_tree_view_get_model (tree_view);
593   
594   gtk_tree_model_get_iter (model, &iter, path);
595   gtk_tree_model_get (GTK_TREE_MODEL (model),
596                       &iter,
597                       FUNC_COLUMN, &func,
598                       ITALIC_COLUMN, &italic,
599                       -1);
600
601   if (func)
602     {
603       gtk_tree_store_set (GTK_TREE_STORE (model),
604                           &iter,
605                           ITALIC_COLUMN, !italic,
606                           -1);
607       window = (func) (gtk_widget_get_toplevel (GTK_WIDGET (tree_view)));
608       
609       if (window != NULL)
610         {
611           CallbackData *cbdata;
612           
613           cbdata = g_new (CallbackData, 1);
614           cbdata->model = model;
615           cbdata->path = gtk_tree_path_copy (path);
616           
617           g_signal_connect (window, "destroy",
618                             G_CALLBACK (window_closed_cb), cbdata);
619         }
620     }
621 }
622
623 static void
624 selection_cb (GtkTreeSelection *selection,
625               GtkTreeModel     *model)
626 {
627   GtkTreeIter iter;
628   GValue value = {0, };
629
630   if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
631     return;
632
633   gtk_tree_model_get_value (model, &iter,
634                             FILENAME_COLUMN,
635                             &value);
636   if (g_value_get_string (&value))
637     load_file (g_value_get_string (&value));
638   g_value_unset (&value);
639 }
640
641 static GtkWidget *
642 create_text (GtkTextBuffer **buffer,
643              gboolean        is_source)
644 {
645   GtkWidget *scrolled_window;
646   GtkWidget *text_view;
647   PangoFontDescription *font_desc;
648
649   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
650   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
651                                   GTK_POLICY_AUTOMATIC,
652                                   GTK_POLICY_AUTOMATIC);
653   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
654                                        GTK_SHADOW_IN);
655   
656   text_view = gtk_text_view_new ();
657   
658   *buffer = gtk_text_buffer_new (NULL);
659   gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), *buffer);
660   gtk_text_view_set_editable (GTK_TEXT_VIEW (text_view), FALSE);
661   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (text_view), FALSE);
662
663   gtk_container_add (GTK_CONTAINER (scrolled_window), text_view);
664   
665   if (is_source)
666     {
667       font_desc = pango_font_description_from_string ("Courier 12");
668       gtk_widget_modify_font (text_view, font_desc);
669       pango_font_description_free (font_desc);
670
671       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view),
672                                    GTK_WRAP_NONE);
673     }
674   else
675     {
676       /* Make it a bit nicer for text. */
677       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view),
678                                    GTK_WRAP_WORD);
679       gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (text_view),
680                                             2);
681       gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (text_view),
682                                             2);
683     }
684   
685   return scrolled_window;
686 }
687
688 static GtkWidget *
689 create_tree (void)
690 {
691   GtkTreeSelection *selection;
692   GtkCellRenderer *cell;
693   GtkWidget *tree_view;
694   GtkTreeViewColumn *column;
695   GtkTreeStore *model;
696   GtkTreeIter iter;
697
698   Demo *d = testgtk_demos;
699
700   model = gtk_tree_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);
701   tree_view = gtk_tree_view_new ();
702   gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
703   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
704
705   gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection),
706                                GTK_SELECTION_BROWSE);
707   gtk_widget_set_size_request (tree_view, 200, -1);
708
709   /* this code only supports 1 level of children. If we
710    * want more we probably have to use a recursing function.
711    */
712   while (d->title)
713     {
714       Demo *children = d->children;
715
716       gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
717
718       gtk_tree_store_set (GTK_TREE_STORE (model),
719                           &iter,
720                           TITLE_COLUMN, d->title,
721                           FILENAME_COLUMN, d->filename,
722                           FUNC_COLUMN, d->func,
723                           ITALIC_COLUMN, FALSE,
724                           -1);
725
726       d++;
727
728       if (!children)
729         continue;
730       
731       while (children->title)
732         {
733           GtkTreeIter child_iter;
734
735           gtk_tree_store_append (GTK_TREE_STORE (model), &child_iter, &iter);
736           
737           gtk_tree_store_set (GTK_TREE_STORE (model),
738                               &child_iter,
739                               TITLE_COLUMN, children->title,
740                               FILENAME_COLUMN, children->filename,
741                               FUNC_COLUMN, children->func,
742                               ITALIC_COLUMN, FALSE,
743                               -1);
744           
745           children++;
746         }
747     }
748
749   cell = gtk_cell_renderer_text_new ();
750
751   g_object_set (cell,
752                 "style", PANGO_STYLE_ITALIC,
753                 NULL);
754   
755   column = gtk_tree_view_column_new_with_attributes ("Widget (double click for demo)",
756                                                      cell,
757                                                      "text", TITLE_COLUMN,
758                                                      "style_set", ITALIC_COLUMN,
759                                                      NULL);
760   
761   gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
762                                GTK_TREE_VIEW_COLUMN (column));
763
764   g_signal_connect (selection, "changed", G_CALLBACK (selection_cb), model);
765   g_signal_connect (tree_view, "row_activated", G_CALLBACK (row_activated_cb), model);
766
767   gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));
768   return tree_view;
769 }
770
771 static void
772 setup_default_icon (void)
773 {
774   GdkPixbuf *pixbuf;
775   char *filename;
776   GError *err;
777
778   err = NULL;
779
780   pixbuf = NULL;
781   filename = demo_find_file ("gtk-logo-rgb.gif", &err);
782   if (filename)
783     {
784       pixbuf = gdk_pixbuf_new_from_file (filename, &err);
785       g_free (filename);
786     }
787
788   /* Ignoring this error (passing NULL instead of &err above)
789    * would probably be reasonable for most apps.  We're just
790    * showing off.
791    */
792   if (err)
793     {
794       GtkWidget *dialog;
795       
796       dialog = gtk_message_dialog_new (NULL, 0,
797                                        GTK_MESSAGE_ERROR,
798                                        GTK_BUTTONS_CLOSE,
799                                        "Failed to read icon file: %s",
800                                        err->message);
801       g_error_free (err);
802
803       g_signal_connect (dialog, "response",
804                         G_CALLBACK (gtk_widget_destroy), NULL);
805     }
806
807   if (pixbuf)
808     {
809       GList *list;      
810       GdkPixbuf *transparent;
811
812       /* The gtk-logo-rgb icon has a white background, make it transparent */
813       transparent = gdk_pixbuf_add_alpha (pixbuf, TRUE, 0xff, 0xff, 0xff);
814
815       list = NULL;
816       list = g_list_append (list, transparent);
817       gtk_window_set_default_icon_list (list);
818       g_list_free (list);
819       g_object_unref (pixbuf);
820       g_object_unref (transparent);
821     }
822 }
823
824 int
825 main (int argc, char **argv)
826 {
827   GtkWidget *window;
828   GtkWidget *notebook;
829   GtkWidget *hbox;
830   GtkWidget *tree;
831   GtkTextTag *tag;
832
833   /* Most code in gtk-demo is intended to be exemplary, but not
834    * these few lines, which are just a hack so gtk-demo will work
835    * in the GTK tree without installing it.
836    */
837   if (g_file_test ("../../gdk-pixbuf/libpixbufloader-pnm.la",
838                    G_FILE_TEST_EXISTS))
839     {
840       g_setenv ("GDK_PIXBUF_MODULE_FILE", "../../gdk-pixbuf/gdk-pixbuf.loaders", TRUE);
841       g_setenv ("GTK_IM_MODULE_FILE", "../../modules/input/gtk.immodules", TRUE);
842     }
843   /* -- End of hack -- */
844   
845   gtk_init (&argc, &argv);
846
847   setup_default_icon ();
848   
849   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
850   gtk_window_set_title (GTK_WINDOW (window), "GTK+ Code Demos");
851   g_signal_connect (window, "destroy",
852                     G_CALLBACK (gtk_main_quit), NULL);
853
854   hbox = gtk_hbox_new (FALSE, 0);
855   gtk_container_add (GTK_CONTAINER (window), hbox);
856
857   tree = create_tree ();
858   gtk_box_pack_start (GTK_BOX (hbox), tree, FALSE, FALSE, 0);
859
860   notebook = gtk_notebook_new ();
861   gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
862
863   gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
864                             create_text (&info_buffer, FALSE),
865                             gtk_label_new_with_mnemonic ("_Info"));
866
867   gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
868                             create_text (&source_buffer, TRUE),
869                             gtk_label_new_with_mnemonic ("_Source"));
870
871   tag = gtk_text_buffer_create_tag (info_buffer, "title",
872                                     "font", "Sans 18",
873                                     NULL);
874
875   tag = gtk_text_buffer_create_tag (source_buffer, "comment",
876                                     "foreground", "red",
877                                     NULL);
878   tag = gtk_text_buffer_create_tag (source_buffer, "type",
879                                     "foreground", "ForestGreen",
880                                     NULL);
881   tag = gtk_text_buffer_create_tag (source_buffer, "string",
882                                     "foreground", "RosyBrown",
883                                     "weight", PANGO_WEIGHT_BOLD,
884                                     NULL);
885   tag = gtk_text_buffer_create_tag (source_buffer, "control",
886                                     "foreground", "purple",
887                                     NULL);
888   tag = gtk_text_buffer_create_tag (source_buffer, "preprocessor",
889                                     "style", PANGO_STYLE_OBLIQUE,
890                                     "foreground", "burlywood4",
891                                     NULL);
892   tag = gtk_text_buffer_create_tag (source_buffer, "function",
893                                     "weight", PANGO_WEIGHT_BOLD,
894                                     "foreground", "DarkGoldenrod4",
895                                     NULL);
896   
897   gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
898   gtk_widget_show_all (window);
899   
900
901   load_file (testgtk_demos[0].filename);
902   
903   gtk_main ();
904
905   return 0;
906 }