2008-07-16 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/x11/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 G_DEFINE_TYPE (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
67
68 #define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_CLUTTER_EMBED, GtkClutterEmbedPrivate))
69
70 struct _GtkClutterEmbedPrivate
71 {
72   ClutterActor *stage;
73 };
74
75 static void
76 gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
77 {
78   GtkWidget *widget;
79   GdkEvent *event = gdk_event_new (GDK_CONFIGURE);
80
81   widget = GTK_WIDGET (embed);
82
83   event->configure.window = g_object_ref (widget->window);
84   event->configure.send_event = TRUE;
85   event->configure.x = widget->allocation.x;
86   event->configure.y = widget->allocation.y;
87   event->configure.width = widget->allocation.width;
88   event->configure.height = widget->allocation.height;
89   
90   gtk_widget_event (widget, event);
91   gdk_event_free (event);
92 }
93
94 static void
95 gtk_clutter_embed_dispose (GObject *gobject)
96 {
97   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
98
99   if (priv->stage)
100     {
101       clutter_actor_destroy (priv->stage);
102       priv->stage = NULL;
103     }
104
105   G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
106 }
107
108 static void
109 gtk_clutter_embed_show (GtkWidget *widget)
110 {
111   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
112
113   /* Make sure the widget is realised before we show */
114   if (!GTK_WIDGET_REALIZED (widget))
115     gtk_widget_realize (widget);
116
117   clutter_actor_show (priv->stage);
118
119   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
120 }
121
122 static void
123 gtk_clutter_embed_hide (GtkWidget *widget)
124 {
125   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
126
127   clutter_actor_hide (priv->stage);
128
129   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
130 }
131
132 static void
133 gtk_clutter_embed_realize (GtkWidget *widget)
134 {
135   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv; 
136   GdkWindowAttr attributes;
137   int attributes_mask;
138   
139   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
140
141   attributes.window_type = GDK_WINDOW_CHILD;
142   attributes.x = widget->allocation.x;
143   attributes.y = widget->allocation.y;
144   attributes.width = widget->allocation.width;
145   attributes.height = widget->allocation.height;
146   attributes.wclass = GDK_INPUT_OUTPUT;
147   attributes.visual = gtk_widget_get_visual (widget);
148   attributes.colormap = gtk_widget_get_colormap (widget);
149
150   /* NOTE: GDK_MOTION_NOTIFY above should be safe as Clutter does its own
151    *       throtling. 
152   */
153   attributes.event_mask = gtk_widget_get_events (widget)
154                         | GDK_EXPOSURE_MASK
155                         | GDK_BUTTON_PRESS_MASK
156                         | GDK_BUTTON_RELEASE_MASK
157                         | GDK_KEY_PRESS_MASK
158                         | GDK_KEY_RELEASE_MASK
159                         | GDK_MOTION_NOTIFY;
160
161   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
162
163   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
164                                    &attributes,
165                                    attributes_mask);
166   gdk_window_set_user_data (widget->window, widget);
167
168   widget->style = gtk_style_attach (widget->style, widget->window);
169   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
170   
171   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
172
173 #if defined(HAVE_CLUTTER_GTK_X11)
174   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
175                                  GDK_WINDOW_XID (widget->window));
176 #elif defined(HAVE_CLUTTER_GTK_WIN32)
177   clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
178                                    GDK_WINDOW_HWND (widget->window));
179 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
180
181   clutter_actor_queue_redraw (CLUTTER_ACTOR (priv->stage));
182
183   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
184 }
185
186 static void
187 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
188                                  GtkAllocation *allocation)
189 {
190   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
191
192   widget->allocation = *allocation;
193
194   if (GTK_WIDGET_REALIZED (widget))
195     {
196       gdk_window_move_resize (widget->window,
197                               allocation->x, allocation->y,
198                               allocation->width, allocation->height);
199
200       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
201     }
202
203   clutter_actor_set_size (priv->stage,
204                           allocation->width,
205                           allocation->height);
206
207   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
208     clutter_actor_queue_redraw (priv->stage);
209 }
210
211 static gboolean
212 gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
213                                        GdkEventMotion *event)
214 {
215   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
216   ClutterEvent cevent = { 0, };
217
218   cevent.type = CLUTTER_MOTION;
219   cevent.any.stage = CLUTTER_STAGE (priv->stage);
220   cevent.motion.x = event->x;
221   cevent.motion.y = event->y;
222   cevent.motion.time = event->time;
223
224   clutter_do_event (&cevent);
225
226   /* doh - motion events can push ENTER/LEAVE events onto Clutters
227    * internal event queue which we do really ever touch (essentially
228    * proxying from gtks queue). The below pumps them back out and
229    * processes.
230    * *could* be side effects with below though doubful as no other
231    * events reach the queue (we shut down event collection). Maybe
232    * a peek_mask type call could be even safer. 
233   */
234   while (clutter_events_pending())
235     {
236       ClutterEvent *ev = clutter_event_get ();
237       if (ev)
238         {
239           clutter_do_event (ev);
240           clutter_event_free (ev);
241         }
242     }
243
244   return FALSE;
245 }
246
247 static gboolean
248 gtk_clutter_embed_button_event (GtkWidget      *widget,
249                                 GdkEventButton *event)
250 {
251   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
252   ClutterEvent cevent = { 0, };
253
254   if (event->type == GDK_BUTTON_PRESS ||
255       event->type == GDK_2BUTTON_PRESS ||
256       event->type == GDK_3BUTTON_PRESS)
257     cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
258   else if (event->type == GDK_BUTTON_RELEASE)
259     cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
260   else
261     return FALSE;
262
263   cevent.any.stage = CLUTTER_STAGE (priv->stage);
264   cevent.button.x = event->x;
265   cevent.button.y = event->y;
266   cevent.button.time = event->time;
267   cevent.button.click_count =
268     (event->type == GDK_BUTTON_PRESS ? 1
269                                      : (event->type == GDK_2BUTTON_PRESS ? 2
270                                                                          : 3));
271   cevent.button.modifier_state = event->state;
272   cevent.button.button = event->button;
273
274   clutter_do_event (&cevent);
275
276   return FALSE;
277 }
278
279 static gboolean
280 gtk_clutter_embed_key_event (GtkWidget   *widget,
281                              GdkEventKey *event)
282 {
283   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
284   ClutterEvent cevent = { 0, };
285
286   if (event->type == GDK_KEY_PRESS)
287     cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
288   else if (event->type == GDK_KEY_RELEASE)
289     cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
290   else
291     return FALSE;
292
293   cevent.any.stage = CLUTTER_STAGE (priv->stage);
294   cevent.key.time = event->time;
295   cevent.key.modifier_state = event->state;
296   cevent.key.keyval = event->keyval;
297   cevent.key.hardware_keycode = event->hardware_keycode;
298
299   clutter_do_event (&cevent);
300
301   return FALSE;
302 }
303
304 static gboolean
305 gtk_clutter_embed_expose_event (GtkWidget      *widget,
306                                 GdkEventExpose *event)
307 {
308   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
309
310   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
311     clutter_actor_queue_redraw (priv->stage);
312
313   return FALSE;
314 }
315
316 static gboolean
317 gtk_clutter_embed_map_event (GtkWidget   *widget,
318                              GdkEventAny *event)
319 {
320   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
321
322   /* The backend wont get the XEvent as we go strait to do_event().
323    * So we have to make sure we set the event here.
324   */
325   CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
326
327   return FALSE;
328 }
329
330 static gboolean
331 gtk_clutter_embed_focus_out (GtkWidget     *widget,
332                              GdkEventFocus *event)
333 {
334   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
335
336   /* give back key focus to the stage */
337   clutter_stage_set_key_focus (CLUTTER_STAGE (priv->stage), NULL);
338
339   return FALSE;
340 }
341
342 static void
343 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
344 {
345   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
346   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
347
348   g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
349
350   gobject_class->dispose = gtk_clutter_embed_dispose;
351
352   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
353   widget_class->realize = gtk_clutter_embed_realize;
354   widget_class->show = gtk_clutter_embed_show;
355   widget_class->hide = gtk_clutter_embed_hide;
356   widget_class->button_press_event = gtk_clutter_embed_button_event;
357   widget_class->button_release_event = gtk_clutter_embed_button_event;
358   widget_class->key_press_event = gtk_clutter_embed_key_event;
359   widget_class->key_release_event = gtk_clutter_embed_key_event;
360   widget_class->motion_notify_event = gtk_clutter_embed_motion_notify_event;
361   widget_class->expose_event = gtk_clutter_embed_expose_event;
362   widget_class->map_event = gtk_clutter_embed_map_event;
363   widget_class->focus_out_event = gtk_clutter_embed_focus_out;
364 }
365
366 static void
367 gtk_clutter_embed_init (GtkClutterEmbed *embed)
368 {
369   GtkClutterEmbedPrivate *priv;
370
371   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
372
373   GTK_WIDGET_SET_FLAGS (embed, GTK_CAN_FOCUS);
374
375   /* disable double-buffering: it's automatically provided
376    * by OpenGL
377    */
378   gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
379
380   /* we always create new stages rather than use the default */
381   priv->stage = clutter_stage_new ();
382
383   /* we must realize the stage to get it ready for embedding */
384   clutter_actor_realize (priv->stage);
385
386 #ifdef HAVE_CLUTTER_GTK_X11
387   {
388     const XVisualInfo *xvinfo;
389     GdkVisual *visual;
390     GdkColormap *colormap;
391
392     /* We need to use the colormap from the Clutter visual */
393     xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
394     visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
395                                            xvinfo->visualid);
396     colormap = gdk_colormap_new (visual, FALSE);
397     gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
398   }
399 #endif
400 }
401
402 /**
403  * gtk_clutter_init:
404  * @argc: pointer to the arguments count, or %NULL
405  * @argv: pointer to the arguments vector, or %NULL
406  *
407  * This function should be called instead of clutter_init() and
408  * gtk_init().
409  *
410  * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
411  *   on failure.
412  *
413  * Since: 0.8
414  */
415 ClutterInitError
416 gtk_clutter_init (int    *argc,
417                   char ***argv)
418 {
419   if (!gtk_init_check (argc, argv))
420     return CLUTTER_INIT_ERROR_GTK;
421
422 #if defined(HAVE_CLUTTER_GTK_X11)
423   clutter_x11_set_display (GDK_DISPLAY());
424   clutter_x11_disable_event_retrieval ();
425 #elif defined(HAVE_CLUTTER_GTK_WIN32)
426   clutter_win32_disable_event_retrieval ();
427 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
428
429   return clutter_init (argc, argv);
430 }
431
432 /**
433  * gtk_clutter_embed_new:
434  *
435  * Creates a new #GtkClutterEmbed widget. This widget can be
436  * used to build a scene using Clutter API into a GTK+ application.
437  *
438  * Return value: the newly created #GtkClutterEmbed
439  *
440  * Since: 0.6
441  */
442 GtkWidget *
443 gtk_clutter_embed_new (void)
444 {
445   return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
446 }
447
448 /**
449  * gtk_clutter_embed_get_stage:
450  * @embed: a #GtkClutterEmbed
451  *
452  * Retrieves the #ClutterStage from @embed. The returned stage can be
453  * used to add actors to the Clutter scene.
454  *
455  * Return value: the Clutter stage. You should never destroy or unref
456  *   the returned actor.
457  *
458  * Since: 0.6
459  */
460 ClutterActor *
461 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
462 {
463   g_return_val_if_fail (GTK_IS_CLUTTER_EMBED (embed), NULL);
464
465   return embed->priv->stage;
466 }