Merge branch '1.0-integration'
[clutter-gtk] / clutter-gtk / gtk-clutter-embed.c
index 82d4dc9..94269e0 100644 (file)
 #include "config.h"
 #endif
 
+#include "gtk-clutter-embed.h"
+
 #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>
 
 #if defined(HAVE_CLUTTER_GTK_X11)
 
-#include <clutter/clutter-x11.h>
+#include <clutter/x11/clutter-x11.h>
 #include <gdk/gdkx.h>
 
 #elif defined(HAVE_CLUTTER_GTK_WIN32)
 
 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
 
-#include "gtk-clutter-embed.h"
-
-static void clutter_container_iface_init (ClutterContainerIface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (GtkClutterEmbed,
-                         gtk_clutter_embed,
-                         GTK_TYPE_WIDGET,
-                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
-                                                clutter_container_iface_init));
+G_DEFINE_TYPE (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
 
-#define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_CLUTTER_EMBED, GtkClutterEmbedPrivate))
+#define GTK_CLUTTER_EMBED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_CLUTTER_TYPE_EMBED, GtkClutterEmbedPrivate))
 
 struct _GtkClutterEmbedPrivate
 {
   ClutterActor *stage;
+
+  guint queue_redraw_id;
 };
 
 static void
@@ -98,10 +89,34 @@ gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
 }
 
 static void
+on_stage_queue_redraw (ClutterStage *stage,
+                       ClutterActor *origin,
+                       gpointer      user_data)
+{
+  GtkWidget *embed = user_data;
+
+  /* we stop the emission of the Stage::queue-redraw signal to prevent
+   * the default handler from running; then we queue a redraw on the
+   * GtkClutterEmbed widget which will cause an expose event to be
+   * emitted. the Stage is redrawn in the expose event handler, thus
+   * "slaving" the Clutter redraw cycle to GTK+'s own
+   */
+  g_signal_stop_emission_by_name (stage, "queue-redraw");
+
+  gtk_widget_queue_draw (embed);
+}
+
+static void
 gtk_clutter_embed_dispose (GObject *gobject)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
 
+  if (priv->queue_redraw_id)
+    {
+      g_signal_handler_disconnect (priv->stage, priv->queue_redraw_id);
+      priv->queue_redraw_id = 0;
+    }
+
   if (priv->stage)
     {
       clutter_actor_destroy (priv->stage);
@@ -116,12 +131,10 @@ 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);
+  if (GTK_WIDGET_REALIZED (widget))
+    clutter_actor_show (priv->stage);
 
   GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->show (widget);
-
-  clutter_actor_show (priv->stage);
 }
 
 static void
@@ -129,9 +142,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
@@ -140,7 +153,28 @@ gtk_clutter_embed_realize (GtkWidget *widget)
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv; 
   GdkWindowAttr attributes;
   int attributes_mask;
-  
+
+#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));
+    if (xvinfo == None)
+      {
+        g_critical ("Unable to retrieve the XVisualInfo from Clutter");
+        return;
+      }
+
+    visual = gdk_x11_screen_lookup_visual (gtk_widget_get_screen (widget),
+                                           xvinfo->visualid);
+    colormap = gdk_colormap_new (visual, FALSE);
+    gtk_widget_set_colormap (widget, colormap);
+  }
+#endif
+
   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
 
   attributes.window_type = GDK_WINDOW_CHILD;
@@ -156,9 +190,12 @@ gtk_clutter_embed_realize (GtkWidget *widget)
    *       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_MOTION_NOTIFY;
-
+                        | 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;
 
@@ -180,12 +217,25 @@ gtk_clutter_embed_realize (GtkWidget *widget)
                                   GDK_WINDOW_HWND (widget->window));
 #endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
 
-  clutter_redraw (CLUTTER_STAGE (priv->stage));
+  clutter_actor_realize (priv->stage);
+
+  if (GTK_WIDGET_VISIBLE (widget))
+    clutter_actor_show (priv->stage);
 
   gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
 }
 
 static void
+gtk_clutter_embed_unrealize (GtkWidget *widget)
+{
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+
+  clutter_actor_hide (priv->stage);
+
+  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unrealize (widget);
+}
+
+static void
 gtk_clutter_embed_size_allocate (GtkWidget     *widget,
                                  GtkAllocation *allocation)
 {
@@ -202,12 +252,26 @@ gtk_clutter_embed_size_allocate (GtkWidget     *widget,
       gtk_clutter_embed_send_configure (GTK_CLUTTER_EMBED (widget));
     }
 
+  /* change the size of the stage and ensure that the viewport
+   * has been updated as well
+   */
   clutter_actor_set_size (priv->stage,
                           allocation->width,
                           allocation->height);
 
-  if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
-    clutter_actor_queue_redraw (priv->stage);
+  clutter_stage_ensure_viewport (CLUTTER_STAGE (priv->stage));
+}
+
+static gboolean
+gtk_clutter_embed_expose_event (GtkWidget *widget,
+                                GdkEventExpose *event)
+{
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+
+  /* force a redraw on expose */
+  clutter_redraw (CLUTTER_STAGE (priv->stage));
+
+  return FALSE;
 }
 
 static gboolean
@@ -222,6 +286,7 @@ gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
   cevent.motion.x = event->x;
   cevent.motion.y = event->y;
   cevent.motion.time = event->time;
+  cevent.motion.modifier_state = event->state;
 
   clutter_do_event (&cevent);
 
@@ -297,6 +362,7 @@ gtk_clutter_embed_key_event (GtkWidget   *widget,
   cevent.key.modifier_state = event->state;
   cevent.key.keyval = event->keyval;
   cevent.key.hardware_keycode = event->hardware_keycode;
+  cevent.key.unicode_value = gdk_keyval_to_unicode (event->keyval);
 
   clutter_do_event (&cevent);
 
@@ -304,107 +370,144 @@ gtk_clutter_embed_key_event (GtkWidget   *widget,
 }
 
 static gboolean
-gtk_clutter_embed_expose_event (GtkWidget *widget, GdkEventExpose *event)
+gtk_clutter_embed_map_event (GtkWidget  *widget,
+                             GdkEventAny *event)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+  GtkWidgetClass *parent_class;
+  gboolean res = FALSE;
+
+  parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
+  if (parent_class->map_event)
+    res = parent_class->map_event (widget, event);
 
-  if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
-    clutter_actor_queue_redraw (priv->stage);
+  clutter_actor_map (priv->stage);
 
-  return TRUE;
+  return res;
 }
 
 static gboolean
-gtk_clutter_embed_map_event (GtkWidget      *widget,
-                             GdkEventAny     *event)
+gtk_clutter_embed_unmap_event (GtkWidget   *widget,
+                               GdkEventAny *event)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+  GtkWidgetClass *parent_class;
+  gboolean res = FALSE;
 
-  /* The backend wont get the XEvent as we go strait to do_event().
-   * So we have to make sure we set the event here.
-  */
-  CLUTTER_ACTOR_SET_FLAGS (priv->stage, CLUTTER_ACTOR_MAPPED);
+  parent_class = GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class);
+  if (parent_class->unmap_event)
+    res = parent_class->unmap_event (widget, event);
+
+  clutter_actor_unmap (priv->stage);
 
-  return TRUE;
+  return res;
 }
 
 static void
-gtk_clutter_embed_add (ClutterContainer *container,
-                       ClutterActor     *actor)
+gtk_clutter_embed_unmap (GtkWidget *widget)
 {
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+
+  clutter_actor_unmap (priv->stage);
 
-  clutter_container_add_actor (stage, actor);
-  g_signal_emit_by_name (container, "actor-added", actor);
+  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->unmap (widget);
 }
 
-static void
-gtk_clutter_embed_remove (ClutterContainer *container,
-                          ClutterActor     *actor)
+static gboolean
+gtk_clutter_embed_focus_in (GtkWidget     *widget,
+                            GdkEventFocus *event)
 {
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
-
-  g_object_ref (actor);
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
-  clutter_container_remove_actor (stage, actor);
-  g_signal_emit_by_name (container, "actor-removed", actor);
+  g_signal_emit_by_name (priv->stage, "activate");
 
-  g_object_unref (actor);
+  return FALSE;
 }
 
-static void
-gtk_clutter_embed_foreach (ClutterContainer *container,
-                           ClutterCallback   callback,
-                           gpointer          callback_data)
+static gboolean
+gtk_clutter_embed_focus_out (GtkWidget     *widget,
+                             GdkEventFocus *event)
 {
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
-  clutter_container_foreach (stage, callback, callback_data);
-}
+  g_signal_emit_by_name (priv->stage, "deactivate");
 
-static void
-gtk_clutter_embed_raise (ClutterContainer *container,
-                         ClutterActor     *child,
-                         ClutterActor     *sibling)
-{
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
+  /* give back key focus to the stage */
+  clutter_stage_set_key_focus (CLUTTER_STAGE (priv->stage), NULL);
 
-  clutter_container_raise_child (stage, child, sibling);
+  return FALSE;
 }
 
-static void
-gtk_clutter_embed_lower (ClutterContainer *container,
-                         ClutterActor     *child,
-                         ClutterActor     *sibling)
+static gboolean
+gtk_clutter_embed_scroll_event (GtkWidget      *widget,
+                                GdkEventScroll *event)
 {
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
 
-  clutter_container_lower_child (stage, child, sibling);
-}
+  ClutterEvent cevent = { 0, };
 
-static void
-gtk_clutter_embed_sort_depth_order (ClutterContainer *container)
-{
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (container)->priv;
-  ClutterContainer *stage = CLUTTER_CONTAINER (priv->stage);
+  if (event->type == GDK_SCROLL)
+    cevent.type = cevent.scroll.type = CLUTTER_SCROLL;
+  else
+    return FALSE;
+
+  cevent.any.stage = CLUTTER_STAGE (priv->stage);
+  cevent.scroll.x = (gint) event->x;
+  cevent.scroll.y = (gint) event->y;
+  cevent.scroll.time = event->time;
+  cevent.scroll.direction = event->direction;
+  cevent.scroll.modifier_state = event->state;
+
+  clutter_do_event (&cevent);
 
-  clutter_container_sort_depth_order (stage);
+  return FALSE;
 }
 
 static void
-clutter_container_iface_init (ClutterContainerIface *iface)
+gtk_clutter_embed_style_set (GtkWidget *widget,
+                             GtkStyle  *old_style)
 {
-  iface->add = gtk_clutter_embed_add;
-  iface->remove = gtk_clutter_embed_remove;
-  iface->foreach = gtk_clutter_embed_foreach;
-  iface->raise = gtk_clutter_embed_raise;
-  iface->lower = gtk_clutter_embed_lower;
-  iface->sort_depth_order = gtk_clutter_embed_sort_depth_order;
+  GdkScreen *screen;
+  GtkSettings *settings;
+  gdouble dpi;
+  gchar *font_name;
+  const cairo_font_options_t *font_options;
+  gint double_click_time, double_click_distance;
+  ClutterBackend *backend;
+
+  GTK_WIDGET_CLASS (gtk_clutter_embed_parent_class)->style_set (widget,
+                                                                old_style);
+
+  if (gtk_widget_has_screen (widget))
+    screen = gtk_widget_get_screen (widget);
+  else
+    screen = gdk_screen_get_default ();
+
+  dpi = gdk_screen_get_resolution (screen);
+  if (dpi < 0)
+    dpi = 96.0;
+
+  font_options = gdk_screen_get_font_options (screen);
+
+  settings = gtk_settings_get_for_screen (screen);
+  g_object_get (G_OBJECT (settings),
+                "gtk-font-name", &font_name,
+                "gtk-double-click-time", &double_click_time,
+                "gtk-double-click-distance", &double_click_distance,
+                NULL);
+
+  /* copy all settings and values coming from GTK+ into
+   * the ClutterBackend; this way, a scene embedded into
+   * a GtkClutterEmbed will not look completely alien
+   */
+  backend = clutter_get_default_backend ();
+  clutter_backend_set_resolution (backend, dpi);
+  clutter_backend_set_font_options (backend, font_options);
+  clutter_backend_set_font_name (backend, font_name);
+  clutter_backend_set_double_click_time (backend, double_click_time);
+  clutter_backend_set_double_click_distance (backend, double_click_distance);
+
+  g_free (font_name);
 }
 
 static void
@@ -417,10 +520,14 @@ gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
 
   gobject_class->dispose = gtk_clutter_embed_dispose;
 
+  widget_class->style_set = gtk_clutter_embed_style_set;
   widget_class->size_allocate = gtk_clutter_embed_size_allocate;
   widget_class->realize = gtk_clutter_embed_realize;
+  widget_class->unrealize = gtk_clutter_embed_unrealize;
   widget_class->show = gtk_clutter_embed_show;
   widget_class->hide = gtk_clutter_embed_hide;
+  widget_class->unmap = gtk_clutter_embed_unmap;
+  widget_class->expose_event = gtk_clutter_embed_expose_event;
   widget_class->button_press_event = gtk_clutter_embed_button_event;
   widget_class->button_release_event = gtk_clutter_embed_button_event;
   widget_class->key_press_event = gtk_clutter_embed_key_event;
@@ -428,6 +535,10 @@ gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
   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->unmap_event = gtk_clutter_embed_unmap_event;
+  widget_class->focus_in_event = gtk_clutter_embed_focus_in;
+  widget_class->focus_out_event = gtk_clutter_embed_focus_out;
+  widget_class->scroll_event = gtk_clutter_embed_scroll_event;
 }
 
 static void
@@ -437,6 +548,9 @@ gtk_clutter_embed_init (GtkClutterEmbed *embed)
 
   embed->priv = priv = GTK_CLUTTER_EMBED_GET_PRIVATE (embed);
 
+  GTK_WIDGET_SET_FLAGS (embed, GTK_CAN_FOCUS);
+  GTK_WIDGET_UNSET_FLAGS (embed, GTK_NO_WINDOW);
+
   /* disable double-buffering: it's automatically provided
    * by OpenGL
    */
@@ -445,53 +559,14 @@ gtk_clutter_embed_init (GtkClutterEmbed *embed)
   /* 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
- * gtk_init().
- *
- * Return value: %CLUTTER_INIT_SUCCESS on success, a negative integer
- *   on failure.
- *
- * Since: 0.8
- */
-ClutterInitError
-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 ();
-#elif defined(HAVE_CLUTTER_GTK_WIN32)
-  clutter_win32_disable_event_retrieval ();
-#endif /* HAVE_CLUTTER_GTK_{X11,WIN32} */
-
-  return clutter_init (argc, argv);
+  /* intercept the queue-redraw signal of the stage to know when
+   * Clutter-side requests a redraw; this way we can also request
+   * a redraw GTK-side
+   */
+  priv->queue_redraw_id =
+    g_signal_connect (priv->stage,
+                      "queue-redraw", G_CALLBACK (on_stage_queue_redraw),
+                      embed);
 }
 
 /**
@@ -507,7 +582,7 @@ gtk_clutter_init (int    *argc,
 GtkWidget *
 gtk_clutter_embed_new (void)
 {
-  return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
+  return g_object_new (GTK_CLUTTER_TYPE_EMBED, NULL);
 }
 
 /**
@@ -525,7 +600,7 @@ gtk_clutter_embed_new (void)
 ClutterActor *
 gtk_clutter_embed_get_stage (GtkClutterEmbed *embed)
 {
-  g_return_val_if_fail (GTK_IS_CLUTTER_EMBED (embed), NULL);
+  g_return_val_if_fail (GTK_CLUTTER_IS_EMBED (embed), NULL);
 
   return embed->priv->stage;
 }