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 #include "gtk-reftest.h"
27
28 #include <string.h>
29
30 typedef enum {
31 SNAPSHOT_WINDOW,
32 SNAPSHOT_DRAW
33 } SnapshotMode;
34
35 static GtkWidget *
builder_get_toplevel(GtkBuilder * builder)36 builder_get_toplevel (GtkBuilder *builder)
37 {
38 GSList *list, *walk;
39 GtkWidget *window = NULL;
40
41 list = gtk_builder_get_objects (builder);
42 for (walk = list; walk; walk = walk->next)
43 {
44 if (GTK_IS_WINDOW (walk->data) &&
45 gtk_widget_get_parent (walk->data) == NULL)
46 {
47 window = walk->data;
48 break;
49 }
50 }
51
52 g_slist_free (list);
53
54 return window;
55 }
56
57 static gboolean
quit_when_idle(gpointer loop)58 quit_when_idle (gpointer loop)
59 {
60 g_main_loop_quit (loop);
61
62 return G_SOURCE_REMOVE;
63 }
64
65 static gint inhibit_count;
66 static GMainLoop *loop;
67
68 void
reftest_inhibit_snapshot(void)69 reftest_inhibit_snapshot (void)
70 {
71 inhibit_count++;
72 }
73
74 void
reftest_uninhibit_snapshot(void)75 reftest_uninhibit_snapshot (void)
76 {
77 g_assert (inhibit_count > 0);
78 inhibit_count--;
79
80 if (inhibit_count == 0)
81 g_idle_add (quit_when_idle, loop);
82 }
83
84 static void
check_for_draw(GdkEvent * event,gpointer data)85 check_for_draw (GdkEvent *event, gpointer data)
86 {
87 if (event->type == GDK_EXPOSE)
88 {
89 reftest_uninhibit_snapshot ();
90 gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL);
91 }
92
93 gtk_main_do_event (event);
94 }
95
96 static cairo_surface_t *
snapshot_widget(GtkWidget * widget,SnapshotMode mode)97 snapshot_widget (GtkWidget *widget, SnapshotMode mode)
98 {
99 cairo_surface_t *surface;
100 cairo_pattern_t *bg;
101 cairo_t *cr;
102
103 g_assert (gtk_widget_get_realized (widget));
104
105 loop = g_main_loop_new (NULL, FALSE);
106
107 /* We wait until the widget is drawn for the first time.
108 * We can not wait for a GtkWidget::draw event, because that might not
109 * happen if the window is fully obscured by windowed child widgets.
110 * Alternatively, we could wait for an expose event on widget's window.
111 * Both of these are rather hairy, not sure what's best.
112 *
113 * We also use an inhibit mechanism, to give module functions a chance
114 * to delay the snapshot.
115 */
116 reftest_inhibit_snapshot ();
117 gdk_event_handler_set (check_for_draw, NULL, NULL);
118 g_main_loop_run (loop);
119
120 surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
121 CAIRO_CONTENT_COLOR,
122 gtk_widget_get_allocated_width (widget),
123 gtk_widget_get_allocated_height (widget));
124
125 cr = cairo_create (surface);
126
127 switch (mode)
128 {
129 case SNAPSHOT_WINDOW:
130 {
131 GdkWindow *window = gtk_widget_get_window (widget);
132 if (gdk_window_get_window_type (window) == GDK_WINDOW_TOPLEVEL ||
133 gdk_window_get_window_type (window) == GDK_WINDOW_FOREIGN)
134 {
135 /* give the WM/server some time to sync. They need it.
136 * Also, do use popups instead of toplevels in your tests
137 * whenever you can.
138 */
139 gdk_display_sync (gdk_window_get_display (window));
140 g_timeout_add (500, quit_when_idle, loop);
141 g_main_loop_run (loop);
142 }
143 gdk_cairo_set_source_window (cr, window, 0, 0);
144 cairo_paint (cr);
145 }
146 break;
147 case SNAPSHOT_DRAW:
148 bg = gdk_window_get_background_pattern (gtk_widget_get_window (widget));
149 if (bg)
150 {
151 cairo_set_source (cr, bg);
152 cairo_paint (cr);
153 }
154 gtk_widget_draw (widget, cr);
155 break;
156 default:
157 g_assert_not_reached();
158 break;
159 }
160
161 cairo_destroy (cr);
162 g_main_loop_unref (loop);
163 gtk_widget_destroy (widget);
164
165 return surface;
166 }
167
168 static void
connect_signals(GtkBuilder * builder,GObject * object,const gchar * signal_name,const gchar * handler_name,GObject * connect_object,GConnectFlags flags,gpointer user_data)169 connect_signals (GtkBuilder *builder,
170 GObject *object,
171 const gchar *signal_name,
172 const gchar *handler_name,
173 GObject *connect_object,
174 GConnectFlags flags,
175 gpointer user_data)
176 {
177 ReftestModule *module;
178 const char *directory;
179 GCallback func;
180 GClosure *closure;
181 char **split;
182
183 directory = user_data;
184 split = g_strsplit (handler_name, ":", -1);
185
186 switch (g_strv_length (split))
187 {
188 case 1:
189 func = gtk_builder_lookup_callback_symbol (builder, split[0]);
190
191 if (func)
192 {
193 module = NULL;
194 }
195 else
196 {
197 module = reftest_module_new_self ();
198 if (module == NULL)
199 {
200 g_error ("glib compiled without module support.");
201 return;
202 }
203 func = reftest_module_lookup (module, split[0]);
204 if (!func)
205 {
206 g_error ("failed to lookup handler for name '%s' when connecting signals", split[0]);
207 return;
208 }
209 }
210 break;
211 case 2:
212 if (g_getenv ("REFTEST_MODULE_DIR"))
213 directory = g_getenv ("REFTEST_MODULE_DIR");
214 module = reftest_module_new (directory, split[0]);
215 if (module == NULL)
216 {
217 g_error ("Could not load module '%s' from '%s' when looking up '%s'", split[0], directory, handler_name);
218 return;
219 }
220 func = reftest_module_lookup (module, split[1]);
221 if (!func)
222 {
223 g_error ("failed to lookup handler for name '%s' in module '%s'", split[1], split[0]);
224 return;
225 }
226 break;
227 default:
228 g_error ("Could not connect signal handler named '%s'", handler_name);
229 return;
230 }
231
232 g_strfreev (split);
233
234 if (connect_object)
235 {
236 if (flags & G_CONNECT_SWAPPED)
237 closure = g_cclosure_new_object_swap (func, connect_object);
238 else
239 closure = g_cclosure_new_object (func, connect_object);
240 }
241 else
242 {
243 if (flags & G_CONNECT_SWAPPED)
244 closure = g_cclosure_new_swap (func, NULL, NULL);
245 else
246 closure = g_cclosure_new (func, NULL, NULL);
247 }
248
249 if (module)
250 g_closure_add_finalize_notifier (closure, module, (GClosureNotify) reftest_module_unref);
251
252 g_signal_connect_closure (object, signal_name, closure, flags & G_CONNECT_AFTER ? TRUE : FALSE);
253 }
254
255 cairo_surface_t *
reftest_snapshot_ui_file(const char * ui_file)256 reftest_snapshot_ui_file (const char *ui_file)
257 {
258 GtkWidget *window;
259 GtkBuilder *builder;
260 GError *error = NULL;
261 char *directory;
262
263 directory = g_path_get_dirname (ui_file);
264
265 builder = gtk_builder_new ();
266 gtk_builder_add_from_file (builder, ui_file, &error);
267 g_assert_no_error (error);
268 gtk_builder_connect_signals_full (builder, connect_signals, directory);
269 window = builder_get_toplevel (builder);
270 g_object_unref (builder);
271 g_free (directory);
272 g_assert (window);
273
274 gtk_widget_show (window);
275
276 return snapshot_widget (window, SNAPSHOT_WINDOW);
277 }
278