/* GNOME GUI Library
 * Copyright (C) 1998, 1999 the Free Software Foundation
 *
 * Author: Ettore Perazzoli (ettore@comm2000.it)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA. 
 */

/* This is a simple widget for adding animations to GNOME
   applications.  It is updated using double buffering for maximum
   smoothness.  */

#include <config.h>
#include <gtk/gtkmain.h>

#include "gnome-animator.h"

/* ------------------------------------------------------------------------- */

/* This is kept private as things might change in the future.  */
struct _GnomeAnimatorFrame
  {
    /* Rendered pixmap.  */
    GdkPixmap *pixmap;

    /* Shape mask (NULL if no shape mask).  */
    GdkBitmap *mask;

    /* Size.  */
    guint width;
    guint height;

    /* X/Y position in the widget.  */
    gint x_offset;
    gint y_offset;

    /* Interval for the next frame (as for `gtk_timeout_add()').  This
       value is divided by `playback_speed' before being used.  */
    guint32 interval;

    /* Pointer to the next and previous frames.  */
    GnomeAnimatorFrame *prev, *next;
  };

struct _GnomeAnimatorPrivate
  {
    /* Timeout callback ID used for advancing playback.  */
    guint timeout_id;

    /* Rectangle being used for the animation.  */
    GdkRectangle area;

    /* Pixmap used for drawing offscreen.  */
    GdkPixmap *offscreen_pixmap;

    /* Pixmap storing the window's background.  This is kept in sync
       with the parent's window background.  */
    GdkPixmap *background_pixmap;

    /* Pointer to the first frame.  */
    GnomeAnimatorFrame *first_frame;

    /* Pointer to the last frame.  */
    GnomeAnimatorFrame *last_frame;

    /* Pointer to the current frame.  */
    GnomeAnimatorFrame *current_frame;
  };

/* ------------------------------------------------------------------------- */

static void                class_init              (GnomeAnimatorClass *class);
static void                init                    (GnomeAnimator *animator);
static void                destroy                 (GtkObject *object);
static void                prepare_aux_pixmaps     (GnomeAnimator *animator);
static void                draw_background_pixmap  (GnomeAnimator *animator);
static void                size_allocate           (GtkWidget *widget,
                                                    GtkAllocation *allocation);
static void                draw                    (GtkWidget *widget,
                                                    GdkRectangle *area);
static gint                expose                  (GtkWidget *widget,
                                                    GdkEventExpose *event);
static void                update                  (GnomeAnimator *animator);
static GnomeAnimatorFrame *append_frame            (GnomeAnimator *animator);
static void                state_or_style_changed  (GnomeAnimator *animator);
static void                state_changed           (GtkWidget *widget,
                                                    guint previous_state);
static void                style_set               (GtkWidget *widget,
                                                    GtkStyle *previous_style);
static gint                timer_cb                (gpointer data);

static GtkWidgetClass *parent_class;

/* ------------------------------------------------------------------------- */

static void
init (GnomeAnimator *animator)
{
  GTK_WIDGET_SET_FLAGS (animator, GTK_NO_WINDOW);

  animator->num_frames = 0;
  animator->current_frame_number = 0;
  animator->status = GNOME_ANIMATOR_STATUS_STOPPED;
  animator->loop_type = GNOME_ANIMATOR_LOOP_NONE;
  animator->playback_direction = +1;
  animator->playback_speed = 1.0;

  animator->privat = g_new (GnomeAnimatorPrivate, 1);
  animator->privat->timeout_id = 0;
  animator->privat->offscreen_pixmap = NULL;
  animator->privat->background_pixmap = NULL;
  animator->privat->first_frame = NULL;
  animator->privat->last_frame = NULL;
  animator->privat->current_frame = NULL;
}

static void
class_init (GnomeAnimatorClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

  object_class = (GtkObjectClass *) class;
  widget_class = (GtkWidgetClass *) class;

  parent_class = gtk_type_class (gtk_widget_get_type ());

  widget_class->size_allocate = size_allocate;
  widget_class->draw = draw;
  widget_class->expose_event = expose;
  widget_class->style_set = style_set;
  widget_class->state_changed = state_changed;

  object_class->destroy = destroy;
}

static void
free_all_frames (GnomeAnimator *animator)
{
  GnomeAnimatorFrame *p = animator->privat->first_frame;

  while (p != NULL)
    {
      GnomeAnimatorFrame *pnext;

      if (p->pixmap != NULL)
        gdk_imlib_free_pixmap (p->pixmap);
      if (p->mask != NULL)
        gdk_imlib_free_bitmap (p->mask);

      pnext = p->next;
      g_free (p);
      p = pnext;
    }
}

static void
destroy (GtkObject *object)
{
  g_return_if_fail (object != NULL);
  g_return_if_fail (GNOME_IS_ANIMATOR (object));

  free_all_frames (GNOME_ANIMATOR (object));

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

/* Create the offscreen and background pixmaps if they do not exist,
   or resize them in case the widget's size has changed.  */
static void
prepare_aux_pixmaps (GnomeAnimator *animator)
{
  GtkWidget *widget = GTK_WIDGET (animator);
  GnomeAnimatorPrivate *privat = animator->privat;
  gint old_width, old_height;

  if (privat->offscreen_pixmap != NULL)
    gdk_window_get_size (privat->offscreen_pixmap, &old_width, &old_height);
  else
    old_width = old_height = 0;

  if ((widget->requisition.width != (guint16) old_width)
      && (widget->requisition.height != (guint16) old_height))
    {
      GdkVisual *visual = gtk_widget_get_visual (widget);

      if (privat->offscreen_pixmap != NULL)
        {
          gdk_pixmap_unref (privat->offscreen_pixmap);
          privat->offscreen_pixmap = NULL;
        }

      if (privat->background_pixmap != NULL)
        {
          gdk_pixmap_unref (privat->background_pixmap);
          privat->background_pixmap = NULL;
        }

      if (widget->requisition.width > 0
          && widget->requisition.height > 0)
        {
          privat->offscreen_pixmap = gdk_pixmap_new (widget->window,
                                                     widget->requisition.width,
                                                     widget->requisition.height,
                                                     visual->depth);
          privat->background_pixmap = gdk_pixmap_new (widget->window,
                                                      widget->requisition.width,
                                                      widget->requisition.height,
                                                      visual->depth);
        }
    }
}

/* Prepare the background pixmap.  This is called whenever the
   parent's background changes.  Using a background pixmap makes it
   faster to update the window, because this we can just copy this
   pixmap instead of actually redrawing the background rectangle.  */
static void
draw_background_pixmap (GnomeAnimator *animator)
{
  GnomeAnimatorPrivate *privat = animator->privat;
  GtkWidget *widget;

  widget = GTK_WIDGET (animator);
  if (privat->background_pixmap != NULL && widget->window != NULL)
    {
      GtkStyle *style;
      guint state;
      GdkGC *gc;

      style = gtk_widget_get_style (widget->parent);
      state = GTK_WIDGET_STATE (widget->parent);
      gc = gdk_gc_new (widget->window);
      gdk_gc_copy (gc, style->bg_gc[state]);

      if (style->bg_pixmap[state] != NULL)
        {
          gdk_gc_set_tile (gc, style->bg_pixmap[state]);
          gdk_gc_set_fill (gc, GDK_TILED);
        }
      else
        gdk_gc_set_fill (gc, GDK_SOLID);

      gdk_gc_set_ts_origin (gc,
                            -animator->privat->area.x,
                            -animator->privat->area.y);

      gdk_draw_rectangle (privat->background_pixmap,
                          gc,
                          TRUE,
                          0, 0,
                          widget->requisition.width,
                          widget->requisition.height);

      gdk_gc_destroy (gc);
    }
}

static void
size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
  GnomeAnimator *animator;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GNOME_IS_ANIMATOR (widget));
  g_return_if_fail (allocation != NULL);

  animator = GNOME_ANIMATOR (widget);

  gdk_window_clear_area (widget->window,
                         widget->allocation.x, widget->allocation.y,
                         widget->allocation.width, widget->allocation.height);

  widget->allocation = *allocation;

  gdk_window_clear_area (widget->window,
                         widget->allocation.x, widget->allocation.y,
                         widget->allocation.width, widget->allocation.height);

  animator->privat->area.x = (widget->allocation.x
                              + (widget->allocation.width
                                 - widget->requisition.width) / 2);
  animator->privat->area.y = (widget->allocation.y
                              + (widget->allocation.height
                                 - widget->requisition.height) / 2);
  animator->privat->area.width = widget->requisition.width;
  animator->privat->area.height = widget->requisition.height;

  prepare_aux_pixmaps (animator);
  draw_background_pixmap (animator);

  update (animator);
}

static void
paint (GnomeAnimator *animator, GdkRectangle *area)
{
  if (animator->num_frames > 0
      && animator->privat->offscreen_pixmap != NULL)
    {
      GtkWidget *widget;
      GnomeAnimatorPrivate *privat;
      GnomeAnimatorFrame *frame;

      widget = GTK_WIDGET (animator);
      privat = animator->privat;
      frame = privat->current_frame;

      /* Update the window using double buffering to make the
         animation as smooth as possible.  */

      /* Copy the background into the offscreen pixmap.  */
      gdk_draw_pixmap (privat->offscreen_pixmap,
                       widget->style->black_gc,
                       privat->background_pixmap,
                       area->x - privat->area.x, area->y - privat->area.y,
                       area->x - privat->area.x, area->y - privat->area.y,
                       area->width,
                       area->height);

      /* Draw the (shaped) frame into the offscreen pixmap.  */
      gdk_gc_set_clip_mask (widget->style->black_gc, frame->mask);
      gdk_gc_set_clip_origin (widget->style->black_gc,
                              frame->x_offset, frame->y_offset);
      gdk_window_copy_area (privat->offscreen_pixmap,
                            widget->style->black_gc,
                            frame->x_offset, frame->y_offset,
                            frame->pixmap,
                            0, 0,
                            frame->width, frame->height);
      gdk_gc_set_clip_mask (widget->style->black_gc, NULL);

      /* Copy the offscreen pixmap into the window.  */
      gdk_draw_pixmap (widget->window,
                       widget->style->black_gc,
                       privat->offscreen_pixmap,
                       area->x - privat->area.x, area->y - privat->area.y,
                       area->x, area->y,
                       area->width, area->height);
    }
  else
    gdk_window_clear_area (animator->widget.window,
                           area->x, area->y,
                           area->width, area->height);
}

static void
draw (GtkWidget *widget, GdkRectangle *area)
{
  GnomeAnimator *animator;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GNOME_IS_ANIMATOR (widget));
  g_return_if_fail (area != NULL);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
      GdkRectangle p_area;

      animator = GNOME_ANIMATOR (widget);

      if (gdk_rectangle_intersect (area, &animator->privat->area, &p_area))
          paint (animator, &p_area);

      gdk_flush ();
    }
}

static gint
expose (GtkWidget *widget, GdkEventExpose *event)
{
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GNOME_IS_ANIMATOR (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (GTK_WIDGET_REALIZED (widget))
    draw (widget, &event->area);

  return FALSE;
}

static void
update (GnomeAnimator *animator)
{
  GtkWidget *widget = GTK_WIDGET (animator);

  if (GTK_WIDGET_REALIZED (widget))
    {
      if (animator->num_frames > 0)
        gtk_widget_queue_draw (widget);
      else
        gdk_window_clear_area (widget->window,
                               widget->allocation.x,
                               widget->allocation.y,
                               widget->allocation.width,
                               widget->allocation.height);
    }
}

static GnomeAnimatorFrame *
append_frame (GnomeAnimator *animator)
{
  GnomeAnimatorPrivate *privat = animator->privat;
  GnomeAnimatorFrame *new_frame;

  new_frame = g_new (GnomeAnimatorFrame, 1);

  if (privat->first_frame == NULL)
    {
      privat->first_frame = privat->last_frame = privat->current_frame
        = new_frame;
      new_frame->prev = NULL;
    }
  else
    {
      privat->last_frame->next = new_frame;
      new_frame->prev = privat->last_frame;
      privat->last_frame = new_frame;
    }

  new_frame->next = 0;
  animator->num_frames++;

  return new_frame;
}

static void
state_or_style_changed (GnomeAnimator *animator)
{
  prepare_aux_pixmaps (animator);
  draw_background_pixmap (animator);
}

static void
state_changed (GtkWidget *widget, guint previous_state)
{
  g_assert (GNOME_IS_ANIMATOR (widget));
  state_or_style_changed (GNOME_ANIMATOR (widget));
}

static void
style_set (GtkWidget *widget, GtkStyle *previous_style)
{
  g_assert (GNOME_IS_ANIMATOR (widget));
  state_or_style_changed (GNOME_ANIMATOR (widget));
}

/* ------------------------------------------------------------------------- */

static gint
timer_cb (gpointer data)
{
  GnomeAnimator *animator;

  GDK_THREADS_ENTER ();

  animator = GNOME_ANIMATOR (data);
  if (animator->status != GNOME_ANIMATOR_STATUS_STOPPED)
    gnome_animator_advance (animator, +1);

  GDK_THREADS_LEAVE ();

  return FALSE;
}

/* ------------------------------------------------------------------------- */

/* Public interface.  */

guint
gnome_animator_get_type (void)
{
  static guint animator_type = 0;

  if (!animator_type)
    {
      GtkTypeInfo animator_info =
      {
        "GnomeAnimator",
        sizeof (GnomeAnimator),
        sizeof (GnomeAnimatorClass),
        (GtkClassInitFunc) class_init,
        (GtkObjectInitFunc) init,
        NULL,
        NULL
      };

      animator_type = gtk_type_unique (gtk_widget_get_type (), &animator_info);
    }

  return animator_type;
}

/**
 * gnome_animator_new_with_size
 * @width: pixel width of animator widget
 * @height: pixel height of animator widget
 *
 * Description: Creates a new animator widget of the specified size.
 *
 * Returns: Pointer to new animator widget.
 **/

GtkWidget *
gnome_animator_new_with_size (guint width, guint height)
{
  GnomeAnimator *animator;
  GtkWidget *widget;

  animator = gtk_type_new (gnome_animator_get_type ());
  widget = GTK_WIDGET (animator);

  widget->requisition.width = width;
  widget->requisition.height = height;

  if (GTK_WIDGET_VISIBLE (widget))
    gtk_widget_queue_resize (widget);

  return widget;
}

/**
 * gnome_animator_set_loop_type
 * @animator: Animator widget to be updated
 * @loop_type: Type of animation loop desired
 *
 * Description: Sets desired animation loop type.  Available loop
 * types are %GNOME_ANIMATOR_LOOP_NONE (play animation once only),
 * %GNOME_ANIMATOR_LOOP_RESTART (play animation over and over again),
 * and %GNOME_ANIMATOR_LOOP_PING_PONG (play animation over and over
 * again, reversing the playing direction every time.)
 **/

void
gnome_animator_set_loop_type (GnomeAnimator *animator,
                              GnomeAnimatorLoopType loop_type)
{
  g_return_if_fail (animator != NULL);
  g_return_if_fail (GNOME_IS_ANIMATOR (animator));

  animator->loop_type = loop_type;
}

/**
 * gnome_animator_get_loop_type
 * @animator: Animator widget to be queried
 *
 * Description: Obtains current animator loop type.  Available loop
 * types are %GNOME_ANIMATOR_LOOP_NONE (play animation once only),
 * %GNOME_ANIMATOR_LOOP_RESTART (play animation over and over again),
 * and %GNOME_ANIMATOR_LOOP_PING_PONG (play animation over and over
 * again, reversing the playing direction every time.)
 *
 * Returns: Loop type.
 **/

GnomeAnimatorLoopType
gnome_animator_get_loop_type (GnomeAnimator *animator)
{
  g_return_val_if_fail (animator != NULL, GNOME_ANIMATOR_LOOP_NONE);

  return animator->loop_type;
}

/**
 * gnome_animator_set_playback_direction
 * @animator: Animator widget to be updated
 * @playback_direction: Direction animation should be played.
 *
 * Description: Sets direction (forwards or backwards) to play the
 * animation.  If @playback_direction is a positive number, the
 * animation is played from the first to the last frame.  If
 * @playback_direction is negative, the animation is played from the
 * last to the first frame.
 **/

void
gnome_animator_set_playback_direction (GnomeAnimator *animator,
                                       gint playback_direction)
{
  g_return_if_fail (animator != NULL);

  animator->playback_direction = playback_direction;
}

/**
 * gnome_animator_get_playback_direction
 * @animator: Animator widget to be updated
 *
 * Description: Returns the current playing direction (forwards or
 * backwards) for the animation.  If the returned value is a positive
 * number, the animation is played from the first to the last frame.
 * If it is negative, the animation is played from the last to the
 * first frame.
 *
 * Returns: Positive or negative number indicating direction.
 **/

gint
gnome_animator_get_playback_direction (GnomeAnimator *animator)
{
  g_return_val_if_fail (animator != NULL, 0);

  return animator->playback_direction;
}

/**
 * gnome_animator_append_frame_from_imlib_at_size
 * @animator: Animator widget to be updated
 * @image: Image to be added to animator
 * @x_offset: horizontal offset of frame within animator widget
 * @y_offset: vertical offset of frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @width: pixel width of frame
 * @height: pixel height of frame
 *
 * Description: Adds frame contained within a &GdkImlibImage image at
 * the end of the current animation.  If @width and @height are
 * different from the actual @image size, the image is scaled
 * proportionally.  The frame display interval is @interval is divided
 * by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frame_from_imlib_at_size (GnomeAnimator *animator,
                                                GdkImlibImage *image,
                                                gint x_offset,
                                                gint y_offset,
                                                guint32 interval,
                                                guint width,
                                                guint height)
{
  GnomeAnimatorFrame *new_frame;

  g_return_val_if_fail (animator != NULL, FALSE);
  g_return_val_if_fail (image != NULL, FALSE);

  if (width == 0)
    width = image->rgb_width;
  if (height == 0)
    height = image->rgb_height;

  new_frame = append_frame (animator);

  gdk_imlib_render (image, width, height);

  new_frame->pixmap = gdk_imlib_move_image (image);
  new_frame->mask = gdk_imlib_move_mask (image);

  new_frame->width = width;
  new_frame->height = height;

  new_frame->x_offset = x_offset;
  new_frame->y_offset = y_offset;

  new_frame->interval = interval;

  return TRUE;
}

/**
 * gnome_animator_append_frame_from_imlib
 * @animator: Animator widget to be updated
 * @image: Image to be added to animator
 * @x_offset: horizontal offset of frame within animator widget
 * @y_offset: vertical offset of frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 *
 * Description: Adds frame contained within a &GdkImlibImage image at
 * the end of the current animation.  The frame display interval is
 * @interval is divided by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frame_from_imlib (GnomeAnimator *animator,
                                        GdkImlibImage *image,
                                        gint x_offset,
                                        gint y_offset,
                                        guint32 interval)
{
  return gnome_animator_append_frame_from_imlib_at_size (animator, image,
                                                         x_offset,
                                                         y_offset,
                                                         interval,
                                                         0, 0);
}

/**
 * gnome_animator_append_frame_from_file_at_size
 * @animator: Animator widget to be updated
 * @name: File path of image to be added to animator
 * @x_offset: horizontal offset of frame within animator widget
 * @y_offset: vertical offset of frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @width: pixel width of frame
 * @height: pixel height of frame
 *
 * Description: Adds frame from the given file to the end of the
 * current animation.  If @width and @height are different from the
 * actual @image size, the image is scaled proportionally.  The frame
 * display interval is @interval divided by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frame_from_file_at_size (GnomeAnimator *animator,
                                               const gchar *name,
                                               gint x_offset,
                                               gint y_offset,
                                               guint32 interval,
                                               guint width,
                                               guint height)
{
  GdkImlibImage *image;

  g_return_val_if_fail (animator != NULL, FALSE);
  g_return_val_if_fail (name != NULL, FALSE);

  image = gdk_imlib_load_image ((char *) name); /* FIXME: This cast is evil. */
  if (image == NULL)
    return FALSE;

  gnome_animator_append_frame_from_imlib_at_size (animator,
                                                  image,
                                                  x_offset,
                                                  y_offset,
                                                  interval,
                                                  width, height);
  gdk_imlib_destroy_image (image);

  return TRUE;
}

/**
 * gnome_animator_append_frame_from_file
 * @animator: Animator widget to be updated
 * @name: File path of image to be added to animator
 * @x_offset: horizontal offset of frame within animator widget
 * @y_offset: vertical offset of frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 *
 * Description: Adds frame from the given file to the end of the
 * current animation.  The frame display interval is @interval divided
 * by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frame_from_file (GnomeAnimator *animator,
                                       const gchar *name,
                                       gint x_offset,
                                       gint y_offset,
                                       guint32 interval)
{
  return gnome_animator_append_frame_from_file_at_size (animator,
                                                        name,
                                                        x_offset,
                                                        y_offset,
                                                        interval,
                                                        0, 0);
}

/**
 * gnome_animator_append_frames_from_imlib_at_size
 * @animator: Animator widget to be updated
 * @image: Image containing frames to be added to animator
 * @x_offset: horizontal offset of a frame within animator widget
 * @y_offset: vertical offset of a frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @x_unit: pixel width of a single frame
 * @width: pixel width of frame
 * @height: pixel height of frame
 *
 * Description: Adds multiple frames contained within a &GdkImlibImage
 * image at the end of the current animation.  Each frame within the
 * image should be next to one another in a single, horizontal row.
 * If @width and @height are different from the actual frame size, the
 * image is scaled proportionally.  The frame display interval is
 * @interval divided by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frames_from_imlib_at_size (GnomeAnimator *animator,
                                                 GdkImlibImage *image,
                                                 gint x_offset,
                                                 gint y_offset,
                                                 guint32 interval,
                                                 gint x_unit,
                                                 guint width,
                                                 guint height)
{
  GtkWidget *widget = GTK_WIDGET (animator);
  GdkPixmap *tmp_pixmap;
  GdkBitmap *tmp_mask;
  GdkGC *pixmap_gc, *mask_gc;
  GdkGCValues gc_values;
  guint num_frames;
  guint render_width;
  guint rest;
  guint offs;
  guint i;
  guint depth;

  rest = image->rgb_width % x_unit;
  g_return_val_if_fail (rest == 0, FALSE);

  if (width == 0)
    width = x_unit;
  if (height == 0)
    height = image->rgb_height;

  num_frames = image->rgb_width / x_unit;
  render_width = width * num_frames;

  gdk_imlib_render (image, render_width, height);

  tmp_pixmap = gdk_imlib_move_image (image);
  tmp_mask = gdk_imlib_move_mask (image);

  depth = gtk_widget_get_visual (widget) -> depth;

  gc_values.function = GDK_COPY;

  pixmap_gc = gdk_gc_new_with_values (tmp_pixmap, &gc_values, GDK_GC_FUNCTION);

  if (tmp_mask != NULL)
    mask_gc = gdk_gc_new_with_values (tmp_mask, &gc_values, GDK_GC_FUNCTION);
  else
    mask_gc = NULL;             /* Make compiler happy.  */

  for (i = offs = 0; i < num_frames; i++, offs += width)
    {
      GnomeAnimatorFrame *new_frame;

      new_frame = append_frame (animator);

      new_frame->pixmap = gdk_pixmap_new (tmp_pixmap, width, height, depth);
      gdk_window_copy_area (new_frame->pixmap,
                            pixmap_gc,
                            0, 0,
                            tmp_pixmap,
                            offs, 0,
                            width, height);

      if (tmp_mask != NULL)
        {
          new_frame->mask = gdk_pixmap_new (tmp_pixmap, width, height, 1);
          gdk_window_copy_area (new_frame->mask,
                                mask_gc,
                                0, 0,
                                tmp_mask,
                                offs, 0,
                                width, height);
        }
      else
        new_frame->mask = NULL;

      new_frame->width = width;
      new_frame->height = height;

      new_frame->x_offset = x_offset;
      new_frame->y_offset = y_offset;

      new_frame->interval = interval;
    }

  gdk_gc_unref (pixmap_gc);
  gdk_imlib_free_pixmap (tmp_pixmap);

  if (tmp_mask != NULL)
    {
      gdk_imlib_free_bitmap (tmp_mask);
      gdk_gc_unref (mask_gc);
    }

  return TRUE;
}

/**
 * gnome_animator_append_frames_from_imlib
 * @animator: Animator widget to be updated
 * @image: Image containing frames to be added to animator
 * @x_offset: horizontal offset of a frame within animator widget
 * @y_offset: vertical offset of a frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @x_unit: pixel width of a single frame
 *
 * Description: Adds multiple frames contained within a &GdkImlibImage
 * image to the end of the current animation.  Each frame within the
 * image should be next to one another in a single, horizontal row.
 * The frame display interval is @interval divided by the
 * playbackspeed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frames_from_imlib (GnomeAnimator *animator,
                                         GdkImlibImage *image,
                                         gint x_offset,
                                         gint y_offset,
                                         guint32 interval,
                                         gint x_unit)
{
  return gnome_animator_append_frames_from_imlib_at_size (animator,
                                                          image,
                                                          x_offset,
                                                          y_offset,
                                                          interval,
                                                          x_unit,
                                                          0, 0);
}

/**
 * gnome_animator_append_frames_from_file_at_size
 * @animator: Animator widget to be updated
 * @name: File path to image containing frames to be added to animator
 * @x_offset: horizontal offset of a frame within animator widget
 * @y_offset: vertical offset of a frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @x_unit: pixel width of a single frame
 * @width: pixel width of frame
 * @height: pixel height of frame
 *
 * Description: Adds multiple frames contained within a single image
 * file to the end of the current animation.  Each frame within the
 * image should be next to one another in a single, horizontal row.
 * If @width and @height are different from the actual frame size, the
 * image is scaled proportionally.  The frame display interval is
 * @interval divided by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frames_from_file_at_size (GnomeAnimator *animator,
                                                const gchar *name,
                                                gint x_offset,
                                                gint y_offset,
                                                guint32 interval,
                                                gint x_unit,
                                                guint width,
                                                guint height)
{
  GdkImlibImage *image;
  gboolean retval;

  g_return_val_if_fail (animator != NULL, FALSE);
  g_return_val_if_fail (name != NULL, FALSE);

  image = gdk_imlib_load_image ((char *) name); /* FIXME: This cast is evil. */
  if (image == NULL)
    return FALSE;

  retval = gnome_animator_append_frames_from_imlib_at_size (animator,
                                                            image,
                                                            x_offset,
                                                            y_offset,
                                                            interval,
                                                            x_unit,
                                                            width,
                                                            height);

  gdk_imlib_destroy_image (image);

  return retval;
}

/**
 * gnome_animator_append_frames_from_file
 * @animator: Animator widget to be updated
 * @name: File path to image containing frames to be added to animator
 * @x_offset: horizontal offset of a frame within animator widget
 * @y_offset: vertical offset of a frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 * @x_unit: pixel width of a single frame
 *
 * Description:  Adds multiple frames contained within a single image file
 * to the end of the current animation.  Each frame within the image
 * should be next to one another in a single, horizontal row.
 * The frame display interval is @interval divided by the playback_speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frames_from_file (GnomeAnimator *animator,
                                        const gchar *name,
                                        gint x_offset,
                                        gint y_offset,
                                        guint32 interval,
                                        gint x_unit)
{
  return gnome_animator_append_frames_from_file_at_size (animator,
                                                         name,
                                                         x_offset,
                                                         y_offset,
                                                         interval,
                                                         x_unit,
                                                         0, 0);
}

/**
 * gnome_animator_append_frame_from_gnome_pixmap
 * @animator: Animator widget to be updated
 * @pixmap: GNOME pixmap to be added to animator
 * @x_offset: horizontal offset of frame within animator widget
 * @y_offset: vertical offset of frame within animator widget
 * @interval: Number of milliseconds to delay before showing next frame
 *
 * Description: Adds frame contained within a &GnomePixmap image to
 * the end of the current animation.  The frame display interval is
 * @interval divided by the playback speed.
 *
 * Returns: %TRUE if append succeeded.
 **/

gboolean
gnome_animator_append_frame_from_gnome_pixmap (GnomeAnimator *animator,
                                               GnomePixmap *pixmap,
                                               gint x_offset,
                                               gint y_offset,
                                               guint32 interval)
{
  GnomeAnimatorFrame *new_frame;
  guint width, height;

  g_return_val_if_fail (animator != NULL, FALSE);
  g_return_val_if_fail (pixmap != NULL, FALSE);

  new_frame = append_frame (animator);

  new_frame->pixmap = gdk_pixmap_ref (pixmap->pixmap);
  gdk_window_get_size (new_frame->pixmap, &width, &height);

  if (pixmap->mask != NULL)
    new_frame->mask = gdk_bitmap_ref (pixmap->mask);
  else
    new_frame->mask = NULL;

  new_frame->x_offset = x_offset;
  new_frame->y_offset = y_offset;

  new_frame->width = width;
  new_frame->height = height;

  new_frame->interval = interval;

  return TRUE;
}

/**
 * gnome_animator_start
 * @animator: Animator widget to be started
 *
 * Description:  Initiate display of animated frames.
 **/

void
gnome_animator_start (GnomeAnimator *animator)
{
  g_return_if_fail (animator != NULL);

  if (animator->num_frames > 0)
    {
      animator->status = GNOME_ANIMATOR_STATUS_RUNNING;

      /* This forces the animator to start playback.  */
      gnome_animator_advance (animator, 0);
    }
}

/**
 * gnome_animator_stop
 * @animator: Animator widget to be stopped
 *
 * Description:  Halts display of animated frames.  The current frame
 * in the animation will remain in the animator widget.
 **/

void
gnome_animator_stop (GnomeAnimator *animator)
{
  g_return_if_fail (animator != NULL);

  if (animator->status == GNOME_ANIMATOR_STATUS_RUNNING)
    gtk_timeout_remove (animator->privat->timeout_id);
  animator->status = GNOME_ANIMATOR_STATUS_STOPPED;
}

/**
 * gnome_animator_advance
 * @animator: Animator widget to be updated
 * @num: Number of frames to advance by
 *
 * Description: Advance the animator @num frames.  If @num is
 * positive, use the specified @playback_direction; if it is negative,
 * go in the opposite direction.  After the call, the animator is in
 * the same state it would be if it had actually executed the specified
 * number of iterations. 
 *
 * Returns:  %TRUE if the animator is now stopped.
 **/

gboolean
gnome_animator_advance (GnomeAnimator *animator, gint num)
{
  gboolean stop;
  guint new_frame;

  g_return_val_if_fail (animator != NULL, FALSE);

  if (num == 0)
    {
      stop = (animator->status == GNOME_ANIMATOR_STATUS_STOPPED);
      new_frame = animator->current_frame_number;
    }
  else
    {
      if (animator->playback_direction < 0)
        num = -num;

      if ((num < 0
           && animator->current_frame_number >= (guint)-num)
          || (num > 0
              && (guint) num < (animator->num_frames
                                - animator->current_frame_number)))
        {
          new_frame = animator->current_frame_number + num;
          stop = FALSE;
        }
      else
        /* We are overflowing the frame list: handle the various loop
           types.  */
        switch (animator->loop_type)
          {
          case GNOME_ANIMATOR_LOOP_NONE:
            if (num < 0)
              new_frame = 0;
            else
              new_frame = animator->num_frames - 1;
            if ((num < 0 && animator->playback_direction > 0)
                || (num > 0 && animator->playback_direction < 0))
              stop = FALSE;
            else
              stop = TRUE;
            break;
          case GNOME_ANIMATOR_LOOP_RESTART:
            if (num > 0)
              new_frame = ((num - (animator->num_frames
                                   - animator->current_frame_number))
                           % animator->num_frames);
            else
              new_frame = (animator->num_frames - 1
                           - ((-num - (animator->current_frame_number + 1))
                              % animator->num_frames));
            stop = FALSE;
            break;
          case GNOME_ANIMATOR_LOOP_PING_PONG:
            {
              guint num1;
              gboolean back;

              if (num > 0)
                num1 = num - (animator->num_frames - 1
                              - animator->current_frame_number);
              else
                num1 = -num - animator->current_frame_number;

              back = (((num1 / (animator->num_frames - 1)) % 2) == 0);
              if (num < 0)
                back = !back;

              if (back)
                {
                  new_frame = (animator->num_frames - 1
                               - (num1 % (animator->num_frames - 1)));
                  animator->playback_direction = -1;
                }
              else
                {
                  new_frame = num1 % (animator->num_frames - 1);
                  animator->playback_direction = +1;
                }
            }
            stop = FALSE;
            break;
          default:
            g_warning ("Unknown GnomeAnimatorLoopType %d",
		       animator->loop_type);
            stop = TRUE;
            new_frame = animator->current_frame_number;
            break;
          }
    }

  if (stop)
    gnome_animator_stop (animator);

  gnome_animator_goto_frame (animator, new_frame);

  return stop;
}

/**
 * gnome_animator_goto_frame
 * @animator: Animator widget to be updated
 * @frame_number: Frame number to be made current
 *
 * Description: Jump to the specified @frame_number and display it.
 **/

void
gnome_animator_goto_frame (GnomeAnimator *animator, guint frame_number)
{
  GnomeAnimatorPrivate *privat;
  GnomeAnimatorFrame *frame;
  gint dist1;
  guint dist2;
  guint i;

  g_return_if_fail (animator != NULL);
  g_return_if_fail (frame_number < animator->num_frames);

  privat = animator->privat;

  /* Try to be smart and minimize the number of steps spent walking on
     the linked list.  */

  dist1 = frame_number - animator->current_frame_number;
  dist2 = animator->num_frames - 1 - frame_number;

  if (frame_number < (guint) ABS (dist1) && frame_number < dist2)
    {
      frame = privat->first_frame;
      for (i = 0; i < frame_number; i++)
        frame = frame->next;
    }
  else if (dist2 < (guint) ABS (dist1))
    {
      frame = privat->last_frame;
      for (i = 0; i < dist2; i++)
        frame = frame->prev;
    }
  else
    {
      frame = privat->current_frame;
      if (dist1 > 0)
        for (i = 0; i < (guint) dist1; i++)
          frame = frame->next;
      else
        for (i = 0; i < (guint) -dist1; i++)
          frame = frame->prev;
    }

  animator->current_frame_number = frame_number;
  privat->current_frame = frame;

  update (animator);

  if (animator->status == GNOME_ANIMATOR_STATUS_RUNNING)
    {
      guint32 interval;

      if (privat->timeout_id != 0)
        gtk_timeout_remove (privat->timeout_id);

      interval = (guint32) ((double) frame->interval
                            / animator->playback_speed + .5);

      privat->timeout_id = gtk_timeout_add (interval,
                                            timer_cb,
                                            (gpointer) animator);
    }
}

/**
 * gnome_animator_get_current_frame_number
 * @animator: Animator widget to be queried
 *
 * Description:  Obtains current frame number from animator widget.
 *
 * Returns: Current frame number.
 **/

guint
gnome_animator_get_current_frame_number (GnomeAnimator *animator)
{
  g_return_val_if_fail (animator != NULL, 0);

  return animator->current_frame_number;
}

/**
 * gnome_animator_get_status
 * @animator: Animator widget to be queried
 *
 * Description:  Obtains current status from animator widget.  Possible
 * return values include %GNOME_ANIMATOR_STATUS_STOPPED and
 * %GNOME_ANIMATOR_STATUS_RUNNING.
 *
 * Returns: Status constant.
 **/

GnomeAnimatorStatus
gnome_animator_get_status (GnomeAnimator *animator)
{
  g_return_val_if_fail (animator != NULL, GNOME_ANIMATOR_STATUS_STOPPED);

  return animator->status;
}

/**
 * gnome_animator_set_playback_speed
 * @animator: Animator widget to be updated
 * @speed: Rate multiplier for playback speed
 *
 * Description: Sets the playback speed.  The delay between every
 * frame is divided by this value before being used.  As a
 * consequence, higher values give higher playback speeds.
 **/

void
gnome_animator_set_playback_speed (GnomeAnimator *animator, double speed)
{
  g_return_if_fail (animator != NULL);
  g_return_if_fail (speed > 0.0);

  animator->playback_speed = speed;
}

/**
 * gnome_animator_get_playback_speed
 * @animator: Animator widget to be queried
 *
 * Description: Returns the current playback speed.
 *
 * Returns: &double indicating the playback speed.
 **/

double
gnome_animator_get_playback_speed (GnomeAnimator *animator)
{
  g_return_val_if_fail (animator != NULL, -1.0);

  return animator->playback_speed;
}

