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