1 /*
2  * Copyright (C) 2010 David King <davidk@openismus.com>
3  * Copyright (C) 2010 - 2012 Vivien Malerba <malerba@gnome-db.org>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301, USA.
20  */
21 #include "widget-embedder.h"
22 #include <gdaui-decl.h>
23 
24 static void     widget_embedder_realize       (GtkWidget       *widget);
25 static void     widget_embedder_unrealize     (GtkWidget       *widget);
26 static void     widget_embedder_get_preferred_width  (GtkWidget *widget,
27 						      gint      *minimum,
28 						      gint      *natural);
29 static void     widget_embedder_get_preferred_height (GtkWidget *widget,
30 						      gint      *minimum,
31 						      gint      *natural);
32 static void     widget_embedder_size_allocate (GtkWidget       *widget,
33                                                GtkAllocation   *allocation);
34 static gboolean widget_embedder_damage        (GtkWidget       *widget,
35                                                GdkEventExpose  *event);
36 static gboolean widget_embedder_draw          (GtkWidget       *widget,
37 					       cairo_t         *cr);
38 static void     widget_embedder_add           (GtkContainer    *container,
39                                                GtkWidget       *child);
40 static void     widget_embedder_remove        (GtkContainer    *container,
41                                                GtkWidget       *widget);
42 static void     widget_embedder_forall        (GtkContainer    *container,
43                                                gboolean         include_internals,
44                                                GtkCallback      callback,
45                                                gpointer         callback_data);
46 static GType    widget_embedder_child_type    (GtkContainer    *container);
47 
48 G_DEFINE_TYPE (WidgetEmbedder, widget_embedder, GTK_TYPE_CONTAINER);
49 
50 static void
to_child(G_GNUC_UNUSED WidgetEmbedder * bin,double widget_x,double widget_y,double * x_out,double * y_out)51 to_child (G_GNUC_UNUSED WidgetEmbedder *bin,
52           double         widget_x,
53           double         widget_y,
54           double        *x_out,
55           double        *y_out)
56 {
57 	*x_out = widget_x;
58 	*y_out = widget_y;
59 }
60 
61 static void
to_parent(G_GNUC_UNUSED WidgetEmbedder * bin,double offscreen_x,double offscreen_y,double * x_out,double * y_out)62 to_parent (G_GNUC_UNUSED WidgetEmbedder *bin,
63            double         offscreen_x,
64            double         offscreen_y,
65            double        *x_out,
66            double        *y_out)
67 {
68 	*x_out = offscreen_x;
69 	*y_out = offscreen_y;
70 }
71 
72 static void
widget_embedder_class_init(WidgetEmbedderClass * klass)73 widget_embedder_class_init (WidgetEmbedderClass *klass)
74 {
75 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
76 	GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
77 
78 	widget_class->realize = widget_embedder_realize;
79 	widget_class->unrealize = widget_embedder_unrealize;
80 	widget_class->get_preferred_width = widget_embedder_get_preferred_width;
81 	widget_class->get_preferred_height = widget_embedder_get_preferred_height;
82 	widget_class->size_allocate = widget_embedder_size_allocate;
83 	widget_class->draw = widget_embedder_draw;
84 
85 	g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET),
86 					 WIDGET_EMBEDDER_TYPE,
87 					 g_cclosure_new (G_CALLBACK (widget_embedder_damage),
88 							 NULL, NULL));
89 
90 	container_class->add = widget_embedder_add;
91 	container_class->remove = widget_embedder_remove;
92 	container_class->forall = widget_embedder_forall;
93 	container_class->child_type = widget_embedder_child_type;
94 }
95 
96 static void
widget_embedder_init(WidgetEmbedder * bin)97 widget_embedder_init (WidgetEmbedder *bin)
98 {
99 	gtk_widget_set_has_window (GTK_WIDGET (bin), TRUE);
100 	bin->valid = TRUE;
101 	bin->red = -1.;
102 	bin->green = -1.;
103 	bin->blue = -1.;
104 	bin->alpha = -1.;
105 }
106 
107 GtkWidget *
widget_embedder_new(void)108 widget_embedder_new (void)
109 {
110 	return g_object_new (WIDGET_EMBEDDER_TYPE, NULL);
111 }
112 
113 static GdkWindow *
pick_offscreen_child(G_GNUC_UNUSED GdkWindow * offscreen_window,double widget_x,double widget_y,WidgetEmbedder * bin)114 pick_offscreen_child (G_GNUC_UNUSED GdkWindow     *offscreen_window,
115                       double         widget_x,
116                       double         widget_y,
117                       WidgetEmbedder *bin)
118 {
119 	GtkAllocation child_area;
120 	double x, y;
121 
122 	if (bin->child && gtk_widget_get_visible (bin->child)) {
123 		to_child (bin, widget_x, widget_y, &x, &y);
124 
125 		gtk_widget_get_allocation ((GtkWidget*) bin, &child_area);
126 		if (x >= 0 && x < child_area.width &&
127 		    y >= 0 && y < child_area.height)
128 			return bin->offscreen_window;
129 	}
130 
131 	return NULL;
132 }
133 
134 static void
offscreen_window_to_parent(G_GNUC_UNUSED GdkWindow * offscreen_window,double offscreen_x,double offscreen_y,double * parent_x,double * parent_y,WidgetEmbedder * bin)135 offscreen_window_to_parent (G_GNUC_UNUSED GdkWindow     *offscreen_window,
136                             double         offscreen_x,
137                             double         offscreen_y,
138                             double        *parent_x,
139                             double        *parent_y,
140                             WidgetEmbedder *bin)
141 {
142 	to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
143 }
144 
145 static void
offscreen_window_from_parent(G_GNUC_UNUSED GdkWindow * window,double parent_x,double parent_y,double * offscreen_x,double * offscreen_y,WidgetEmbedder * bin)146 offscreen_window_from_parent (G_GNUC_UNUSED GdkWindow     *window,
147                               double         parent_x,
148                               double         parent_y,
149                               double        *offscreen_x,
150                               double        *offscreen_y,
151                               WidgetEmbedder *bin)
152 {
153 	to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
154 }
155 
156 static void
widget_embedder_realize(GtkWidget * widget)157 widget_embedder_realize (GtkWidget *widget)
158 {
159 	WidgetEmbedder *bin = WIDGET_EMBEDDER (widget);
160 	GdkWindowAttr attributes;
161 	gint attributes_mask;
162 	gint border_width;
163 	GtkRequisition child_requisition;
164 	GtkAllocation allocation;
165 
166 	gtk_widget_set_realized (widget, TRUE);
167 
168 	border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
169 
170 	gtk_widget_get_allocation (widget, &allocation);
171 	attributes.x = allocation.x + border_width;
172 	attributes.y = allocation.y + border_width;
173 	attributes.width = allocation.width - 2 * border_width;
174 	attributes.height = allocation.height - 2 * border_width;
175 	attributes.window_type = GDK_WINDOW_CHILD;
176 	attributes.event_mask = gtk_widget_get_events (widget)
177 		| GDK_EXPOSURE_MASK
178 		| GDK_POINTER_MOTION_MASK
179 		| GDK_BUTTON_PRESS_MASK
180 		| GDK_BUTTON_RELEASE_MASK
181 		| GDK_SCROLL_MASK
182 		| GDK_ENTER_NOTIFY_MASK
183 		| GDK_LEAVE_NOTIFY_MASK;
184 
185 	attributes.visual = gtk_widget_get_visual (widget);
186 	attributes.wclass = GDK_INPUT_OUTPUT;
187 
188 	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
189 
190 	GdkWindow *win;
191 	win = gdk_window_new (gtk_widget_get_parent_window (widget),
192 			      &attributes, attributes_mask);
193 	gtk_widget_set_window (widget, win);
194 	gdk_window_set_user_data (win, widget);
195 	g_signal_connect (win, "pick-embedded-child",
196 			  G_CALLBACK (pick_offscreen_child), bin);
197 
198 	attributes.window_type = GDK_WINDOW_OFFSCREEN;
199 
200 	child_requisition.width = child_requisition.height = 0;
201 	if (bin->child && gtk_widget_get_visible (bin->child)) {
202 		GtkAllocation allocation;
203 		gtk_widget_get_allocation (bin->child, &allocation);
204 		attributes.width = allocation.width;
205 		attributes.height = allocation.height;
206 	}
207 	bin->offscreen_window = gdk_window_new (gtk_widget_get_root_window (widget),
208 						&attributes, attributes_mask);
209 	gdk_window_set_user_data (bin->offscreen_window, widget);
210 	if (bin->child)
211 		gtk_widget_set_parent_window (bin->child, bin->offscreen_window);
212 	gdk_offscreen_window_set_embedder (bin->offscreen_window, win);
213 	g_signal_connect (bin->offscreen_window, "to-embedder",
214 			  G_CALLBACK (offscreen_window_to_parent), bin);
215 	g_signal_connect (bin->offscreen_window, "from-embedder",
216 			  G_CALLBACK (offscreen_window_from_parent), bin);
217 
218 	GtkStyleContext *style;
219 	style = gtk_widget_get_style_context (widget);
220 
221 	gtk_style_context_set_background (style, win);
222 	gtk_style_context_set_background (style, bin->offscreen_window);
223 	gdk_window_show (bin->offscreen_window);
224 }
225 
226 static void
widget_embedder_unrealize(GtkWidget * widget)227 widget_embedder_unrealize (GtkWidget *widget)
228 {
229 	WidgetEmbedder *bin = WIDGET_EMBEDDER (widget);
230 
231 	gdk_window_set_user_data (bin->offscreen_window, NULL);
232 	gdk_window_destroy (bin->offscreen_window);
233 	bin->offscreen_window = NULL;
234 
235 	GTK_WIDGET_CLASS (widget_embedder_parent_class)->unrealize (widget);
236 }
237 
238 static GType
widget_embedder_child_type(GtkContainer * container)239 widget_embedder_child_type (GtkContainer *container)
240 {
241 	WidgetEmbedder *bin = WIDGET_EMBEDDER (container);
242 
243 	if (bin->child)
244 		return G_TYPE_NONE;
245 
246 	return GTK_TYPE_WIDGET;
247 }
248 
249 static void
widget_embedder_add(GtkContainer * container,GtkWidget * widget)250 widget_embedder_add (GtkContainer *container,
251                      GtkWidget    *widget)
252 {
253 	WidgetEmbedder *bin = WIDGET_EMBEDDER (container);
254 
255 	if (!bin->child) {
256 		gtk_widget_set_parent_window (widget, bin->offscreen_window);
257 		gtk_widget_set_parent (widget, GTK_WIDGET (bin));
258 		bin->child = widget;
259 	}
260 	else
261 		g_warning ("WidgetEmbedder cannot have more than one child\n");
262 }
263 
264 static void
widget_embedder_remove(GtkContainer * container,GtkWidget * widget)265 widget_embedder_remove (GtkContainer *container,
266                         GtkWidget    *widget)
267 {
268 	WidgetEmbedder *bin = WIDGET_EMBEDDER (container);
269 	gboolean was_visible;
270 
271 	was_visible = gtk_widget_get_visible (widget);
272 
273 	if (bin->child == widget) {
274 		gtk_widget_unparent (widget);
275 
276 		bin->child = NULL;
277 
278 		if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container)))
279 			gtk_widget_queue_resize (GTK_WIDGET (container));
280 	}
281 }
282 
283 static void
widget_embedder_forall(GtkContainer * container,G_GNUC_UNUSED gboolean include_internals,GtkCallback callback,gpointer callback_data)284 widget_embedder_forall (GtkContainer *container,
285                         G_GNUC_UNUSED gboolean      include_internals,
286                         GtkCallback   callback,
287                         gpointer      callback_data)
288 {
289 	WidgetEmbedder *bin = WIDGET_EMBEDDER (container);
290 
291 	g_return_if_fail (callback != NULL);
292 
293 	if (bin->child)
294 		(*callback) (bin->child, callback_data);
295 }
296 
297 static void
widget_embedder_size_request(GtkWidget * widget,GtkRequisition * requisition)298 widget_embedder_size_request (GtkWidget      *widget,
299                               GtkRequisition *requisition)
300 {
301 	WidgetEmbedder *bin = WIDGET_EMBEDDER (widget);
302 	GtkRequisition child_requisition;
303 
304 	child_requisition.width = 0;
305 	child_requisition.height = 0;
306 
307 	if (bin->child && gtk_widget_get_visible (bin->child))
308 		gtk_widget_get_preferred_size (bin->child, &child_requisition, NULL);
309 
310 	guint border_width;
311 	border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
312 	requisition->width = border_width * 2 + child_requisition.width;
313 	requisition->height = border_width * 2 + child_requisition.height;
314 }
315 
316 static void
widget_embedder_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)317 widget_embedder_get_preferred_width (GtkWidget *widget,
318 				     gint      *minimum,
319 				     gint      *natural)
320 {
321 	GtkRequisition requisition;
322 	widget_embedder_size_request (widget, &requisition);
323 	*minimum = *natural = requisition.width;
324 }
325 
326 static void
widget_embedder_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)327 widget_embedder_get_preferred_height (GtkWidget *widget,
328 				      gint      *minimum,
329 				      gint      *natural)
330 {
331 	GtkRequisition requisition;
332 	widget_embedder_size_request (widget, &requisition);
333 	*minimum = *natural = requisition.height;
334 }
335 
336 static void
widget_embedder_size_allocate(GtkWidget * widget,GtkAllocation * allocation)337 widget_embedder_size_allocate (GtkWidget     *widget,
338                                GtkAllocation *allocation)
339 {
340 	WidgetEmbedder *bin = WIDGET_EMBEDDER (widget);
341 	gint border_width;
342 	gint w, h;
343 
344 	gtk_widget_set_allocation (widget, allocation);
345 
346 	border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
347 
348 	w = allocation->width - border_width * 2;
349 	h = allocation->height - border_width * 2;
350 
351 	if (gtk_widget_get_realized (widget)) {
352 		GdkWindow *win;
353 		win = gtk_widget_get_window (widget);
354 		gdk_window_move_resize (win,
355 					allocation->x + border_width,
356 					allocation->y + border_width,
357 					w, h);
358 	}
359 
360 	if (bin->child && gtk_widget_get_visible (bin->child)){
361 		GtkAllocation child_allocation;
362 
363 		child_allocation.x = 0;
364 		child_allocation.y = 0;
365 		child_allocation.height = h;
366 		child_allocation.width = w;
367 
368 		if (gtk_widget_get_realized (widget))
369 			gdk_window_move_resize (bin->offscreen_window,
370 						child_allocation.x,
371 						child_allocation.y,
372 						child_allocation.width,
373 						child_allocation.height);
374 
375 		child_allocation.x = child_allocation.y = 0;
376 		gtk_widget_size_allocate (bin->child, &child_allocation);
377 	}
378 }
379 
380 static gboolean
widget_embedder_damage(GtkWidget * widget,G_GNUC_UNUSED GdkEventExpose * event)381 widget_embedder_damage (GtkWidget      *widget,
382                         G_GNUC_UNUSED GdkEventExpose *event)
383 {
384 	gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, FALSE);
385 
386 	return TRUE;
387 }
388 
389 void
widget_embedder_set_ucolor(WidgetEmbedder * bin,gdouble red,gdouble green,gdouble blue,gdouble alpha)390 widget_embedder_set_ucolor (WidgetEmbedder *bin, gdouble red, gdouble green,
391 			    gdouble blue, gdouble alpha)
392 {
393 	bin->red = red;
394 	bin->green = green;
395 	bin->blue = blue;
396 	bin->alpha = alpha;
397 	gtk_widget_queue_draw (GTK_WIDGET (bin));
398 }
399 
400 
401 static gboolean
widget_embedder_draw(GtkWidget * widget,cairo_t * cr)402 widget_embedder_draw (GtkWidget *widget, cairo_t *cr)
403 {
404 	WidgetEmbedder *bin = WIDGET_EMBEDDER (widget);
405 #define MARGIN 1.5
406 	GdkWindow *window;
407 
408 	window = gtk_widget_get_window (widget);
409 	if (gtk_cairo_should_draw_window (cr, window)) {
410 		cairo_surface_t *surface;
411 		GtkAllocation child_area;
412 
413 		if (bin->child && gtk_widget_get_visible (bin->child)) {
414 			surface = gdk_offscreen_window_get_surface (bin->offscreen_window);
415 			gtk_widget_get_allocation (bin->child, &child_area);
416 			cairo_set_source_surface (cr, surface, 0, 0);
417 			cairo_paint (cr);
418 
419 			if (! bin->valid) {
420 				if ((bin->red >= 0.) && (bin->red <= 1.) &&
421 				    (bin->green >= 0.) && (bin->green <= 1.) &&
422 				    (bin->blue >= 0.) && (bin->blue <= 1.) &&
423 				    (bin->alpha >= 0.) && (bin->alpha <= 1.))
424 					cairo_set_source_rgba (cr, bin->red, bin->green,
425 							       bin->blue, bin->alpha);
426 				else
427 					cairo_set_source_rgba (cr, GDAUI_COLOR_UNKNOWN_MASK);
428 				cairo_rectangle (cr, child_area.x + MARGIN, child_area.y + MARGIN,
429 						 child_area.width - 2. * MARGIN,
430 						 child_area.height - 2. * MARGIN);
431 				cairo_fill (cr);
432 			}
433 		}
434 	}
435 
436 	if (gtk_cairo_should_draw_window (cr, bin->offscreen_window)) {
437 		if (bin->child)
438 			gtk_container_propagate_draw (GTK_CONTAINER (widget),
439 						      bin->child,
440 						      cr);
441 	}
442 
443 	return FALSE;
444 }
445 
446 /**
447  * widget_embedder_set_valid
448  * @bin: a #WidgetEmbedder
449  * @valid: set to %TRUE for a valid entry
450  *
451  * Changes the validity aspect of @bin
452  */
453 void
widget_embedder_set_valid(WidgetEmbedder * bin,gboolean valid)454 widget_embedder_set_valid (WidgetEmbedder *bin, gboolean valid)
455 {
456 	bin->valid = valid;
457 	gtk_widget_queue_draw (GTK_WIDGET (bin));
458 }
459