2008-04-01 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 <clutter/clutter-main.h>
46 #include <clutter/clutter-stage.h>
47
48 #include <gdk/gdk.h>
49 #include <gdk/gdkx.h>
50 #include <clutter/clutter-x11.h>
51
52 #include "gtk-clutter-embed.h"
53
54 G_DEFINE_TYPE (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
55
56 #define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_CLUTTER_EMBED, GtkClutterEmbedPrivate))
57
58 struct _GtkClutterEmbedPrivate
59 {
60   ClutterActor *stage;
61 };
62
63 static void
64 gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
65 {
66   GtkWidget *widget;
67   GdkEvent *event = gdk_event_new (GDK_CONFIGURE);
68
69   widget = GTK_WIDGET (embed);
70
71   event->configure.window = g_object_ref (widget->window);
72   event->configure.send_event = TRUE;
73   event->configure.x = widget->allocation.x;
74   event->configure.y = widget->allocation.y;
75   event->configure.width = widget->allocation.width;
76   event->configure.height = widget->allocation.height;
77   
78   gtk_widget_event (widget, event);
79   gdk_event_free (event);
80 }
81
82 static void
83 gtk_clutter_embed_dispose (GObject *gobject)
84 {
85   G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
86 }
87
88 static void
89 gtk_clutter_embed_show (GtkWidget *widget)
90 {
91   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
92
93   /* Make sure the widget is realised before we show */
94   gtk_widget_realize (widget);
95
96   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
97
98   clutter_actor_show (priv->stage);
99 }
100
101 static void
102 gtk_clutter_embed_hide (GtkWidget *widget)
103 {
104   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
105
106   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
107
108   clutter_actor_hide (priv->stage);
109 }
110
111 static void
112 gtk_clutter_embed_realize (GtkWidget *widget)
113 {
114   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv; 
115   GdkWindowAttr attributes;
116   int attributes_mask;
117   
118   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
119
120   attributes.window_type = GDK_WINDOW_CHILD;
121   attributes.x = widget->allocation.x;
122   attributes.y = widget->allocation.y;
123   attributes.width = widget->allocation.width;
124   attributes.height = widget->allocation.height;
125   attributes.wclass = GDK_INPUT_OUTPUT;
126   attributes.visual = gtk_widget_get_visual (widget);
127   attributes.colormap = gtk_widget_get_colormap (widget);
128   attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
129
130   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
131
132   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
133                                    &attributes,
134                                    attributes_mask);
135   gdk_window_set_user_data (widget->window, widget);
136
137   widget->style = gtk_style_attach (widget->style, widget->window);
138   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
139   
140   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
141
142   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
143                                  GDK_WINDOW_XID (widget->window));
144   clutter_redraw (CLUTTER_STAGE(priv->stage));
145
146   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
147 }
148
149 static void
150 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
151                                  GtkAllocation *allocation)
152 {
153   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
154
155   widget->allocation = *allocation;
156
157   if (GTK_WIDGET_REALIZED (widget))
158     {
159       gdk_window_move_resize (widget->window,
160                               allocation->x, allocation->y,
161                               allocation->width, allocation->height);
162
163       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
164     }
165
166   clutter_actor_set_size (priv->stage,
167                           allocation->width,
168                           allocation->height);
169
170   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
171     clutter_actor_queue_redraw (priv->stage);
172 }
173
174 static gboolean
175 gtk_clutter_embed_button_event (GtkWidget      *widget,
176                                 GdkEventButton *event)
177 {
178   ClutterEvent cevent = { 0, };
179
180   if (event->type == GDK_BUTTON_PRESS ||
181       event->type == GDK_2BUTTON_PRESS ||
182       event->type == GDK_3BUTTON_PRESS)
183     cevent.type = cevent.button.type = CLUTTER_BUTTON_PRESS;
184   else if (event->type == GDK_BUTTON_RELEASE)
185     cevent.type = cevent.button.type = CLUTTER_BUTTON_RELEASE;
186   else
187     return FALSE;
188
189   cevent.button.x = event->x;
190   cevent.button.y = event->y;
191   cevent.button.time = event->time;
192   cevent.button.click_count =
193     (event->type == GDK_BUTTON_PRESS ? 1
194                                      : (event->type == GDK_2BUTTON_PRESS ? 2
195                                                                          : 3));
196   cevent.button.modifier_state = event->state;
197   cevent.button.button = event->button;
198
199   clutter_do_event (&cevent);
200
201   return TRUE;
202 }
203
204 static gboolean
205 gtk_clutter_embed_key_event (GtkWidget   *widget,
206                              GdkEventKey *event)
207 {
208   ClutterEvent cevent = { 0, };
209
210   if (event->type == GDK_KEY_PRESS)
211     cevent.type = cevent.key.type = CLUTTER_KEY_PRESS;
212   else if (event->type == GDK_KEY_RELEASE)
213     cevent.type = cevent.key.type = CLUTTER_KEY_RELEASE;
214   else
215     return FALSE;
216
217   cevent.key.time = event->time;
218   cevent.key.modifier_state = event->state;
219   cevent.key.keyval = event->keyval;
220   cevent.key.hardware_keycode = event->hardware_keycode;
221
222   clutter_do_event (&cevent);
223
224   return TRUE;
225 }
226
227 static gboolean
228 gtk_clutter_embed_expose_event (GtkWidget *widget, GdkEventExpose *event)
229 {
230   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
231
232   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
233     clutter_actor_queue_redraw (priv->stage);
234
235   return TRUE;
236 }
237
238 static gboolean
239 gtk_clutter_embed_map_event (GtkWidget       *widget,
240                              GdkEventAny     *event)
241 {
242   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
243
244   /* The backend wont get the XEvent as we go strait to do_event().
245    * So we have to make sure we set the event here.
246   */
247   CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
248
249   return TRUE;
250 }
251
252 static void
253 gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
254 {
255   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
256   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
257
258   g_type_class_add_private (klass, sizeof (GtkClutterEmbedPrivate));
259
260   gobject_class->dispose = gtk_clutter_embed_dispose;
261
262   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
263   widget_class->realize = gtk_clutter_embed_realize;
264   widget_class->show = gtk_clutter_embed_show;
265   widget_class->hide = gtk_clutter_embed_hide;
266   widget_class->button_press_event = gtk_clutter_embed_button_event;
267   widget_class->button_release_event = gtk_clutter_embed_button_event;
268   widget_class->key_press_event = gtk_clutter_embed_key_event;
269   widget_class->key_release_event = gtk_clutter_embed_key_event;
270   widget_class->expose_event = gtk_clutter_embed_expose_event;
271   widget_class->map_event = gtk_clutter_embed_map_event;
272 }
273
274 static void
275 gtk_clutter_embed_init (GtkClutterEmbed *embed)
276 {
277   GtkClutterEmbedPrivate *priv;
278   const XVisualInfo *xvinfo;
279   GdkVisual *visual;
280   GdkColormap *colormap;
281
282   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
283
284   gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
285
286   /* note we never ref or unref this */
287   priv->stage = clutter_stage_create_new (); 
288   /* we always create new stages rather than use the default */
289   /* priv->stage = clutter_stage_get_default (); */
290
291   /* We need to use the colormap from the Clutter visual */
292   xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
293   visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
294                                          xvinfo->visualid);
295   colormap = gdk_colormap_new (visual, FALSE);
296   gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
297 }
298
299 /**
300  * gtk_clutter_init:
301  *
302  * This function should be called instead of #clutter_init() and after
303  * #gtk_init()
304  *
305  * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
306  *   on failure.
307  *
308  * Since: 0.8
309  */
310 ClutterInitError
311 gtk_clutter_init (int *argc, char ***argv)
312 {
313   clutter_x11_set_display (GDK_DISPLAY());
314   clutter_x11_disable_event_retrieval ();
315
316   /* FIXME: call gtk_init() here? */
317
318   return clutter_init (argc, argv);
319 }
320
321 /**
322  * gtk_clutter_embed_new:
323  *
324  * Creates a new #GtkClutterEmbed widget. This widget can be
325  * used to build a scene using Clutter API into a GTK+ application.
326  *
327  * Return value: the newly created #GtkClutterEmbed
328  *
329  * Since: 0.6
330  */
331 GtkWidget *
332 gtk_clutter_embed_new (void)
333 {
334   return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
335 }
336
337 /**
338  * gtk_clutter_embed_get_stage:
339  * @embed: a #GtkClutterEmbed
340  *
341  * Retrieves the #ClutterStage from @embed. The returned stage can be
342  * used to add actors to the Clutter scene.
343  *
344  * Return value: the Clutter stage. You should never destroy or unref
345  *   the returned actor.
346  *
347  * Since: 0.6
348  */
349 ClutterActor *
350 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
351 {
352   g_return_val_if_fail (GTK_IS_CLUTTER_EMBED (embed), NULL);
353
354   return embed->priv->stage;
355 }