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