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