2008-05-07 Matthew Allum <mallum@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
155   /* NOTE: GDK_MOTION_NOTIFY above should be safe as Clutter does its own
156    *       throtling. 
157   */
158   attributes.event_mask = gtk_widget_get_events (widget)
159     | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
160     | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_MOTION_NOTIFY;
161
162
163   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
164
165   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
166                                    &attributes,
167                                    attributes_mask);
168   gdk_window_set_user_data (widget->window, widget);
169
170   widget->style = gtk_style_attach (widget->style, widget->window);
171   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
172   
173   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
174
175 #if defined(HAVE_CLUTTER_GTK_X11)
176   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
177                                  GDK_WINDOW_XID (widget->window));
178 #elif defined(HAVE_CLUTTER_GTK_WIN32)
179   clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
180                                    GDK_WINDOW_HWND (widget->window));
181 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
182
183   clutter_redraw (CLUTTER_STAGE (priv->stage));
184
185   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
186 }
187
188 static void
189 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
190                                  GtkAllocation *allocation)
191 {
192   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
193
194   widget->allocation = *allocation;
195
196   if (GTK_WIDGET_REALIZED (widget))
197     {
198       gdk_window_move_resize (widget->window,
199                               allocation->x, allocation->y,
200                               allocation->width, allocation->height);
201
202       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
203     }
204
205   clutter_actor_set_size (priv->stage,
206                           allocation->width,
207                           allocation->height);
208
209   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
210     clutter_actor_queue_redraw (priv->stage);
211 }
212
213 static gboolean
214 gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
215                                        GdkEventMotion *event)
216 {
217   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
218   ClutterEvent cevent = { 0, };
219
220   cevent.type = CLUTTER_MOTION;
221   cevent.any.stage = CLUTTER_STAGE (priv->stage);
222   cevent.motion.x = event->x;
223   cevent.motion.y = event->y;
224   cevent.motion.time = event->time;
225
226   clutter_do_event (&cevent);
227
228   /* doh - motion events can push ENTER/LEAVE events onto Clutters
229    * internal event queue which we do really ever touch (essentially
230    * proxying from gtks queue). The below pumps them back out and
231    * processes.
232    * *could* be side effects with below though doubful as no other
233    * events reach the queue (we shut down event collection). Maybe
234    * a peek_mask type call could be even safer. 
235   */
236   while (clutter_events_pending())
237     {
238       ClutterEvent *ev = clutter_event_get ();
239       if (ev)
240         {
241           clutter_do_event (ev);
242           clutter_event_free (ev);
243         }
244     }
245
246   return FALSE;
247 }
248
249 static gboolean
250 gtk_clutter_embed_button_event (GtkWidget      *widget,
251                                 GdkEventButton *event)
252 {
253   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
254   ClutterEvent cevent = { 0, };
255
256   if (event->type == GDK_BUTTON_PRESS ||
257       event->type == GDK_2BUTTON_PRESS ||
258       event->type == GDK_3BUTTON_PRESS)
259     cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
260   else if (event->type == GDK_BUTTON_RELEASE)
261     cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
262   else
263     return FALSE;
264
265   cevent.any.stage = CLUTTER_STAGE (priv->stage);
266   cevent.button.x = event->x;
267   cevent.button.y = event->y;
268   cevent.button.time = event->time;
269   cevent.button.click_count =
270     (event->type == GDK_BUTTON_PRESS ? 1
271                                      : (event->type == GDK_2BUTTON_PRESS ? 2
272                                                                          : 3));
273   cevent.button.modifier_state = event->state;
274   cevent.button.button = event->button;
275
276   clutter_do_event (&cevent);
277
278   return FALSE;
279 }
280
281 static gboolean
282 gtk_clutter_embed_key_event (GtkWidget   *widget,
283                              GdkEventKey *event)
284 {
285   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
286   ClutterEvent cevent = { 0, };
287
288   if (event->type == GDK_KEY_PRESS)
289     cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
290   else if (event->type == GDK_KEY_RELEASE)
291     cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
292   else
293     return FALSE;
294
295   cevent.any.stage = CLUTTER_STAGE (priv->stage);
296   cevent.key.time = event->time;
297   cevent.key.modifier_state = event->state;
298   cevent.key.keyval = event->keyval;
299   cevent.key.hardware_keycode = event->hardware_keycode;
300
301   clutter_do_event (&cevent);
302
303   return FALSE;
304 }
305
306 static gboolean
307 gtk_clutter_embed_expose_event (GtkWidget *widget, GdkEventExpose *event)
308 {
309   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
310
311   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
312     clutter_actor_queue_redraw (priv->stage);
313
314   return TRUE;
315 }
316
317 static gboolean
318 gtk_clutter_embed_map_event (GtkWidget       *widget,
319                              GdkEventAny     *event)
320 {
321   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
322
323   /* The backend wont get the XEvent as we go strait to do_event().
324    * So we have to make sure we set the event here.
325   */
326   CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
327
328   return TRUE;
329 }
330
331 static void
332 gtk_clutter_embed_add (ClutterContainer *container,
333                        ClutterActor     *actor)
334 {
335   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
336   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
337
338   clutter_container_add_actor (stage, actor);
339   g_signal_emit_by_name (container, "actor-added", actor);
340 }
341
342 static void
343 gtk_clutter_embed_remove (ClutterContainer *container,
344                           ClutterActor     *actor)
345 {
346   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
347   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
348
349   g_object_ref (actor);
350
351   clutter_container_remove_actor (stage, actor);
352   g_signal_emit_by_name (container, "actor-removed", actor);
353
354   g_object_unref (actor);
355 }
356
357 static void
358 gtk_clutter_embed_foreach (ClutterContainer *container,
359                            ClutterCallback   callback,
360                            gpointer          callback_data)
361 {
362   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
363   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
364
365   clutter_container_foreach (stage, callback, callback_data);
366 }
367
368 static void
369 gtk_clutter_embed_raise (ClutterContainer *container,
370                          ClutterActor     *child,
371                          ClutterActor     *sibling)
372 {
373   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
374   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
375
376   clutter_container_raise_child (stage, child, sibling);
377 }
378
379 static void
380 gtk_clutter_embed_lower (ClutterContainer *container,
381                          ClutterActor     *child,
382                          ClutterActor     *sibling)
383 {
384   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
385   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
386
387   clutter_container_lower_child (stage, child, sibling);
388 }
389
390 static void
391 gtk_clutter_embed_sort_depth_order (ClutterContainer *container)
392 {
393   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
394   ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
395
396   clutter_container_sort_depth_order (stage);
397 }
398
399 static void
400 clutter_container_iface_init (ClutterContainerIface *iface)
401 {
402   iface->add = gtk_clutter_embed_add;
403   iface->remove = gtk_clutter_embed_remove;
404   iface->foreach = gtk_clutter_embed_foreach;
405   iface->raise = gtk_clutter_embed_raise;
406   iface->lower = gtk_clutter_embed_lower;
407   iface->sort_depth_order = gtk_clutter_embed_sort_depth_order;
408 }
409
410 static void
411 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
412 {
413   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
414   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
415
416   g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
417
418   gobject_class->dispose = gtk_clutter_embed_dispose;
419
420   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
421   widget_class->realize = gtk_clutter_embed_realize;
422   widget_class->show = gtk_clutter_embed_show;
423   widget_class->hide = gtk_clutter_embed_hide;
424   widget_class->button_press_event = gtk_clutter_embed_button_event;
425   widget_class->button_release_event = gtk_clutter_embed_button_event;
426   widget_class->key_press_event = gtk_clutter_embed_key_event;
427   widget_class->key_release_event = gtk_clutter_embed_key_event;
428   widget_class->motion_notify_event = gtk_clutter_embed_motion_notify_event;
429   widget_class->expose_event = gtk_clutter_embed_expose_event;
430   widget_class->map_event = gtk_clutter_embed_map_event;
431 }
432
433 static void
434 gtk_clutter_embed_init (GtkClutterEmbed *embed)
435 {
436   GtkClutterEmbedPrivate *priv;
437
438   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
439
440   /* disable double-buffering: it's automatically provided
441    * by OpenGL
442    */
443   gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
444
445   /* we always create new stages rather than use the default */
446   priv->stage = clutter_stage_new ();
447
448 #ifdef HAVE_CLUTTER_GTK_X11
449   {
450     const XVisualInfo *xvinfo;
451     GdkVisual *visual;
452     GdkColormap *colormap;
453
454     /* We need to use the colormap from the Clutter visual */
455     xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
456     visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
457                                            xvinfo->visualid);
458     colormap = gdk_colormap_new (visual, FALSE);
459     gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
460   }
461 #endif
462 }
463
464 /**
465  * gtk_clutter_init:
466  * @argc: pointer to the arguments count, or %NULL
467  * @argv: pointer to the arguments vector, or %NULL
468  *
469  * This function should be called instead of clutter_init() and
470  * gtk_init().
471  *
472  * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
473  *   on failure.
474  *
475  * Since: 0.8
476  */
477 ClutterInitError
478 gtk_clutter_init (int    *argc,
479                   char ***argv)
480 {
481   if (!gtk_init_check (argc, argv))
482     return CLUTTER_INIT_ERROR_GTK;
483
484 #if defined(HAVE_CLUTTER_GTK_X11)
485   clutter_x11_set_display (GDK_DISPLAY());
486   clutter_x11_disable_event_retrieval ();
487 #elif defined(HAVE_CLUTTER_GTK_WIN32)
488   clutter_win32_disable_event_retrieval ();
489 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
490
491   return clutter_init (argc, argv);
492 }
493
494 /**
495  * gtk_clutter_embed_new:
496  *
497  * Creates a new #GtkClutterEmbed widget. This widget can be
498  * used to build a scene using Clutter API into a GTK+ application.
499  *
500  * Return value: the newly created #GtkClutterEmbed
501  *
502  * Since: 0.6
503  */
504 GtkWidget *
505 gtk_clutter_embed_new (void)
506 {
507   return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
508 }
509
510 /**
511  * gtk_clutter_embed_get_stage:
512  * @embed: a #GtkClutterEmbed
513  *
514  * Retrieves the #ClutterStage from @embed. The returned stage can be
515  * used to add actors to the Clutter scene.
516  *
517  * Return value: the Clutter stage. You should never destroy or unref
518  *   the returned actor.
519  *
520  * Since: 0.6
521  */
522 ClutterActor *
523 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
524 {
525   g_return_val_if_fail (GTK_IS_CLUTTER_EMBED (embed), NULL);
526
527   return embed->priv->stage;
528 }