Esempio n. 1
0
void
gnibbles_worm_rescale (GnibblesWorm *worm, gint tilesize)
{
  int i;
  gfloat x_pos, y_pos;
  gint count;
  ClutterActor *tmp;
  GError *err = NULL;

  if (!worm)
    return;
  if (!worm->actors)
    return;

  count = clutter_group_get_n_children (CLUTTER_GROUP (worm->actors));

  for (i = 0; i < count; i++) {
    tmp = clutter_group_get_nth_child (CLUTTER_GROUP (worm->actors), i);
    clutter_actor_get_position (tmp, &x_pos, &y_pos);

    clutter_actor_set_position (tmp,
                                (x_pos / properties->tilesize) * tilesize,
                                (y_pos / properties->tilesize) * tilesize);

    gtk_clutter_texture_set_from_pixbuf (
       CLUTTER_TEXTURE (tmp),
       worm_pixmaps[properties->wormprops[worm->number]->color - 12],
       &err);
    if (err)
      gnibbles_error (err->message);
  }

}
Esempio n. 2
0
static gint rc_overlay_add(void *renderer, GdkPixbuf *pixbuf, gint x, gint y, OverlayRendererFlags flags)
{
	RendererClutter *rc = (RendererClutter *)renderer;
	PixbufRenderer *pr = rc->pr;
	OverlayData *od;
	gint id;

	g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), -1);
	g_return_val_if_fail(pixbuf != NULL, -1);

	id = 1;
	while (rc_overlay_find(rc, id)) id++;

	od = g_new0(OverlayData, 1);
	od->id = id;
	od->pixbuf = pixbuf;
	g_object_ref(G_OBJECT(od->pixbuf));
	od->x = x;
	od->y = y;
	od->flags = flags;

	od->actor = gtk_clutter_texture_new();
	g_signal_connect (od->actor, "destroy", G_CALLBACK(rc_overlay_actor_destroy_cb), od);

	gtk_clutter_texture_set_from_pixbuf(GTK_CLUTTER_TEXTURE (od->actor), pixbuf, NULL);
	clutter_container_add_actor(CLUTTER_CONTAINER(rc->group), od->actor);

	rc->overlay_list = g_list_append(rc->overlay_list, od);
	rc_overlay_update_position(rc, od);

	return od->id;
}
Esempio n. 3
0
static void rc_overlay_set(void *renderer, gint id, GdkPixbuf *pixbuf, gint x, gint y)
{
	RendererClutter *rc = (RendererClutter *)renderer;
	PixbufRenderer *pr = rc->pr;
	OverlayData *od;

	g_return_if_fail(IS_PIXBUF_RENDERER(pr));

	od = rc_overlay_find(rc, id);
	if (!od) return;

	if (pixbuf)
		{
		g_object_ref(G_OBJECT(pixbuf));
		g_object_unref(G_OBJECT(od->pixbuf));
		od->pixbuf = pixbuf;

		od->x = x;
		od->y = y;

		if (od->actor) gtk_clutter_texture_set_from_pixbuf(GTK_CLUTTER_TEXTURE(od->actor), pixbuf, NULL);
		rc_overlay_update_position(rc, od);
		}
	else
		{
		rc_overlay_free(rc, od);
		}
}
static void
art_cb (RBExtDBKey *key, const char *filename, GValue *data, MxFrame *frame)
{
    ClutterActor *image;
    GdkPixbuf *pixbuf;

    if (data == NULL || G_VALUE_HOLDS (data, GDK_TYPE_PIXBUF) == FALSE) {
        return;
    }

    clutter_threads_enter ();

    image = gtk_clutter_texture_new ();
    pixbuf = GDK_PIXBUF (g_value_get_object (data));
    gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (image), pixbuf, NULL);
    if (clutter_actor_get_height (image) > MAX_IMAGE_HEIGHT) {
        clutter_actor_set_height (image, MAX_IMAGE_HEIGHT);
        clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (image), TRUE);
    }
    if (clutter_actor_get_width (image) > MAX_IMAGE_HEIGHT) {
        clutter_actor_set_width (image, MAX_IMAGE_HEIGHT);
    }
    mx_bin_set_child (MX_BIN (frame), image);
    clutter_actor_show_all (CLUTTER_ACTOR (frame));

    clutter_threads_leave ();
}
/**
 * gtk_clutter_texture_set_from_stock:
 * @texture: a #GtkClutterTexture
 * @widget: a #GtkWidget
 * @stock_id: the stock id of the icon
 * @icon_size: the size of the icon, or -1
 * @error: a return location for errors, or %NULL
 *
 * Sets the contents of @texture using the stock icon @stock_id, as
 * rendered by @widget.
 *
 * Return value: %TRUE on success, %FALSE on failure.
 */
gboolean
gtk_clutter_texture_set_from_stock (GtkClutterTexture  *texture,
                                    GtkWidget          *widget,
                                    const gchar        *stock_id,
                                    GtkIconSize         icon_size,
                                    GError            **error)
{
  GdkPixbuf *pixbuf;
  gboolean returnval;

  g_return_val_if_fail (GTK_CLUTTER_IS_TEXTURE (texture), FALSE);
  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
  g_return_val_if_fail (stock_id != NULL, FALSE);
  g_return_val_if_fail ((icon_size > GTK_ICON_SIZE_INVALID) || (icon_size == -1), FALSE);

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
  pixbuf = gtk_widget_render_icon_pixbuf (widget, stock_id, icon_size);
  G_GNUC_END_IGNORE_DEPRECATIONS

  if (pixbuf == NULL)
    {
      g_set_error (error,
                   GTK_CLUTTER_TEXTURE_ERROR,
                   GTK_CLUTTER_TEXTURE_ERROR_INVALID_STOCK_ID,
                   _("Stock ID '%s' not found"),
                   stock_id);
      return FALSE;
    }

  returnval = gtk_clutter_texture_set_from_pixbuf (texture, pixbuf, error);
  g_object_unref (pixbuf);

  return returnval;
}
Esempio n. 6
0
void clarity_cover_set_album_item (ClarityCover *self, AlbumItem *item) {
    g_return_if_fail(CLARITY_IS_COVER(self));

    ClarityCoverPrivate *priv = CLARITY_COVER_GET_PRIVATE (self);
    g_return_if_fail(priv);

    GError *error = NULL;
    gint y_offset;

    if (!priv->texture) {
        priv->texture = gtk_clutter_texture_new();
        clutter_container_add_actor(CLUTTER_CONTAINER(self), priv->texture);
    }

    // Set cover artwork
    gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE(priv->texture), item->albumart, &error);
    if (error) {
        g_warning("%s", error->message);
        g_error_free(error);
        return;
    }

    // Add reflection
    if (! priv->reflection) {
        y_offset = clutter_actor_get_height (priv->texture) + V_PADDING;

        priv->reflection = clutter_clone_new (priv->texture);
        clutter_actor_add_constraint (priv->reflection, clutter_bind_constraint_new (priv->texture, CLUTTER_BIND_X, 0.0));
        clutter_actor_add_constraint (priv->reflection, clutter_bind_constraint_new (priv->texture, CLUTTER_BIND_Y, y_offset));
        clutter_actor_add_constraint (priv->reflection, clutter_bind_constraint_new (priv->texture, CLUTTER_BIND_WIDTH, 0.0));
        clutter_actor_add_constraint (priv->reflection, clutter_bind_constraint_new (priv->texture, CLUTTER_BIND_HEIGHT, 0.0));
        g_signal_connect (priv->reflection,
                       "paint",
                       G_CALLBACK (_clone_paint_cb),
                       NULL);

        clutter_container_add_actor(CLUTTER_CONTAINER(self), priv->reflection);
    }

    ClutterActorBox box;
    gfloat w, h;
    clutter_actor_get_allocation_box (priv->texture, &box);
    clutter_actor_box_get_size (&box, &w, &h);

    if( h > DEFAULT_IMG_SIZE) {
        gfloat temp = w * DEFAULT_IMG_SIZE / h;
        clutter_actor_set_size(priv->texture, temp, DEFAULT_IMG_SIZE);
    }

    // Add title / artist data
    if (priv->title)
        g_free(priv->title);

    priv->title = g_strdup(item->albumname);

    if (priv->artist)
            g_free(priv->artist);

    priv->artist = g_strdup(item->artist);
}
/**
 * gtk_clutter_texture_set_from_icon_name:
 * @texture: a #GtkClutterTexture
 * @widget: (allow-none): a #GtkWidget or %NULL
 * @icon_name: the name of the icon
 * @icon_size: the icon size or -1
 * @error: a return location for errors, or %NULL
 *
 * Sets the contents of @texture using the @icon_name from the
 * current icon theme.
 *
 * Return value: %TRUE on success, %FALSE on failure
 *
 * Since: 1.0
 */
gboolean
gtk_clutter_texture_set_from_icon_name (GtkClutterTexture  *texture,
                                        GtkWidget          *widget,
                                        const gchar        *icon_name,
                                        GtkIconSize         icon_size,
                                        GError            **error)
{
  GError *local_error = NULL;
  GtkSettings *settings;
  GtkIconTheme *icon_theme;
  gboolean returnval;
  gint width, height;
  GdkPixbuf *pixbuf;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
  g_return_val_if_fail (icon_name != NULL, FALSE);
  g_return_val_if_fail ((icon_size > GTK_ICON_SIZE_INVALID) || (icon_size == -1), FALSE);

  if (widget && gtk_widget_has_screen (widget))
    {
      GdkScreen *screen;

      screen = gtk_widget_get_screen (widget);
      settings = gtk_settings_get_for_screen (screen);
      icon_theme = gtk_icon_theme_get_for_screen (screen);
    }
  else
    {
      settings = gtk_settings_get_default ();
      icon_theme = gtk_icon_theme_get_default ();
    }

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
  if (icon_size == -1 ||
      !gtk_icon_size_lookup_for_settings (settings, icon_size, &width, &height))
    {
      width = height = 48;
    }
  G_GNUC_END_IGNORE_DEPRECATIONS

  pixbuf = gtk_icon_theme_load_icon (icon_theme,
                                     icon_name,
                                     MIN (width, height), 0,
                                     &local_error);
  if (local_error)
    {
      g_propagate_error (error, local_error);
      return FALSE;
    }

  returnval = gtk_clutter_texture_set_from_pixbuf (texture, pixbuf, error);
  g_object_unref (pixbuf);

  return returnval;
}
Esempio n. 8
0
static void bar_pane_gps_thumb_done_cb(ThumbLoader *tl, gpointer data)
{
	FileData *fd;
	ClutterActor *marker;
	ClutterActor *actor;

	marker = CLUTTER_ACTOR(data);
	fd = g_object_get_data(G_OBJECT(marker), "file_fd");
	if (fd->thumb_pixbuf != NULL)
		{
		actor = clutter_texture_new();
		gtk_clutter_texture_set_from_pixbuf(CLUTTER_TEXTURE(actor), fd->thumb_pixbuf, NULL);
		champlain_marker_set_image(CHAMPLAIN_MARKER(marker), actor);
		}
	thumb_loader_free(tl);
}
Esempio n. 9
0
void
gnibbles_warpmanager_rescale (GnibblesWarpManager *warpmanager, gint tilesize)
{
  int i;
  gfloat x_pos, y_pos;
  GError *err = NULL;

  for (i = 0; i < warpmanager->numwarps; i++) {
    clutter_actor_get_position (warpmanager->warps[i]->actor, &x_pos, &y_pos);
    clutter_actor_set_position (warpmanager->warps[i]->actor,
                                (x_pos / properties->tilesize) * tilesize,
                                (y_pos / properties->tilesize) * tilesize);
    gtk_clutter_texture_set_from_pixbuf
      (CLUTTER_TEXTURE (warpmanager->warps[i]->actor), boni_pixmaps[WARP], &err);
    if (err)
      gnibbles_error (err->message);
  }
}
Esempio n. 10
0
int
main (int argc, char *argv[])
{
  ClutterTimeline *timeline;
  ClutterActor *stage;
  GtkWidget *window, *stack, *clutter;
  GtkWidget *label, *button, *vbox;
  GdkPixbuf *pixbuf;
  SuperOH *oh;
  gint i;
  GError *error;

  error = NULL;
  if (gtk_clutter_init_with_args (&argc, &argv,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &error) != CLUTTER_INIT_SUCCESS)
    {
      if (error)
        {
          g_critical ("Unable to initialize Clutter-GTK: %s", error->message);
          g_error_free (error);
          return EXIT_FAILURE;
        }
      else
        g_error ("Unable to initialize Clutter-GTK");
    }

  /* calling gtk_clutter_init* multiple times should be safe */
  g_assert (gtk_clutter_init (NULL, NULL) == CLUTTER_INIT_SUCCESS);

  pixbuf = gdk_pixbuf_new_from_file (EXAMPLES_DATADIR G_DIR_SEPARATOR_S "redhand.png", NULL);

  if (!pixbuf)
    g_error("pixbuf load failed");

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW (window), WINWIDTH, WINHEIGHT);
  gtk_window_set_title (GTK_WINDOW (window), "Clutter Embedding");
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);

  vbox = gtk_grid_new ();
  gtk_orientable_set_orientation (GTK_ORIENTABLE (vbox), GTK_ORIENTATION_VERTICAL);
  gtk_widget_set_hexpand (vbox, TRUE);
  gtk_widget_set_vexpand (vbox, TRUE);
  gtk_container_add (GTK_CONTAINER (window), vbox);

  stack = gtk_stack_new ();
  gtk_container_add (GTK_CONTAINER (vbox), stack);

  label = gtk_label_new ("This is a label in a stack");
  gtk_stack_add_named (GTK_STACK (stack), label, "label");

  clutter = gtk_clutter_embed_new ();
  gtk_stack_add_named (GTK_STACK (stack), clutter, "clutter");
  gtk_widget_realize (clutter);

  stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (clutter));
  clutter_actor_set_background_color (stage, CLUTTER_COLOR_LightSkyBlue);

  label = gtk_label_new ("This is a label");
  gtk_container_add (GTK_CONTAINER (vbox), label);
  gtk_widget_set_hexpand (label, TRUE);

  button = gtk_button_new_with_label ("This is a button...clicky");
  g_signal_connect (button, "clicked", G_CALLBACK (clickity), stack);
  gtk_container_add (GTK_CONTAINER (vbox), button);
  gtk_widget_set_hexpand (button, TRUE);

  button = gtk_button_new_with_mnemonic ("_Fullscreen");
  g_signal_connect (button, "clicked",
                    G_CALLBACK (on_fullscreen),
                    window);
  gtk_container_add (GTK_CONTAINER (vbox), button);
  gtk_widget_set_hexpand (button, TRUE);

  button = gtk_button_new_with_mnemonic ("_Quit");
  g_signal_connect_swapped (button, "clicked",
                            G_CALLBACK (gtk_widget_destroy),
                            window);
  gtk_container_add (GTK_CONTAINER (vbox), button);
  gtk_widget_set_hexpand (button, TRUE);

  oh = g_new (SuperOH, 1);
  oh->stage = stage;

  oh->group = clutter_actor_new ();
  clutter_actor_set_pivot_point (oh->group, 0.5, 0.5);
  
  for (i = 0; i < NHANDS; i++)
    {
      gint x, y, w, h;

      /* Create a texture from pixbuf, then clone in to same resources */
      if (i == 0)
        {
          oh->hand[i] = gtk_clutter_texture_new ();
          gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (oh->hand[i]), pixbuf, NULL);
        }
      else
        oh->hand[i] = clutter_clone_new (oh->hand[0]);

      /* Place around a circle */
      w = clutter_actor_get_width (oh->hand[0]);
      h = clutter_actor_get_height (oh->hand[0]);

      x = WINWIDTH / 2  + RADIUS * cos (i * M_PI / (NHANDS / 2)) - w / 2;
      y = WINHEIGHT / 2 + RADIUS * sin (i * M_PI / (NHANDS / 2)) - h / 2;

      clutter_actor_set_position (oh->hand[i], x, y);
      clutter_actor_set_pivot_point (oh->hand[i], 0.5, 0.5);

      /* Add to our group group */
      clutter_actor_add_child (oh->group, oh->hand[i]);
    }

  /* Add the group to the stage */
  clutter_actor_add_child (stage, oh->group);

  clutter_actor_add_constraint (oh->group, clutter_align_constraint_new (oh->stage, CLUTTER_ALIGN_BOTH, 0.5));

  g_signal_connect (stage, "button-press-event",
		    G_CALLBACK (input_cb), 
		    oh);
  g_signal_connect (stage, "key-release-event",
		    G_CALLBACK (input_cb),
		    oh);

  gtk_widget_show_all (window);

  /* Create a timeline to manage animation */
  timeline = clutter_timeline_new (6000);
  clutter_timeline_set_repeat_count (timeline, -1);

  /* fire a callback for frame change */
  g_signal_connect (timeline, "new-frame",  G_CALLBACK (frame_cb), oh);

  /* and start it */
  clutter_timeline_start (timeline);

  gtk_main ();

  return 0;
}
Esempio n. 11
0
static gboolean bar_pane_gps_marker_keypress_cb(GtkWidget *widget, ClutterButtonEvent *bevent, gpointer data)
{
	//PaneGPSData *pgd = data;
	FileData *fd;
	ClutterActor *marker;
	ClutterColor marker_colour = { MARKER_COLOUR };
	ClutterColor text_colour = { TEXT_COLOUR };
	ClutterColor thumb_colour = { THUMB_COLOUR };
	gchar *current_text;
	ClutterActor *actor;
	ClutterActor *current_image;
	GString *text;
	gint height, width, rotate;
	gchar *altitude = NULL;
	ThumbLoader *tl;

	if (bevent->button == MOUSE_BUTTON_LEFT)
		{
		marker = CLUTTER_ACTOR(widget);
		fd = g_object_get_data(G_OBJECT(marker), "file_fd");

		/* If the marker is showing a thumbnail, delete it
		 */
		current_image = champlain_marker_get_image(CHAMPLAIN_MARKER(marker));
		if (current_image != NULL)
			{
			clutter_actor_destroy(CLUTTER_ACTOR(current_image));
		 	champlain_marker_set_image(CHAMPLAIN_MARKER(marker), NULL);
			}
			
		current_text = g_strdup(champlain_marker_get_text(CHAMPLAIN_MARKER(marker)));

		/* If the marker is showing only the text character, replace it with a
		 * thumbnail and date and altitude
		 */
		if (g_strcmp0(current_text, "i") == 0)
			{
			/* If a thumbail has already been generated, use that. If not try the pixbuf of the full image.
			 * If not, call the thumb_loader to generate a thumbnail and update the marker later in the
			 * thumb_loader callback
			 */
			 if (fd->thumb_pixbuf != NULL)
				{
				actor = clutter_texture_new();
				gtk_clutter_texture_set_from_pixbuf(CLUTTER_TEXTURE(actor), fd->thumb_pixbuf, NULL);
				champlain_marker_set_image(CHAMPLAIN_MARKER(marker), actor);
				}
			else if (fd->pixbuf != NULL)
				{
				actor = clutter_texture_new();
				width = gdk_pixbuf_get_width (fd->pixbuf);
				height = gdk_pixbuf_get_height (fd->pixbuf);
				switch (fd->exif_orientation)
					{
					case 8:
						rotate = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE;
						break;
					case 3:
						rotate = GDK_PIXBUF_ROTATE_UPSIDEDOWN;
						break;
					case 6:
						rotate = GDK_PIXBUF_ROTATE_CLOCKWISE;
						break;
					default:
						rotate = GDK_PIXBUF_ROTATE_NONE;
					}
										
					gtk_clutter_texture_set_from_pixbuf(CLUTTER_TEXTURE(actor),
										gdk_pixbuf_rotate_simple(gdk_pixbuf_scale_simple(fd->pixbuf, THUMB_SIZE, height * THUMB_SIZE / width,
										GDK_INTERP_NEAREST), rotate), NULL);
					champlain_marker_set_image(CHAMPLAIN_MARKER(marker), actor);
				}
			else
				{
				tl = thumb_loader_new(THUMB_SIZE, THUMB_SIZE);
				thumb_loader_set_callbacks(tl,
											bar_pane_gps_thumb_done_cb,
											bar_pane_gps_thumb_error_cb,
											NULL,
											marker);
				thumb_loader_start(tl, fd);
				}
				
			text = g_string_new(fd->name);
			g_string_append(text, "\n");
			g_string_append(text, text_from_time(fd->date));
			g_string_append(text, "\n");
			altitude = metadata_read_string(fd, "formatted.GPSAltitude", METADATA_FORMATTED);
			if (altitude != NULL)
				{
				g_string_append(text, altitude);
				}

			champlain_marker_set_text(CHAMPLAIN_MARKER(marker), text->str);
			champlain_marker_set_color(CHAMPLAIN_MARKER(marker), &thumb_colour);
			champlain_marker_set_text_color(CHAMPLAIN_MARKER(marker), &text_colour);
			champlain_marker_set_font_name(CHAMPLAIN_MARKER(marker), "sans 8");

			g_free(altitude);
			g_string_free(text, TRUE);
			}
		/* otherwise, revert to the hidden text marker
		 */
		else
			{
			champlain_marker_set_text(CHAMPLAIN_MARKER(marker), "i");
			champlain_marker_set_color(CHAMPLAIN_MARKER(marker), &marker_colour);
			champlain_marker_set_text_color(CHAMPLAIN_MARKER(marker), &marker_colour);
			champlain_marker_set_font_name(CHAMPLAIN_MARKER(marker), "courier 5");
			}

		g_free(current_text);
		
		return TRUE;
		}
	return TRUE;
}
int
main (int argc, char *argv[])
{
  ClutterTimeline *timeline;
  ClutterActor    *stage;
  ClutterColor     stage_color = { 0x61, 0x64, 0x8c, 0xff };
  ClutterConstraint *constraint;
  GtkWidget       *window, *clutter;
  GtkWidget       *label, *button, *vbox;
  GdkPixbuf       *pixbuf;
  SuperOH         *oh;
  gint             i;
  GError          *error;

  error = NULL;
  gtk_clutter_init_with_args (&argc, &argv,
                              NULL,
                              NULL,
                              NULL,
                              &error);
  if (error)
    g_error ("Unable to initialize: %s", error->message);

  pixbuf = gdk_pixbuf_new_from_file ("redhand.png", NULL);

  if (!pixbuf)
    g_error("pixbuf load failed");

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (window, "destroy",
                    G_CALLBACK (gtk_main_quit), NULL);

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_add (GTK_CONTAINER (window), vbox);

  clutter = gtk_clutter_embed_new ();
  gtk_widget_set_size_request (clutter, WINWIDTH, WINHEIGHT);

  gtk_container_add (GTK_CONTAINER (vbox), clutter);

  stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (clutter));

  label = gtk_label_new ("This is a label");
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

  button = gtk_button_new_with_label ("This is a button...clicky");
  g_signal_connect (button, "clicked",
                    G_CALLBACK (clickity), NULL);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

  button = gtk_button_new_with_label ("Fullscreen");
  gtk_button_set_image (GTK_BUTTON (button),
                        gtk_image_new_from_stock (GTK_STOCK_FULLSCREEN,
                                                  GTK_ICON_SIZE_BUTTON));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (on_fullscreen),
                    window);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

  button = gtk_button_new_from_stock (GTK_STOCK_QUIT);
  g_signal_connect_swapped (button, "clicked",
                            G_CALLBACK (gtk_widget_destroy),
                            window);
  gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  
  /* and its background color */

  clutter_stage_set_color (CLUTTER_STAGE (stage),
		           &stage_color);

  oh = g_new (SuperOH, 1);
  oh->stage = stage;

  /* create a new group to hold multiple actors in a group */
  oh->group = clutter_group_new ();
  
  for (i = 0; i < NHANDS; i++)
    {
      gint x, y, w, h;

      /* Create a texture from pixbuf, then clone in to same resources */
      if (i == 0)
        {
          oh->hand[i] = gtk_clutter_texture_new ();
          gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (oh->hand[i]), pixbuf, NULL);
        }
      else
        oh->hand[i] = clutter_clone_new (oh->hand[0]);

      /* Place around a circle */
      w = clutter_actor_get_width (oh->hand[0]);
      h = clutter_actor_get_height (oh->hand[0]);

      x = WINWIDTH/2  + RADIUS * cos (i * M_PI / (NHANDS/2)) - w/2;
      y = WINHEIGHT/2 + RADIUS * sin (i * M_PI / (NHANDS/2)) - h/2;

      clutter_actor_set_position (oh->hand[i], x, y);

      /* Add to our group group */
      clutter_container_add_actor (CLUTTER_CONTAINER (oh->group),
                                   oh->hand[i]);
    }

  /* Add the group to the stage */
  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
                               CLUTTER_ACTOR (oh->group));

  constraint = clutter_align_constraint_new (oh->stage, CLUTTER_ALIGN_X_AXIS, 0.5);
  clutter_actor_add_constraint (oh->group, constraint);
  constraint = clutter_align_constraint_new (oh->stage, CLUTTER_ALIGN_Y_AXIS, 0.5);
  clutter_actor_add_constraint (oh->group, constraint);

  g_signal_connect (stage, "button-press-event",
		    G_CALLBACK (input_cb), 
		    oh);
  g_signal_connect (stage, "key-release-event",
		    G_CALLBACK (input_cb),
		    oh);

  gtk_widget_show_all (window);

  /* Only show the actors after parent show otherwise it will just be
   * unrealized when the clutter foreign window is set. widget_show
   * will call show on the stage.
   */
  clutter_actor_show_all (CLUTTER_ACTOR (oh->group));

  /* Create a timeline to manage animation */
  timeline = clutter_timeline_new (6000);
  g_object_set(timeline, "loop", TRUE, NULL);   /* have it loop */

  /* fire a callback for frame change */
  g_signal_connect(timeline, "new-frame",  G_CALLBACK (frame_cb), oh);

  /* and start it */
  clutter_timeline_start (timeline);

  gtk_main();

  return 0;
}