1 /* gtk-clutter-embed.c: Embeddable ClutterStage
3 * Copyright (C) 2007 OpenedHand
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not see <http://www.fsf.org/licensing>.
19 * Iain Holmes <iain@openedhand.com>
20 * Emmanuele Bassi <ebassi@openedhand.com>
24 * SECTION:gtk-clutter-embed
25 * @short_description: Widget for embedding a Clutter scene
27 * #GtkClutterEmbed is a GTK+ widget embedding a #ClutterStage. Using
28 * a #GtkClutterEmbed widget is possible to build, show and interact with
29 * a scene built using Clutter inside a GTK+ application.
31 * <note>To avoid flickering on show, you should call gtk_widget_show()
32 * or gtk_widget_realize() before calling clutter_actor_show() on the
33 * embedded #ClutterStage actor. This is needed for Clutter to be able
34 * to paint on the #GtkClutterEmbed widget.</note>
43 #include "gtk-clutter-embed.h"
45 #include <glib-object.h>
49 #if defined(HAVE_CLUTTER_GTK_X11)
51 #include <clutter/x11/clutter-x11.h>
54 #elif defined(HAVE_CLUTTER_GTK_WIN32)
56 #include <clutter/clutter-win32.h>
57 #include <gdk/gdkwin32.h>
59 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
61 G_DEFINE_TYPE (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
63 #define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_CLUTTER_TYPE_EMBED, GtkClutterEmbedPrivate))
65 struct _GtkClutterEmbedPrivate
69 guint queue_redraw_id;
73 gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
76 GdkEvent *event = gdk_event_new (GDK_CONFIGURE);
78 widget = GTK_WIDGET (embed);
80 event->configure.window = g_object_ref (widget->window);
81 event->configure.send_event = TRUE;
82 event->configure.x = widget->allocation.x;
83 event->configure.y = widget->allocation.y;
84 event->configure.width = widget->allocation.width;
85 event->configure.height = widget->allocation.height;
87 gtk_widget_event (widget, event);
88 gdk_event_free (event);
92 on_stage_queue_redraw (ClutterStage *stage,
96 GtkWidget *embed = user_data;
98 /* we stop the emission of the Stage::queue-redraw signal to prevent
99 * the default handler from running; then we queue a redraw on the
100 * GtkClutterEmbed widget which will cause an expose event to be
101 * emitted. the Stage is redrawn in the expose event handler, thus
102 * "slaving" the Clutter redraw cycle to GTK+'s own
104 g_signal_stop_emission_by_name (stage, "queue-redraw");
106 gtk_widget_queue_draw (embed);
110 gtk_clutter_embed_dispose (GObject *gobject)
112 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
114 if (priv->queue_redraw_id)
116 g_signal_handler_disconnect (priv->stage, priv->queue_redraw_id);
117 priv->queue_redraw_id = 0;
122 clutter_actor_destroy (priv->stage);
126 G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
130 gtk_clutter_embed_show (GtkWidget *widget)
132 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
134 if (GTK_WIDGET_REALIZED (widget))
135 clutter_actor_show (priv->stage);
137 GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
141 gtk_clutter_embed_hide (GtkWidget *widget)
143 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
145 /* gtk emits a hide signal during dispose, so it's possible we may
146 * have already disposed priv->stage. */
148 clutter_actor_hide (priv->stage);
150 GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
154 gtk_clutter_embed_realize (GtkWidget *widget)
156 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
157 GdkWindowAttr attributes;
160 #ifdef HAVE_CLUTTER_GTK_X11
162 const XVisualInfo *xvinfo;
164 GdkColormap *colormap;
166 /* We need to use the colormap from the Clutter visual */
167 xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
170 g_critical ("Unable to retrieve the XVisualInfo from Clutter");
174 visual = gdk_x11_screen_lookup_visual (gtk_widget_get_screen (widget),
176 colormap = gdk_colormap_new (visual, FALSE);
177 gtk_widget_set_colormap (widget, colormap);
181 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
183 attributes.window_type = GDK_WINDOW_CHILD;
184 attributes.x = widget->allocation.x;
185 attributes.y = widget->allocation.y;
186 attributes.width = widget->allocation.width;
187 attributes.height = widget->allocation.height;
188 attributes.wclass = GDK_INPUT_OUTPUT;
189 attributes.visual = gtk_widget_get_visual (widget);
190 attributes.colormap = gtk_widget_get_colormap (widget);
192 /* NOTE: GDK_MOTION_NOTIFY above should be safe as Clutter does its own
195 attributes.event_mask = gtk_widget_get_events (widget)
197 | GDK_BUTTON_PRESS_MASK
198 | GDK_BUTTON_RELEASE_MASK
200 | GDK_KEY_RELEASE_MASK
201 | GDK_POINTER_MOTION_MASK;
203 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
205 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
208 gdk_window_set_user_data (widget->window, widget);
210 widget->style = gtk_style_attach (widget->style, widget->window);
211 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
213 gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
215 #if defined(HAVE_CLUTTER_GTK_X11)
216 clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage),
217 GDK_WINDOW_XID (widget->window));
218 #elif defined(HAVE_CLUTTER_GTK_WIN32)
219 clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage),
220 GDK_WINDOW_HWND (widget->window));
221 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
223 clutter_actor_realize (priv->stage);
225 if (GTK_WIDGET_VISIBLE (widget))
226 clutter_actor_show (priv->stage);
228 gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
232 gtk_clutter_embed_unrealize (GtkWidget *widget)
234 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
236 clutter_actor_hide (priv->stage);
238 GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unrealize (widget);
242 gtk_clutter_embed_size_allocate (GtkWidget *widget,
243 GtkAllocation *allocation)
245 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
247 widget->allocation = *allocation;
249 if (GTK_WIDGET_REALIZED (widget))
251 gdk_window_move_resize (widget->window,
252 allocation->x, allocation->y,
253 allocation->width, allocation->height);
255 gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
258 /* change the size of the stage and ensure that the viewport
259 * has been updated as well
261 clutter_actor_set_size (priv->stage,
265 clutter_stage_ensure_viewport (CLUTTER_STAGE (priv->stage));
269 gtk_clutter_embed_expose_event (GtkWidget *widget,
270 GdkEventExpose *event)
272 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
274 /* force a redraw on expose */
275 clutter_redraw (CLUTTER_STAGE (priv->stage));
281 gtk_clutter_embed_motion_notify_event (GtkWidget *widget,
282 GdkEventMotion *event)
284 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
285 ClutterEvent cevent = { 0, };
287 cevent.type = CLUTTER_MOTION;
288 cevent.any.stage = CLUTTER_STAGE (priv->stage);
289 cevent.motion.x = event->x;
290 cevent.motion.y = event->y;
291 cevent.motion.time = event->time;
292 cevent.motion.modifier_state = event->state;
294 clutter_do_event (&cevent);
296 /* doh - motion events can push ENTER/LEAVE events onto Clutters
297 * internal event queue which we do really ever touch (essentially
298 * proxying from gtks queue). The below pumps them back out and
300 * *could* be side effects with below though doubful as no other
301 * events reach the queue (we shut down event collection). Maybe
302 * a peek_mask type call could be even safer.
304 while (clutter_events_pending())
306 ClutterEvent *ev = clutter_event_get ();
309 clutter_do_event (ev);
310 clutter_event_free (ev);
318 gtk_clutter_embed_button_event (GtkWidget *widget,
319 GdkEventButton *event)
321 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
322 ClutterEvent cevent = { 0, };
324 if (event->type == GDK_BUTTON_PRESS ||
325 event->type == GDK_2BUTTON_PRESS ||
326 event->type == GDK_3BUTTON_PRESS)
327 cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
328 else if (event->type == GDK_BUTTON_RELEASE)
329 cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
333 cevent.any.stage = CLUTTER_STAGE (priv->stage);
334 cevent.button.x = event->x;
335 cevent.button.y = event->y;
336 cevent.button.time = event->time;
337 cevent.button.click_count =
338 (event->type == GDK_BUTTON_PRESS ? 1
339 : (event->type == GDK_2BUTTON_PRESS ? 2
341 cevent.button.modifier_state = event->state;
342 cevent.button.button = event->button;
344 clutter_do_event (&cevent);
350 gtk_clutter_embed_key_event (GtkWidget *widget,
353 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
354 ClutterEvent cevent = { 0, };
356 if (event->type == GDK_KEY_PRESS)
357 cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
358 else if (event->type == GDK_KEY_RELEASE)
359 cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
363 cevent.any.stage = CLUTTER_STAGE (priv->stage);
364 cevent.key.time = event->time;
365 cevent.key.modifier_state = event->state;
366 cevent.key.keyval = event->keyval;
367 cevent.key.hardware_keycode = event->hardware_keycode;
368 cevent.key.unicode_value = gdk_keyval_to_unicode (event->keyval);
370 clutter_do_event (&cevent);
376 gtk_clutter_embed_map_event (GtkWidget *widget,
379 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
380 GtkWidgetClass *parent_class;
381 gboolean res = FALSE;
383 parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
384 if (parent_class->map_event)
385 res = parent_class->map_event (widget, event);
387 clutter_actor_map (priv->stage);
393 gtk_clutter_embed_unmap_event (GtkWidget *widget,
396 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
397 GtkWidgetClass *parent_class;
398 gboolean res = FALSE;
400 parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
401 if (parent_class->unmap_event)
402 res = parent_class->unmap_event (widget, event);
404 clutter_actor_unmap (priv->stage);
410 gtk_clutter_embed_unmap (GtkWidget *widget)
412 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
414 clutter_actor_unmap (priv->stage);
416 GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unmap (widget);
420 gtk_clutter_embed_focus_in (GtkWidget *widget,
421 GdkEventFocus *event)
423 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
425 g_signal_emit_by_name (priv->stage, "activate");
431 gtk_clutter_embed_focus_out (GtkWidget *widget,
432 GdkEventFocus *event)
434 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
436 g_signal_emit_by_name (priv->stage, "deactivate");
438 /* give back key focus to the stage */
439 clutter_stage_set_key_focus (CLUTTER_STAGE (priv->stage), NULL);
445 gtk_clutter_embed_scroll_event (GtkWidget *widget,
446 GdkEventScroll *event)
448 GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
450 ClutterEvent cevent = { 0, };
452 if (event->type == GDK_SCROLL)
453 cevent.type = cevent.scroll.type = CLUTTER_SCROLL;
457 cevent.any.stage = CLUTTER_STAGE (priv->stage);
458 cevent.scroll.x = (gint) event->x;
459 cevent.scroll.y = (gint) event->y;
460 cevent.scroll.time = event->time;
461 cevent.scroll.direction = event->direction;
462 cevent.scroll.modifier_state = event->state;
464 clutter_do_event (&cevent);
470 gtk_clutter_embed_style_set (GtkWidget *widget,
474 GtkSettings *settings;
477 const cairo_font_options_t *font_options;
478 gint double_click_time, double_click_distance;
479 ClutterBackend *backend;
481 GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->style_set (widget,
484 if (gtk_widget_has_screen (widget))
485 screen = gtk_widget_get_screen (widget);
487 screen = gdk_screen_get_default ();
489 dpi = gdk_screen_get_resolution (screen);
493 font_options = gdk_screen_get_font_options (screen);
495 settings = gtk_settings_get_for_screen (screen);
496 g_object_get (G_OBJECT (settings),
497 "gtk-font-name", &font_name,
498 "gtk-double-click-time", &double_click_time,
499 "gtk-double-click-distance", &double_click_distance,
502 /* copy all settings and values coming from GTK+ into
503 * the ClutterBackend; this way, a scene embedded into
504 * a GtkClutterEmbed will not look completely alien
506 backend = clutter_get_default_backend ();
507 clutter_backend_set_resolution (backend, dpi);
508 clutter_backend_set_font_options (backend, font_options);
509 clutter_backend_set_font_name (backend, font_name);
510 clutter_backend_set_double_click_time (backend, double_click_time);
511 clutter_backend_set_double_click_distance (backend, double_click_distance);
517 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
519 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
520 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
522 g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
524 gobject_class->dispose = gtk_clutter_embed_dispose;
526 widget_class->style_set = gtk_clutter_embed_style_set;
527 widget_class->size_allocate = gtk_clutter_embed_size_allocate;
528 widget_class->realize = gtk_clutter_embed_realize;
529 widget_class->unrealize = gtk_clutter_embed_unrealize;
530 widget_class->show = gtk_clutter_embed_show;
531 widget_class->hide = gtk_clutter_embed_hide;
532 widget_class->unmap = gtk_clutter_embed_unmap;
533 widget_class->expose_event = gtk_clutter_embed_expose_event;
534 widget_class->button_press_event = gtk_clutter_embed_button_event;
535 widget_class->button_release_event = gtk_clutter_embed_button_event;
536 widget_class->key_press_event = gtk_clutter_embed_key_event;
537 widget_class->key_release_event = gtk_clutter_embed_key_event;
538 widget_class->motion_notify_event = gtk_clutter_embed_motion_notify_event;
539 widget_class->expose_event = gtk_clutter_embed_expose_event;
540 widget_class->map_event = gtk_clutter_embed_map_event;
541 widget_class->unmap_event = gtk_clutter_embed_unmap_event;
542 widget_class->focus_in_event = gtk_clutter_embed_focus_in;
543 widget_class->focus_out_event = gtk_clutter_embed_focus_out;
544 widget_class->scroll_event = gtk_clutter_embed_scroll_event;
548 gtk_clutter_embed_init (GtkClutterEmbed *embed)
550 GtkClutterEmbedPrivate *priv;
552 embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
554 GTK_WIDGET_SET_FLAGS (embed, GTK_CAN_FOCUS);
555 GTK_WIDGET_UNSET_FLAGS (embed, GTK_NO_WINDOW);
557 /* disable double-buffering: it's automatically provided
560 gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
562 /* we always create new stages rather than use the default */
563 priv->stage = clutter_stage_new ();
565 /* intercept the queue-redraw signal of the stage to know when
566 * Clutter-side requests a redraw; this way we can also request
569 priv->queue_redraw_id =
570 g_signal_connect (priv->stage,
571 "queue-redraw", G_CALLBACK (on_stage_queue_redraw),
576 * gtk_clutter_embed_new:
578 * Creates a new #GtkClutterEmbed widget. This widget can be
579 * used to build a scene using Clutter API into a GTK+ application.
581 * Return value: the newly created #GtkClutterEmbed
586 gtk_clutter_embed_new (void)
588 return g_object_new (GTK_CLUTTER_TYPE_EMBED, NULL);
592 * gtk_clutter_embed_get_stage:
593 * @embed: a #GtkClutterEmbed
595 * Retrieves the #ClutterStage from @embed. The returned stage can be
596 * used to add actors to the Clutter scene.
598 * Return value: the Clutter stage. You should never destroy or unref
599 * the returned actor.
604 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
606 g_return_val_if_fail (GTK_CLUTTER_IS_EMBED (embed), NULL);
608 return embed->priv->stage;