2008-04-21 Emmanuele Bassi <ebassi@openedhand.com>
[clutter-gtk] / clutter-gtk / gtk-clutter-embed.c
1 /* gtk-clutter-embed.c: Embeddable ClutterStage
2  *
3  * Copyright (C) 2007 OpenedHand
4  *
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.
9  *
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.
14  *
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>.
17  *
18  * Authors:
19  *   Iain Holmes  <iain@openedhand.com>
20  *   Emmanuele Bassi  <ebassi@openedhand.com>
21  */
22
23 /**
24  * SECTION:gtk-clutter-embed
25  * @short_description: Widget for embedding a Clutter scene
26  *
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.
30  *
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>
35  *
36  * Since: 0.6
37  */
38
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42
43 #include <glib-object.h>
44
45 #include <gdk/gdk.h>
46 #include <gtk/gtkmain.h>
47
48 #include <clutter/clutter-main.h>
49 #include <clutter/clutter-stage.h>
50 #include <clutter/clutter-container.h>
51
52 #if defined(HAVE_CLUTTER_GTK_X11)
53
54 #include <clutter/clutter-x11.h>
55 #include <gdk/gdkx.h>
56
57 #elif defined(HAVE_CLUTTER_GTK_WIN32)
58
59 #include <clutter/clutter-win32.h>
60 #include <gdk/gdkwin32.h>
61
62 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
63
64 #include "gtk-clutter-embed.h"
65
66 static void clutter_container_iface_init (ClutterContainerIface *iface);
67
68 G_DEFINE_TYPE_WITH_CODE (GtkClutterEmbed,
69                          gtk_clutter_embed,
70                          GTK_TYPE_WIDGET,
71                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
72                                                 clutter_container_iface_init));
73
74 #define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_CLUTTER_EMBED, GtkClutterEmbedPrivate))
75
76 struct _GtkClutterEmbedPrivate
77 {
78   ClutterActor *stage;
79 };
80
81 static void
82 gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
83 {
84   GtkWidget *widget;
85   GdkEvent *event = gdk_event_new (GDK_CONFIGURE);
86
87   widget = GTK_WIDGET (embed);
88
89   event->configure.window = g_object_ref (widget->window);
90   event->configure.send_event = TRUE;
91   event->configure.x = widget->allocation.x;
92   event->configure.y = widget->allocation.y;
93   event->configure.width = widget->allocation.width;
94   event->configure.height = widget->allocation.height;
95   
96   gtk_widget_event (widget, event);
97   gdk_event_free (event);
98 }
99
100 static void
101 gtk_clutter_embed_dispose (GObject *gobject)
102 {
103   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
104
105   if (priv->stage)
106     {
107       clutter_actor_destroy (priv->stage);
108       priv->stage = NULL;
109     }
110
111   G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
112 }
113
114 static void
115 gtk_clutter_embed_show (GtkWidget *widget)
116 {
117   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
118
119   /* Make sure the widget is realised before we show */
120   gtk_widget_realize (widget);
121
122   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
123
124   clutter_actor_show (priv->stage);
125 }
126
127 static void
128 gtk_clutter_embed_hide (GtkWidget *widget)
129 {
130   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
131
132   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
133
134   clutter_actor_hide (priv->stage);
135 }
136
137 static void
138 gtk_clutter_embed_realize (GtkWidget *widget)
139 {
140   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv; 
141   GdkWindowAttr attributes;
142   int attributes_mask;
143   
144   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
145
146   attributes.window_type = GDK_WINDOW_CHILD;
147   attributes.x = widget->allocation.x;
148   attributes.y = widget->allocation.y;
149   attributes.width = widget->allocation.width;
150   attributes.height = widget->allocation.height;
151   attributes.wclass = GDK_INPUT_OUTPUT;
152   attributes.visual = gtk_widget_get_visual (widget);
153   attributes.colormap = gtk_widget_get_colormap (widget);
154   attributes.event_mask = gtk_widget_get_events (widget)
155     | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
156     | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK;
157
158   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
159
160   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
161                                    &attributes,
162                                    attributes_mask);
163   gdk_window_set_user_data (widget->window, widget);
164
165   widget->style = gtk_style_attach (widget->style, widget->window);
166   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
167   
168   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
169
170 #if defined(HAVE_CLUTTER_GTK_X11)
171   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
172                                  GDK_WINDOW_XID (widget->window));
173 #elif defined(HAVE_CLUTTER_GTK_WIN32)
174   clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
175                                    GDK_WINDOW_HWND (widget->window));
176 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
177
178   clutter_redraw (CLUTTER_STAGE (priv->stage));
179
180   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
181 }
182
183 static void
184 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
185                                  GtkAllocation *allocation)
186 {
187   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
188
189   widget->allocation = *allocation;
190
191   if (GTK_WIDGET_REALIZED (widget))
192     {
193       gdk_window_move_resize (widget->window,
194                               allocation->x, allocation->y,
195                               allocation->width, allocation->height);
196
197       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
198     }
199
200   clutter_actor_set_size (priv->stage,
201                           allocation->width,
202                           allocation->height);
203
204   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
205     clutter_actor_queue_redraw (priv->stage);
206 }
207
208 static gboolean
209 gtk_clutter_embed_button_event (GtkWidget      *widget,
210                                 GdkEventButton *event)
211 {
212   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
213   ClutterEvent cevent = { 0, };
214
215   if (event->type == GDK_BUTTON_PRESS ||
216       event->type == GDK_2BUTTON_PRESS ||
217       event->type == GDK_3BUTTON_PRESS)
218     cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
219   else if (event->type == GDK_BUTTON_RELEASE)
220     cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
221   else
222     return FALSE;
223
224   cevent.any.stage = CLUTTER_STAGE (priv->stage);
225   cevent.button.x = event->x;
226   cevent.button.y = event->y;
227   cevent.button.time = event->time;
228   cevent.button.click_count =
229     (event->type == GDK_BUTTON_PRESS ? 1
230                                      : (event->type == GDK_2BUTTON_PRESS ? 2
231                                                                          : 3));
232   cevent.button.modifier_state = event->state;
233   cevent.button.button = event->button;
234
235   clutter_do_event (&cevent);
236
237   return FALSE;
238 }
239
240 static gboolean
241 gtk_clutter_embed_key_event (GtkWidget   *widget,
242                              GdkEventKey *event)
243 {
244   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
245   ClutterEvent cevent = { 0, };
246
247   if (event->type == GDK_KEY_PRESS)
248     cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
249   else if (event->type == GDK_KEY_RELEASE)
250     cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
251   else
252     return FALSE;
253
254   cevent.any.stage = CLUTTER_STAGE (priv->stage);
255   cevent.key.time = event->time;
256   cevent.key.modifier_state = event->state;
257   cevent.key.keyval = event->keyval;
258   cevent.key.hardware_keycode = event->hardware_keycode;
259
260   clutter_do_event (&cevent);
261
262   return FALSE;
263 }
264
265 static gboolean
266 gtk_clutter_embed_expose_event (GtkWidget *widget, GdkEventExpose *event)
267 {
268   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
269
270   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
271     clutter_actor_queue_redraw (priv->stage);
272
273   return TRUE;
274 }
275
276 static gboolean
277 gtk_clutter_embed_map_event (GtkWidget       *widget,
278                              GdkEventAny     *event)
279 {
280   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
281
282   /* The backend wont get the XEvent as we go strait to do_event().
283    * So we have to make sure we set the event here.
284   */
285   CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
286
287   return TRUE;
288 }
289
290 static void
291 gtk_clutter_embed_add (ClutterContainer *container,
292                        ClutterActor     *actor)
293 {
294   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
295   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
296
297   clutter_container_add_actor (stage, actor);
298   g_signal_emit_by_name (container, "actor-added", actor);
299 }
300
301 static void
302 gtk_clutter_embed_remove (ClutterContainer *container,
303                           ClutterActor     *actor)
304 {
305   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
306   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
307
308   g_object_ref (actor);
309
310   clutter_container_remove_actor (stage, actor);
311   g_signal_emit_by_name (container, "actor-removed", actor);
312
313   g_object_unref (actor);
314 }
315
316 static void
317 gtk_clutter_embed_foreach (ClutterContainer *container,
318                            ClutterCallback   callback,
319                            gpointer          callback_data)
320 {
321   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
322   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
323
324   clutter_container_foreach (stage, callback, callback_data);
325 }
326
327 static void
328 gtk_clutter_embed_raise (ClutterContainer *container,
329                          ClutterActor     *child,
330                          ClutterActor     *sibling)
331 {
332   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
333   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
334
335   clutter_container_raise_child (stage, child, sibling);
336 }
337
338 static void
339 gtk_clutter_embed_lower (ClutterContainer *container,
340                          ClutterActor     *child,
341                          ClutterActor     *sibling)
342 {
343   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
344   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
345
346   clutter_container_lower_child (stage, child, sibling);
347 }
348
349 static void
350 gtk_clutter_embed_sort_depth_order (ClutterContainer *container)
351 {
352   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
353   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
354
355   clutter_container_sort_depth_order (stage);
356 }
357
358 static void
359 clutter_container_iface_init (ClutterContainerIface *iface)
360 {
361   iface->add = gtk_clutter_embed_add;
362   iface->remove = gtk_clutter_embed_remove;
363   iface->foreach = gtk_clutter_embed_foreach;
364   iface->raise = gtk_clutter_embed_raise;
365   iface->lower = gtk_clutter_embed_lower;
366   iface->sort_depth_order = gtk_clutter_embed_sort_depth_order;
367 }
368
369 static void
370 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
371 {
372   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
373   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
374
375   g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
376
377   gobject_class->dispose = gtk_clutter_embed_dispose;
378
379   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
380   widget_class->realize = gtk_clutter_embed_realize;
381   widget_class->show = gtk_clutter_embed_show;
382   widget_class->hide = gtk_clutter_embed_hide;
383   widget_class->button_press_event = gtk_clutter_embed_button_event;
384   widget_class->button_release_event = gtk_clutter_embed_button_event;
385   widget_class->key_press_event = gtk_clutter_embed_key_event;
386   widget_class->key_release_event = gtk_clutter_embed_key_event;
387   widget_class->expose_event = gtk_clutter_embed_expose_event;
388   widget_class->map_event = gtk_clutter_embed_map_event;
389 }
390
391 static void
392 gtk_clutter_embed_init (GtkClutterEmbed *embed)
393 {
394   GtkClutterEmbedPrivate *priv;
395
396   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
397
398   /* disable double-buffering: it's automatically provided
399    * by OpenGL
400    */
401   gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
402
403   /* we always create new stages rather than use the default */
404   priv->stage = clutter_stage_new ();
405
406 #ifdef HAVE_CLUTTER_GTK_X11
407   {
408     const XVisualInfo *xvinfo;
409     GdkVisual *visual;
410     GdkColormap *colormap;
411
412     /* We need to use the colormap from the Clutter visual */
413     xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
414     visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
415                                            xvinfo->visualid);
416     colormap = gdk_colormap_new (visual, FALSE);
417     gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
418   }
419 #endif
420 }
421
422 /**
423  * gtk_clutter_init:
424  * @argc: pointer to the arguments count, or %NULL
425  * @argv: pointer to the arguments vector, or %NULL
426  *
427  * This function should be called instead of clutter_init() and
428  * gtk_init().
429  *
430  * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
431  *   on failure.
432  *
433  * Since: 0.8
434  */
435 ClutterInitError
436 gtk_clutter_init (int    *argc,
437                   char ***argv)
438 {
439   if (!gtk_init_check (argc, argv))
440     return CLUTTER_INIT_ERROR_GTK;
441
442 #if defined(HAVE_CLUTTER_GTK_X11)
443   clutter_x11_set_display (GDK_DISPLAY());
444   clutter_x11_disable_event_retrieval ();
445 #elif defined(HAVE_CLUTTER_GTK_WIN32)
446   clutter_win32_disable_event_retrieval ();
447 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
448
449   return clutter_init (argc, argv);
450 }
451
452 /**
453  * gtk_clutter_embed_new:
454  *
455  * Creates a new #GtkClutterEmbed widget. This widget can be
456  * used to build a scene using Clutter API into a GTK+ application.
457  *
458  * Return value: the newly created #GtkClutterEmbed
459  *
460  * Since: 0.6
461  */
462 GtkWidget *
463 gtk_clutter_embed_new (void)
464 {
465   return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
466 }
467
468 /**
469  * gtk_clutter_embed_get_stage:
470  * @embed: a #GtkClutterEmbed
471  *
472  * Retrieves the #ClutterStage from @embed. The returned stage can be
473  * used to add actors to the Clutter scene.
474  *
475  * Return value: the Clutter stage. You should never destroy or unref
476  *   the returned actor.
477  *
478  * Since: 0.6
479  */
480 ClutterActor *
481 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
482 {
483   g_return_val_if_fail (GTK_IS_CLUTTER_EMBED (embed), NULL);
484
485   return embed->priv->stage;
486 }