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