#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 (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
-G_DEFINE_TYPE_WITH_CODE (GtkClutterEmbed,
- gtk_clutter_embed,
- GTK_TYPE_WIDGET,
- G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
- clutter_container_iface_init));
-
-#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
}
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);
{
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
{
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
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;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
+
+ /* 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_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;
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)
{
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
+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;
+ cevent.motion.modifier_state = event->state;
+
+ 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
clutter_do_event (&cevent);
- return TRUE;
+ return FALSE;
}
static gboolean
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);
- return TRUE;
+ return FALSE;
}
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
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;
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->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
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
*/
/* we always create new stages rather than use the default */
priv->stage = clutter_stage_new ();
-#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);
}
/**
GtkWidget *
gtk_clutter_embed_new (void)
{
- return g_object_new (GTK_TYPE_CLUTTER_EMBED, NULL);
+ return g_object_new (GTK_CLUTTER_TYPE_EMBED, NULL);
}
/**
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;
}