1/* GTK Demo
2 *
3 * GTK Demo is a collection of useful examples to demonstrate
4 * GTK widgets and features. It is a useful example in itself.
5 *
6 * You can select examples in the sidebar or search for them by
7 * typing a search term. Double-clicking or hitting the “Run” button
8 * will run the demo. The source code and other resources used in the
9 * demo are shown in this area.
10 *
11 * You can also use the GTK Inspector, available from the menu on the
12 * top right, to poke at the running demos, and see how they are put
13 * together.
14 */
15#include <errno.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include "config.h"
21
22#include <gtk/gtk.h>
23#include <glib/gstdio.h>
24
25#include "demos.h"
26#include "fontify.h"
27
28#include "demo_conf.h"
29
30static GtkWidget *info_view;
31static GtkWidget *source_view;
32
33static char *current_file = NULL;
34
35static GtkWidget *notebook;
36static GtkSingleSelection *selection;
37static GtkWidget *toplevel;
38static char **search_needle;
39
40typedef struct _GtkDemo GtkDemo;
41struct _GtkDemo
42{
43 GObject parent_instance;
44
45 const char *name;
46 const char *title;
47 const char **keywords;
48 const char *filename;
49 GDoDemoFunc func;
50 GListModel *children_model;
51};
52
53enum {
54 PROP_0,
55 PROP_FILENAME,
56 PROP_NAME,
57 PROP_TITLE,
58 PROP_KEYWORDS,
59
60 N_PROPS
61};
62
63# define GTK_TYPE_DEMO (gtk_demo_get_type ())
64G_DECLARE_FINAL_TYPE (GtkDemo, gtk_demo, GTK, DEMO, GObject);
65
66G_DEFINE_TYPE (GtkDemo, gtk_demo, G_TYPE_OBJECT);
67static GParamSpec *properties[N_PROPS] = { NULL, };
68
69static void
70gtk_demo_get_property (GObject *object,
71 guint property_id,
72 GValue *value,
73 GParamSpec *pspec)
74{
75 GtkDemo *self = GTK_DEMO (ptr: object);
76
77 switch (property_id)
78 {
79 case PROP_FILENAME:
80 g_value_set_string (value, v_string: self->filename);
81 break;
82
83 case PROP_NAME:
84 g_value_set_string (value, v_string: self->name);
85 break;
86
87 case PROP_TITLE:
88 g_value_set_string (value, v_string: self->title);
89 break;
90
91 case PROP_KEYWORDS:
92 g_value_set_boxed (value, v_boxed: self->keywords);
93 break;
94
95 default:
96 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
97 break;
98 }
99}
100
101static void gtk_demo_class_init (GtkDemoClass *klass)
102{
103 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
104
105 gobject_class->get_property = gtk_demo_get_property;
106
107 properties[PROP_FILENAME] =
108 g_param_spec_string (name: "filename",
109 nick: "filename",
110 blurb: "filename",
111 NULL,
112 flags: G_PARAM_READABLE);
113 properties[PROP_NAME] =
114 g_param_spec_string (name: "name",
115 nick: "name",
116 blurb: "name",
117 NULL,
118 flags: G_PARAM_READABLE);
119 properties[PROP_TITLE] =
120 g_param_spec_string (name: "title",
121 nick: "title",
122 blurb: "title",
123 NULL,
124 flags: G_PARAM_READABLE);
125 properties[PROP_KEYWORDS] =
126 g_param_spec_string (name: "keywords",
127 nick: "keywords",
128 blurb: "keywords",
129 NULL,
130 flags: G_PARAM_READABLE);
131
132 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
133}
134
135static void gtk_demo_init (GtkDemo *self)
136{
137}
138
139typedef struct _CallbackData CallbackData;
140struct _CallbackData
141{
142 GtkTreeModel *model;
143 GtkTreePath *path;
144};
145
146static gboolean
147gtk_demo_run (GtkDemo *self,
148 GtkWidget *window)
149{
150 GtkWidget *result;
151
152 if (!self->func)
153 return FALSE;
154
155 result = self->func (window);
156 if (result == NULL)
157 return FALSE;
158
159 if (GTK_IS_WINDOW (result))
160 {
161 gtk_window_set_transient_for (GTK_WINDOW (result), GTK_WINDOW (window));
162 gtk_window_set_modal (GTK_WINDOW (result), TRUE);
163 }
164 return TRUE;
165}
166
167static void
168activate_about (GSimpleAction *action,
169 GVariant *parameter,
170 gpointer user_data)
171{
172 GtkApplication *app = user_data;
173 const char *authors[] = {
174 "The GTK Team",
175 NULL
176 };
177 char *version;
178 char *os_name;
179 char *os_version;
180 GString *s;
181
182 s = g_string_new (init: "");
183
184 os_name = g_get_os_info (G_OS_INFO_KEY_NAME);
185 os_version = g_get_os_info (G_OS_INFO_KEY_VERSION_ID);
186 if (os_name && os_version)
187 g_string_append_printf (string: s, format: "OS\t%s %s\n\n", os_name, os_version);
188 g_string_append (string: s, val: "System libraries\n");
189 g_string_append_printf (string: s, format: "\tGLib\t%d.%d.%d\n",
190 glib_major_version,
191 glib_minor_version,
192 glib_micro_version);
193 g_string_append_printf (string: s, format: "\tPango\t%s\n",
194 pango_version_string ());
195 g_string_append_printf (string: s, format: "\tGTK \t%d.%d.%d\n",
196 gtk_get_major_version (),
197 gtk_get_minor_version (),
198 gtk_get_micro_version ());
199 g_string_append_printf (string: s, format: "\nA link can appear here: <http://www.gtk.org>");
200
201 version = g_strdup_printf (format: "%s%s%s\nRunning against GTK %d.%d.%d",
202 PACKAGE_VERSION,
203 g_strcmp0 (str1: PROFILE, str2: "devel") == 0 ? "-" : "",
204 g_strcmp0 (str1: PROFILE, str2: "devel") == 0 ? VCS_TAG : "",
205 gtk_get_major_version (),
206 gtk_get_minor_version (),
207 gtk_get_micro_version ());
208
209 gtk_show_about_dialog (GTK_WINDOW (gtk_application_get_active_window (app)),
210 first_property_name: "program-name", g_strcmp0 (str1: PROFILE, str2: "devel") == 0
211 ? "GTK Demo (Development)"
212 : "GTK Demo",
213 "version", version,
214 "copyright", "© 1997—2021 The GTK Team",
215 "license-type", GTK_LICENSE_LGPL_2_1,
216 "website", "http://www.gtk.org",
217 "comments", "Program to demonstrate GTK widgets",
218 "authors", authors,
219 "logo-icon-name", "org.gtk.Demo4",
220 "title", "About GTK Demo",
221 "system-information", s->str,
222 NULL);
223
224 g_string_free (string: s, TRUE);
225 g_free (mem: version);
226 g_free (mem: os_name);
227 g_free (mem: os_version);
228}
229
230static void
231activate_quit (GSimpleAction *action,
232 GVariant *parameter,
233 gpointer user_data)
234{
235 GtkApplication *app = user_data;
236 GtkWidget *win;
237 GList *list, *next;
238
239 list = gtk_application_get_windows (application: app);
240 while (list)
241 {
242 win = list->data;
243 next = list->next;
244
245 gtk_window_destroy (GTK_WINDOW (win));
246
247 list = next;
248 }
249}
250
251static void
252activate_inspector (GSimpleAction *action,
253 GVariant *parameter,
254 gpointer user_data)
255{
256 gtk_window_set_interactive_debugging (TRUE);
257}
258
259static void
260activate_run (GSimpleAction *action,
261 GVariant *parameter,
262 gpointer window)
263{
264 GtkTreeListRow *row = gtk_single_selection_get_selected_item (self: selection);
265 GtkDemo *demo = gtk_tree_list_row_get_item (self: row);
266
267 gtk_demo_run (self: demo, window);
268}
269
270static GtkWidget *
271display_image (const char *format,
272 const char *resource,
273 char **label)
274{
275 GtkWidget *sw, *image;
276
277 image = gtk_picture_new_for_resource (resource_path: resource);
278 gtk_widget_set_halign (widget: image, align: GTK_ALIGN_CENTER);
279 gtk_widget_set_valign (widget: image, align: GTK_ALIGN_CENTER);
280 sw = gtk_scrolled_window_new ();
281 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: image);
282
283 return sw;
284}
285
286static GtkWidget *
287display_images (const char *format,
288 const char *resource_dir,
289 char **label)
290{
291 char **resources;
292 GtkWidget *grid;
293 GtkWidget *sw;
294 GtkWidget *widget;
295 guint i;
296
297 resources = g_resources_enumerate_children (path: resource_dir, lookup_flags: 0, NULL);
298 if (resources == NULL)
299 return NULL;
300
301 sw = gtk_scrolled_window_new ();
302 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
303 hscrollbar_policy: GTK_POLICY_NEVER,
304 vscrollbar_policy: GTK_POLICY_AUTOMATIC);
305 grid = gtk_flow_box_new ();
306 gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (grid), mode: GTK_SELECTION_NONE);
307 gtk_widget_set_valign (widget: grid, align: GTK_ALIGN_START);
308 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: grid);
309
310 for (i = 0; resources[i]; i++)
311 {
312 char *resource_name;
313 GtkWidget *box;
314
315 resource_name = g_strconcat (string1: resource_dir, "/", resources[i], NULL);
316
317 widget = display_image (NULL, resource: resource_name, NULL);
318 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
319 gtk_box_append (GTK_BOX (box), child: widget);
320 gtk_box_append (GTK_BOX (box), child: gtk_label_new (str: resources[i]));
321 gtk_flow_box_insert (GTK_FLOW_BOX (grid), widget: box, position: -1);
322
323 g_free (mem: resource_name);
324 }
325
326 g_strfreev (str_array: resources);
327
328 *label = g_strdup (str: "Images");
329
330 return sw;
331}
332
333static GtkWidget *
334display_text (const char *format,
335 const char *resource,
336 char **label)
337{
338 GtkTextBuffer *buffer;
339 GtkWidget *textview, *sw;
340 GBytes *bytes;
341 const char *text;
342 gsize len;
343
344 bytes = g_resources_lookup_data (path: resource, lookup_flags: 0, NULL);
345 g_assert (bytes);
346 text = g_bytes_get_data (bytes, size: &len);
347 g_assert (g_utf8_validate (text, len, NULL));
348
349 textview = gtk_text_view_new ();
350 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), left_margin: 20);
351 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), right_margin: 20);
352 gtk_text_view_set_top_margin (GTK_TEXT_VIEW (textview), top_margin: 20);
353 gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (textview), bottom_margin: 20);
354 gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
355 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (textview), FALSE);
356 /* Make it a bit nicer for text. */
357 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), wrap_mode: GTK_WRAP_WORD);
358 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (textview), pixels_above_lines: 2);
359 gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (textview), pixels_below_lines: 2);
360 gtk_text_view_set_monospace (GTK_TEXT_VIEW (textview), TRUE);
361
362 buffer = gtk_text_buffer_new (NULL);
363 gtk_text_buffer_set_text (buffer, text, len);
364 g_bytes_unref (bytes);
365
366 if (format)
367 fontify (format, buffer);
368
369 gtk_text_view_set_buffer (GTK_TEXT_VIEW (textview), buffer);
370
371 sw = gtk_scrolled_window_new ();
372 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
373 hscrollbar_policy: GTK_POLICY_AUTOMATIC,
374 vscrollbar_policy: GTK_POLICY_AUTOMATIC);
375 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: textview);
376
377 return sw;
378}
379
380static GtkWidget *
381display_video (const char *format,
382 const char *resource,
383 char **label)
384{
385 GtkWidget *video;
386
387 video = gtk_video_new_for_resource (resource_path: resource);
388 gtk_video_set_loop (self: GTK_VIDEO (ptr: video), TRUE);
389
390 return video;
391}
392
393static GtkWidget *
394display_nothing (const char *resource)
395{
396 GtkWidget *widget;
397 char *str;
398
399 str = g_strdup_printf (format: "The contents of the resource at '%s' cannot be displayed", resource);
400 widget = gtk_label_new (str);
401 gtk_label_set_wrap (GTK_LABEL (widget), TRUE);
402
403 g_free (mem: str);
404
405 return widget;
406}
407
408static struct {
409 const char *extension;
410 const char *format;
411 GtkWidget * (* display_func) (const char *format,
412 const char *resource,
413 char **label);
414} display_funcs[] = {
415 { ".gif", NULL, display_image },
416 { ".jpg", NULL, display_image },
417 { ".png", NULL, display_image },
418 { ".svg", NULL, display_image },
419 { ".c", "c", display_text },
420 { ".css", "css", display_text },
421 { ".glsl", NULL, display_text },
422 { ".h", "c", display_text },
423 { ".txt", NULL, display_text },
424 { ".ui", "xml", display_text },
425 { ".webm", NULL, display_video },
426 { "images/", NULL, display_images }
427};
428
429static void
430add_data_tab (const char *demoname)
431{
432 char *resource_dir, *resource_name;
433 char **resources;
434 GtkWidget *widget, *label;
435 guint i, j;
436 char *label_string;
437
438 resource_dir = g_strconcat (string1: "/", demoname, NULL);
439 resources = g_resources_enumerate_children (path: resource_dir, lookup_flags: 0, NULL);
440 if (resources == NULL)
441 {
442 g_free (mem: resource_dir);
443 return;
444 }
445
446 for (i = 0; resources[i]; i++)
447 {
448 resource_name = g_strconcat (string1: resource_dir, "/", resources[i], NULL);
449
450 for (j = 0; j < G_N_ELEMENTS (display_funcs); j++)
451 {
452 if (g_str_has_suffix (str: resource_name, suffix: display_funcs[j].extension))
453 break;
454 }
455
456 label_string = NULL;
457
458 if (j < G_N_ELEMENTS (display_funcs))
459 widget = display_funcs[j].display_func (display_funcs[j].format,
460 resource_name,
461 &label_string);
462 else
463 widget = display_nothing (resource: resource_name);
464
465 label = gtk_label_new (str: label_string ? label_string : resources[i]);
466 gtk_widget_show (widget: label);
467 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child: widget, tab_label: label);
468 g_object_set (object: gtk_notebook_get_page (GTK_NOTEBOOK (notebook), child: widget),
469 first_property_name: "tab-expand", FALSE,
470 NULL);
471
472 g_free (mem: resource_name);
473 g_free (mem: label_string);
474 }
475
476 g_strfreev (str_array: resources);
477 g_free (mem: resource_dir);
478}
479
480static void
481remove_data_tabs (void)
482{
483 int i;
484
485 for (i = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) - 1; i > 1; i--)
486 gtk_notebook_remove_page (GTK_NOTEBOOK (notebook), page_num: i);
487}
488
489void
490load_file (const char *demoname,
491 const char *filename)
492{
493 GtkTextBuffer *info_buffer, *source_buffer;
494 GtkTextIter start, end;
495 char *resource_filename;
496 GError *err = NULL;
497 int state = 0;
498 gboolean in_para = 0;
499 char **lines;
500 GBytes *bytes;
501 int i;
502
503 if (!g_strcmp0 (str1: current_file, str2: filename))
504 return;
505
506 remove_data_tabs ();
507
508 add_data_tab (demoname);
509
510 g_free (mem: current_file);
511 current_file = g_strdup (str: filename);
512
513 info_buffer = gtk_text_buffer_new (NULL);
514 gtk_text_buffer_create_tag (buffer: info_buffer, tag_name: "title",
515 first_property_name: "font", "Sans 18",
516 "pixels-below-lines", 10,
517 NULL);
518
519 source_buffer = gtk_text_buffer_new (NULL);
520
521 gtk_text_buffer_begin_irreversible_action (buffer: info_buffer);
522 gtk_text_buffer_begin_irreversible_action (buffer: source_buffer);
523
524 resource_filename = g_strconcat (string1: "/sources/", filename, NULL);
525 bytes = g_resources_lookup_data (path: resource_filename, lookup_flags: 0, error: &err);
526 g_free (mem: resource_filename);
527
528 if (bytes == NULL)
529 {
530 g_warning ("Cannot open source for %s: %s", filename, err->message);
531 g_error_free (error: err);
532 return;
533 }
534
535 lines = g_strsplit (string: g_bytes_get_data (bytes, NULL), delimiter: "\n", max_tokens: -1);
536 g_bytes_unref (bytes);
537
538 gtk_text_buffer_get_iter_at_offset (buffer: info_buffer, iter: &start, char_offset: 0);
539 for (i = 0; lines[i] != NULL; i++)
540 {
541 char *p;
542 char *q;
543 char *r;
544
545 /* Make sure \r is stripped at the end for the poor windows people */
546 lines[i] = g_strchomp (string: lines[i]);
547
548 p = lines[i];
549 switch (state)
550 {
551 case 0:
552 /* Reading title */
553 while (*p == '/' || *p == '*' || g_ascii_isspace (*p))
554 p++;
555 r = p;
556 while (*r != '\0')
557 {
558 while (*r != '/' && *r != ':' && *r != '\0')
559 r++;
560 if (*r == '/')
561 {
562 r++;
563 p = r;
564 }
565 if (r[0] == ':' && r[1] == ':')
566 *r = '\0';
567 }
568 q = p + strlen (s: p);
569 while (q > p && g_ascii_isspace (*(q - 1)))
570 q--;
571
572
573 if (q > p)
574 {
575 int len_chars = g_utf8_pointer_to_offset (str: p, pos: q);
576
577 end = start;
578
579 g_assert (strlen (p) >= q - p);
580 gtk_text_buffer_insert (buffer: info_buffer, iter: &end, text: p, len: q - p);
581 start = end;
582
583 gtk_text_iter_backward_chars (iter: &start, count: len_chars);
584 gtk_text_buffer_apply_tag_by_name (buffer: info_buffer, name: "title", start: &start, end: &end);
585
586 start = end;
587
588 while (*p && *p != '\n') p++;
589
590 state++;
591 }
592 break;
593
594 case 1:
595 /* Reading body of info section */
596 while (g_ascii_isspace (*p))
597 p++;
598 if (*p == '*' && *(p + 1) == '/')
599 {
600 gtk_text_buffer_get_iter_at_offset (buffer: source_buffer, iter: &start, char_offset: 0);
601 state++;
602 }
603 else
604 {
605 int len;
606
607 while (*p == '*' || g_ascii_isspace (*p))
608 p++;
609
610 len = strlen (s: p);
611 while (g_ascii_isspace (*(p + len - 1)))
612 len--;
613
614 if (*p == '#')
615 break;
616
617 if (len > 0)
618 {
619 if (in_para)
620 gtk_text_buffer_insert (buffer: info_buffer, iter: &start, text: " ", len: 1);
621
622 g_assert (strlen (p) >= len);
623 gtk_text_buffer_insert (buffer: info_buffer, iter: &start, text: p, len);
624 in_para = 1;
625 }
626 else
627 {
628 gtk_text_buffer_insert (buffer: info_buffer, iter: &start, text: "\n", len: 1);
629 in_para = 0;
630 }
631 }
632 break;
633
634 case 2:
635 /* Skipping blank lines */
636 while (g_ascii_isspace (*p))
637 p++;
638
639 if (!*p)
640 break;
641
642 p = lines[i];
643 state++;
644 G_GNUC_FALLTHROUGH;
645
646 case 3:
647 /* Reading program body */
648 gtk_text_buffer_insert (buffer: source_buffer, iter: &start, text: p, len: -1);
649 if (lines[i+1] != NULL)
650 gtk_text_buffer_insert (buffer: source_buffer, iter: &start, text: "\n", len: 1);
651 break;
652
653 default:
654 g_assert_not_reached ();
655 }
656 }
657
658 g_strfreev (str_array: lines);
659
660 fontify (format: "c", buffer: source_buffer);
661
662 gtk_text_buffer_end_irreversible_action (buffer: source_buffer);
663 gtk_text_view_set_buffer (GTK_TEXT_VIEW (source_view), buffer: source_buffer);
664 g_object_unref (object: source_buffer);
665
666 gtk_text_buffer_end_irreversible_action (buffer: info_buffer);
667 gtk_text_view_set_buffer (GTK_TEXT_VIEW (info_view), buffer: info_buffer);
668 g_object_unref (object: info_buffer);
669}
670
671static void
672activate_cb (GtkWidget *widget,
673 guint position,
674 gpointer window)
675{
676 GListModel *model = G_LIST_MODEL (ptr: gtk_list_view_get_model (GTK_LIST_VIEW (widget)));
677 GtkTreeListRow *row = g_list_model_get_item (list: model, position);
678 GtkDemo *demo = gtk_tree_list_row_get_item (self: row);
679
680 gtk_demo_run (self: demo, window);
681
682 g_object_unref (object: row);
683}
684
685static void
686selection_cb (GtkSingleSelection *sel,
687 GParamSpec *pspec,
688 gpointer user_data)
689{
690 GtkTreeListRow *row = gtk_single_selection_get_selected_item (self: sel);
691 GtkDemo *demo;
692 GAction *action;
693
694 gtk_widget_set_sensitive (GTK_WIDGET (notebook), sensitive: !!row);
695
696 if (!row)
697 {
698 gtk_window_set_title (GTK_WINDOW (toplevel), title: "No match");
699
700 return;
701 }
702
703 demo = gtk_tree_list_row_get_item (self: row);
704
705 if (demo->filename)
706 load_file (demoname: demo->name, filename: demo->filename);
707
708 action = g_action_map_lookup_action (G_ACTION_MAP (toplevel), action_name: "run");
709 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled: demo->func != NULL);
710
711 gtk_window_set_title (GTK_WINDOW (toplevel), title: demo->title);
712}
713
714static gboolean
715filter_demo (GtkDemo *demo)
716{
717 int i;
718
719 /* Show only if the name matches every needle */
720 for (i = 0; search_needle[i]; i++)
721 {
722 if (!demo->title)
723 return FALSE;
724
725 if (g_str_match_string (search_term: search_needle[i], potential_hit: demo->title, TRUE))
726 continue;
727
728 if (demo->keywords)
729 {
730 int j;
731 gboolean found = FALSE;
732
733 for (j = 0; !found && demo->keywords[j]; j++)
734 {
735 if (strstr (haystack: demo->keywords[j], needle: search_needle[i]))
736 found = TRUE;
737 }
738
739 if (found)
740 continue;
741 }
742
743 return FALSE;
744 }
745
746 return TRUE;
747}
748
749static gboolean
750demo_filter_by_name (gpointer item,
751 gpointer user_data)
752{
753 GtkTreeListRow *row = item;
754 GListModel *children;
755 GtkDemo *demo;
756 guint i, n;
757 GtkTreeListRow *parent;
758
759 /* Show all items if search is empty */
760 if (!search_needle || !search_needle[0] || !*search_needle[0])
761 return TRUE;
762
763 g_assert (GTK_IS_TREE_LIST_ROW (row));
764 g_assert (GTK_IS_FILTER_LIST_MODEL (user_data));
765
766 /* Show a row if itself of any parent matches */
767 for (parent = row; parent; parent = gtk_tree_list_row_get_parent (self: parent))
768 {
769 demo = gtk_tree_list_row_get_item (self: parent);
770 g_assert (GTK_IS_DEMO (demo));
771
772 if (filter_demo (demo))
773 return TRUE;
774 }
775
776 /* Show a row if any child matches */
777 children = gtk_tree_list_row_get_children (self: row);
778 if (children)
779 {
780 n = g_list_model_get_n_items (list: children);
781 for (i = 0; i < n; i++)
782 {
783 demo = g_list_model_get_item (list: children, position: i);
784 g_assert (GTK_IS_DEMO (demo));
785
786 if (filter_demo (demo))
787 {
788 g_object_unref (object: demo);
789 return TRUE;
790 }
791 g_object_unref (object: demo);
792 }
793 }
794
795 return FALSE;
796}
797
798static void
799demo_search_changed_cb (GtkSearchEntry *entry,
800 GtkFilter *filter)
801{
802 const char *text;
803
804 g_assert (GTK_IS_SEARCH_ENTRY (entry));
805 g_assert (GTK_IS_FILTER (filter));
806
807 text = gtk_editable_get_text (GTK_EDITABLE (entry));
808
809 g_clear_pointer (&search_needle, g_strfreev);
810
811 if (text && *text)
812 search_needle = g_str_tokenize_and_fold (string: text, NULL, NULL);
813
814 gtk_filter_changed (self: filter, change: GTK_FILTER_CHANGE_DIFFERENT);
815}
816
817static GListModel *
818create_demo_model (void)
819{
820 GListStore *store = g_list_store_new (GTK_TYPE_DEMO);
821 DemoData *demo = gtk_demos;
822 GtkDemo *d;
823
824 d = GTK_DEMO (ptr: g_object_new (GTK_TYPE_DEMO, NULL));
825 d->name = "main";
826 d->title = "GTK Demo";
827 d->keywords = NULL;
828 d->filename = "main.c";
829 d->func = NULL;
830
831 g_list_store_append (store, item: d);
832
833 while (demo->title)
834 {
835 d = GTK_DEMO (ptr: g_object_new (GTK_TYPE_DEMO, NULL));
836 DemoData *children = demo->children;
837
838 d->name = demo->name;
839 d->title = demo->title;
840 d->keywords = demo->keywords;
841 d->filename = demo->filename;
842 d->func = demo->func;
843
844 g_list_store_append (store, item: d);
845
846 if (children)
847 {
848 d->children_model = G_LIST_MODEL (ptr: g_list_store_new (GTK_TYPE_DEMO));
849
850 while (children->title)
851 {
852 GtkDemo *child = GTK_DEMO (ptr: g_object_new (GTK_TYPE_DEMO, NULL));
853
854 child->name = children->name;
855 child->title = children->title;
856 child->keywords = children->keywords;
857 child->filename = children->filename;
858 child->func = children->func;
859
860 g_list_store_append (store: G_LIST_STORE (ptr: d->children_model), item: child);
861 children++;
862 }
863 }
864
865 demo++;
866 }
867
868 return G_LIST_MODEL (ptr: store);
869}
870
871static GListModel *
872get_child_model (gpointer item,
873 gpointer user_data)
874{
875 GtkDemo *demo = item;
876
877 if (demo->children_model)
878 return g_object_ref (G_LIST_MODEL (demo->children_model));
879
880 return NULL;
881}
882
883static void
884clear_search (GtkSearchBar *bar)
885{
886 if (!gtk_search_bar_get_search_mode (bar))
887 {
888 GtkWidget *entry = gtk_search_bar_get_child (GTK_SEARCH_BAR (bar));
889 gtk_editable_set_text (GTK_EDITABLE (entry), text: "");
890 }
891}
892
893static void
894activate (GApplication *app)
895{
896 GtkBuilder *builder;
897 GListModel *listmodel;
898 GtkTreeListModel *treemodel;
899 GtkWidget *window, *listview, *search_entry, *search_bar;
900 GtkFilterListModel *filter_model;
901 GtkFilter *filter;
902 GSimpleAction *action;
903
904 builder = gtk_builder_new_from_resource (resource_path: "/ui/main.ui");
905
906 window = (GtkWidget *)gtk_builder_get_object (builder, name: "window");
907 gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (window));
908
909 if (g_strcmp0 (str1: PROFILE, str2: "devel") == 0)
910 gtk_widget_add_css_class (widget: window, css_class: "devel");
911
912 action = g_simple_action_new (name: "run", NULL);
913 g_signal_connect (action, "activate", G_CALLBACK (activate_run), window);
914 g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (action));
915
916 notebook = GTK_WIDGET (gtk_builder_get_object (builder, "notebook"));
917
918 info_view = GTK_WIDGET (gtk_builder_get_object (builder, "info-textview"));
919 source_view = GTK_WIDGET (gtk_builder_get_object (builder, "source-textview"));
920 toplevel = GTK_WIDGET (window);
921 listview = GTK_WIDGET (gtk_builder_get_object (builder, "listview"));
922 g_signal_connect (listview, "activate", G_CALLBACK (activate_cb), window);
923 search_bar = GTK_WIDGET (gtk_builder_get_object (builder, "searchbar"));
924 g_signal_connect (search_bar, "notify::search-mode-enabled", G_CALLBACK (clear_search), NULL);
925
926 listmodel = create_demo_model ();
927 treemodel = gtk_tree_list_model_new (root: G_LIST_MODEL (ptr: listmodel),
928 FALSE,
929 TRUE,
930 create_func: get_child_model,
931 NULL,
932 NULL);
933 filter_model = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: treemodel), NULL);
934 filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: demo_filter_by_name, user_data: filter_model, NULL));
935 gtk_filter_list_model_set_filter (self: filter_model, filter);
936 g_object_unref (object: filter);
937
938 search_entry = GTK_WIDGET (gtk_builder_get_object (builder, "search-entry"));
939 g_signal_connect (search_entry, "search-changed", G_CALLBACK (demo_search_changed_cb), filter);
940
941 selection = gtk_single_selection_new (model: G_LIST_MODEL (ptr: filter_model));
942 g_signal_connect (selection, "notify::selected-item", G_CALLBACK (selection_cb), NULL);
943 gtk_list_view_set_model (GTK_LIST_VIEW (listview), model: GTK_SELECTION_MODEL (ptr: selection));
944
945 selection_cb (sel: selection, NULL, NULL);
946 g_object_unref (object: selection);
947
948 g_object_unref (object: builder);
949}
950
951static gboolean
952auto_quit (gpointer data)
953{
954 g_application_quit (G_APPLICATION (data));
955 return G_SOURCE_REMOVE;
956}
957
958static void
959list_demos (void)
960{
961 DemoData *d, *c;
962
963 d = gtk_demos;
964
965 while (d->title)
966 {
967 c = d->children;
968 if (d->name)
969 g_print (format: "%s\n", d->name);
970 d++;
971 while (c && c->title)
972 {
973 if (c->name)
974 g_print (format: "%s\n", c->name);
975 c++;
976 }
977 }
978}
979
980static int
981command_line (GApplication *app,
982 GApplicationCommandLine *cmdline)
983{
984 GVariantDict *options;
985 const char *name = NULL;
986 gboolean autoquit = FALSE;
987 gboolean list = FALSE;
988 DemoData *d, *c;
989 GDoDemoFunc func = 0;
990 GtkWidget *window, *demo;
991
992 activate (app);
993
994 options = g_application_command_line_get_options_dict (cmdline);
995 g_variant_dict_lookup (dict: options, key: "run", format_string: "&s", &name);
996 g_variant_dict_lookup (dict: options, key: "autoquit", format_string: "b", &autoquit);
997 g_variant_dict_lookup (dict: options, key: "list", format_string: "b", &list);
998
999 if (list)
1000 {
1001 list_demos ();
1002 g_application_quit (application: app);
1003 return 0;
1004 }
1005
1006 window = gtk_application_get_windows (GTK_APPLICATION (app))->data;
1007
1008 if (name == NULL)
1009 goto out;
1010
1011 d = gtk_demos;
1012
1013 while (d->title)
1014 {
1015 c = d->children;
1016 if (g_strcmp0 (d->name, name) == 0)
1017 {
1018 func = d->func;
1019 goto out;
1020 }
1021 d++;
1022 while (c && c->title)
1023 {
1024 if (g_strcmp0 (c->name, name) == 0)
1025 {
1026 func = c->func;
1027 goto out;
1028 }
1029 c++;
1030 }
1031 }
1032
1033out:
1034 if (func)
1035 {
1036 demo = (func) (window);
1037
1038 gtk_window_set_transient_for (GTK_WINDOW (demo), GTK_WINDOW (window));
1039
1040 g_signal_connect_swapped (G_OBJECT (demo), "destroy", G_CALLBACK (g_application_quit), app);
1041 }
1042 else
1043 gtk_window_present (GTK_WINDOW (window));
1044
1045 if (autoquit)
1046 g_timeout_add_seconds (interval: 1, function: auto_quit, data: app);
1047
1048 return 0;
1049}
1050
1051static void
1052print_version (void)
1053{
1054 g_print ("gtk4-demo %s%s%s\n",
1055 PACKAGE_VERSION,
1056 g_strcmp0 (PROFILE, "devel") == 0 ? "-" : "",
1057 g_strcmp0 (PROFILE, "devel") == 0 ? VCS_TAG : "");
1058}
1059
1060static int
1061local_options (GApplication *app,
1062 GVariantDict *options,
1063 gpointer data)
1064{
1065 gboolean version = FALSE;
1066
1067 g_variant_dict_lookup (dict: options, key: "version", format_string: "b", &version);
1068
1069 if (version)
1070 {
1071 print_version ();
1072 return 0;
1073 }
1074
1075 return -1;
1076}
1077
1078int
1079main (int argc, char **argv)
1080{
1081 GtkApplication *app;
1082 static GActionEntry app_entries[] = {
1083 { "about", activate_about, NULL, NULL, NULL },
1084 { "quit", activate_quit, NULL, NULL, NULL },
1085 { "inspector", activate_inspector, NULL, NULL, NULL },
1086 };
1087 struct {
1088 const char *action_and_target;
1089 const char *accelerators[2];
1090 } accels[] = {
1091 { "app.about", { "F1", NULL } },
1092 { "app.quit", { "<Control>q", NULL } },
1093 };
1094 int i;
1095
1096 app = gtk_application_new (application_id: "org.gtk.Demo4", flags: G_APPLICATION_NON_UNIQUE|G_APPLICATION_HANDLES_COMMAND_LINE);
1097
1098 g_action_map_add_action_entries (G_ACTION_MAP (app),
1099 entries: app_entries, G_N_ELEMENTS (app_entries),
1100 user_data: app);
1101
1102 for (i = 0; i < G_N_ELEMENTS (accels); i++)
1103 gtk_application_set_accels_for_action (application: app, detailed_action_name: accels[i].action_and_target, accels: accels[i].accelerators);
1104
1105 g_application_add_main_option (G_APPLICATION (app), long_name: "version", short_name: 0, flags: 0, arg: G_OPTION_ARG_NONE, description: "Show program version", NULL);
1106 g_application_add_main_option (G_APPLICATION (app), long_name: "run", short_name: 0, flags: 0, arg: G_OPTION_ARG_STRING, description: "Run an example", arg_description: "EXAMPLE");
1107 g_application_add_main_option (G_APPLICATION (app), long_name: "list", short_name: 0, flags: 0, arg: G_OPTION_ARG_NONE, description: "List examples", NULL);
1108 g_application_add_main_option (G_APPLICATION (app), long_name: "autoquit", short_name: 0, flags: 0, arg: G_OPTION_ARG_NONE, description: "Quit after a delay", NULL);
1109
1110 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
1111 g_signal_connect (app, "command-line", G_CALLBACK (command_line), NULL);
1112 g_signal_connect (app, "handle-local-options", G_CALLBACK (local_options), NULL);
1113
1114 g_application_run (G_APPLICATION (app), argc, argv);
1115
1116 return 0;
1117}
1118

source code of gtk/demos/gtk-demo/main.c