2008-08-05 Emmanuele Bassi <ebassi@openedhand.com>
[clutter-gtk] / clutter-gtk / gtk-clutter-embed.c
index 94594c0..cc1ce5f 100644 (file)
  * a #GtkClutterEmbed widget is possible to build, show and interact with
  * a scene built using Clutter inside a GTK+ application.
  *
- * <note>You should never resize the #ClutterStage embedded into the
- * #GtkClutterEmbed widget. Instead, resize the widget using
- * gtk_widget_set_size_request().</note>
- *
- * <note>You should only call #clutter_actor_show_all() after the
- * widget itself has been shown</note>
- *
- * <note>Only a single #GtkClutterEmbed instace per application is
- * currently supported</note>
+ * <note>To avoid flickering on show, you should call gtk_widget_show()
+ * or gtk_widget_realize() before calling clutter_actor_show() on the
+ * embedded #ClutterStage actor. This is needed for Clutter to be able
+ * to paint on the #GtkClutterEmbed widget.</note>
  *
  * Since: 0.6
  */
 
 #include <glib-object.h>
 
+#include <gdk/gdk.h>
+#include <gtk/gtkmain.h>
+
 #include <clutter/clutter-main.h>
 #include <clutter/clutter-stage.h>
+#include <clutter/clutter-container.h>
 
-#include <gdk/gdk.h>
+#if defined(HAVE_CLUTTER_GTK_X11)
+
+#include <clutter/x11/clutter-x11.h>
 #include <gdk/gdkx.h>
-#include <clutter/clutter-x11.h>
+
+#elif defined(HAVE_CLUTTER_GTK_WIN32)
+
+#include <clutter/clutter-win32.h>
+#include <gdk/gdkwin32.h>
+
+#endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
 
 #include "gtk-clutter-embed.h"
 
@@ -87,6 +94,14 @@ gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
 static void
 gtk_clutter_embed_dispose (GObject *gobject)
 {
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
+
+  if (priv->stage)
+    {
+      clutter_actor_destroy (priv->stage);
+      priv->stage = NULL;
+    }
+
   G_OBJECT_CLASS (gtk_clutter_embed_parent_class)->dispose (gobject);
 }
 
@@ -96,11 +111,12 @@ gtk_clutter_embed_show (GtkWidget *widget)
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
   /* Make sure the widget is realised before we show */
-  gtk_widget_realize(widget);
-
-  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
+  if (!GTK_WIDGET_REALIZED (widget))
+    gtk_widget_realize (widget);
 
   clutter_actor_show (priv->stage);
+
+  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
 }
 
 static void
@@ -108,9 +124,9 @@ gtk_clutter_embed_hide (GtkWidget *widget)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
-  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
-
   clutter_actor_hide (priv->stage);
+
+  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->hide (widget);
 }
 
 static void
@@ -130,7 +146,17 @@ gtk_clutter_embed_realize (GtkWidget *widget)
   attributes.wclass = GDK_INPUT_OUTPUT;
   attributes.visual = gtk_widget_get_visual (widget);
   attributes.colormap = gtk_widget_get_colormap (widget);
-  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
+
+  /* NOTE: GDK_MOTION_NOTIFY above should be safe as Clutter does its own
+   *       throtling. 
+  */
+  attributes.event_mask = gtk_widget_get_events (widget)
+                        | GDK_EXPOSURE_MASK
+                        | GDK_BUTTON_PRESS_MASK
+                        | GDK_BUTTON_RELEASE_MASK
+                        | GDK_KEY_PRESS_MASK
+                        | GDK_KEY_RELEASE_MASK
+                        | GDK_POINTER_MOTION_MASK;
 
   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
 
@@ -144,9 +170,15 @@ gtk_clutter_embed_realize (GtkWidget *widget)
   
   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
 
+#if defined(HAVE_CLUTTER_GTK_X11)
   clutter_x11_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
                                  GDK_WINDOW_XID (widget->window));
-  clutter_redraw ();
+#elif defined(HAVE_CLUTTER_GTK_WIN32)
+  clutter_win32_set_stage_foreign (CLUTTER_STAGE (priv->stage), 
+                                  GDK_WINDOW_HWND (widget->window));
+#endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
+
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (priv->stage));
 
   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
 }
@@ -172,14 +204,50 @@ gtk_clutter_embed_size_allocate (GtkWidget     *widget,
                           allocation->width,
                           allocation->height);
 
-  if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
-    clutter_actor_queue_redraw (priv->stage);
+  clutter_actor_queue_relayout (priv->stage);
+}
+
+static gboolean
+gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
+                                       GdkEventMotion *event)
+{
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+  ClutterEvent cevent = { 0, };
+
+  cevent.type = CLUTTER_MOTION;
+  cevent.any.stage = CLUTTER_STAGE (priv->stage);
+  cevent.motion.x = event->x;
+  cevent.motion.y = event->y;
+  cevent.motion.time = event->time;
+
+  clutter_do_event (&cevent);
+
+  /* doh - motion events can push ENTER/LEAVE events onto Clutters
+   * internal event queue which we do really ever touch (essentially
+   * proxying from gtks queue). The below pumps them back out and
+   * processes.
+   * *could* be side effects with below though doubful as no other
+   * events reach the queue (we shut down event collection). Maybe
+   * a peek_mask type call could be even safer. 
+  */
+  while (clutter_events_pending())
+    {
+      ClutterEvent *ev = clutter_event_get ();
+      if (ev)
+        {
+          clutter_do_event (ev);
+          clutter_event_free (ev);
+        }
+    }
+
+  return FALSE;
 }
 
 static gboolean
 gtk_clutter_embed_button_event (GtkWidget      *widget,
                                 GdkEventButton *event)
 {
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
   ClutterEvent cevent = { 0, };
 
   if (event->type == GDK_BUTTON_PRESS ||
@@ -191,6 +259,7 @@ gtk_clutter_embed_button_event (GtkWidget      *widget,
   else
     return FALSE;
 
+  cevent.any.stage = CLUTTER_STAGE (priv->stage);
   cevent.button.x = event->x;
   cevent.button.y = event->y;
   cevent.button.time = event->time;
@@ -203,13 +272,14 @@ gtk_clutter_embed_button_event (GtkWidget      *widget,
 
   clutter_do_event (&cevent);
 
-  return TRUE;
+  return FALSE;
 }
 
 static gboolean
 gtk_clutter_embed_key_event (GtkWidget   *widget,
                              GdkEventKey *event)
 {
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
   ClutterEvent cevent = { 0, };
 
   if (event->type == GDK_KEY_PRESS)
@@ -219,6 +289,7 @@ gtk_clutter_embed_key_event (GtkWidget   *widget,
   else
     return FALSE;
 
+  cevent.any.stage = CLUTTER_STAGE (priv->stage);
   cevent.key.time = event->time;
   cevent.key.modifier_state = event->state;
   cevent.key.keyval = event->keyval;
@@ -226,23 +297,24 @@ gtk_clutter_embed_key_event (GtkWidget   *widget,
 
   clutter_do_event (&cevent);
 
-  return TRUE;
+  return FALSE;
 }
 
 static gboolean
-gtk_clutter_embed_expose_event (GtkWidget *widget, GdkEventExpose *event)
+gtk_clutter_embed_expose_event (GtkWidget      *widget,
+                                GdkEventExpose *event)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
   if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
     clutter_actor_queue_redraw (priv->stage);
 
-  return TRUE;
+  return FALSE;
 }
 
 static gboolean
-gtk_clutter_embed_map_event (GtkWidget      *widget,
-                             GdkEventAny     *event)
+gtk_clutter_embed_map_event (GtkWidget  *widget,
+                             GdkEventAny *event)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
@@ -251,7 +323,19 @@ gtk_clutter_embed_map_event (GtkWidget          *widget,
   */
   CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
 
-  return TRUE;
+  return FALSE;
+}
+
+static gboolean
+gtk_clutter_embed_focus_out (GtkWidget     *widget,
+                             GdkEventFocus *event)
+{
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+
+  /* give back key focus to the stage */
+  clutter_stage_set_key_focus (CLUTTER_STAGE (priv->stage), NULL);
+
+  return FALSE;
 }
 
 static void
@@ -272,50 +356,74 @@ gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
   widget_class->button_release_event = gtk_clutter_embed_button_event;
   widget_class->key_press_event = gtk_clutter_embed_key_event;
   widget_class->key_release_event = gtk_clutter_embed_key_event;
+  widget_class->motion_notify_event = gtk_clutter_embed_motion_notify_event;
   widget_class->expose_event = gtk_clutter_embed_expose_event;
   widget_class->map_event = gtk_clutter_embed_map_event;
+  widget_class->focus_out_event = gtk_clutter_embed_focus_out;
 }
 
 static void
 gtk_clutter_embed_init (GtkClutterEmbed *embed)
 {
   GtkClutterEmbedPrivate *priv;
-  const XVisualInfo *xvinfo;
-  GdkVisual *visual;
-  GdkColormap *colormap;
 
   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
 
-  gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
+  GTK_WIDGET_SET_FLAGS (embed, GTK_CAN_FOCUS);
 
-  /* note we never ref or unref this */
-  priv->stage = clutter_stage_get_default ();
+  /* disable double-buffering: it's automatically provided
+   * by OpenGL
+   */
+  gtk_widget_set_double_buffered (GTK_WIDGET (embed), FALSE);
 
-  /* We need to use the colormap from the Clutter visual */
-  xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
-  visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
-                                         xvinfo->visualid);
-  colormap = gdk_colormap_new (visual, FALSE);
-  gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
+  /* we always create new stages rather than use the default */
+  priv->stage = clutter_stage_new ();
+
+  /* we must realize the stage to get it ready for embedding */
+  clutter_actor_realize (priv->stage);
+
+#ifdef HAVE_CLUTTER_GTK_X11
+  {
+    const XVisualInfo *xvinfo;
+    GdkVisual *visual;
+    GdkColormap *colormap;
+
+    /* We need to use the colormap from the Clutter visual */
+    xvinfo = clutter_x11_get_stage_visual (CLUTTER_STAGE (priv->stage));
+    visual = gdk_x11_screen_lookup_visual (gdk_screen_get_default (),
+                                           xvinfo->visualid);
+    colormap = gdk_colormap_new (visual, FALSE);
+    gtk_widget_set_colormap (GTK_WIDGET (embed), colormap);
+  }
+#endif
 }
 
 /**
  * gtk_clutter_init:
+ * @argc: pointer to the arguments count, or %NULL
+ * @argv: pointer to the arguments vector, or %NULL
  *
- * This function should be called instead of #clutter_init() and after
- * #gtk_init()
+ * This function should be called instead of clutter_init() and
+ * gtk_init().
  *
- * Return value: 1 on success, < 0 on failure.
+ * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
+ *   on failure.
  *
  * Since: 0.8
  */
 ClutterInitError
-gtk_clutter_init (int *argc, char ***argv)
+gtk_clutter_init (int    *argc,
+                  char ***argv)
 {
+  if (!gtk_init_check (argc, argv))
+    return CLUTTER_INIT_ERROR_GTK;
+
+#if defined(HAVE_CLUTTER_GTK_X11)
   clutter_x11_set_display (GDK_DISPLAY());
   clutter_x11_disable_event_retrieval ();
-
-  /* FIXME: call gtk_init() here? */
+#elif defined(HAVE_CLUTTER_GTK_WIN32)
+  clutter_win32_disable_event_retrieval ();
+#endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
 
   return clutter_init (argc, argv);
 }
@@ -323,7 +431,8 @@ gtk_clutter_init (int *argc, char ***argv)
 /**
  * gtk_clutter_embed_new:
  *
- * Creates a new embedded Clutter widget.
+ * Creates a new #GtkClutterEmbed widget. This widget can be
+ * used to build a scene using Clutter API into a GTK+ application.
  *
  * Return value: the newly created #GtkClutterEmbed
  *