1/*
2 * Copyright (c) 2014 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19#include <glib/gi18n-lib.h>
20
21#include "visual.h"
22
23#include "fpsoverlay.h"
24#include "updatesoverlay.h"
25#include "layoutoverlay.h"
26#include "focusoverlay.h"
27#include "baselineoverlay.h"
28#include "window.h"
29
30#include "gtkadjustment.h"
31#include "gtkbox.h"
32#include "gtkdropdown.h"
33#include "gtkcssproviderprivate.h"
34#include "gtkdebug.h"
35#include "gtkprivate.h"
36#include "gtksettings.h"
37#include "gtkswitch.h"
38#include "gtkscale.h"
39#include "gtkwindow.h"
40#include "gtklistbox.h"
41#include "gskdebugprivate.h"
42#include "gskrendererprivate.h"
43#include "gtknative.h"
44#include "gtkbinlayout.h"
45#include "gtkeditable.h"
46#include "gtkentry.h"
47#include "gtkstringlist.h"
48
49#ifdef GDK_WINDOWING_X11
50#include "x11/gdkx.h"
51#endif
52#ifdef GDK_WINDOWING_WAYLAND
53#include "wayland/gdkwayland.h"
54#endif
55#ifdef GDK_WINDOWING_MACOS
56#include "macos/gdkmacos.h"
57#endif
58#ifdef GDK_WINDOWING_BROADWAY
59#include "broadway/gdkbroadway.h"
60#endif
61
62#include "gdk/gdkdebug.h"
63
64#define EPSILON 1e-10
65
66struct _GtkInspectorVisual
67{
68 GtkWidget widget;
69
70 GtkWidget *swin;
71 GtkWidget *box;
72 GtkWidget *visual_box;
73 GtkWidget *theme_combo;
74 GtkWidget *dark_switch;
75 GtkWidget *icon_combo;
76 GtkWidget *cursor_combo;
77 GtkWidget *cursor_size_spin;
78 GtkWidget *direction_combo;
79 GtkWidget *font_button;
80 GtkWidget *hidpi_spin;
81 GtkWidget *animation_switch;
82 GtkWidget *font_scale_entry;
83 GtkAdjustment *font_scale_adjustment;
84 GtkAdjustment *scale_adjustment;
85 GtkAdjustment *slowdown_adjustment;
86 GtkWidget *slowdown_entry;
87 GtkAdjustment *cursor_size_adjustment;
88
89 GtkWidget *debug_box;
90 GtkWidget *fps_switch;
91 GtkWidget *updates_switch;
92 GtkWidget *fallback_switch;
93 GtkWidget *baselines_switch;
94 GtkWidget *layout_switch;
95 GtkWidget *focus_switch;
96
97 GtkWidget *misc_box;
98 GtkWidget *touchscreen_switch;
99 GtkWidget *software_gl_switch;
100
101 GtkInspectorOverlay *fps_overlay;
102 GtkInspectorOverlay *updates_overlay;
103 GtkInspectorOverlay *layout_overlay;
104 GtkInspectorOverlay *focus_overlay;
105 GtkInspectorOverlay *baseline_overlay;
106
107 GdkDisplay *display;
108};
109
110typedef struct _GtkInspectorVisualClass
111{
112 GtkWidgetClass parent_class;
113} GtkInspectorVisualClass;
114
115G_DEFINE_TYPE (GtkInspectorVisual, gtk_inspector_visual, GTK_TYPE_WIDGET)
116
117static void
118fix_direction_recurse (GtkWidget *widget,
119 GtkTextDirection dir)
120{
121 GtkWidget *child;
122
123 g_object_ref (widget);
124
125 gtk_widget_set_direction (widget, dir);
126 for (child = gtk_widget_get_first_child (widget);
127 child != NULL;
128 child = gtk_widget_get_next_sibling (widget: child))
129 {
130 fix_direction_recurse (widget: child, dir);
131 }
132
133 g_object_unref (object: widget);
134}
135
136static GtkTextDirection initial_direction;
137
138static void
139fix_direction (GtkWidget *iw)
140{
141 fix_direction_recurse (widget: iw, dir: initial_direction);
142}
143
144static void
145direction_changed (GtkDropDown *combo)
146{
147 GtkWidget *iw;
148 guint selected;
149
150 iw = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (combo)));
151 if (iw)
152 fix_direction (iw);
153
154 selected = gtk_drop_down_get_selected (self: combo);
155
156 if (selected == 0)
157 gtk_widget_set_default_direction (dir: GTK_TEXT_DIR_LTR);
158 else
159 gtk_widget_set_default_direction (dir: GTK_TEXT_DIR_RTL);
160}
161
162static void
163init_direction (GtkInspectorVisual *vis)
164{
165 initial_direction = gtk_widget_get_default_direction ();
166 if (initial_direction == GTK_TEXT_DIR_LTR)
167 gtk_drop_down_set_selected (self: GTK_DROP_DOWN (ptr: vis->direction_combo), position: 0);
168 else
169 gtk_drop_down_set_selected (self: GTK_DROP_DOWN (ptr: vis->direction_combo), position: 1);
170}
171
172static void
173redraw_everything (void)
174{
175 GList *toplevels;
176 toplevels = gtk_window_list_toplevels ();
177 g_list_foreach (list: toplevels, func: (GFunc) gtk_widget_queue_draw, NULL);
178 g_list_free (list: toplevels);
179}
180
181static double
182get_dpi_ratio (GtkInspectorVisual *vis)
183{
184#ifdef GDK_WINDOWING_MACOS
185 if (GDK_IS_MACOS_DISPLAY (vis->display))
186 return 72.0 * 1024.0;
187#endif
188
189 return 96.0 * 1024.0;
190}
191
192static double
193get_font_scale (GtkInspectorVisual *vis)
194{
195 double ratio = get_dpi_ratio (vis);
196 int dpi_int;
197
198 g_object_get (object: gtk_settings_get_for_display (display: vis->display),
199 first_property_name: "gtk-xft-dpi", &dpi_int,
200 NULL);
201
202 return dpi_int / ratio;
203}
204
205static void
206update_font_scale (GtkInspectorVisual *vis,
207 double factor,
208 gboolean update_adjustment,
209 gboolean update_entry)
210{
211 double ratio = get_dpi_ratio (vis);
212
213 g_object_set (object: gtk_settings_get_for_display (display: vis->display),
214 first_property_name: "gtk-xft-dpi", (int)(factor * ratio),
215 NULL);
216
217 if (update_adjustment)
218 gtk_adjustment_set_value (adjustment: vis->font_scale_adjustment, value: factor);
219
220 if (update_entry)
221 {
222 char *str = g_strdup_printf (format: "%0.2f", factor);
223
224 gtk_editable_set_text (GTK_EDITABLE (vis->font_scale_entry), text: str);
225 g_free (mem: str);
226 }
227}
228
229static void
230font_scale_adjustment_changed (GtkAdjustment *adjustment,
231 GtkInspectorVisual *vis)
232{
233 double factor;
234
235 factor = gtk_adjustment_get_value (adjustment);
236 update_font_scale (vis, factor, FALSE, TRUE);
237}
238
239static void
240font_scale_entry_activated (GtkEntry *entry,
241 GtkInspectorVisual *vis)
242{
243 double factor;
244 char *err = NULL;
245
246 factor = g_strtod (nptr: gtk_editable_get_text (GTK_EDITABLE (entry)), endptr: &err);
247 if (err != NULL)
248 update_font_scale (vis, factor, TRUE, FALSE);
249}
250
251static void
252fps_activate (GtkSwitch *sw,
253 GParamSpec *pspec,
254 GtkInspectorVisual *vis)
255{
256 GtkInspectorWindow *iw;
257 gboolean fps;
258
259 fps = gtk_switch_get_active (self: sw);
260 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
261 if (iw == NULL)
262 return;
263
264 if (fps)
265 {
266 if (vis->fps_overlay == NULL)
267 {
268 vis->fps_overlay = gtk_fps_overlay_new ();
269 gtk_inspector_window_add_overlay (iw, overlay: vis->fps_overlay);
270 g_object_unref (object: vis->fps_overlay);
271 }
272 }
273 else
274 {
275 if (vis->fps_overlay != NULL)
276 {
277 gtk_inspector_window_remove_overlay (iw, overlay: vis->fps_overlay);
278 vis->fps_overlay = NULL;
279 }
280 }
281
282 redraw_everything ();
283}
284
285static void
286updates_activate (GtkSwitch *sw,
287 GParamSpec *pspec,
288 GtkInspectorVisual *vis)
289{
290 GtkInspectorWindow *iw;
291 gboolean updates;
292
293 updates = gtk_switch_get_active (self: sw);
294 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
295 if (iw == NULL)
296 return;
297
298 if (updates)
299 {
300 if (vis->updates_overlay == NULL)
301 {
302 vis->updates_overlay = gtk_updates_overlay_new ();
303 gtk_inspector_window_add_overlay (iw, overlay: vis->updates_overlay);
304 g_object_unref (object: vis->updates_overlay);
305 }
306 }
307 else
308 {
309 if (vis->updates_overlay != NULL)
310 {
311 gtk_inspector_window_remove_overlay (iw, overlay: vis->updates_overlay);
312 vis->updates_overlay = NULL;
313 }
314 }
315
316 redraw_everything ();
317}
318
319static void
320fallback_activate (GtkSwitch *sw,
321 GParamSpec *pspec,
322 GtkInspectorVisual *vis)
323{
324 GtkInspectorWindow *iw;
325 gboolean fallback;
326 guint flags;
327 GList *toplevels, *l;
328
329 fallback = gtk_switch_get_active (self: sw);
330 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
331 if (iw == NULL)
332 return;
333
334 flags = gsk_get_debug_flags ();
335 if (fallback)
336 flags = flags | GSK_DEBUG_FALLBACK;
337 else
338 flags = flags & ~GSK_DEBUG_FALLBACK;
339 gsk_set_debug_flags (flags);
340
341 toplevels = gtk_window_list_toplevels ();
342 for (l = toplevels; l; l = l->next)
343 {
344 GtkWidget *toplevel = l->data;
345 GskRenderer *renderer;
346
347 if ((GtkRoot *)toplevel == gtk_widget_get_root (GTK_WIDGET (sw))) /* skip the inspector */
348 continue;
349
350 renderer = gtk_native_get_renderer (self: GTK_NATIVE (ptr: toplevel));
351 if (!renderer)
352 continue;
353
354 gsk_renderer_set_debug_flags (renderer, flags);
355 }
356 g_list_free (list: toplevels);
357
358 redraw_everything ();
359}
360
361static void
362baselines_activate (GtkSwitch *sw,
363 GParamSpec *pspec,
364 GtkInspectorVisual *vis)
365{
366 GtkInspectorWindow *iw;
367 gboolean baselines;
368
369 baselines = gtk_switch_get_active (self: sw);
370 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
371 if (iw == NULL)
372 return;
373
374 if (baselines)
375 {
376 if (vis->baseline_overlay == NULL)
377 {
378 vis->baseline_overlay = gtk_baseline_overlay_new ();
379 gtk_inspector_window_add_overlay (iw, overlay: vis->baseline_overlay);
380 g_object_unref (object: vis->baseline_overlay);
381 }
382 }
383 else
384 {
385 if (vis->baseline_overlay != NULL)
386 {
387 gtk_inspector_window_remove_overlay (iw, overlay: vis->baseline_overlay);
388 vis->baseline_overlay = NULL;
389 }
390 }
391
392 redraw_everything ();
393}
394
395static void
396layout_activate (GtkSwitch *sw,
397 GParamSpec *pspec,
398 GtkInspectorVisual *vis)
399{
400 GtkInspectorWindow *iw;
401 gboolean draw_layout;
402
403 draw_layout = gtk_switch_get_active (self: sw);
404 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
405 if (iw == NULL)
406 return;
407
408 if (draw_layout)
409 {
410 if (vis->layout_overlay == NULL)
411 {
412 vis->layout_overlay = gtk_layout_overlay_new ();
413 gtk_inspector_window_add_overlay (iw, overlay: vis->layout_overlay);
414 g_object_unref (object: vis->layout_overlay);
415 }
416 }
417 else
418 {
419 if (vis->layout_overlay != NULL)
420 {
421 gtk_inspector_window_remove_overlay (iw, overlay: vis->layout_overlay);
422 vis->layout_overlay = NULL;
423 }
424 }
425
426 redraw_everything ();
427}
428
429static void
430focus_activate (GtkSwitch *sw,
431 GParamSpec *pspec,
432 GtkInspectorVisual *vis)
433{
434 GtkInspectorWindow *iw;
435 gboolean focus;
436
437 focus = gtk_switch_get_active (self: sw);
438 iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
439 if (iw == NULL)
440 return;
441
442 if (focus)
443 {
444 if (vis->focus_overlay == NULL)
445 {
446 vis->focus_overlay = gtk_focus_overlay_new ();
447 gtk_inspector_window_add_overlay (iw, overlay: vis->focus_overlay);
448 g_object_unref (object: vis->focus_overlay);
449 }
450 }
451 else
452 {
453 if (vis->focus_overlay != NULL)
454 {
455 gtk_inspector_window_remove_overlay (iw, overlay: vis->focus_overlay);
456 vis->focus_overlay = NULL;
457 }
458 }
459
460 redraw_everything ();
461}
462
463static void
464fill_gtk (const char *path,
465 GHashTable *t)
466{
467 const char *dir_entry;
468 GDir *dir = g_dir_open (path, flags: 0, NULL);
469
470 if (!dir)
471 return;
472
473 while ((dir_entry = g_dir_read_name (dir)))
474 {
475 char *filename = g_build_filename (first_element: path, dir_entry, "gtk-4.0", "gtk.css", NULL);
476
477 if (g_file_test (filename, test: G_FILE_TEST_IS_REGULAR) &&
478 !g_hash_table_contains (hash_table: t, key: dir_entry))
479 g_hash_table_add (hash_table: t, key: g_strdup (str: dir_entry));
480
481 g_free (mem: filename);
482 }
483
484 g_dir_close (dir);
485}
486
487static char *
488get_data_path (const char *subdir)
489{
490 char *base_datadir, *full_datadir;
491#if defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_MACOS)
492 base_datadir = g_strdup (_gtk_get_datadir ());
493#else
494 base_datadir = g_strdup (GTK_DATADIR);
495#endif
496 full_datadir = g_build_filename (first_element: base_datadir, subdir, NULL);
497 g_free (mem: base_datadir);
498 return full_datadir;
499}
500
501static gboolean
502theme_to_pos (GBinding *binding,
503 const GValue *from,
504 GValue *to,
505 gpointer user_data)
506{
507 GtkStringList *names = user_data;
508 const char *theme = g_value_get_string (value: from);
509 guint i, n;
510
511 for (i = 0, n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: names)); i < n; i++)
512 {
513 const char *name = gtk_string_list_get_string (self: names, position: i);
514 if (g_strcmp0 (str1: name, str2: theme) == 0)
515 {
516 g_value_set_uint (value: to, v_uint: i);
517 return TRUE;
518 }
519 }
520 return FALSE;
521}
522
523static gboolean
524pos_to_theme (GBinding *binding,
525 const GValue *from,
526 GValue *to,
527 gpointer user_data)
528{
529 GtkStringList *names = user_data;
530 int pos = g_value_get_uint (value: from);
531 g_value_set_string (value: to, v_string: gtk_string_list_get_string (self: names, position: pos));
532 return TRUE;
533}
534
535static void
536init_theme (GtkInspectorVisual *vis)
537{
538 GHashTable *t;
539 GHashTableIter iter;
540 char *theme, *path;
541 char **builtin_themes;
542 GList *list, *l;
543 GtkStringList *names;
544 guint i;
545 const char * const *dirs;
546
547 t = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL);
548 /* Builtin themes */
549 builtin_themes = g_resources_enumerate_children (path: "/org/gtk/libgtk/theme", lookup_flags: 0, NULL);
550 for (i = 0; builtin_themes[i] != NULL; i++)
551 {
552 if (g_str_has_suffix (str: builtin_themes[i], suffix: "/"))
553 g_hash_table_add (hash_table: t, key: g_strndup (str: builtin_themes[i], n: strlen (s: builtin_themes[i]) - 1));
554 }
555 g_strfreev (str_array: builtin_themes);
556
557 path = _gtk_get_theme_dir ();
558 fill_gtk (path, t);
559 g_free (mem: path);
560
561 path = g_build_filename (first_element: g_get_user_data_dir (), "themes", NULL);
562 fill_gtk (path, t);
563 g_free (mem: path);
564
565 path = g_build_filename (first_element: g_get_home_dir (), ".themes", NULL);
566 fill_gtk (path, t);
567 g_free (mem: path);
568
569 dirs = g_get_system_data_dirs ();
570 for (i = 0; dirs[i]; i++)
571 {
572 path = g_build_filename (first_element: dirs[i], "themes", NULL);
573 fill_gtk (path, t);
574 g_free (mem: path);
575 }
576
577 list = NULL;
578 g_hash_table_iter_init (iter: &iter, hash_table: t);
579 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&theme, NULL))
580 list = g_list_insert_sorted (list, data: theme, func: (GCompareFunc)strcmp);
581
582 names = gtk_string_list_new (NULL);
583 for (l = list, i = 0; l; l = l->next, i++)
584 gtk_string_list_append (self: names, string: (const char *)l->data);
585
586 g_list_free (list);
587 g_hash_table_destroy (hash_table: t);
588
589 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: vis->theme_combo), model: G_LIST_MODEL (ptr: names));
590
591 g_object_bind_property_full (source: gtk_settings_get_for_display (display: vis->display), source_property: "gtk-theme-name",
592 target: vis->theme_combo, target_property: "selected",
593 flags: G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
594 transform_to: theme_to_pos, transform_from: pos_to_theme, user_data: names, notify: (GDestroyNotify)g_object_unref);
595
596 if (g_getenv (variable: "GTK_THEME") != NULL)
597 {
598 GtkWidget *row;
599
600 /* theme is hardcoded, nothing we can do */
601 gtk_widget_set_sensitive (widget: vis->theme_combo, FALSE);
602 row = gtk_widget_get_ancestor (widget: vis->theme_combo, GTK_TYPE_LIST_BOX_ROW);
603 gtk_widget_set_tooltip_text (widget: row, _("Theme is hardcoded by GTK_THEME"));
604 }
605}
606
607static void
608init_dark (GtkInspectorVisual *vis)
609{
610 g_object_bind_property (source: gtk_settings_get_for_display (display: vis->display),
611 source_property: "gtk-application-prefer-dark-theme",
612 target: vis->dark_switch, target_property: "active",
613 flags: G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
614
615 if (g_getenv (variable: "GTK_THEME") != NULL)
616 {
617 GtkWidget *row;
618
619 /* theme is hardcoded, nothing we can do */
620 gtk_widget_set_sensitive (widget: vis->dark_switch, FALSE);
621 row = gtk_widget_get_ancestor (widget: vis->theme_combo, GTK_TYPE_LIST_BOX_ROW);
622 gtk_widget_set_tooltip_text (widget: row, _("Theme is hardcoded by GTK_THEME"));
623 }
624}
625
626static void
627fill_icons (const char *path,
628 GHashTable *t)
629{
630 const char *dir_entry;
631 GDir *dir;
632
633 dir = g_dir_open (path, flags: 0, NULL);
634 if (!dir)
635 return;
636
637 while ((dir_entry = g_dir_read_name (dir)))
638 {
639 char *filename = g_build_filename (first_element: path, dir_entry, "index.theme", NULL);
640
641 if (g_file_test (filename, test: G_FILE_TEST_IS_REGULAR) &&
642 g_strcmp0 (str1: dir_entry, str2: "hicolor") != 0 &&
643 !g_hash_table_contains (hash_table: t, key: dir_entry))
644 g_hash_table_add (hash_table: t, key: g_strdup (str: dir_entry));
645
646 g_free (mem: filename);
647 }
648
649 g_dir_close (dir);
650}
651
652static void
653init_icons (GtkInspectorVisual *vis)
654{
655 GHashTable *t;
656 GHashTableIter iter;
657 char *theme, *path;
658 GList *list, *l;
659 int i;
660 GtkStringList *names;
661
662 t = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL);
663
664 path = get_data_path (subdir: "icons");
665 fill_icons (path, t);
666 g_free (mem: path);
667
668 path = g_build_filename (first_element: g_get_user_data_dir (), "icons", NULL);
669 fill_icons (path, t);
670 g_free (mem: path);
671
672 list = NULL;
673 g_hash_table_iter_init (iter: &iter, hash_table: t);
674 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&theme, NULL))
675 list = g_list_insert_sorted (list, data: theme, func: (GCompareFunc)strcmp);
676
677 names = gtk_string_list_new (NULL);
678 for (l = list, i = 0; l; l = l->next, i++)
679 gtk_string_list_append (self: names, string: (const char *)l->data);
680
681 g_hash_table_destroy (hash_table: t);
682 g_list_free (list);
683
684 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: vis->icon_combo), model: G_LIST_MODEL (ptr: names));
685
686 g_object_bind_property_full (source: gtk_settings_get_for_display (display: vis->display), source_property: "gtk-icon-theme-name",
687 target: vis->icon_combo, target_property: "selected",
688 flags: G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
689 transform_to: theme_to_pos, transform_from: pos_to_theme, user_data: names, notify: (GDestroyNotify)g_object_unref);
690}
691
692static void
693fill_cursors (const char *path,
694 GHashTable *t)
695{
696 const char *dir_entry;
697 GDir *dir;
698
699 dir = g_dir_open (path, flags: 0, NULL);
700 if (!dir)
701 return;
702
703 while ((dir_entry = g_dir_read_name (dir)))
704 {
705 char *filename = g_build_filename (first_element: path, dir_entry, "cursors", NULL);
706
707 if (g_file_test (filename, test: G_FILE_TEST_IS_DIR) &&
708 !g_hash_table_contains (hash_table: t, key: dir_entry))
709 g_hash_table_add (hash_table: t, key: g_strdup (str: dir_entry));
710
711 g_free (mem: filename);
712 }
713
714 g_dir_close (dir);
715}
716
717static void
718init_cursors (GtkInspectorVisual *vis)
719{
720 GHashTable *t;
721 GHashTableIter iter;
722 char *theme, *path;
723 GList *list, *l;
724 GtkStringList *names;
725 int i;
726
727 t = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL);
728
729 path = get_data_path (subdir: "icons");
730 fill_cursors (path, t);
731 g_free (mem: path);
732
733 path = g_build_filename (first_element: g_get_user_data_dir (), "icons", NULL);
734 fill_cursors (path, t);
735 g_free (mem: path);
736
737 list = NULL;
738 g_hash_table_iter_init (iter: &iter, hash_table: t);
739 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&theme, NULL))
740 list = g_list_insert_sorted (list, data: theme, func: (GCompareFunc)strcmp);
741
742 names = gtk_string_list_new (NULL);
743 for (l = list, i = 0; l; l = l->next, i++)
744 gtk_string_list_append (self: names, string: (const char *)l->data);
745
746 g_hash_table_destroy (hash_table: t);
747 g_list_free (list);
748
749 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: vis->cursor_combo), model: G_LIST_MODEL (ptr: names));
750
751 g_object_bind_property_full (source: gtk_settings_get_for_display (display: vis->display), source_property: "gtk-cursor-theme-name",
752 target: vis->cursor_combo, target_property: "selected",
753 flags: G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
754 transform_to: theme_to_pos, transform_from: pos_to_theme, user_data: names, notify: (GDestroyNotify)g_object_unref);
755}
756
757static void
758cursor_size_changed (GtkAdjustment *adjustment, GtkInspectorVisual *vis)
759{
760 int size;
761
762 size = gtk_adjustment_get_value (adjustment);
763 g_object_set (object: gtk_settings_get_for_display (display: vis->display), first_property_name: "gtk-cursor-theme-size", size, NULL);
764}
765
766static void
767init_cursor_size (GtkInspectorVisual *vis)
768{
769 int size;
770
771 g_object_get (object: gtk_settings_get_for_display (display: vis->display), first_property_name: "gtk-cursor-theme-size", &size, NULL);
772 if (size == 0)
773 size = 32;
774
775 gtk_adjustment_set_value (adjustment: vis->scale_adjustment, value: (double)size);
776 g_signal_connect (vis->cursor_size_adjustment, "value-changed",
777 G_CALLBACK (cursor_size_changed), vis);
778}
779
780static void
781init_font (GtkInspectorVisual *vis)
782{
783 g_object_bind_property (source: gtk_settings_get_for_display (display: vis->display),
784 source_property: "gtk-font-name",
785 target: vis->font_button, target_property: "font",
786 flags: G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE);
787}
788
789static void
790init_font_scale (GtkInspectorVisual *vis)
791{
792 double scale;
793
794 scale = get_font_scale (vis);
795 update_font_scale (vis, factor: scale, TRUE, TRUE);
796 g_signal_connect (vis->font_scale_adjustment, "value-changed",
797 G_CALLBACK (font_scale_adjustment_changed), vis);
798 g_signal_connect (vis->font_scale_entry, "activate",
799 G_CALLBACK (font_scale_entry_activated), vis);
800}
801
802#if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_BROADWAY)
803static void
804scale_changed (GtkAdjustment *adjustment, GtkInspectorVisual *vis)
805{
806 int scale;
807
808 scale = gtk_adjustment_get_value (adjustment);
809#if defined (GDK_WINDOWING_X11)
810 if (GDK_IS_X11_DISPLAY (vis->display))
811 gdk_x11_display_set_surface_scale (display: vis->display, scale);
812#endif
813#if defined (GDK_WINDOWING_BROADWAY)
814 if (GDK_IS_BROADWAY_DISPLAY (vis->display))
815 gdk_broadway_display_set_surface_scale (vis->display, scale);
816#endif
817}
818#endif
819
820static void
821init_scale (GtkInspectorVisual *vis)
822{
823#if defined (GDK_WINDOWING_X11)
824 if (GDK_IS_X11_DISPLAY (vis->display))
825 {
826 double scale;
827
828 scale = gdk_monitor_get_scale_factor (monitor: gdk_x11_display_get_primary_monitor (display: vis->display));
829 gtk_adjustment_set_value (adjustment: vis->scale_adjustment, value: scale);
830 g_signal_connect (vis->scale_adjustment, "value-changed",
831 G_CALLBACK (scale_changed), vis);
832 }
833 else
834#endif
835#if defined (GDK_WINDOWING_BROADWAY)
836 if (GDK_IS_BROADWAY_DISPLAY (vis->display))
837 {
838 int scale;
839
840 scale = gdk_broadway_display_get_surface_scale (vis->display);
841 gtk_adjustment_set_value (vis->scale_adjustment, scale);
842 g_signal_connect (vis->scale_adjustment, "value-changed",
843 G_CALLBACK (scale_changed), vis);
844 }
845 else
846#endif
847 {
848 GtkWidget *row;
849
850 gtk_adjustment_set_value (adjustment: vis->scale_adjustment, value: 1);
851 gtk_widget_set_sensitive (widget: vis->hidpi_spin, FALSE);
852 row = gtk_widget_get_ancestor (widget: vis->hidpi_spin, GTK_TYPE_LIST_BOX_ROW);
853 gtk_widget_set_tooltip_text (widget: row, _("Backend does not support window scaling"));
854 }
855}
856
857static void
858init_animation (GtkInspectorVisual *vis)
859{
860 g_object_bind_property (source: gtk_settings_get_for_display (display: vis->display), source_property: "gtk-enable-animations",
861 target: vis->animation_switch, target_property: "active",
862 flags: G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE);
863}
864
865static void
866update_slowdown (GtkInspectorVisual *vis,
867 double slowdown,
868 gboolean update_adjustment,
869 gboolean update_entry)
870{
871 _gtk_set_slowdown (slowdown_factor: slowdown);
872
873 if (update_adjustment)
874 gtk_adjustment_set_value (adjustment: vis->slowdown_adjustment,
875 value: log2 (x: slowdown));
876
877 if (update_entry)
878 {
879 char *str = g_strdup_printf (format: "%0.*f", 2, slowdown);
880
881 gtk_editable_set_text (GTK_EDITABLE (vis->slowdown_entry), text: str);
882 g_free (mem: str);
883 }
884}
885
886static void
887slowdown_adjustment_changed (GtkAdjustment *adjustment,
888 GtkInspectorVisual *vis)
889{
890 double value = gtk_adjustment_get_value (adjustment);
891 double previous = CLAMP (log2 (_gtk_get_slowdown ()),
892 gtk_adjustment_get_lower (adjustment),
893 gtk_adjustment_get_upper (adjustment));
894
895 if (fabs (x: value - previous) > EPSILON)
896 update_slowdown (vis, slowdown: exp2 (x: value), FALSE, TRUE);
897}
898
899static void
900slowdown_entry_activated (GtkEntry *entry,
901 GtkInspectorVisual *vis)
902{
903 double slowdown;
904 char *err = NULL;
905
906 slowdown = g_strtod (nptr: gtk_editable_get_text (GTK_EDITABLE (entry)), endptr: &err);
907 if (err != NULL)
908 update_slowdown (vis, slowdown, TRUE, FALSE);
909}
910
911static void
912init_slowdown (GtkInspectorVisual *vis)
913{
914 update_slowdown (vis, slowdown: _gtk_get_slowdown (), TRUE, TRUE);
915 g_signal_connect (vis->slowdown_adjustment, "value-changed",
916 G_CALLBACK (slowdown_adjustment_changed), vis);
917 g_signal_connect (vis->slowdown_entry, "activate",
918 G_CALLBACK (slowdown_entry_activated), vis);
919}
920
921static void
922update_touchscreen (GtkSwitch *sw)
923{
924 GtkDebugFlags flags;
925
926 flags = gtk_get_debug_flags ();
927
928 if (gtk_switch_get_active (self: sw))
929 flags |= GTK_DEBUG_TOUCHSCREEN;
930 else
931 flags &= ~GTK_DEBUG_TOUCHSCREEN;
932
933 gtk_set_debug_flags (flags);
934}
935
936static void
937init_touchscreen (GtkInspectorVisual *vis)
938{
939 gtk_switch_set_active (GTK_SWITCH (vis->touchscreen_switch), is_active: (gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0);
940 g_signal_connect (vis->touchscreen_switch, "notify::active",
941 G_CALLBACK (update_touchscreen), NULL);
942}
943
944static gboolean
945keynav_failed (GtkWidget *widget, GtkDirectionType direction, GtkInspectorVisual *vis)
946{
947 GtkWidget *next;
948
949 if (direction == GTK_DIR_DOWN &&
950 widget == vis->visual_box)
951 next = vis->debug_box;
952 else if (direction == GTK_DIR_DOWN &&
953 widget == vis->debug_box)
954 next = vis->misc_box;
955 else if (direction == GTK_DIR_UP &&
956 widget == vis->debug_box)
957 next = vis->visual_box;
958 else if (direction == GTK_DIR_UP &&
959 widget == vis->misc_box)
960 next = vis->debug_box;
961 else
962 next = NULL;
963
964 if (next)
965 {
966 gtk_widget_child_focus (widget: next, direction);
967 return TRUE;
968 }
969
970 return FALSE;
971}
972
973static void
974row_activated (GtkListBox *box,
975 GtkListBoxRow *row,
976 GtkInspectorVisual *vis)
977{
978 if (gtk_widget_is_ancestor (widget: vis->dark_switch, GTK_WIDGET (row)))
979 {
980 GtkSwitch *sw = GTK_SWITCH (vis->dark_switch);
981 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
982 }
983 else if (gtk_widget_is_ancestor (widget: vis->animation_switch, GTK_WIDGET (row)))
984 {
985 GtkSwitch *sw = GTK_SWITCH (vis->animation_switch);
986 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
987 }
988 else if (gtk_widget_is_ancestor (widget: vis->fps_switch, GTK_WIDGET (row)))
989 {
990 GtkSwitch *sw = GTK_SWITCH (vis->fps_switch);
991 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
992 }
993 else if (gtk_widget_is_ancestor (widget: vis->updates_switch, GTK_WIDGET (row)))
994 {
995 GtkSwitch *sw = GTK_SWITCH (vis->updates_switch);
996 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
997 }
998 else if (gtk_widget_is_ancestor (widget: vis->fallback_switch, GTK_WIDGET (row)))
999 {
1000 GtkSwitch *sw = GTK_SWITCH (vis->fallback_switch);
1001 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1002 }
1003 else if (gtk_widget_is_ancestor (widget: vis->baselines_switch, GTK_WIDGET (row)))
1004 {
1005 GtkSwitch *sw = GTK_SWITCH (vis->baselines_switch);
1006 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1007 }
1008 else if (gtk_widget_is_ancestor (widget: vis->layout_switch, GTK_WIDGET (row)))
1009 {
1010 GtkSwitch *sw = GTK_SWITCH (vis->layout_switch);
1011 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1012 }
1013 else if (gtk_widget_is_ancestor (widget: vis->focus_switch, GTK_WIDGET (row)))
1014 {
1015 GtkSwitch *sw = GTK_SWITCH (vis->focus_switch);
1016 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1017 }
1018 else if (gtk_widget_is_ancestor (widget: vis->touchscreen_switch, GTK_WIDGET (row)))
1019 {
1020 GtkSwitch *sw = GTK_SWITCH (vis->touchscreen_switch);
1021 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1022 }
1023 else if (gtk_widget_is_ancestor (widget: vis->software_gl_switch, GTK_WIDGET (row)))
1024 {
1025 GtkSwitch *sw = GTK_SWITCH (vis->software_gl_switch);
1026 gtk_switch_set_active (self: sw, is_active: !gtk_switch_get_active (self: sw));
1027 }
1028}
1029
1030static void
1031init_gl (GtkInspectorVisual *vis)
1032{
1033 GdkDebugFlags flags = gdk_display_get_debug_flags (display: vis->display);
1034
1035 gtk_switch_set_active (GTK_SWITCH (vis->software_gl_switch), is_active: flags & GDK_DEBUG_GL_SOFTWARE);
1036
1037 if (flags & GDK_DEBUG_GL_DISABLE)
1038 {
1039 GtkWidget *row;
1040
1041 gtk_widget_set_sensitive (widget: vis->software_gl_switch, FALSE);
1042 row = gtk_widget_get_ancestor (widget: vis->software_gl_switch, GTK_TYPE_LIST_BOX_ROW);
1043 gtk_widget_set_tooltip_text (widget: row, _("GL rendering is disabled"));
1044 }
1045}
1046
1047static void
1048update_gl_flag (GtkSwitch *sw,
1049 GdkDebugFlags flag,
1050 GtkInspectorVisual *vis)
1051{
1052 GdkDebugFlags flags = gdk_display_get_debug_flags (display: vis->display);
1053
1054 if (gtk_switch_get_active (self: sw))
1055 flags |= flag;
1056 else
1057 flags &= ~flag;
1058
1059 gdk_display_set_debug_flags (display: vis->display, flags);
1060}
1061
1062static void
1063software_gl_activate (GtkSwitch *sw,
1064 GParamSpec *pspec,
1065 GtkInspectorVisual *vis)
1066{
1067 update_gl_flag (sw, flag: GDK_DEBUG_GL_SOFTWARE, vis);
1068}
1069
1070static void
1071gtk_inspector_visual_init (GtkInspectorVisual *vis)
1072{
1073 gtk_widget_init_template (GTK_WIDGET (vis));
1074}
1075
1076static void
1077gtk_inspector_visual_constructed (GObject *object)
1078{
1079 GtkInspectorVisual *vis = GTK_INSPECTOR_VISUAL (object);
1080
1081 G_OBJECT_CLASS (gtk_inspector_visual_parent_class)->constructed (object);
1082
1083 g_signal_connect (vis->visual_box, "keynav-failed", G_CALLBACK (keynav_failed), vis);
1084 g_signal_connect (vis->debug_box, "keynav-failed", G_CALLBACK (keynav_failed), vis);
1085 g_signal_connect (vis->misc_box, "keynav-failed", G_CALLBACK (keynav_failed), vis);
1086 g_signal_connect (vis->visual_box, "row-activated", G_CALLBACK (row_activated), vis);
1087 g_signal_connect (vis->debug_box, "row-activated", G_CALLBACK (row_activated), vis);
1088 g_signal_connect (vis->misc_box, "row-activated", G_CALLBACK (row_activated), vis);
1089}
1090
1091static void
1092gtk_inspector_visual_unroot (GtkWidget *widget)
1093{
1094 GtkInspectorVisual *vis = GTK_INSPECTOR_VISUAL (widget);
1095 GtkInspectorWindow *iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_root (GTK_WIDGET (vis)));
1096
1097 if (vis->layout_overlay)
1098 {
1099 gtk_inspector_window_remove_overlay (iw, overlay: vis->layout_overlay);
1100 vis->layout_overlay = NULL;
1101 }
1102 if (vis->updates_overlay)
1103 {
1104 gtk_inspector_window_remove_overlay (iw, overlay: vis->updates_overlay);
1105 vis->updates_overlay = NULL;
1106 }
1107 if (vis->fps_overlay)
1108 {
1109 gtk_inspector_window_remove_overlay (iw, overlay: vis->fps_overlay);
1110 vis->fps_overlay = NULL;
1111 }
1112 if (vis->focus_overlay)
1113 {
1114 gtk_inspector_window_remove_overlay (iw, overlay: vis->focus_overlay);
1115 vis->focus_overlay = NULL;
1116 }
1117
1118 GTK_WIDGET_CLASS (gtk_inspector_visual_parent_class)->unroot (widget);
1119}
1120
1121static void
1122gtk_inspector_visual_dispose (GObject *object)
1123{
1124 GtkInspectorVisual *vis = GTK_INSPECTOR_VISUAL (object);
1125
1126 g_clear_pointer (&vis->swin, gtk_widget_unparent);
1127
1128 G_OBJECT_CLASS (gtk_inspector_visual_parent_class)->dispose (object);
1129}
1130
1131static void
1132gtk_inspector_visual_class_init (GtkInspectorVisualClass *klass)
1133{
1134 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1136
1137 object_class->constructed = gtk_inspector_visual_constructed;
1138 object_class->dispose = gtk_inspector_visual_dispose;
1139
1140 widget_class->unroot = gtk_inspector_visual_unroot;
1141
1142 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/visual.ui");
1143 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, swin);
1144 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, box);
1145 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, direction_combo);
1146 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, theme_combo);
1147 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, dark_switch);
1148 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, cursor_combo);
1149 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, cursor_size_spin);
1150 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, cursor_size_adjustment);
1151 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, icon_combo);
1152 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, hidpi_spin);
1153 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, scale_adjustment);
1154 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, animation_switch);
1155 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, slowdown_adjustment);
1156 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, slowdown_entry);
1157 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, touchscreen_switch);
1158 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, visual_box);
1159 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, debug_box);
1160 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, font_button);
1161 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, misc_box);
1162 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, software_gl_switch);
1163 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, font_scale_entry);
1164 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, font_scale_adjustment);
1165 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, fps_switch);
1166 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, updates_switch);
1167 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, fallback_switch);
1168 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, baselines_switch);
1169 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, layout_switch);
1170 gtk_widget_class_bind_template_child (widget_class, GtkInspectorVisual, focus_switch);
1171
1172 gtk_widget_class_bind_template_callback (widget_class, fps_activate);
1173 gtk_widget_class_bind_template_callback (widget_class, updates_activate);
1174 gtk_widget_class_bind_template_callback (widget_class, fallback_activate);
1175 gtk_widget_class_bind_template_callback (widget_class, direction_changed);
1176 gtk_widget_class_bind_template_callback (widget_class, baselines_activate);
1177 gtk_widget_class_bind_template_callback (widget_class, layout_activate);
1178 gtk_widget_class_bind_template_callback (widget_class, focus_activate);
1179 gtk_widget_class_bind_template_callback (widget_class, software_gl_activate);
1180
1181 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
1182}
1183
1184void
1185gtk_inspector_visual_set_display (GtkInspectorVisual *vis,
1186 GdkDisplay *display)
1187{
1188 vis->display = display;
1189
1190 init_direction (vis);
1191 init_theme (vis);
1192 init_dark (vis);
1193 init_icons (vis);
1194 init_cursors (vis);
1195 init_cursor_size (vis);
1196 init_font (vis);
1197 init_font_scale (vis);
1198 init_scale (vis);
1199 init_animation (vis);
1200 init_slowdown (vis);
1201 init_touchscreen (vis);
1202 init_gl (vis);
1203}
1204
1205// vim: set et sw=2 ts=2:
1206

source code of gtk/gtk/inspector/visual.c