1/*
2 * Copyright (C) 2011 Red Hat Inc.
3 *
4 * Author:
5 * Benjamin Otte <otte@gnome.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "config.h"
22
23#include "reftest-snapshot.h"
24
25#include "reftest-module.h"
26
27#ifdef GDK_WINDOWING_X11
28#include <gdk/x11/gdkx.h>
29#include <cairo-xlib.h>
30#endif
31
32#include <string.h>
33
34#define REFTEST_TYPE_SCOPE (reftest_scope_get_type ())
35
36G_DECLARE_FINAL_TYPE (ReftestScope, reftest_scope, REFTEST, SCOPE, GtkBuilderCScope)
37
38static GtkBuilderScopeInterface *parent_scope_iface;
39
40struct _ReftestScope
41{
42 GtkBuilderCScope parent_instance;
43
44 char *directory;
45};
46
47static GClosure *
48reftest_scope_create_closure (GtkBuilderScope *scope,
49 GtkBuilder *builder,
50 const char *function_name,
51 GtkBuilderClosureFlags flags,
52 GObject *object,
53 GError **error)
54{
55 ReftestScope *self = REFTEST_SCOPE (ptr: scope);
56 ReftestModule *module;
57 GCallback func;
58 GClosure *closure;
59 char **split;
60
61 split = g_strsplit (string: function_name, delimiter: ":", max_tokens: -1);
62
63 switch (g_strv_length (str_array: split))
64 {
65 case 1:
66 closure = parent_scope_iface->create_closure (scope, builder, split[0], flags, object, error);
67 break;
68
69 case 2:
70 module = reftest_module_new (directory: self->directory, module_name: split[0]);
71 if (module == NULL)
72 {
73 g_set_error (err: error,
74 GTK_BUILDER_ERROR,
75 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
76 format: "Could not load module '%s' from '%s' when looking up '%s': %s", split[0], self->directory, function_name, g_module_error ());
77 return NULL;
78 }
79 func = reftest_module_lookup (module, function_name: split[1]);
80 if (!func)
81 {
82 g_set_error (err: error,
83 GTK_BUILDER_ERROR,
84 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
85 format: "failed to lookup function for name '%s' in module '%s'", split[1], split[0]);
86 return NULL;
87 }
88
89 if (object)
90 {
91 if (flags & GTK_BUILDER_CLOSURE_SWAPPED)
92 closure = g_cclosure_new_object_swap (callback_func: func, object);
93 else
94 closure = g_cclosure_new_object (callback_func: func, object);
95 }
96 else
97 {
98 if (flags & GTK_BUILDER_CLOSURE_SWAPPED)
99 closure = g_cclosure_new_swap (callback_func: func, NULL, NULL);
100 else
101 closure = g_cclosure_new (callback_func: func, NULL, NULL);
102 }
103
104 if (module)
105 g_closure_add_finalize_notifier (closure, notify_data: module, notify_func: (GClosureNotify) reftest_module_unref);
106 break;
107
108 default:
109 g_set_error (err: error,
110 GTK_BUILDER_ERROR,
111 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
112 format: "Could not find function named '%s'", function_name);
113 return NULL;
114 }
115
116 g_strfreev (str_array: split);
117
118 return closure;
119}
120
121static void
122reftest_scope_scope_init (GtkBuilderScopeInterface *iface)
123{
124 iface->create_closure = reftest_scope_create_closure;
125
126 parent_scope_iface = g_type_interface_peek_parent (g_iface: iface);
127}
128
129G_DEFINE_TYPE_WITH_CODE (ReftestScope, reftest_scope, GTK_TYPE_BUILDER_CSCOPE,
130 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDER_SCOPE,
131 reftest_scope_scope_init))
132
133static void
134reftest_scope_finalize (GObject *object)
135{
136 ReftestScope *self = REFTEST_SCOPE (ptr: object);
137
138 g_free (mem: self->directory);
139
140 G_OBJECT_CLASS (reftest_scope_parent_class)->finalize (object);
141}
142
143static void
144reftest_scope_class_init (ReftestScopeClass *scope_class)
145{
146 GObjectClass *object_class = G_OBJECT_CLASS (scope_class);
147
148 object_class->finalize = reftest_scope_finalize;
149}
150
151static void
152reftest_scope_init (ReftestScope *self)
153{
154}
155
156static GtkBuilderScope *
157reftest_scope_new (const char *directory)
158{
159 ReftestScope *result;
160
161 g_return_val_if_fail (directory != NULL, NULL);
162
163 result = g_object_new (REFTEST_TYPE_SCOPE, NULL);
164
165 result->directory = g_strdup (str: directory);
166
167 return GTK_BUILDER_SCOPE (ptr: result);
168}
169
170static GtkWidget *
171builder_get_toplevel (GtkBuilder *builder)
172{
173 GSList *list, *walk;
174 GtkWidget *window = NULL;
175
176 list = gtk_builder_get_objects (builder);
177 for (walk = list; walk; walk = walk->next)
178 {
179 if (GTK_IS_WINDOW (walk->data) &&
180 gtk_widget_get_parent (widget: walk->data) == NULL)
181 {
182 window = walk->data;
183 break;
184 }
185 }
186
187 g_slist_free (list);
188
189 return window;
190}
191
192static gboolean
193quit_when_idle (gpointer loop)
194{
195 g_main_loop_quit (loop);
196
197 return G_SOURCE_REMOVE;
198}
199
200static int inhibit_count;
201static GMainLoop *loop;
202
203G_MODULE_EXPORT void
204reftest_inhibit_snapshot (void)
205{
206 inhibit_count++;
207}
208
209G_MODULE_EXPORT void
210reftest_uninhibit_snapshot (void)
211{
212 g_assert_true (inhibit_count > 0);
213 inhibit_count--;
214}
215
216static void
217draw_paintable (GdkPaintable *paintable,
218 gpointer out_texture)
219{
220 GtkSnapshot *snapshot;
221 GskRenderNode *node;
222 GdkTexture *texture;
223 GskRenderer *renderer;
224
225 if (inhibit_count > 0)
226 return;
227
228 snapshot = gtk_snapshot_new ();
229 gdk_paintable_snapshot (paintable,
230 snapshot,
231 width: gdk_paintable_get_intrinsic_width (paintable),
232 height: gdk_paintable_get_intrinsic_height (paintable));
233 node = gtk_snapshot_free_to_node (snapshot);
234
235 /* If the window literally draws nothing, we assume it hasn't been mapped yet and as such
236 * the invalidations were only side effects of resizes.
237 */
238 if (node == NULL)
239 return;
240
241 renderer = gtk_native_get_renderer (
242 self: gtk_widget_get_native (
243 widget: gtk_widget_paintable_get_widget (self: GTK_WIDGET_PAINTABLE (ptr: paintable))));
244 texture = gsk_renderer_render_texture (renderer,
245 root: node,
246 viewport: &GRAPHENE_RECT_INIT (
247 0, 0,
248 gdk_paintable_get_intrinsic_width (paintable),
249 gdk_paintable_get_intrinsic_height (paintable)
250 ));
251 g_object_set_data_full (G_OBJECT (texture),
252 key: "source-render-node",
253 data: node,
254 destroy: (GDestroyNotify) gsk_render_node_unref);
255
256 g_signal_handlers_disconnect_by_func (paintable, draw_paintable, out_texture);
257
258 *(GdkTexture **) out_texture = texture;
259
260 g_idle_add (function: quit_when_idle, data: loop);
261}
262
263static GdkTexture *
264snapshot_widget (GtkWidget *widget)
265{
266 GdkPaintable *paintable;
267 GdkTexture *texture = NULL;
268
269 g_assert_true (gtk_widget_get_realized (widget));
270
271 loop = g_main_loop_new (NULL, FALSE);
272
273 /* We wait until the widget is drawn for the first time.
274 *
275 * We also use an inhibit mechanism, to give module functions a chance
276 * to delay the snapshot.
277 */
278 paintable = gtk_widget_paintable_new (widget);
279 g_signal_connect (paintable, "invalidate-contents", G_CALLBACK (draw_paintable), &texture);
280 g_main_loop_run (loop);
281
282 g_main_loop_unref (loop);
283 g_object_unref (object: paintable);
284 gtk_window_destroy (GTK_WINDOW (widget));
285
286 return texture;
287}
288
289GdkTexture *
290reftest_snapshot_ui_file (const char *ui_file)
291{
292 GtkWidget *window;
293 GtkBuilder *builder;
294 GtkBuilderScope *scope;
295 GError *error = NULL;
296 char *directory;
297
298 if (g_getenv (variable: "REFTEST_MODULE_DIR"))
299 directory = g_strdup (str: g_getenv (variable: "REFTEST_MODULE_DIR"));
300 else
301 directory = g_path_get_dirname (file_name: ui_file);
302 scope = reftest_scope_new (directory);
303 g_free (mem: directory);
304
305 builder = gtk_builder_new ();
306 gtk_builder_set_scope (builder, scope);
307 g_object_unref (object: scope);
308
309 gtk_builder_add_from_file (builder, filename: ui_file, error: &error);
310 g_assert_no_error (error);
311 window = builder_get_toplevel (builder);
312 g_object_unref (object: builder);
313 g_assert_true (window);
314
315 gtk_widget_show (widget: window);
316
317 return snapshot_widget (widget: window);
318}
319

source code of gtk/testsuite/reftests/reftest-snapshot.c