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