185f3f6e7ca048654d209183c528a8be7ec9e910
[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_CLUTTER_TYPE_EMBED, GtkClutterEmbedPrivate))
64
65 struct _GtkClutterEmbedPrivate
66 {
67   ClutterActor *stage;
68
69   guint queue_redraw_id;
70 };
71
72 static void
73 gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
74 {
75   GtkWidget *widget;
76   GdkEvent *event = gdk_event_new (GDK_CONFIGURE);
77
78   widget = GTK_WIDGET (embed);
79
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;
86   
87   gtk_widget_event (widget, event);
88   gdk_event_free (event);
89 }
90
91 static void
92 on_stage_queue_redraw (ClutterStage *stage,
93                        gboolean      queue_origin,
94                        gpointer      user_data)
95 {
96   GtkWidget *embed = user_data;
97
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
103    */
104   g_signal_stop_emission_by_name (stage, "queue-redraw");
105
106   gtk_widget_queue_draw (embed);
107 }
108
109 static void
110 gtk_clutter_embed_dispose (GObject *gobject)
111 {
112   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
113
114   if (priv->queue_redraw_id)
115     {
116       g_signal_handler_disconnect (priv->stage, priv->queue_redraw_id);
117       priv->queue_redraw_id = 0;
118     }
119
120   if (priv->stage)
121     {
122       clutter_actor_destroy (priv->stage);
123       priv->stage = NULL;
124     }
125
126   G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
127 }
128
129 static void
130 gtk_clutter_embed_show (GtkWidget *widget)
131 {
132   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
133
134   if (GTK_WIDGET_REALIZED (widget))
135     clutter_actor_show (priv->stage);
136
137   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
138 }
139
140 static void
141 gtk_clutter_embed_hide (GtkWidget *widget)
142 {
143   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
144
145   clutter_actor_hide (priv->stage);
146
147   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
148 }
149
150 static void
151 gtk_clutter_embed_realize (GtkWidget *widget)
152 {
153   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv; 
154   GdkWindowAttr attributes;
155   int attributes_mask;
156
157 #ifdef HAVE_CLUTTER_GTK_X11
158   {
159     const XVisualInfo *xvinfo;
160     GdkVisual *visual;
161     GdkColormap *colormap;
162
163     /* We need to use the colormap from the Clutter visual */
164     xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
165     if (xvinfo == None)
166       {
167         g_critical ("Unable to retrieve the XVisualInfo from Clutter");
168         return;
169       }
170
171     visual = gdk_x11_screen_lookup_visual (gtk_widget_get_screen (widget),
172                                            xvinfo->visualid);
173     colormap = gdk_colormap_new (visual, FALSE);
174     gtk_widget_set_colormap (widget, colormap);
175   }
176 #endif
177
178   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
179
180   attributes.window_type = GDK_WINDOW_CHILD;
181   attributes.x = widget->allocation.x;
182   attributes.y = widget->allocation.y;
183   attributes.width = widget->allocation.width;
184   attributes.height = widget->allocation.height;
185   attributes.wclass = GDK_INPUT_OUTPUT;
186   attributes.visual = gtk_widget_get_visual (widget);
187   attributes.colormap = gtk_widget_get_colormap (widget);
188
189   /* NOTE: GDK_MOTION_NOTIFY above should be safe as Clutter does its own
190    *       throtling. 
191   */
192   attributes.event_mask = gtk_widget_get_events (widget)
193                         | GDK_EXPOSURE_MASK
194                         | GDK_BUTTON_PRESS_MASK
195                         | GDK_BUTTON_RELEASE_MASK
196                         | GDK_KEY_PRESS_MASK
197                         | GDK_KEY_RELEASE_MASK
198                         | GDK_POINTER_MOTION_MASK;
199
200   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
201
202   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
203                                    &attributes,
204                                    attributes_mask);
205   gdk_window_set_user_data (widget->window, widget);
206
207   widget->style = gtk_style_attach (widget->style, widget->window);
208   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
209   
210   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
211
212 #if defined(HAVE_CLUTTER_GTK_X11)
213   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
214                                  GDK_WINDOW_XID (widget->window));
215 #elif defined(HAVE_CLUTTER_GTK_WIN32)
216   clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
217                                    GDK_WINDOW_HWND (widget->window));
218 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
219
220   clutter_actor_realize (priv->stage);
221
222   if (GTK_WIDGET_VISIBLE (widget))
223     clutter_actor_show (priv->stage);
224
225   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
226 }
227
228 static void
229 gtk_clutter_embed_unrealize (GtkWidget *widget)
230 {
231   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
232
233   clutter_actor_unmap (priv->stage);
234   clutter_actor_hide (priv->stage);
235   clutter_actor_unrealize (priv->stage);
236
237   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unrealize (widget);
238 }
239
240 static void
241 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
242                                  GtkAllocation *allocation)
243 {
244   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
245
246   widget->allocation = *allocation;
247
248   if (GTK_WIDGET_REALIZED (widget))
249     {
250       gdk_window_move_resize (widget->window,
251                               allocation->x, allocation->y,
252                               allocation->width, allocation->height);
253
254       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
255     }
256
257   /* change the size of the stage and ensure that the viewport
258    * has been updated as well
259    */
260   clutter_actor_set_size (priv->stage,
261                           allocation->width,
262                           allocation->height);
263
264   clutter_stage_ensure_viewport (CLUTTER_STAGE (priv->stage));
265 }
266
267 static gboolean
268 gtk_clutter_embed_expose_event (GtkWidget *widget,
269                                 GdkEventExpose *event)
270 {
271   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
272
273   /* force a redraw on expose */
274   clutter_redraw (CLUTTER_STAGE (priv->stage));
275
276   return FALSE;
277 }
278
279 static gboolean
280 gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
281                                        GdkEventMotion *event)
282 {
283   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
284   ClutterEvent cevent = { 0, };
285
286   cevent.type = CLUTTER_MOTION;
287   cevent.any.stage = CLUTTER_STAGE (priv->stage);
288   cevent.motion.x = event->x;
289   cevent.motion.y = event->y;
290   cevent.motion.time = event->time;
291   cevent.motion.modifier_state = event->state;
292
293   clutter_do_event (&cevent);
294
295   /* doh - motion events can push ENTER/LEAVE events onto Clutters
296    * internal event queue which we do really ever touch (essentially
297    * proxying from gtks queue). The below pumps them back out and
298    * processes.
299    * *could* be side effects with below though doubful as no other
300    * events reach the queue (we shut down event collection). Maybe
301    * a peek_mask type call could be even safer. 
302   */
303   while (clutter_events_pending())
304     {
305       ClutterEvent *ev = clutter_event_get ();
306       if (ev)
307         {
308           clutter_do_event (ev);
309           clutter_event_free (ev);
310         }
311     }
312
313   return FALSE;
314 }
315
316 static gboolean
317 gtk_clutter_embed_button_event (GtkWidget      *widget,
318                                 GdkEventButton *event)
319 {
320   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
321   ClutterEvent cevent = { 0, };
322
323   if (event->type == GDK_BUTTON_PRESS ||
324       event->type == GDK_2BUTTON_PRESS ||
325       event->type == GDK_3BUTTON_PRESS)
326     cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
327   else if (event->type == GDK_BUTTON_RELEASE)
328     cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
329   else
330     return FALSE;
331
332   cevent.any.stage = CLUTTER_STAGE (priv->stage);
333   cevent.button.x = event->x;
334   cevent.button.y = event->y;
335   cevent.button.time = event->time;
336   cevent.button.click_count =
337     (event->type == GDK_BUTTON_PRESS ? 1
338                                      : (event->type == GDK_2BUTTON_PRESS ? 2
339                                                                          : 3));
340   cevent.button.modifier_state = event->state;
341   cevent.button.button = event->button;
342
343   clutter_do_event (&cevent);
344
345   return FALSE;
346 }
347
348 static gboolean
349 gtk_clutter_embed_key_event (GtkWidget   *widget,
350                              GdkEventKey *event)
351 {
352   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
353   ClutterEvent cevent = { 0, };
354
355   if (event->type == GDK_KEY_PRESS)
356     cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
357   else if (event->type == GDK_KEY_RELEASE)
358     cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
359   else
360     return FALSE;
361
362   cevent.any.stage = CLUTTER_STAGE (priv->stage);
363   cevent.key.time = event->time;
364   cevent.key.modifier_state = event->state;
365   cevent.key.keyval = event->keyval;
366   cevent.key.hardware_keycode = event->hardware_keycode;
367
368   clutter_do_event (&cevent);
369
370   return FALSE;
371 }
372
373 static gboolean
374 gtk_clutter_embed_map_event (GtkWidget   *widget,
375                              GdkEventAny *event)
376 {
377   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
378   GtkWidgetClass *parent_class;
379   gboolean res = FALSE;
380
381   parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
382   if (parent_class->map_event)
383     res = parent_class->map_event (widget, event);
384
385   clutter_actor_map (priv->stage);
386
387   return res;
388 }
389
390 static gboolean
391 gtk_clutter_embed_unmap_event (GtkWidget   *widget,
392                                GdkEventAny *event)
393 {
394   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
395   GtkWidgetClass *parent_class;
396   gboolean res = FALSE;
397
398   parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
399   if (parent_class->unmap_event)
400     res = parent_class->unmap_event (widget, event);
401
402   clutter_actor_unmap (priv->stage);
403
404   return res;
405 }
406
407 static void
408 gtk_clutter_embed_unmap (GtkWidget *widget)
409 {
410   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
411
412   clutter_actor_unmap (priv->stage);
413
414   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unmap (widget);
415 }
416
417 static gboolean
418 gtk_clutter_embed_focus_in (GtkWidget     *widget,
419                             GdkEventFocus *event)
420 {
421   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
422
423   g_signal_emit_by_name (priv->stage, "activate");
424
425   return FALSE;
426 }
427
428 static gboolean
429 gtk_clutter_embed_focus_out (GtkWidget     *widget,
430                              GdkEventFocus *event)
431 {
432   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
433
434   g_signal_emit_by_name (priv->stage, "deactivate");
435
436   /* give back key focus to the stage */
437   clutter_stage_set_key_focus (CLUTTER_STAGE (priv->stage), NULL);
438
439   return FALSE;
440 }
441
442 static gboolean
443 gtk_clutter_embed_scroll_event (GtkWidget      *widget,
444                                 GdkEventScroll *event)
445 {
446   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
447
448   ClutterEvent cevent = { 0, };
449
450   if (event->type == GDK_SCROLL)
451     cevent.type = cevent.scroll.type = CLUTTER_SCROLL;
452   else
453     return FALSE;
454
455   cevent.any.stage = CLUTTER_STAGE (priv->stage);
456   cevent.scroll.x = (gint) event->x;
457   cevent.scroll.y = (gint) event->y;
458   cevent.scroll.time = event->time;
459   cevent.scroll.direction = event->direction;
460   cevent.scroll.modifier_state = event->state;
461
462   clutter_do_event (&cevent);
463
464   return FALSE;
465 }
466
467 static void
468 gtk_clutter_embed_style_set (GtkWidget *widget,
469                              GtkStyle  *old_style)
470 {
471   GdkScreen *screen;
472   GtkSettings *settings;
473   gdouble dpi;
474   gchar *font_name;
475   const cairo_font_options_t *font_options;
476   gint double_click_time, double_click_distance;
477   ClutterBackend *backend;
478
479   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->style_set (widget,
480                                                                 old_style);
481
482   if (gtk_widget_has_screen (widget))
483     screen = gtk_widget_get_screen (widget);
484   else
485     screen = gdk_screen_get_default ();
486
487   dpi = gdk_screen_get_resolution (screen);
488   if (dpi < 0)
489     dpi = 96.0;
490
491   font_options = gdk_screen_get_font_options (screen);
492
493   settings = gtk_settings_get_for_screen (screen);
494   g_object_get (G_OBJECT (settings),
495                 "gtk-font-name", &font_name,
496                 "gtk-double-click-time", &double_click_time,
497                 "gtk-double-click-distance", &double_click_distance,
498                 NULL);
499
500   /* copy all settings and values coming from GTK+ into
501    * the ClutterBackend; this way, a scene embedded into
502    * a GtkClutterEmbed will not look completely alien
503    */
504   backend = clutter_get_default_backend ();
505   clutter_backend_set_resolution (backend, dpi);
506   clutter_backend_set_font_options (backend, font_options);
507   clutter_backend_set_font_name (backend, font_name);
508   clutter_backend_set_double_click_time (backend, double_click_time);
509   clutter_backend_set_double_click_distance (backend, double_click_distance);
510
511   g_free (font_name);
512 }
513
514 static void
515 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
516 {
517   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
518   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
519
520   g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
521
522   gobject_class->dispose = gtk_clutter_embed_dispose;
523
524   widget_class->style_set = gtk_clutter_embed_style_set;
525   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
526   widget_class->realize = gtk_clutter_embed_realize;
527   widget_class->unrealize = gtk_clutter_embed_unrealize;
528   widget_class->show = gtk_clutter_embed_show;
529   widget_class->hide = gtk_clutter_embed_hide;
530   widget_class->unmap = gtk_clutter_embed_unmap;
531   widget_class->expose_event = gtk_clutter_embed_expose_event;
532   widget_class->button_press_event = gtk_clutter_embed_button_event;
533   widget_class->button_release_event = gtk_clutter_embed_button_event;
534   widget_class->key_press_event = gtk_clutter_embed_key_event;
535   widget_class->key_release_event = gtk_clutter_embed_key_event;
536   widget_class->motion_notify_event = gtk_clutter_embed_motion_notify_event;
537   widget_class->expose_event = gtk_clutter_embed_expose_event;
538   widget_class->map_event = gtk_clutter_embed_map_event;
539   widget_class->unmap_event = gtk_clutter_embed_unmap_event;
540   widget_class->focus_in_event = gtk_clutter_embed_focus_in;
541   widget_class->focus_out_event = gtk_clutter_embed_focus_out;
542   widget_class->scroll_event = gtk_clutter_embed_scroll_event;
543 }
544
545 static void
546 gtk_clutter_embed_init (GtkClutterEmbed *embed)
547 {
548   GtkClutterEmbedPrivate *priv;
549
550   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
551
552   GTK_WIDGET_SET_FLAGS (embed, GTK_CAN_FOCUS);
553   GTK_WIDGET_UNSET_FLAGS (embed, GTK_NO_WINDOW);
554
555   /* disable double-buffering: it's automatically provided
556    * by OpenGL
557    */
558   gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
559
560   /* we always create new stages rather than use the default */
561   priv->stage = clutter_stage_new ();
562
563   /* intercept the queue-redraw signal of the stage to know when
564    * Clutter-side requests a redraw; this way we can also request
565    * a redraw GTK-side
566    */
567   priv->queue_redraw_id =
568     g_signal_connect (priv->stage,
569                       "queue-redraw", G_CALLBACK (on_stage_queue_redraw),
570                       embed);
571 }
572
573 /**
574  * gtk_clutter_embed_new:
575  *
576  * Creates a new #GtkClutterEmbed widget. This widget can be
577  * used to build a scene using Clutter API into a GTK+ application.
578  *
579  * Return value: the newly created #GtkClutterEmbed
580  *
581  * Since: 0.6
582  */
583 GtkWidget *
584 gtk_clutter_embed_new (void)
585 {
586   return g_object_new (GTK_CLUTTER_TYPE_EMBED, NULL);
587 }
588
589 /**
590  * gtk_clutter_embed_get_stage:
591  * @embed: a #GtkClutterEmbed
592  *
593  * Retrieves the #ClutterStage from @embed. The returned stage can be
594  * used to add actors to the Clutter scene.
595  *
596  * Return value: the Clutter stage. You should never destroy or unref
597  *   the returned actor.
598  *
599  * Since: 0.6
600  */
601 ClutterActor *
602 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
603 {
604   g_return_val_if_fail (GTK_CLUTTER_IS_EMBED (embed), NULL);
605
606   return embed->priv->stage;
607 }