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 | |
36 | G_DECLARE_FINAL_TYPE (ReftestScope, reftest_scope, REFTEST, SCOPE, GtkBuilderCScope) |
37 | |
38 | static GtkBuilderScopeInterface *parent_scope_iface; |
39 | |
40 | struct _ReftestScope |
41 | { |
42 | GtkBuilderCScope parent_instance; |
43 | |
44 | char *directory; |
45 | }; |
46 | |
47 | static GClosure * |
48 | reftest_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 | |
121 | static void |
122 | reftest_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 | |
129 | G_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 | |
133 | static void |
134 | reftest_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 | |
143 | static void |
144 | reftest_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 | |
151 | static void |
152 | reftest_scope_init (ReftestScope *self) |
153 | { |
154 | } |
155 | |
156 | static GtkBuilderScope * |
157 | reftest_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 | |
170 | static GtkWidget * |
171 | builder_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 | |
192 | static gboolean |
193 | quit_when_idle (gpointer loop) |
194 | { |
195 | g_main_loop_quit (loop); |
196 | |
197 | return G_SOURCE_REMOVE; |
198 | } |
199 | |
200 | static int inhibit_count; |
201 | static GMainLoop *loop; |
202 | |
203 | G_MODULE_EXPORT void |
204 | reftest_inhibit_snapshot (void) |
205 | { |
206 | inhibit_count++; |
207 | } |
208 | |
209 | G_MODULE_EXPORT void |
210 | reftest_uninhibit_snapshot (void) |
211 | { |
212 | g_assert_true (inhibit_count > 0); |
213 | inhibit_count--; |
214 | } |
215 | |
216 | static void |
217 | draw_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 | |
263 | static GdkTexture * |
264 | snapshot_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 | |
289 | GdkTexture * |
290 | reftest_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 | |