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 | |
30 | static GtkWidget *info_view; |
31 | static GtkWidget *source_view; |
32 | |
33 | static char *current_file = NULL; |
34 | |
35 | static GtkWidget *notebook; |
36 | static GtkSingleSelection *selection; |
37 | static GtkWidget *toplevel; |
38 | static char **search_needle; |
39 | |
40 | typedef struct _GtkDemo GtkDemo; |
41 | struct _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 | |
53 | enum { |
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 ()) |
64 | G_DECLARE_FINAL_TYPE (GtkDemo, gtk_demo, GTK, DEMO, GObject); |
65 | |
66 | G_DEFINE_TYPE (GtkDemo, gtk_demo, G_TYPE_OBJECT); |
67 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
68 | |
69 | static void |
70 | gtk_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 | |
101 | static 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 | |
135 | static void gtk_demo_init (GtkDemo *self) |
136 | { |
137 | } |
138 | |
139 | typedef struct _CallbackData CallbackData; |
140 | struct _CallbackData |
141 | { |
142 | GtkTreeModel *model; |
143 | GtkTreePath *path; |
144 | }; |
145 | |
146 | static gboolean |
147 | gtk_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 | |
167 | static void |
168 | activate_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 | |
230 | static void |
231 | activate_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 | |
251 | static void |
252 | activate_inspector (GSimpleAction *action, |
253 | GVariant *parameter, |
254 | gpointer user_data) |
255 | { |
256 | gtk_window_set_interactive_debugging (TRUE); |
257 | } |
258 | |
259 | static void |
260 | activate_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 | |
270 | static GtkWidget * |
271 | display_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 | |
286 | static GtkWidget * |
287 | display_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 | |
333 | static GtkWidget * |
334 | display_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 | |
380 | static GtkWidget * |
381 | display_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 | |
393 | static GtkWidget * |
394 | display_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 | |
408 | static 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 | |
429 | static void |
430 | add_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 | |
480 | static void |
481 | remove_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 | |
489 | void |
490 | load_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 | |
671 | static void |
672 | activate_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 | |
685 | static void |
686 | selection_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 | |
714 | static gboolean |
715 | filter_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 | |
749 | static gboolean |
750 | demo_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 | |
798 | static void |
799 | demo_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 | |
817 | static GListModel * |
818 | create_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 | |
871 | static GListModel * |
872 | get_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 | |
883 | static void |
884 | clear_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 | |
893 | static void |
894 | activate (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 | |
951 | static gboolean |
952 | auto_quit (gpointer data) |
953 | { |
954 | g_application_quit (G_APPLICATION (data)); |
955 | return G_SOURCE_REMOVE; |
956 | } |
957 | |
958 | static void |
959 | list_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 | |
980 | static int |
981 | command_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 | |
1033 | out: |
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 | |
1051 | static void |
1052 | print_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 | |
1060 | static int |
1061 | local_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 | |
1078 | int |
1079 | main (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 | |