[gimp/wip/animation: 48/197] plug-ins: animation-play reorganized into several files.



commit a33b0cc0fe34dc7aedf2861354a0fa3867e43cc9
Author: Jehan <jehan girinstud io>
Date:   Tue Aug 18 17:01:21 2015 +0200

    plug-ins: animation-play reorganized into several files.

 plug-ins/animation-play/Makefile.am        |    6 +
 plug-ins/animation-play/animation-dialog.c | 2450 +++++++++++++++++++++
 plug-ins/animation-play/animation-dialog.h |   48 +
 plug-ins/animation-play/animation-play.c   | 3269 +---------------------------
 plug-ins/animation-play/animation-utils.c  |   98 +
 plug-ins/animation-play/animation-utils.h  |   34 +
 plug-ins/animation-play/animation.c        | 1421 ++++++++++++
 plug-ins/animation-play/animation.h        |  101 +
 8 files changed, 4248 insertions(+), 3179 deletions(-)
---
diff --git a/plug-ins/animation-play/Makefile.am b/plug-ins/animation-play/Makefile.am
index 1c9fb62..503793f 100644
--- a/plug-ins/animation-play/Makefile.am
+++ b/plug-ins/animation-play/Makefile.am
@@ -41,4 +41,10 @@ LDADD = \
        $(animation_play_RC)
 
 animation_play_SOURCES = \
+       animation-utils.h       \
+       animation-utils.c       \
+       animation-dialog.h      \
+       animation-dialog.c      \
+       animation.h             \
+       animation.c             \
        animation-play.c
diff --git a/plug-ins/animation-play/animation-dialog.c b/plug-ins/animation-play/animation-dialog.c
new file mode 100755
index 0000000..5447c6e
--- /dev/null
+++ b/plug-ins/animation-play/animation-dialog.c
@@ -0,0 +1,2450 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-dialog.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+#undef GDK_DISABLE_DEPRECATED
+#include <gtk/gtk.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "animation-utils.h"
+#include "animation.h"
+#include "animation-dialog.h"
+
+#include "libgimp/stdplugins-intl.h"
+
+#define MAX_FRAMERATE  300.0
+#define DITHERTYPE     GDK_RGB_DITHER_NORMAL
+
+/* for shaping */
+typedef struct
+{
+  gint x, y;
+} CursorOffset;
+
+enum
+{
+  PROP_0,
+  PROP_ANIMATION
+};
+
+typedef struct _AnimationDialogPrivate AnimationDialogPrivate;
+
+struct _AnimationDialogPrivate
+{
+  Animation      *animation;
+  gdouble         zoom;
+
+  /* GUI */
+  GtkWidget      *play_bar;
+  GtkWidget      *progress_bar;
+  GtkWidget      *settings_bar;
+
+  GtkWidget      *disposalcombo;
+  GtkWidget      *fpscombo;
+  GtkWidget      *zoomcombo;
+  GtkWidget      *proxycombo;
+
+  GtkWidget      *progress;
+  GtkWidget      *startframe_spin;
+  GtkWidget      *endframe_spin;
+
+  GtkWidget      *view_bar;
+  GtkWidget      *refresh; /* refresh button */
+
+  GtkWidget      *drawing_area;
+  guchar         *drawing_area_data;
+  guint           drawing_area_width;
+  guint           drawing_area_height;
+
+  GtkWidget      *shape_window;
+  GtkWidget      *shape_drawing_area;
+  guchar         *shape_drawing_area_data;
+  guint           shape_drawing_area_width;
+  guint           shape_drawing_area_height;
+
+  /* Actions */
+  GtkUIManager   *ui_manager;
+
+  GtkActionGroup *play_actions;
+  GtkActionGroup *settings_actions;
+  GtkActionGroup *view_actions;
+  GtkActionGroup *various_actions;
+};
+
+#define GET_PRIVATE(dialog) \
+        G_TYPE_INSTANCE_GET_PRIVATE (dialog, \
+                                     ANIMATION_TYPE_DIALOG, \
+                                     AnimationDialogPrivate)
+
+static void        animation_dialog_constructed  (GObject      *object);
+static void        animation_dialog_set_property (GObject      *object,
+                                                  guint         property_id,
+                                                  const GValue *value,
+                                                  GParamSpec   *pspec);
+static void        animation_dialog_get_property (GObject      *object,
+                                                  guint         property_id,
+                                                  GValue       *value,
+                                                  GParamSpec   *pspec);
+static void        animation_dialog_finalize     (GObject      *object);
+
+
+/* Initialization. */
+static GtkUIManager
+                 * ui_manager_new            (AnimationDialog  *dialog);
+static void        connect_accelerators      (GtkUIManager     *ui_manager,
+                                              GtkActionGroup   *group);
+static void        animation_dialog_refresh  (AnimationDialog  *dialog);
+
+static void        update_ui_sensitivity     (AnimationDialog  *dialog);
+
+/* UI callbacks */
+static void        close_callback            (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        help_callback             (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+
+static void        disposalcombo_changed     (GtkWidget        *combo,
+                                              AnimationDialog  *dialog);
+static void        fpscombo_activated        (GtkEntry         *combo,
+                                              AnimationDialog  *dialog);
+static void        fpscombo_changed          (GtkWidget        *combo,
+                                              AnimationDialog  *dialog);
+static void        proxy_checkbox_expanded   (GtkExpander      *expander,
+                                              GParamSpec       *param_spec,
+                                              AnimationDialog  *dialog);
+static void        proxycombo_activated      (GtkEntry         *combo_entry,
+                                              AnimationDialog  *dialog);
+static void        proxycombo_changed        (GtkWidget        *combo,
+                                              AnimationDialog  *dialog);
+static void        zoomcombo_activated       (GtkEntry         *combo,
+                                              AnimationDialog  *dialog);
+static void        zoomcombo_changed         (GtkWidget        *combo,
+                                              AnimationDialog  *dialog);
+static void        zoom_in_callback          (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        zoom_out_callback         (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        zoom_reset_callback       (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        speed_up_callback         (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        speed_down_callback       (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static gboolean    adjustment_pressed        (GtkWidget        *widget,
+                                              GdkEventButton   *event,
+                                              AnimationDialog  *dialog);
+static void        startframe_changed        (GtkAdjustment    *adjustment,
+                                              AnimationDialog  *dialog);
+static void        endframe_changed          (GtkAdjustment    *adjustment,
+                                              AnimationDialog  *dialog);
+
+static void        play_callback             (GtkToggleAction  *action,
+                                              AnimationDialog  *dialog);
+static void        step_back_callback        (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        step_callback             (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        rewind_callback           (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        refresh_callback          (GtkAction        *action,
+                                              AnimationDialog  *dialog);
+static void        detach_callback           (GtkToggleAction  *action,
+                                              AnimationDialog  *dialog);
+
+static gboolean    popup_menu                (GtkWidget        *widget,
+                                              AnimationDialog  *dialog);
+
+/* Animation Signals */
+static void        show_loading_progress     (Animation        *animation,
+                                              gdouble           load_rate,
+                                              AnimationDialog  *dialog);
+static void        block_ui                  (Animation        *animation,
+                                              AnimationDialog  *dialog);
+static void        unblock_ui                (Animation        *animation,
+                                              gint              first_frame,
+                                              gint              num_frames,
+                                              gint              playback_start,
+                                              gint              playback_stop,
+                                              gint              preview_width,
+                                              gint              preview_height,
+                                              AnimationDialog  *dialog);
+static void        playback_range_changed    (Animation        *animation,
+                                              gint              playback_start,
+                                              gint              playback_stop,
+                                              AnimationDialog  *dialog);
+static void        framerate_changed         (Animation        *animation,
+                                              gdouble           fps,
+                                              AnimationDialog  *dialog);
+static void        disposal_changed          (Animation        *animation,
+                                              gint              disposal,
+                                              AnimationDialog  *dialog);
+
+/* Rendering/Playing Functions */
+static gboolean    repaint_da                (GtkWidget        *darea,
+                                              GdkEventExpose   *event,
+                                              AnimationDialog  *dialog);
+static gboolean    da_button_press           (GtkWidget        *widget,
+                                              GdkEventButton   *event,
+                                              AnimationDialog  *dialog);
+static void        da_size_callback          (GtkWidget        *widget,
+                                              GtkAllocation    *allocation,
+                                              AnimationDialog  *dialog);
+static gboolean    shape_pressed             (GtkWidget        *widget,
+                                              GdkEventButton   *event,
+                                              AnimationDialog  *dialog);
+static gboolean    shape_released            (GtkWidget        *widget);
+static gboolean    shape_motion              (GtkWidget        *widget,
+                                              GdkEventMotion   *event);
+
+static void        render_callback           (Animation        *animation,
+                                              gint              frame_number,
+                                              GeglBuffer       *buffer,
+                                              AnimationDialog  *dialog);
+static void        render_on_realize         (GtkWidget        *drawing_area,
+                                              AnimationDialog  *dialog);
+static void        render_frame              (AnimationDialog  *dialog,
+                                              GeglBuffer       *buffer);
+static void        reshape_from_bitmap       (AnimationDialog  *dialog,
+                                              const gchar      *bitmap);
+
+/* Progress bar interactions */
+static gboolean    progress_button           (GtkWidget        *widget,
+                                              GdkEventButton   *event,
+                                              AnimationDialog  *dialog);
+static gboolean    progress_entered          (GtkWidget        *widget,
+                                              GdkEventCrossing *event,
+                                              AnimationDialog  *dialog);
+static gboolean    progress_motion           (GtkWidget        *widget,
+                                              GdkEventMotion   *event,
+                                              AnimationDialog  *dialog);
+static gboolean    progress_left             (GtkWidget        *widget,
+                                              GdkEventCrossing *event,
+                                              AnimationDialog  *dialog);
+
+static void        show_goto_progress        (gint              frame_nb,
+                                              AnimationDialog  *dialog);
+static void        show_playing_progress     (AnimationDialog  *dialog);
+
+/* Utils */
+static gboolean    is_playing                (AnimationDialog  *dialog);
+static gboolean    is_detached               (AnimationDialog  *dialog);
+static void        play_pause                (AnimationDialog  *dialog);
+
+static gdouble     get_fps                   (gint              index);
+static gdouble     get_zoom                 (AnimationDialog   *dialog,
+                                              gint              index);
+static void        update_scale              (AnimationDialog  *dialog,
+                                              gdouble           scale);
+
+
+G_DEFINE_TYPE (AnimationDialog, animation_dialog, GTK_TYPE_WINDOW)
+
+#define parent_class animation_dialog_parent_class
+
+static void
+animation_dialog_class_init (AnimationDialogClass *klass)
+{
+  GObjectClass   *object_class     = G_OBJECT_CLASS (klass);
+
+  object_class->constructed        = animation_dialog_constructed;
+  object_class->get_property       = animation_dialog_get_property;
+  object_class->set_property       = animation_dialog_set_property;
+  object_class->finalize           = animation_dialog_finalize;
+
+  /**
+   * AnimationDialog:animation:
+   *
+   * The associated #Animation.
+   */
+  g_object_class_install_property (object_class, PROP_ANIMATION,
+                                   g_param_spec_object ("animation",
+                                                        NULL, NULL,
+                                                        ANIMATION_TYPE_ANIMATION,
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY));
+
+  g_type_class_add_private (klass, sizeof (AnimationDialogPrivate));
+}
+
+static void
+animation_dialog_init (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  priv->drawing_area_width        = -1;
+  priv->drawing_area_height       = -1;
+  priv->shape_drawing_area_width  = -1;
+  priv->shape_drawing_area_height = -1;
+}
+
+/************ Public Functions ****************/
+
+GtkWidget *
+animation_dialog_new (Animation *animation)
+{
+  GtkWidget *dialog;
+
+  dialog = g_object_new (ANIMATION_TYPE_DIALOG,
+                         "type",      GTK_WINDOW_TOPLEVEL,
+                         "animation", animation,
+                         NULL);
+
+  return dialog;
+}
+
+/************ Private Functions ****************/
+
+static void
+animation_dialog_constructed (GObject *object)
+{
+  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkAdjustment   *adjust;
+  GtkWidget       *main_vbox;
+  GtkWidget       *upper_bar;
+  GtkWidget       *abox;
+  GtkWidget       *vbox;
+  GtkWidget       *hbox;
+  GtkWidget       *widget;
+  gchar           *image_name;
+  gchar           *text;
+  GdkCursor       *cursor;
+  gint             preview_width;
+  gint             preview_height;
+  gint             index;
+  gdouble          fps = animation_get_framerate (priv->animation);
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  gtk_window_set_role (GTK_WINDOW (dialog), PLUG_IN_ROLE);
+
+  /* Popup menu */
+  g_signal_connect (dialog, "popup-menu",
+                    G_CALLBACK (popup_menu),
+                    dialog);
+
+  /* Window Title */
+  image_name = animation_get_name (priv->animation);
+  text = g_strconcat (_("Animation Playback:"),
+                      " ", image_name, NULL);
+  gtk_window_set_title (GTK_WINDOW (dialog), text);
+  g_free (image_name);
+  g_free (text);
+
+  priv->ui_manager = ui_manager_new (dialog);
+
+  /* Main vertical box. */
+  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_container_add (GTK_CONTAINER (dialog), main_vbox);
+  gtk_widget_show (main_vbox);
+
+  /* Upper Bar */
+  upper_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_box_pack_start (GTK_BOX (main_vbox), upper_bar, FALSE, FALSE, 0);
+  gtk_widget_show (upper_bar);
+
+  /* The upper bar is itself paned. */
+  hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_pack_start (GTK_BOX (upper_bar), hbox, FALSE, FALSE, 0);
+  gtk_widget_show (hbox);
+
+  /*****************/
+  /* Settings box. */
+  /*****************/
+  priv->settings_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add1 (GTK_PANED (hbox), priv->settings_bar);
+  gtk_widget_show (priv->settings_bar);
+
+  /* Settings: expander to display the proxy settings. */
+  widget = gtk_expander_new_with_mnemonic (_("_Proxy Quality"));
+  gtk_expander_set_expanded (GTK_EXPANDER (widget), FALSE);
+
+  g_signal_connect (GTK_EXPANDER (widget),
+                    "notify::expanded",
+                    G_CALLBACK (proxy_checkbox_expanded),
+                    dialog);
+
+  gimp_help_set_help_data (widget, _("Degrade image quality for lower memory footprint"), NULL);
+
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Settings: proxy image. */
+  priv->proxycombo = gtk_combo_box_text_new_with_entry ();
+
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->proxycombo),
+                                  "25 %");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->proxycombo),
+                                  "50 %");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->proxycombo),
+                                  "75 %");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->proxycombo),
+                                  "100 %");
+
+  /* By default, we are at normal resolution. */
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->proxycombo), 3);
+
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                    "activate",
+                    G_CALLBACK (proxycombo_activated),
+                    dialog);
+  g_signal_connect (priv->proxycombo, "changed",
+                    G_CALLBACK (proxycombo_changed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->proxycombo, _("Proxy resolution quality"), NULL);
+
+  gtk_widget_show (priv->proxycombo);
+  gtk_container_add (GTK_CONTAINER (widget), priv->proxycombo);
+
+  /* Settings: fps */
+  priv->fpscombo = gtk_combo_box_text_new_with_entry ();
+
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->fpscombo), -1);
+  for (index = 0; index < 5; index++)
+    {
+      /* list is given in "fps" - frames per second.
+       * We allow accurate fps (double) for special cases. */
+      text = g_strdup_printf  (_("%g fps"), get_fps (index));
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->fpscombo), text);
+      g_free (text);
+      if (get_fps (index) == fps)
+        gtk_combo_box_set_active (GTK_COMBO_BOX (priv->fpscombo), index);
+    }
+
+  if (gtk_combo_box_get_active (GTK_COMBO_BOX (priv->fpscombo)) == -1)
+    {
+      text = g_strdup_printf  (_("%g fps"), fps);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))), text);
+      g_free (text);
+    }
+
+  g_signal_connect (priv->fpscombo, "changed",
+                    G_CALLBACK (fpscombo_changed),
+                    dialog);
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                    "activate",
+                    G_CALLBACK (fpscombo_activated),
+                    dialog);
+  g_signal_connect (priv->animation, "framerate-changed",
+                    G_CALLBACK (framerate_changed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->fpscombo, _("Frame Rate"), NULL);
+
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar), priv->fpscombo, FALSE, FALSE, 0);
+  gtk_widget_show (priv->fpscombo);
+
+  /* Settings: frame mode. */
+  widget = gtk_combo_box_text_new ();
+
+  text = g_strdup (_("Cumulative layers (combine)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_COMBINE, text);
+  g_free (text);
+
+  text = g_strdup (_("One frame per layer (replace)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_REPLACE, text);
+  g_free (text);
+
+  text = g_strdup (_("Use layer tags (custom)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_TAGS, text);
+  g_free (text);
+
+  gtk_combo_box_set_active (GTK_COMBO_BOX (widget), animation_get_disposal (priv->animation));
+
+  priv->disposalcombo = widget;
+  g_signal_connect (widget, "changed",
+                    G_CALLBACK (disposalcombo_changed),
+                    dialog);
+  g_signal_connect (priv->animation, "disposal-changed",
+                    G_CALLBACK (disposal_changed),
+                    dialog);
+
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /*************/
+  /* View box. */
+  /*************/
+  priv->view_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_pack2 (GTK_PANED (hbox), priv->view_bar, FALSE, TRUE);
+  gtk_widget_show (priv->view_bar);
+
+  /* View: zoom. */
+  priv->zoomcombo = gtk_combo_box_text_new_with_entry ();
+  for (index = 0; index < 5; index++)
+    {
+      text = g_strdup_printf  (_("%.1f %%"), get_zoom (dialog, index) * 100.0);
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->zoomcombo), text);
+      g_free (text);
+
+      if (get_zoom (dialog, index) == 1.0)
+        gtk_combo_box_set_active (GTK_COMBO_BOX (priv->zoomcombo), index);
+    }
+
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->zoomcombo))),
+                    "activate",
+                    G_CALLBACK (zoomcombo_activated),
+                    dialog);
+  g_signal_connect (priv->zoomcombo, "changed",
+                    G_CALLBACK (zoomcombo_changed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->zoomcombo, _("Zoom"), NULL);
+
+  gtk_box_pack_end (GTK_BOX (priv->view_bar), priv->zoomcombo, FALSE, FALSE, 0);
+  gtk_widget_show (priv->zoomcombo);
+
+  /* View: detach. */
+  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (widget), GIMP_ICON_DETACH);
+
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (priv->view_actions, "detach"));
+
+  gtk_box_pack_end (GTK_BOX (priv->view_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /***********/
+  /* Various */
+  /***********/
+
+  /* Various: separator for some spacing in the UI. */
+  widget = GTK_WIDGET (gtk_separator_tool_item_new ());
+  gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (widget), FALSE);
+  gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
+  gtk_box_pack_start (GTK_BOX (upper_bar), widget, TRUE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Various: refresh. */
+  priv->refresh = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Reload the image")));
+
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (priv->refresh),
+                                      gtk_action_group_get_action (priv->settings_actions, "refresh"));
+
+  gtk_box_pack_start (GTK_BOX (upper_bar), priv->refresh, FALSE, FALSE, 0);
+  gtk_widget_show (priv->refresh);
+
+  /***********/
+  /* Drawing */
+  /***********/
+
+  /* Vbox for the preview window and lower option bar */
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
+
+  /* Alignment for the scrolling window, which can be resized by the user. */
+  abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+  gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0);
+  gtk_widget_show (abox);
+
+  widget = gtk_scrolled_window_new (NULL, NULL);
+  gtk_container_add (GTK_CONTAINER (abox), widget);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_widget_show (widget);
+
+  /* I add the drawing area inside an alignment box to prevent it from being resized. */
+  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (widget), abox);
+  gtk_widget_show (abox);
+
+  /* Build a drawing area, with a default size same as the image */
+  priv->drawing_area = gtk_drawing_area_new ();
+  gtk_widget_add_events (priv->drawing_area, GDK_BUTTON_PRESS_MASK);
+  gtk_container_add (GTK_CONTAINER (abox), priv->drawing_area);
+  gtk_widget_show (priv->drawing_area);
+
+  g_signal_connect (priv->drawing_area, "size-allocate",
+                    G_CALLBACK(da_size_callback),
+                    dialog);
+  g_signal_connect (priv->drawing_area, "button-press-event",
+                    G_CALLBACK (da_button_press),
+                    dialog);
+
+  /*****************/
+  /* Play toolbar. */
+  /*****************/
+
+  hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+  gtk_widget_show (hbox);
+
+  /* Play buttons. */
+  priv->play_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add1 (GTK_PANED (hbox), priv->play_bar);
+  gtk_widget_show (priv->play_bar);
+
+  /* Play: play. */
+  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (priv->play_actions, "play"));
+
+  gtk_box_pack_start (GTK_BOX (priv->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: step backward. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step back to previous frame")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (priv->play_actions, "step-back"));
+
+  gtk_box_pack_start (GTK_BOX (priv->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: step forward. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step to next frame")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (priv->play_actions, "step"));
+
+  gtk_box_pack_start (GTK_BOX (priv->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: rewind. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Rewind the animation")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (priv->play_actions, "rewind"));
+
+  gtk_box_pack_start (GTK_BOX (priv->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Progress box. */
+
+  priv->progress_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add2 (GTK_PANED (hbox), priv->progress_bar);
+  gtk_widget_show (priv->progress_bar);
+
+  /* End frame spin button. */
+
+  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
+  priv->endframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
+  gtk_entry_set_width_chars (GTK_ENTRY (priv->endframe_spin), 2);
+
+  gtk_box_pack_end (GTK_BOX (priv->progress_bar), priv->endframe_spin, FALSE, FALSE, 0);
+  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (priv->endframe_spin), GTK_UPDATE_IF_VALID);
+  gtk_widget_show (priv->endframe_spin);
+
+  g_signal_connect (adjust,
+                    "value-changed",
+                    G_CALLBACK (endframe_changed),
+                    dialog);
+
+  g_signal_connect (priv->endframe_spin, "button-press-event",
+                    G_CALLBACK (adjustment_pressed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->endframe_spin, _("End frame"), NULL);
+
+  /* Progress bar. */
+
+  priv->progress = gtk_progress_bar_new ();
+  gtk_box_pack_end (GTK_BOX (priv->progress_bar), priv->progress, TRUE, TRUE, 0);
+  gtk_widget_show (priv->progress);
+
+  gtk_widget_add_events (priv->progress,
+                         GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
+  g_signal_connect (priv->progress, "enter-notify-event",
+                    G_CALLBACK (progress_entered),
+                    dialog);
+  g_signal_connect (priv->progress, "leave-notify-event",
+                    G_CALLBACK (progress_left),
+                    dialog);
+  g_signal_connect (priv->progress, "motion-notify-event",
+                    G_CALLBACK (progress_motion),
+                    dialog);
+  g_signal_connect (priv->progress, "button-press-event",
+                    G_CALLBACK (progress_button),
+                    dialog);
+
+  /* Start frame spin button. */
+
+  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
+  priv->startframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
+  gtk_entry_set_width_chars (GTK_ENTRY (priv->startframe_spin), 2);
+
+  gtk_box_pack_end (GTK_BOX (priv->progress_bar), priv->startframe_spin, FALSE, FALSE, 0);
+  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (priv->startframe_spin), GTK_UPDATE_IF_VALID);
+  gtk_widget_show (priv->startframe_spin);
+
+  g_signal_connect (adjust,
+                    "value-changed",
+                    G_CALLBACK (startframe_changed),
+                    dialog);
+
+  g_signal_connect (GTK_ENTRY (priv->startframe_spin), "button-press-event",
+                    G_CALLBACK (adjustment_pressed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->startframe_spin, _("Start frame"), NULL);
+
+  g_signal_connect (priv->animation,
+                    "playback-range",
+                    G_CALLBACK (playback_range_changed),
+                    dialog);
+
+  /* Finalization. */
+  gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+  gtk_window_set_default_size (GTK_WINDOW (dialog),
+                               preview_width + 20,
+                               preview_height + 90);
+  gtk_widget_show (GTK_WIDGET (dialog));
+
+  /* shape_drawing_area for detached feature. */
+  priv->shape_window = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_window_set_resizable (GTK_WINDOW (priv->shape_window), FALSE);
+
+  priv->shape_drawing_area = gtk_drawing_area_new ();
+  gtk_container_add (GTK_CONTAINER (priv->shape_window), priv->shape_drawing_area);
+  gtk_widget_show (priv->shape_drawing_area);
+  gtk_widget_add_events (priv->shape_drawing_area, GDK_BUTTON_PRESS_MASK);
+  gtk_widget_realize (priv->shape_drawing_area);
+
+  gdk_window_set_back_pixmap (gtk_widget_get_window (priv->shape_window), NULL, FALSE);
+
+  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (priv->shape_window),
+                                       GDK_HAND2);
+  gdk_window_set_cursor (gtk_widget_get_window (priv->shape_window), cursor);
+  gdk_cursor_unref (cursor);
+
+  g_signal_connect (priv->shape_drawing_area, "size-allocate",
+                    G_CALLBACK(da_size_callback),
+                    dialog);
+  g_signal_connect (priv->shape_window, "button-press-event",
+                    G_CALLBACK (shape_pressed),
+                    dialog);
+  g_signal_connect (priv->shape_window, "button-release-event",
+                    G_CALLBACK (shape_released),
+                    NULL);
+  g_signal_connect (priv->shape_window, "motion-notify-event",
+                    G_CALLBACK (shape_motion),
+                    NULL);
+
+  g_object_set_data (G_OBJECT (priv->shape_window),
+                     "cursor-offset", g_new0 (CursorOffset, 1));
+
+  g_signal_connect (priv->drawing_area, "expose-event",
+                    G_CALLBACK (repaint_da),
+                    dialog);
+
+  g_signal_connect (priv->shape_drawing_area, "expose-event",
+                    G_CALLBACK (repaint_da),
+                    dialog);
+
+  /* We request a minimum size *after* having connecting the
+   * size-allocate signal for correct initialization. */
+  gtk_widget_set_size_request (priv->drawing_area, preview_width, preview_height);
+  gtk_widget_set_size_request (priv->shape_drawing_area, preview_width, preview_height);
+
+  g_signal_connect (priv->animation, "loading",
+                    (GCallback) show_loading_progress,
+                    dialog);
+  g_signal_connect (priv->animation, "loading-start",
+                    (GCallback) block_ui,
+                    dialog);
+  g_signal_connect (priv->animation, "loaded",
+                    (GCallback) unblock_ui,
+                    dialog);
+  g_signal_connect (priv->animation, "render",
+                    G_CALLBACK (render_callback),
+                    dialog);
+}
+
+static void
+animation_dialog_set_property (GObject      *object,
+                               guint         property_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  switch (property_id)
+    {
+    case PROP_ANIMATION:
+      priv->animation = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_dialog_get_property (GObject      *object,
+                               guint         property_id,
+                               GValue       *value,
+                               GParamSpec   *pspec)
+{
+  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  switch (property_id)
+    {
+    case PROP_ANIMATION:
+      g_value_set_object (value, priv->animation);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+
+static void
+animation_dialog_finalize (GObject *object)
+{
+  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (priv->shape_window)
+    gtk_widget_destroy (GTK_WIDGET (priv->shape_window));
+
+  if (priv->animation)
+    g_object_unref (priv->animation);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+
+  /* When this window ends, the plugin ends. */
+  gtk_main_quit ();
+}
+
+static GtkUIManager *
+ui_manager_new (AnimationDialog *dialog)
+{
+  static GtkActionEntry play_entries[] =
+  {
+    { "step-back", "media-skip-backward",
+      N_("Step _back"), "d", N_("Step back to previous frame"),
+      G_CALLBACK (step_back_callback) },
+
+    { "step", "media-skip-forward",
+      N_("_Step"), "f", N_("Step to next frame"),
+      G_CALLBACK (step_callback) },
+
+    { "rewind", "media-seek-backward",
+      NULL, "s", N_("Rewind the animation"),
+      G_CALLBACK (rewind_callback) },
+  };
+
+  static GtkToggleActionEntry play_toggle_entries[] =
+  {
+    { "play", "media-playback-start",
+      NULL, "space", N_("Start playback"),
+      G_CALLBACK (play_callback), FALSE },
+  };
+
+  static GtkActionEntry settings_entries[] =
+  {
+    /* Refresh is not really a settings, but it makes sense to be grouped
+     * as such, because we want to be able to refresh and set things in the
+     * same moments. */
+    { "refresh", "view-refresh",
+      N_("Refresh"), "<control>R", N_("Reload the image"),
+      G_CALLBACK (refresh_callback) },
+
+    {
+      "speed-up", NULL,
+      N_("Faster"), "bracketright", N_("Increase the speed of the animation"),
+      G_CALLBACK (speed_up_callback)
+    },
+    {
+      "speed-down", NULL,
+      N_("Slower"), "bracketleft", N_("Decrease the speed of the animation"),
+      G_CALLBACK (speed_down_callback)
+    },
+  };
+
+  static GtkActionEntry view_entries[] =
+  {
+    { "zoom-in", GIMP_ICON_ZOOM_IN,
+      NULL, "plus", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
+
+    { "zoom-in-accel", GIMP_ICON_ZOOM_IN,
+      NULL, "KP_Add", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
+
+    { "zoom-out", GIMP_ICON_ZOOM_OUT,
+      NULL, "minus", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
+
+    { "zoom-out-accel", GIMP_ICON_ZOOM_OUT,
+      NULL, "KP_Subtract", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
+
+    { "zoom-reset", GIMP_ICON_ZOOM_OUT,
+      NULL, "equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
+
+    { "zoom-reset-accel", GIMP_ICON_ZOOM_OUT,
+      NULL, "KP_Equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
+  };
+
+  static GtkToggleActionEntry view_toggle_entries[] =
+  {
+    { "detach", GIMP_ICON_DETACH,
+      N_("Detach"), NULL,
+      N_("Detach the animation from the dialog window"),
+      G_CALLBACK (detach_callback), FALSE }
+  };
+
+  static GtkActionEntry various_entries[] =
+  {
+    { "help", "help-browser",
+      N_("About the animation plug-in"), "question", NULL,
+      G_CALLBACK (help_callback) },
+
+    { "close", "window-close",
+      N_("Quit"), "<control>W", NULL,
+      G_CALLBACK (close_callback)
+    },
+    {
+      "quit", "application-quit",
+      N_("Quit"), "<control>Q", NULL,
+      G_CALLBACK (close_callback)
+    },
+  };
+
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
+  GError         *error      = NULL;
+
+  /* All playback related actions. */
+  priv->play_actions = gtk_action_group_new ("playback");
+  gtk_action_group_set_translation_domain (priv->play_actions, NULL);
+  gtk_action_group_add_actions (priv->play_actions,
+                                play_entries,
+                                G_N_ELEMENTS (play_entries),
+                                dialog);
+  gtk_action_group_add_toggle_actions (priv->play_actions,
+                                       play_toggle_entries,
+                                       G_N_ELEMENTS (play_toggle_entries),
+                                       dialog);
+  connect_accelerators (ui_manager, priv->play_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, priv->play_actions, -1);
+
+  /* All settings related actions. */
+  priv->settings_actions = gtk_action_group_new ("settings");
+  gtk_action_group_set_translation_domain (priv->settings_actions, NULL);
+  gtk_action_group_add_actions (priv->settings_actions,
+                                settings_entries,
+                                G_N_ELEMENTS (settings_entries),
+                                dialog);
+  connect_accelerators (ui_manager, priv->settings_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, priv->settings_actions, -1);
+
+  /* All view actions. */
+  priv->view_actions = gtk_action_group_new ("view");
+  gtk_action_group_set_translation_domain (priv->view_actions, NULL);
+  gtk_action_group_add_actions (priv->view_actions,
+                                view_entries,
+                                G_N_ELEMENTS (view_entries),
+                                dialog);
+  gtk_action_group_add_toggle_actions (priv->view_actions,
+                                       view_toggle_entries,
+                                       G_N_ELEMENTS (view_toggle_entries),
+                                       dialog);
+  connect_accelerators (ui_manager, priv->view_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, priv->view_actions, -1);
+
+  /* Remaining various actions. */
+  priv->various_actions = gtk_action_group_new ("various");
+
+  gtk_action_group_set_translation_domain (priv->various_actions, NULL);
+
+  gtk_action_group_add_actions (priv->various_actions,
+                                various_entries,
+                                G_N_ELEMENTS (various_entries),
+                                dialog);
+  connect_accelerators (ui_manager, priv->various_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, priv->various_actions, -1);
+
+  /* Finalize. */
+  gtk_window_add_accel_group (GTK_WINDOW (dialog),
+                              gtk_ui_manager_get_accel_group (ui_manager));
+  gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager));
+
+  /* Finally make some limited popup menu. */
+  gtk_ui_manager_add_ui_from_string (ui_manager,
+                                     "<ui>"
+                                     "  <popup name=\"anim-play-popup\" accelerators=\"true\">"
+                                     "    <menuitem action=\"refresh\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"zoom-in\" />"
+                                     "    <menuitem action=\"zoom-out\" />"
+                                     "    <menuitem action=\"zoom-reset\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"speed-up\" />"
+                                     "    <menuitem action=\"speed-down\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"help\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"close\" />"
+                                     "  </popup>"
+                                     "</ui>",
+                                     -1, &error);
+
+  if (error)
+    {
+      g_warning ("error parsing ui: %s", error->message);
+      g_clear_error (&error);
+    }
+
+  return ui_manager;
+}
+
+static void
+connect_accelerators (GtkUIManager   *ui_manager,
+                      GtkActionGroup *group)
+{
+  GList          *action_list;
+  GList          *iter;
+
+  action_list = gtk_action_group_list_actions (group);
+  iter = action_list;
+  while (iter)
+    {
+      /* Make sure all the action's accelerator are correctly connected,
+       * even when there are no associated UI item. */
+      GtkAction *action = GTK_ACTION (iter->data);
+
+      gtk_action_set_accel_group (action,
+                                  gtk_ui_manager_get_accel_group (ui_manager));
+      gtk_action_connect_accelerator (action);
+
+      iter = iter->next;
+    }
+  g_list_free (action_list);
+}
+
+static void
+animation_dialog_refresh (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GdkScreen *screen;
+  guint      screen_width, screen_height;
+  gint       window_width, window_height;
+  gint       preview_width, preview_height;
+
+  /* Update GUI size. */
+  screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
+  screen_height = gdk_screen_get_height (screen);
+  screen_width = gdk_screen_get_width (screen);
+  gtk_window_get_size (GTK_WINDOW (dialog), &window_width, &window_height);
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+
+  /* if the *window* size is bigger than the screen size,
+   * diminish the drawing area by as much, then compute the corresponding scale. */
+  if (window_width + 50 > screen_width || window_height + 50 > screen_height)
+  {
+      gint expected_drawing_area_width = MAX (1, preview_width - window_width + screen_width);
+      gint expected_drawing_area_height = MAX (1, preview_height - window_height + screen_height);
+
+      gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) preview_width,
+                                    (gdouble) expected_drawing_area_height / (gdouble) preview_height);
+      update_scale (dialog, expected_scale);
+
+      /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
+       * So we take a wild guess by making the window slightly smaller to fit into most case. */
+      gtk_window_set_default_size (GTK_WINDOW (dialog),
+                                   MIN (expected_drawing_area_width + 20, screen_width - 60),
+                                   MIN (expected_drawing_area_height + 90, screen_height - 60));
+
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog));
+  }
+}
+
+/* Update the tool sensitivity for playing, depending on the number of frames. */
+static void
+update_ui_sensitivity (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gboolean animated;
+
+  animated = animation_get_playback_stop (priv->animation) - animation_get_playback_start (priv->animation) 
1;
+  /* Play actions only if we selected several frames between start/end. */
+  gtk_action_group_set_sensitive (priv->play_actions, animated);
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->play_bar), animated);
+
+  /* We can access the progress bar if there are several frames. */
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->progress_bar),
+                            animation_get_length (priv->animation) > 1);
+
+  /* Settings are always changeable. */
+  gtk_action_group_set_sensitive (priv->settings_actions, TRUE);
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->settings_bar), TRUE);
+
+  /* View are always meaningfull with at least 1 frame. */
+  gtk_action_group_set_sensitive (priv->view_actions,
+                                  animation_get_length (priv->animation) >= 1);
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->view_bar),
+                            animation_get_length (priv->animation) >= 1);
+}
+
+/**** UI CALLBACKS ****/
+
+static void
+close_callback (GtkAction           *action,
+                AnimationDialog *dialog)
+{
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+help_callback (GtkAction           *action,
+               AnimationDialog *dialog)
+{
+  gimp_standard_help_func (PLUG_IN_PROC, dialog);
+}
+
+/*
+ * Callback emitted when the user changes the disposal in UI.
+ */
+static void
+disposalcombo_changed (GtkWidget       *combo,
+                       AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gboolean    was_playing;
+  DisposeType disposal;
+
+  disposal    = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+  was_playing = is_playing (dialog);
+
+  animation_load (priv->animation, disposal, PROXY_KEEP);
+
+  if (was_playing)
+    {
+      /* Initializing frames stopped the playing. I restart it. */
+      play_pause (dialog);
+    }
+}
+
+/*
+ * Callback emitted when the user hits the Enter key of the fps combo.
+ */
+static void
+fpscombo_activated (GtkEntry        *combo_entry,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  const gchar *active_text;
+  gdouble      fps;
+  gchar       *text;
+
+  active_text = gtk_entry_get_text (combo_entry);
+  /* Try a text conversion, locale-aware. */
+  fps         = g_strtod (active_text, NULL);
+
+  if (fps >= MAX_FRAMERATE)
+    {
+      /* Let's avoid huge frame rates. */
+      fps = MAX_FRAMERATE;
+    }
+  else if (fps <= 0)
+    {
+      /* Null or negative framerates are impossible. */
+      fps = 0.1;
+    }
+
+  animation_set_framerate (priv->animation, fps);
+
+  /* Now let's format the text cleanly: "%g fps". */
+  text = g_strdup_printf  (_("%g fps"), fps);
+  gtk_entry_set_text (combo_entry, text);
+  g_free (text);
+}
+
+static void
+fpscombo_changed (GtkWidget       *combo,
+                  AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
+  if (index != -1)
+    {
+      animation_set_framerate (priv->animation, get_fps (index));
+    }
+}
+
+/*
+ * Callback emitted when the user toggle the proxy checkbox.
+ */
+static void
+proxy_checkbox_expanded (GtkExpander     *expander,
+                         GParamSpec      *param_spec,
+                         AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (gtk_expander_get_expanded (expander))
+    {
+      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                            dialog);
+    }
+  else if (animation_get_proxy (priv->animation) != 1.0)
+    {
+      /* Closing the expander. Proxy is deactived. */
+      gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (priv->zoomcombo));
+
+      animation_load (priv->animation, DISPOSE_KEEP, 1.0);
+      update_scale (dialog, get_zoom (dialog, index));
+    }
+}
+
+/*
+ * Callback emitted when the user hits the Enter key of the fps combo.
+ */
+static void
+proxycombo_activated (GtkEntry        *combo_entry,
+                      AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  const gchar *active_text;
+  gchar       *text;
+  gdouble      ratio;
+
+  active_text = gtk_entry_get_text (combo_entry);
+  /* Try a text conversion, locale-aware. */
+  ratio       = g_strtod (active_text, NULL);
+
+  ratio = ratio / 100.0;
+  if (ratio >= 1.0)
+    {
+      ratio = 1.0;
+    }
+  else if (ratio <= 0)
+    {
+      /* Null or negative ratio are impossible. */
+      ratio = 0.1;
+    }
+
+  /* Now let's format the text cleanly: "%.1f %%". */
+  text = g_strdup_printf  (_("%.1f %%"), ratio * 100.0);
+  gtk_entry_set_text (combo_entry, text);
+  g_free (text);
+
+  /* Finally set the preview size, unless they were already good. */
+  if (animation_get_proxy (priv->animation) != ratio)
+    {
+      gboolean     was_playing;
+
+      was_playing = is_playing (dialog);
+
+      animation_load (priv->animation, DISPOSE_KEEP, ratio);
+      update_scale (dialog, get_zoom (dialog, -1));
+
+      if (was_playing)
+        {
+          /* Initializing frames stopped the playing. I restart it. */
+          play_pause (dialog);
+        }
+    }
+}
+
+
+static void
+proxycombo_changed (GtkWidget       *combo,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
+  if (index != -1)
+    {
+      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                            dialog);
+    }
+}
+
+/*
+ * Callback emitted when the user hits the Enter key of the zoom combo.
+ */
+static void
+zoomcombo_activated (GtkEntry        *combo,
+                     AnimationDialog *dialog)
+{
+  update_scale (dialog, get_zoom (dialog, -1));
+}
+
+/*
+ * Callback emitted when the user selects a zoom in the dropdown,
+ * or edits the text entry.
+ * We don't want to process manual edits because it greedily emits
+ * signals after each character deleted or added.
+ */
+static void
+zoomcombo_changed (GtkWidget       *combo,
+                   AnimationDialog *dialog)
+{
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
+  if (index != -1)
+    update_scale (dialog, get_zoom (dialog, index));
+}
+
+static void
+zoom_in_callback (GtkAction       *action,
+                  AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble  scale = get_zoom (dialog, -1);
+
+  g_signal_handlers_block_by_func (priv->zoomcombo,
+                                   G_CALLBACK (zoomcombo_changed),
+                                   dialog);
+  update_scale (dialog, scale + 0.1);
+
+  g_signal_handlers_unblock_by_func (priv->zoomcombo,
+                                     G_CALLBACK (zoomcombo_changed),
+                                     dialog);
+}
+
+static void
+zoom_out_callback (GtkAction       *action,
+                   AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble  scale = get_zoom (dialog, -1);
+
+  if (scale > 0.1)
+    {
+      g_signal_handlers_block_by_func (priv->zoomcombo,
+                                       G_CALLBACK (zoomcombo_changed),
+                                       dialog);
+      update_scale (dialog, scale - 0.1);
+      g_signal_handlers_unblock_by_func (priv->zoomcombo,
+                                         G_CALLBACK (zoomcombo_changed),
+                                         dialog);
+    }
+}
+
+static void
+zoom_reset_callback (GtkAction       *action,
+                     AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble  scale = get_zoom (dialog, -1);
+
+  if (scale != 1.0)
+    {
+      g_signal_handlers_block_by_func (priv->zoomcombo,
+                                       G_CALLBACK (zoomcombo_changed),
+                                       dialog);
+      update_scale (dialog, 1.0);
+      g_signal_handlers_unblock_by_func (priv->zoomcombo,
+                                         G_CALLBACK (zoomcombo_changed),
+                                         dialog);
+    }
+}
+
+static void
+speed_up_callback (GtkAction       *action,
+                   AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble fps = animation_get_framerate (priv->animation);
+
+  if (fps <= MAX_FRAMERATE - 1)
+    {
+      gchar *text;
+
+      animation_set_framerate (priv->animation, fps + 1.0);
+      fps = animation_get_framerate (priv->animation);
+
+      g_signal_handlers_block_by_func (priv->fpscombo,
+                                       G_CALLBACK (fpscombo_changed),
+                                       dialog);
+      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                       G_CALLBACK (fpscombo_activated),
+                                       dialog);
+
+      text = g_strdup_printf  (_("%g fps"), fps);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))), text);
+      g_free (text);
+
+      g_signal_handlers_unblock_by_func (priv->fpscombo,
+                                         G_CALLBACK (fpscombo_changed),
+                                         dialog);
+      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                         G_CALLBACK (fpscombo_activated),
+                                         dialog);
+    }
+}
+
+static void
+speed_down_callback (GtkAction       *action,
+                     AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble fps = animation_get_framerate (priv->animation);
+
+  if (fps > 1)
+    {
+      AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+      gchar *text;
+
+      animation_set_framerate (priv->animation, fps - 1.0);
+      fps = animation_get_framerate (priv->animation);
+
+      g_signal_handlers_block_by_func (priv->fpscombo,
+                                       G_CALLBACK (fpscombo_changed),
+                                       dialog);
+      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                       G_CALLBACK (fpscombo_activated),
+                                       dialog);
+
+      text = g_strdup_printf  (_("%g fps"), fps);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))), text);
+      g_free (text);
+
+      g_signal_handlers_unblock_by_func (priv->fpscombo,
+                                         G_CALLBACK (fpscombo_changed),
+                                         dialog);
+      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                         G_CALLBACK (fpscombo_activated),
+                                         dialog);
+    }
+}
+
+static gboolean
+adjustment_pressed (GtkWidget       *widget,
+                    GdkEventButton  *event,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gboolean event_processed = FALSE;
+
+  if (event->type == GDK_BUTTON_PRESS &&
+      event->button == 2)
+    {
+      GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
+      GtkAdjustment *adj = gtk_spin_button_get_adjustment (spin);
+
+      gtk_adjustment_set_value (adj,
+                                (gdouble) animation_get_position (priv->animation));
+
+      /* We don't want the middle click to have another usage (in
+       * particular, there is likely no need to copy-paste in these spin
+       * buttons). */
+      event_processed = TRUE;
+    }
+
+  return event_processed;
+}
+
+static void
+startframe_changed (GtkAdjustment   *adjustment,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble value = gtk_adjustment_get_value (adjustment);
+
+  animation_set_playback_start (priv->animation, (gint) value);
+
+  update_ui_sensitivity (dialog);
+}
+
+static void
+endframe_changed (GtkAdjustment   *adjustment,
+                  AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gdouble value = gtk_adjustment_get_value (adjustment);
+
+  animation_set_playback_stop (priv->animation, (gint) value);
+
+  update_ui_sensitivity (dialog);
+}
+
+static void
+play_callback (GtkToggleAction *action,
+               AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (is_playing (dialog))
+    {
+      gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause");
+      animation_play (priv->animation);
+    }
+  else
+    {
+      gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-start");
+      animation_stop (priv->animation);
+    }
+
+  g_object_set (action,
+                "tooltip",
+                is_playing (dialog) ?  _("Pause playback") : _("Start playback"),
+                NULL);
+}
+
+static void
+step_back_callback (GtkAction           *action,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (is_playing (dialog))
+    play_pause (dialog);
+  animation_prev (priv->animation);
+}
+
+static void
+step_callback (GtkAction       *action,
+               AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (is_playing (dialog))
+    play_pause (dialog);
+  animation_next (priv->animation);
+}
+
+static void
+rewind_callback (GtkAction       *action,
+                 AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gboolean was_playing = is_playing (dialog);
+
+  if (was_playing)
+    play_pause (dialog);
+
+  animation_jump (priv->animation, animation_get_playback_start (priv->animation));
+
+  /* If we were playing, start playing again. */
+  if (was_playing)
+    play_pause (dialog);
+}
+
+static void
+refresh_callback (GtkAction       *action,
+                  AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  animation_dialog_refresh (dialog);
+  animation_load (priv->animation, DISPOSE_KEEP, PROXY_KEEP);
+}
+
+static void
+detach_callback (GtkToggleAction *action,
+                 AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gboolean detached = gtk_toggle_action_get_active (action);
+  GeglBuffer *buffer;
+
+  if (detached)
+    {
+      gint x, y;
+
+      gtk_window_set_screen (GTK_WINDOW (priv->shape_window),
+                             gtk_widget_get_screen (priv->drawing_area));
+
+      gtk_widget_show (priv->shape_window);
+
+      if (! gtk_widget_get_realized (priv->drawing_area))
+        {
+          gtk_widget_realize (priv->drawing_area);
+        }
+      if (! gtk_widget_get_realized (priv->shape_drawing_area))
+        {
+          gtk_widget_realize (priv->shape_drawing_area);
+        }
+
+      gdk_window_get_origin (gtk_widget_get_window (priv->drawing_area), &x, &y);
+
+      gtk_window_move (GTK_WINDOW (priv->shape_window), x + 6, y + 6);
+
+      gdk_window_set_back_pixmap (gtk_widget_get_window (priv->shape_drawing_area), NULL, TRUE);
+
+      /* Set "alpha grid" background. */
+      total_alpha_preview (priv->drawing_area_data,
+                           priv->drawing_area_width,
+                           priv->drawing_area_height);
+      repaint_da (priv->drawing_area, NULL, dialog);
+    }
+  else
+    {
+      gtk_widget_hide (priv->shape_window);
+    }
+
+  /* Force a refresh after detachment/attachment. */
+  buffer = animation_get_frame (priv->animation,
+                                animation_get_position (priv->animation));
+  render_frame (dialog, buffer);
+}
+
+static gboolean
+popup_menu (GtkWidget       *widget,
+            AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkWidget *menu = gtk_ui_manager_get_widget (priv->ui_manager, "/anim-play-popup");
+
+  gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
+  gtk_menu_popup (GTK_MENU (menu),
+                  NULL, NULL, NULL, NULL,
+                  0, gtk_get_current_event_time ());
+
+  return TRUE;
+}
+
+static void
+show_loading_progress (Animation       *animation,
+                       gdouble          load_rate,
+                       AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar *text;
+
+  block_ui (animation, dialog);
+
+  /* update the dialog's progress bar */
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progress), load_rate);
+
+  text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progress), text);
+  g_free (text);
+
+  /* Forcing the UI to update even with intensive computation. */
+  while (gtk_events_pending ())
+    gtk_main_iteration ();
+}
+
+static void
+block_ui (Animation       *animation,
+          AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (is_playing (dialog))
+    play_pause (dialog);
+
+  gtk_action_group_set_sensitive (priv->play_actions, FALSE);
+  gtk_widget_set_sensitive (priv->play_bar, FALSE);
+  gtk_widget_set_sensitive (priv->progress_bar, FALSE);
+  gtk_action_group_set_sensitive (priv->settings_actions, FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->settings_bar), FALSE);
+  gtk_action_group_set_sensitive (priv->view_actions, FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->view_bar), FALSE);
+}
+
+static void
+unblock_ui (Animation      *animation,
+            gint            first_frame,
+            gint            num_frames,
+            gint            playback_start,
+            gint            playback_stop,
+            gint            preview_width,
+            gint            preview_height,
+            AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gint frame_spin_size;
+  gint last_frame = num_frames + first_frame - 1;
+
+  frame_spin_size = (gint) (log10 (last_frame - (last_frame % 10))) + 1;
+  gtk_entry_set_width_chars (GTK_ENTRY (priv->startframe_spin), frame_spin_size);
+  gtk_entry_set_width_chars (GTK_ENTRY (priv->endframe_spin), frame_spin_size);
+
+  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->startframe_spin)),
+                            playback_start,
+                            first_frame,
+                            last_frame,
+                            1.0, 5.0, 0.0);
+  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin)),
+                            playback_stop,
+                            playback_start,
+                            last_frame,
+                            1.0, 5.0, 0.0);
+
+  update_ui_sensitivity (dialog);
+
+  animation_dialog_refresh (dialog);
+}
+
+static void
+playback_range_changed (Animation       *animation,
+                        gint             playback_start,
+                        gint             playback_stop,
+                        AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkAdjustment *startframe_adjust;
+  GtkAdjustment *stopframe_adjust;
+
+  startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->startframe_spin));
+  stopframe_adjust  = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin));
+
+  g_signal_handlers_block_by_func (startframe_adjust,
+                                   G_CALLBACK (endframe_changed),
+                                   dialog);
+  g_signal_handlers_block_by_func (stopframe_adjust,
+                                   G_CALLBACK (endframe_changed),
+                                   dialog);
+  gtk_adjustment_set_value (startframe_adjust, playback_start);
+  gtk_adjustment_set_value (stopframe_adjust, playback_stop);
+  g_signal_handlers_unblock_by_func (startframe_adjust,
+                                     G_CALLBACK (endframe_changed),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (stopframe_adjust,
+                                     G_CALLBACK (endframe_changed),
+                                     dialog);
+}
+
+static void
+framerate_changed (Animation        *animation,
+                   gdouble           fps,
+                   AnimationDialog  *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar                  *text;
+
+  g_signal_handlers_block_by_func (priv->fpscombo, 
+                                   G_CALLBACK (fpscombo_changed),
+                                   dialog);
+  g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                   G_CALLBACK (fpscombo_activated),
+                                   dialog);
+  text = g_strdup_printf  (_("%g fps"), fps);
+  gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))), text);
+  g_signal_handlers_unblock_by_func (priv->fpscombo, 
+                                     G_CALLBACK (fpscombo_changed),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fpscombo))),
+                                     G_CALLBACK (fpscombo_activated),
+                                     dialog);
+}
+
+static void
+disposal_changed (Animation        *animation,
+                  gint              disposal,
+                  AnimationDialog  *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  g_signal_handlers_block_by_func (priv->disposalcombo,
+                                   G_CALLBACK (disposalcombo_changed),
+                                   dialog);
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->disposalcombo), disposal);
+  g_signal_handlers_unblock_by_func (priv->disposalcombo,
+                                     G_CALLBACK (disposalcombo_changed),
+                                     dialog);
+}
+
+/* Rendering Functions */
+
+static gboolean
+repaint_da (GtkWidget       *darea,
+            GdkEventExpose  *event,
+            AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkStyle *style = gtk_widget_get_style (darea);
+  guchar   *da_data;
+  gint      da_width;
+  gint      da_height;
+  gint      preview_width, preview_height;
+
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+
+  if (darea == priv->drawing_area)
+    {
+      da_width  = priv->drawing_area_width;
+      da_height = priv->drawing_area_height;
+      da_data   = priv->drawing_area_data;
+    }
+  else
+    {
+      da_width  = priv->shape_drawing_area_width;
+      da_height = priv->shape_drawing_area_height;
+      da_data   = priv->shape_drawing_area_data;
+    }
+
+  gdk_draw_rgb_image (gtk_widget_get_window (darea),
+                      style->white_gc,
+                      (gint) ((da_width - priv->zoom * preview_width) / 2),
+                      (gint) ((da_height - priv->zoom * preview_height) / 2),
+                      da_width, da_height,
+                      (animation_get_length (priv->animation) == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
+                      da_data, da_width * 3);
+
+  return TRUE;
+}
+
+static gboolean
+da_button_press (GtkWidget       *widget,
+                 GdkEventButton  *event,
+                 AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  if (gdk_event_triggers_context_menu ((GdkEvent *) event))
+    {
+      GtkWidget *menu = gtk_ui_manager_get_widget (priv->ui_manager, "/anim-play-popup");
+
+      gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
+      gtk_menu_popup (GTK_MENU (menu),
+                      NULL, NULL, NULL, NULL,
+                      event->button,
+                      event->time);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/*
+ * Update the actual drawing area metrics, which may be different from
+ * requested, since there is no full control of the WM.
+ */
+static void
+da_size_callback (GtkWidget       *drawing_area,
+                  GtkAllocation   *allocation,
+                  AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  guchar **drawing_data;
+  gint     preview_width, preview_height;
+
+  if (drawing_area == priv->shape_drawing_area)
+    {
+      if (allocation->width  == priv->shape_drawing_area_width &&
+          allocation->height == priv->shape_drawing_area_height)
+        return;
+
+      priv->shape_drawing_area_width  = allocation->width;
+      priv->shape_drawing_area_height = allocation->height;
+
+      g_free (priv->shape_drawing_area_data);
+      drawing_data = &priv->shape_drawing_area_data;
+    }
+  else
+    {
+      if (allocation->width  == priv->drawing_area_width &&
+          allocation->height == priv->drawing_area_height)
+        return;
+
+      priv->drawing_area_width  = allocation->width;
+      priv->drawing_area_height = allocation->height;
+
+      g_free (priv->drawing_area_data);
+      drawing_data = &priv->drawing_area_data;
+    }
+
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+  priv->zoom = MIN ((gdouble) allocation->width / (gdouble) preview_width,
+                                (gdouble) allocation->height / (gdouble) preview_height);
+
+  *drawing_data = g_malloc (allocation->width * allocation->height * 3);
+
+  if (is_detached (dialog) &&
+      drawing_area == priv->drawing_area)
+    {
+      /* Set "alpha grid" background. */
+      total_alpha_preview (priv->drawing_area_data,
+                           allocation->width,
+                           allocation->height);
+      repaint_da (priv->drawing_area, NULL, dialog);
+    }
+  else 
+    {
+      /* Update the zoom information. */
+      AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+      GtkEntry *zoomcombo_text_child;
+
+      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->zoomcombo)));
+      if (zoomcombo_text_child)
+        {
+          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), priv->zoom * 100.0);
+
+          gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
+          g_free (new_entry_text);
+        }
+
+      /* As we re-allocated the drawn data, let's render it again. */
+      if (animation_loaded (priv->animation))
+        {
+          if (is_detached (dialog) && ! gtk_widget_get_realized (drawing_area))
+            {
+              /* Render will crash if the drawing are is not realized yet.
+               * So I connect a handler to render on realization. */
+              g_signal_connect (drawing_area, "realize",
+                                G_CALLBACK (render_on_realize),
+                                dialog);
+            }
+          else
+            {
+              GeglBuffer *buffer;
+
+              buffer = animation_get_frame (priv->animation,
+                                            animation_get_position (priv->animation));
+              render_frame (dialog, buffer);
+            }
+        }
+    }
+}
+
+static gboolean
+shape_pressed (GtkWidget           *widget,
+               GdkEventButton      *event,
+               AnimationDialog *dialog)
+{
+  if (da_button_press (widget, event, dialog))
+    return TRUE;
+
+  /* ignore double and triple click */
+  if (event->type == GDK_BUTTON_PRESS)
+    {
+      CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset");
+
+      if (!p)
+        return FALSE;
+
+      p->x = (gint) event->x;
+      p->y = (gint) event->y;
+
+      gtk_grab_add (widget);
+      gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
+                        GDK_BUTTON_RELEASE_MASK |
+                        GDK_BUTTON_MOTION_MASK  |
+                        GDK_POINTER_MOTION_HINT_MASK,
+                        NULL, NULL, 0);
+      gdk_window_raise (gtk_widget_get_window (widget));
+    }
+
+  return FALSE;
+}
+
+static gboolean
+shape_released (GtkWidget *widget)
+{
+  gtk_grab_remove (widget);
+  gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0);
+  gdk_flush ();
+
+  return FALSE;
+}
+
+static gboolean
+shape_motion (GtkWidget      *widget,
+              GdkEventMotion *event)
+{
+  GdkModifierType  mask;
+  gint             xp, yp;
+  GdkWindow       *root_win;
+
+  root_win = gdk_get_default_root_window ();
+  gdk_window_get_pointer (root_win, &xp, &yp, &mask);
+
+  /* if a button is still held by the time we process this event... */
+  if (mask & GDK_BUTTON1_MASK)
+    {
+      CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset");
+
+      if (!p)
+        return FALSE;
+
+      gtk_window_move (GTK_WINDOW (widget), xp  - p->x, yp  - p->y);
+    }
+  else /* the user has released all buttons */
+    {
+      shape_released (widget);
+    }
+
+  return FALSE;
+}
+
+static void
+render_callback (Animation       *animation,
+                 gint             frame_number,
+                 GeglBuffer      *buffer,
+                 AnimationDialog *dialog)
+{
+  render_frame (dialog, buffer);
+
+  /* Update UI. */
+  show_playing_progress (dialog);
+}
+
+static void
+render_on_realize (GtkWidget       *drawing_area,
+                   AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GeglBuffer *buffer;
+
+  buffer = animation_get_frame (priv->animation,
+                                animation_get_position (priv->animation));
+  render_frame (dialog, buffer);
+
+  g_signal_handlers_disconnect_by_func (drawing_area,
+                                        G_CALLBACK (render_on_realize),
+                                        dialog);
+}
+
+static void
+render_frame (AnimationDialog *dialog,
+                GeglBuffer      *buffer)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  static gchar  *shape_preview_mask      = NULL;
+  static guint   shape_preview_mask_size = 0;
+  static guchar *rawframe                = NULL;
+  static guint   rawframe_size           = 0;
+  gint           i, j, k;
+  guchar        *srcptr;
+  guchar        *destptr;
+  GtkWidget     *da;
+  guint          drawing_width;
+  guint          drawing_height;
+  guchar        *preview_data;
+  gint           preview_width, preview_height;
+
+  /* If the animation returns a NULL buffer, it means the image to
+   * display hasn't changed. */
+  if (buffer == NULL || ! animation_loaded (priv->animation))
+    return;
+
+  g_assert (animation_get_length (priv->animation) < 1 ||
+            (animation_get_position (priv->animation) >= animation_get_playback_start (priv->animation) &&
+             animation_get_position (priv->animation) <= animation_get_playback_stop (priv->animation)));
+
+  if (is_detached (dialog))
+    {
+      da = priv->shape_drawing_area;
+      preview_data = priv->shape_drawing_area_data;
+      drawing_width = priv->shape_drawing_area_width;
+      drawing_height = priv->shape_drawing_area_height;
+
+      if (animation_get_length (priv->animation) < 1)
+        total_alpha_preview (preview_data, drawing_width, drawing_height);
+    }
+  else
+    {
+      da = priv->drawing_area;
+      preview_data = priv->drawing_area_data;
+      drawing_width = priv->drawing_area_width;
+      drawing_height = priv->drawing_area_height;
+
+      /* Set "alpha grid" background. */
+      total_alpha_preview (preview_data, drawing_width, drawing_height);
+    }
+
+  /* When there is no frame to show, we simply display the alpha background and return. */
+  if (animation_get_length (priv->animation) > 0)
+    {
+      /* Update the rawframe. */
+      if (rawframe_size < drawing_width * drawing_height * 4)
+        {
+          rawframe_size = drawing_width * drawing_height * 4;
+          g_free (rawframe);
+          rawframe = g_malloc (rawframe_size);
+        }
+
+      /* Fetch and scale the whole raw new frame */
+      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
+                       priv->zoom, babl_format ("R'G'B'A u8"),
+                       rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+      /* Number of pixels. */
+      i = drawing_width * drawing_height;
+      destptr = preview_data;
+      srcptr  = rawframe;
+      while (i--)
+        {
+          if (! (srcptr[3] & 128))
+            {
+              srcptr  += 4;
+              destptr += 3;
+              continue;
+            }
+
+          *(destptr++) = *(srcptr++);
+          *(destptr++) = *(srcptr++);
+          *(destptr++) = *(srcptr++);
+
+          srcptr++;
+        }
+
+      /* calculate the shape mask */
+      if (is_detached (dialog))
+        {
+          gint ideal_shape_size = (drawing_width * drawing_height) /
+                                   8 + 1 + drawing_height;
+
+          if (shape_preview_mask_size < ideal_shape_size)
+            {
+              shape_preview_mask_size = ideal_shape_size;
+              g_free (shape_preview_mask);
+              shape_preview_mask = g_malloc (ideal_shape_size);
+            }
+
+          memset (shape_preview_mask, 0,
+                  (drawing_width * drawing_height) / 8 + drawing_height);
+          srcptr = rawframe + 3;
+
+          for (j = 0; j < drawing_height; j++)
+            {
+              k = j * ((7 + drawing_width) / 8);
+
+              for (i = 0; i < drawing_width; i++)
+                {
+                  if ((*srcptr) & 128)
+                    shape_preview_mask[k + i/8] |= (1 << (i&7));
+
+                  srcptr += 4;
+                }
+            }
+          reshape_from_bitmap (dialog, shape_preview_mask);
+        }
+
+      /* clean up */
+      g_object_unref (buffer);
+    }
+
+  /* Display the preview buffer. */
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+  gdk_draw_rgb_image (gtk_widget_get_window (da),
+                      (gtk_widget_get_style (da))->white_gc,
+                      (gint) (((gint)drawing_width - priv->zoom * preview_width) / 2),
+                      (gint) (((gint)drawing_height - priv->zoom * preview_height) / 2),
+                      (gint)drawing_width, (gint)drawing_height,
+                      (animation_get_length (priv->animation) == 1 ?
+                       GDK_RGB_DITHER_MAX : DITHERTYPE),
+                      preview_data, drawing_width * 3);
+}
+
+static void
+reshape_from_bitmap (AnimationDialog *dialog,
+                     const gchar     *bitmap)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  static gchar *prev_bitmap = NULL;
+  static guint  prev_width = -1;
+  static guint  prev_height = -1;
+
+  if ((! prev_bitmap)                                  ||
+      prev_width != priv->shape_drawing_area_width   ||
+      prev_height != priv->shape_drawing_area_height ||
+      (memcmp (prev_bitmap, bitmap,
+               (priv->shape_drawing_area_width *
+                priv->shape_drawing_area_height) / 8 +
+               priv->shape_drawing_area_height)))
+    {
+      GdkBitmap *shape_mask;
+
+      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (priv->shape_window),
+                                                bitmap,
+                                                priv->shape_drawing_area_width, 
priv->shape_drawing_area_height);
+      gtk_widget_shape_combine_mask (priv->shape_window, shape_mask, 0, 0);
+      g_object_unref (shape_mask);
+
+      if (!prev_bitmap || prev_width != priv->shape_drawing_area_width || prev_height != 
priv->shape_drawing_area_height)
+        {
+          g_free(prev_bitmap);
+          prev_bitmap = g_malloc ((priv->shape_drawing_area_width * priv->shape_drawing_area_height) / 8 + 
priv->shape_drawing_area_height);
+          prev_width = priv->shape_drawing_area_width;
+          prev_height = priv->shape_drawing_area_height;
+        }
+
+      memcpy (prev_bitmap, bitmap, (priv->shape_drawing_area_width * priv->shape_drawing_area_height) / 8 + 
priv->shape_drawing_area_height);
+    }
+}
+
+static gboolean
+progress_button (GtkWidget       *widget,
+                 GdkEventButton  *event,
+                 AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(priv->startframe_spin));
+  GtkAdjustment *endframe_adjust   = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin));
+
+  /* ignore double and triple click */
+  if (event->type == GDK_BUTTON_PRESS)
+    {
+      GtkAllocation  allocation;
+      guint          goto_frame;
+
+      gtk_widget_get_allocation (widget, &allocation);
+
+      goto_frame = animation_get_first_frame (priv->animation) + (gint) (event->x / (allocation.width / 
animation_get_length (priv->animation)));
+
+      if (goto_frame < animation_get_playback_start (priv->animation))
+        gtk_adjustment_set_value (startframe_adjust, (gdouble) goto_frame);
+
+      if (goto_frame > animation_get_playback_stop (priv->animation))
+        gtk_adjustment_set_value (endframe_adjust, (gdouble) goto_frame);
+
+      if (goto_frame >= animation_get_first_frame (priv->animation) && goto_frame < 
animation_get_first_frame (priv->animation) + animation_get_length (priv->animation))
+        {
+          animation_jump (priv->animation, goto_frame);
+        }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+progress_entered (GtkWidget        *widget,
+                  GdkEventCrossing *event,
+                  AnimationDialog  *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkAllocation  allocation;
+  guint          goto_frame;
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  goto_frame = animation_get_first_frame (priv->animation) + (gint) (event->x / (allocation.width / 
animation_get_length (priv->animation)));
+
+  show_goto_progress (goto_frame, dialog);
+
+  return FALSE;
+}
+
+static gboolean
+progress_motion (GtkWidget       *widget,
+                 GdkEventMotion  *event,
+                 AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkAllocation  allocation;
+  guint          goto_frame;
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  goto_frame = animation_get_first_frame (priv->animation) + (gint) (event->x / (allocation.width / 
animation_get_length (priv->animation)));
+
+  show_goto_progress (goto_frame, dialog);
+
+  return FALSE;
+}
+
+static gboolean
+progress_left (GtkWidget        *widget,
+               GdkEventCrossing *event,
+               AnimationDialog  *dialog)
+{
+  show_playing_progress (dialog);
+
+  return FALSE;
+}
+
+static void
+show_goto_progress (gint             goto_frame,
+                    AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar         *text;
+
+  /* update the dialog's progress bar */
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progress),
+                                 ((gfloat) (goto_frame - animation_get_first_frame (priv->animation)) /
+                                  (gfloat) (animation_get_length (priv->animation) - 0.999)));
+
+  if (animation_get_disposal (priv->animation) != DISPOSE_TAGS ||
+      animation_get_first_frame (priv->animation) == 1)
+    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, animation_get_length (priv->animation));
+  else
+    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - animation_get_first_frame 
(priv->animation) + 1, goto_frame, animation_get_length (priv->animation));
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progress), text);
+  g_free (text);
+}
+
+static void
+show_playing_progress (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar *text;
+
+  /* update the dialog's progress bar */
+
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progress),
+                                 ((gfloat) (animation_get_position (priv->animation) - 
animation_get_first_frame (priv->animation)) /
+                                  (gfloat) (animation_get_length (priv->animation) - 0.999)));
+
+  if (animation_get_disposal (priv->animation) != DISPOSE_TAGS || animation_get_first_frame 
(priv->animation) == 1)
+    {
+      text = g_strdup_printf (_("Frame %d of %d"),
+                              animation_get_position (priv->animation),
+                              animation_get_length (priv->animation));
+    }
+  else
+    {
+      text = g_strdup_printf (_("Frame %d (%d) of %d"),
+                              animation_get_position (priv->animation) - animation_get_first_frame 
(priv->animation) + 1,
+                              animation_get_position (priv->animation), animation_get_length 
(priv->animation));
+    }
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progress), text);
+  g_free (text);
+}
+
+static gboolean
+is_playing (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  return (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (priv->play_actions, 
"play"))));
+}
+
+static gboolean
+is_detached (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  return (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (priv->view_actions, 
"detach"))));
+}
+
+static void
+play_pause (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+  gtk_action_activate (gtk_action_group_get_action (priv->play_actions, "play"));
+}
+
+/* get_fps:
+ * Frame rate proposed as default.
+ * These are common framerates.
+ */
+static gdouble
+get_fps (gint       index)
+{
+  gdouble fps;
+
+  switch (index)
+    {
+    case 0:
+      fps = 12.0;
+      break;
+    case 1:
+      fps = 24.0;
+      break;
+    case 2:
+      fps = 25.0;
+      break;
+    case 3:
+      fps = 30.0;
+      break;
+    case 4:
+      fps = 48.0;
+      break;
+    default:
+      fps = 24.0;
+      break;
+    }
+
+  return fps;
+}
+
+/* Returns the zoom selected in the UI. */
+static gdouble
+get_zoom (AnimationDialog *dialog,
+          gint             index)
+{
+  switch (index)
+    {
+    case 0:
+      return 0.51;
+    case 1:
+      return 1.0;
+    case 2:
+      return 1.25;
+    case 3:
+      return 1.5;
+    case 4:
+      return 2.0;
+    default:
+      {
+        AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+
+        /* likely -1 returned if there is no active item from the list.
+         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
+        gchar   *active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (priv->zoomcombo));
+        gdouble  zoom        = g_strtod (active_text, NULL);
+
+        /* Negative scales are inconsistent. And we want to avoid huge scaling. */
+        if (zoom > 300.0)
+          zoom = 300.0;
+        else if (zoom <= 50.0)
+          /* FIXME: scales under 0.5 are broken. See bug 690265. */
+          zoom = 50.1;
+        g_free (active_text);
+        return zoom / 100.0;
+      }
+    }
+}
+
+static void
+update_scale (AnimationDialog *dialog,
+              gdouble          scale)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  guint expected_drawing_area_width;
+  guint expected_drawing_area_height;
+  gint  preview_width, preview_height;
+
+  /* FIXME: scales under 0.5 are broken. See bug 690265. */
+  if (scale <= 0.5)
+    scale = 0.51;
+
+  animation_get_size (priv->animation, &preview_width, &preview_height);
+
+  expected_drawing_area_width  = preview_width * scale;
+  expected_drawing_area_height = preview_height * scale;
+
+  /* We don't update the scale directly because this might
+   * end up not being the real scale. Instead we request this size for
+   * the drawing areas, and the actual scale update will be done on the
+   * callback when size is actually allocated. */
+  gtk_widget_set_size_request (priv->drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+  gtk_widget_set_size_request (priv->shape_drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+
+  /* I force the shape window to a smaller size if we scale down. */
+  if (is_detached (dialog))
+    {
+      gint x, y;
+
+      gdk_window_get_origin (gtk_widget_get_window (priv->shape_window), &x, &y);
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (priv->shape_window));
+      gtk_window_move (GTK_WINDOW (priv->shape_window), x, y);
+    }
+}
diff --git a/plug-ins/animation-play/animation-dialog.h b/plug-ins/animation-play/animation-dialog.h
new file mode 100755
index 0000000..6fa08d4
--- /dev/null
+++ b/plug-ins/animation-play/animation-dialog.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-dialog.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ANIMATION_DIALOG_H__
+#define __ANIMATION_DIALOG_H__
+
+#define ANIMATION_TYPE_DIALOG            (animation_dialog_get_type ())
+#define ANIMATION_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_DIALOG, 
AnimationDialog))
+#define ANIMATION_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_DIALOG, 
AnimationDialogClass))
+#define ANIMATION_IS_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_DIALOG))
+#define ANIMATION_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_DIALOG))
+#define ANIMATION_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_DIALOG, 
AnimationDialogClass))
+
+typedef struct _AnimationDialog      AnimationDialog;
+typedef struct _AnimationDialogClass AnimationDialogClass;
+
+struct _AnimationDialog
+{
+  GtkWindow       parent_instance;
+};
+
+struct _AnimationDialogClass
+{
+  GtkWindowClass  parent_class;
+};
+
+GType       animation_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget * animation_dialog_new      (Animation *animation);
+
+#endif  /*  __ANIMATION_DIALOG_H__  */
diff --git a/plug-ins/animation-play/animation-play.c b/plug-ins/animation-play/animation-play.c
index 664b8ab..f5d5b17 100644
--- a/plug-ins/animation-play/animation-play.c
+++ b/plug-ins/animation-play/animation-play.c
@@ -3,7 +3,7 @@
  *
  * (c) Adam D. Moss : 1997-2000 : adam gimp org : adam foxbox org
  * (c) Mircea Purdea : 2009 : someone_else exhalus net
- * (c) Jehan : 2012-2014 : jehan at girinstud.io
+ * (c) Jehan : 2012-2015 : jehan at girinstud.io
  *
  * GIMP - The GNU Image Manipulation Program
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
@@ -32,278 +32,29 @@
 
 #include "libgimp/stdplugins-intl.h"
 
-#define PLUG_IN_PROC   "plug-in-animationplay"
-#define PLUG_IN_BINARY "animation-play"
-#define PLUG_IN_ROLE   "gimp-animation-play"
+#include "animation-utils.h"
+#include "animation.h"
+#include "animation-dialog.h"
 
-#define DITHERTYPE     GDK_RGB_DITHER_NORMAL
-
-#define MAX_FRAMERATE  300.0
-
-/* Some regexp used to parse layer tags.
- * A little ugly to have these here, but since the regexp will be
- * used many times, it is more efficient to store them globally .*/
-static const GRegex *nospace_reg;
-static const GRegex *layers_reg;
-static const GRegex *all_reg;
-
-typedef enum
-{
-  DISPOSE_COMBINE   = 0x00,
-  DISPOSE_REPLACE   = 0x01,
-  DISPOSE_TAGS      = 0x02,
-} DisposeType;
-
-typedef struct
-{
-  gint32  drawable_id;
-  GList  *indexes;
-  GList  *updated_indexes;
+static void        query                     (void);
+static void        run                       (const gchar      *name,
+                                              gint              nparams,
+                                              const GimpParam  *param,
+                                              gint             *nreturn_vals,
+                                              GimpParam       **return_vals);
 
-  guint  duration;
-}
-Frame;
+/* Utils */
+static void        save_settings             (Animation        *animation,
+                                              gint32            image_id);
 
+/* Settings we cache assuming they may be the user's
+ * favorite, like a framerate, or a type of frame disposal. */
 typedef struct
 {
-  /* Settings saved as parasites. */
-  DisposeType frame_disposal;
-  gdouble     frame_rate;
-
-  /* Animation: the frame_min and frame_max are the first and last set
-   * frames, whereas the (start|end)_frame are the range for playback.
-   * These settings are computed at startup, and not saved. */
-  guint       current_frame;
-  gint32      num_frames;
-  guint       frame_min;
-  guint       frame_max;
-  guint       start_frame;
-  guint       end_frame;
-
-  gdouble     scale; /* Zoom. 1.0 is 100%. */
+  DisposeType disposal;
+  gdouble     framerate;
 }
-AnimationSettings;
-
-typedef struct
-{
-  /* Image */
-  gint32             image_id;
-
-  /* GUI */
-  GtkWidget         *window;
-  GtkWidget         *shape_window;
-
-  GtkWidget         *play_bar;
-  GtkWidget         *progress_bar;
-  GtkWidget         *settings_bar;
-  GtkWidget         *view_bar;
-  GtkWidget         *refresh;
-
-  GtkWidget         *fpscombo;
-  GtkWidget         *zoomcombo;
-  GtkWidget         *proxycombo;
-
-  GtkWidget         *progress;
-
-  GtkWidget         *startframe_spin;
-  GtkWidget         *endframe_spin;
-
-  GtkWidget         *drawing_area;
-  guchar            *drawing_area_data;
-  guint              drawing_area_width;
-  guint              drawing_area_height;
-
-  GtkWidget         *shape_drawing_area;
-  guchar            *shape_drawing_area_data;
-  guint              shape_drawing_area_width;
-  guint              shape_drawing_area_height;
-
-  guint              preview_width;
-  guint              preview_height;
-
-  /* Animations */
-  AnimationSettings  settings;
-  /* We don't associate the frames to the AnimationSettings, but to the
-   * dialog box, because it takes quite a bit of memory. So we want to
-   * allocate only the currently running drawables. */
-  Frame            **frames;
-  /* Since the application is single-threaded, a boolean is enough.
-   * It may become a mutex in the future with multi-thread support. */
-  gboolean           frames_lock;
-
-  /* Actions */
-  GtkUIManager      *ui_manager;
-
-  GtkActionGroup    *play_actions;
-  GtkActionGroup    *settings_actions;
-  GtkActionGroup    *view_actions;
-  GtkActionGroup    *various_actions;
-}
-AnimationPlayDialog;
-
-/* for shaping */
-typedef struct
-{
-  gint x, y;
-} CursorOffset;
-
-static void        query                     (void);
-static void        run                       (const gchar          *name,
-                                              gint                  nparams,
-                                              const GimpParam      *param,
-                                              gint                 *nreturn_vals,
-                                              GimpParam           **return_vals);
-
-/* Initialization. */
-static void        initialize                (AnimationPlayDialog  *dialog);
-static void        init_frame_numbers        (AnimationPlayDialog  *dialog,
-                                              gint                 *layers,
-                                              gint                  num_layers);
-static gboolean    init_frames               (AnimationPlayDialog  *dialog);
-static void        rec_init_frames           (AnimationPlayDialog  *dialog,
-                                              gint32                frames_image_id,
-                                              gint32                layer,
-                                              GList                *previous_frames,
-                                              gint                  image_width,
-                                              gint                  image_height);
-static void        init_ui                   (AnimationPlayDialog  *dialog,
-                                              gchar                *imagename);
-static GtkUIManager * ui_manager_new         (AnimationPlayDialog  *dialog);
-
-static void        refresh_dialog            (AnimationPlayDialog  *dialog);
-static void        update_ui_sensitivity     (AnimationPlayDialog  *dialog);
-
-/* All callbacks. */
-static void        close_callback            (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        help_callback             (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        window_destroy            (GtkWidget            *widget,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    popup_menu                (GtkWidget            *widget,
-                                              AnimationPlayDialog  *dialog);
-
-static gboolean    adjustment_pressed        (GtkWidget            *widget,
-                                              GdkEventButton       *event,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    da_button_press           (GtkWidget            *widget,
-                                              GdkEventButton       *event,
-                                              AnimationPlayDialog  *dialog);
-static void        da_size_callback          (GtkWidget            *widget,
-                                              GtkAllocation        *allocation,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    shape_pressed             (GtkWidget            *widget,
-                                              GdkEventButton       *event,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    shape_released            (GtkWidget            *widget);
-static gboolean    shape_motion              (GtkWidget            *widget,
-                                              GdkEventMotion       *event);
-static gboolean    repaint_da                (GtkWidget            *darea,
-                                              GdkEventExpose       *event,
-                                              AnimationPlayDialog  *dialog);
-
-static gboolean    progress_button           (GtkWidget            *widget,
-                                              GdkEventButton       *event,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    progress_entered          (GtkWidget            *widget,
-                                              GdkEventCrossing     *event,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    progress_motion           (GtkWidget            *widget,
-                                              GdkEventMotion       *event,
-                                              AnimationPlayDialog  *dialog);
-static gboolean    progress_left             (GtkWidget            *widget,
-                                              GdkEventCrossing     *event,
-                                              AnimationPlayDialog  *dialog);
-
-static void        detach_callback           (GtkToggleAction      *action,
-                                              AnimationPlayDialog  *dialog);
-static void        play_callback             (GtkToggleAction      *action,
-                                              AnimationPlayDialog  *dialog);
-static void        step_back_callback        (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        step_callback             (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        rewind_callback           (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        refresh_callback          (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        zoom_in_callback          (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        zoom_out_callback         (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        zoom_reset_callback       (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        speed_up_callback         (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        speed_down_callback       (GtkAction            *action,
-                                              AnimationPlayDialog  *dialog);
-static void        framecombo_changed        (GtkWidget             *combo,
-                                              AnimationPlayDialog  *dialog);
-static void        fpscombo_activated        (GtkEntry            *combo,
-                                              AnimationPlayDialog *dialog);
-static void        fpscombo_changed          (GtkWidget             *combo,
-                                              AnimationPlayDialog  *dialog);
-static void        zoomcombo_activated       (GtkEntry             *combo,
-                                              AnimationPlayDialog  *dialog);
-static void        zoomcombo_changed         (GtkWidget            *combo,
-                                              AnimationPlayDialog  *dialog);
-static void        startframe_changed        (GtkAdjustment        *adjustment,
-                                              AnimationPlayDialog  *dialog);
-static void        endframe_changed          (GtkAdjustment        *adjustment,
-                                              AnimationPlayDialog  *dialog);
-static void        proxy_checkbox_expanded   (GtkExpander          *expander,
-                                              GParamSpec           *param_spec,
-                                              AnimationPlayDialog  *dialog);
-static void        proxycombo_activated      (GtkEntry             *combo_entry,
-                                              AnimationPlayDialog  *dialog);
-static void        proxycombo_changed        (GtkWidget            *combo,
-                                              AnimationPlayDialog  *dialog);
-
-/* Rendering/Playing Functions */
-static void        render_frame              (AnimationPlayDialog  *dialog,
-                                              gboolean              force);
-static void        total_alpha_preview       (guchar               *drawing_data,
-                                              guint                 drawing_width,
-                                              guint                 drawing_height);
-static void        reshape_from_bitmap       (AnimationPlayDialog   *dialog,
-                                              const gchar           *bitmap);
-static void        show_playing_progress     (AnimationPlayDialog  *dialog);
-static void        show_loading_progress     (AnimationPlayDialog  *dialog,
-                                              gint                  layer_nb,
-                                              gint                  num_layers);
-static void        do_back_step              (AnimationPlayDialog  *dialog);
-static void        do_step                   (AnimationPlayDialog  *dialog);
-static void        set_timer                 (guint                 new_timer);
-static gboolean    advance_frame_callback    (AnimationPlayDialog  *dialog);
-static void        show_goto_progress        (guint                 frame_nb,
-                                              AnimationPlayDialog  *dialog);
-
-/* Utils */
-static void        connect_accelerators      (GtkUIManager         *ui_manager,
-                                              GtkActionGroup       *group);
-static gdouble     get_fps                   (gint                  index);
-static gdouble     get_proxy                 (AnimationPlayDialog  *dialog,
-                                              gint                  index);
-static void        update_scale              (AnimationPlayDialog  *dialog,
-                                              gdouble               scale);
-static gdouble     get_scale                 (AnimationPlayDialog  *dialog,
-                                              gint                  index);
-static void        save_settings             (AnimationPlayDialog  *dialog);
-static void        clean_exit                (AnimationPlayDialog *dialog);
-
-/* tag util functions*/
-static gboolean    is_disposal_tag           (const gchar          *str,
-                                              DisposeType          *disposal,
-                                              gint                 *taglength);
-static DisposeType parse_disposal_tag        (AnimationPlayDialog  *dialog,
-                                              const gchar          *str);
-static gboolean    is_ms_tag                 (const gchar          *str,
-                                              gint                 *duration,
-                                              gint                 *taglength);
-static gint        parse_ms_tag              (const gchar          *str);
-static void        rec_set_total_frames      (const gint32          layer,
-                                              gint                 *min,
-                                              gint                 *max);
+CachedSettings;
 
 const GimpPlugInInfo PLUG_IN_INFO =
 {
@@ -326,7 +77,7 @@ query (void)
   };
 
   gimp_install_procedure (PLUG_IN_PROC,
-                          N_("Preview a GIMP animation"),
+                          N_("Preview an animation"),
                           "",
                           "Adam D. Moss <adam gimp org>",
                           "Adam D. Moss <adam gimp org>",
@@ -367,2982 +118,142 @@ run (const gchar      *name,
 
   run_mode = param[0].data.d_int32;
 
- if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
-   {
-     /* This plugin is meaningless right now other than interactive. */
-     status = GIMP_PDB_CALLING_ERROR;
-   }
- else
-   {
-     AnimationPlayDialog *dialog;
-     GimpParasite        *parasite;
-     gint                 image_width;
-     gint                 image_height;
-
-     /********************************************/
-     /* Init the global variable: frame parsing. */
-     nospace_reg = g_regex_new("[ \t]*", G_REGEX_OPTIMIZE, 0, NULL);
-     layers_reg  = g_regex_new("\\[(([0-9]+(-[0-9]+)?)(,[0-9]+(-[0-9]+)?)*)\\]", G_REGEX_OPTIMIZE, 0, NULL);
-     all_reg     = g_regex_new("\\[\\*\\]", G_REGEX_OPTIMIZE, 0, NULL);
-
-     /**********************************************************************/
-     /* Now init the struct for the animation, which is its current state. */
-     dialog = g_new (AnimationPlayDialog, 1);
-
-     dialog->image_id    = param[1].data.d_image;
-     image_width  = gimp_image_width (dialog->image_id);
-     image_height = gimp_image_height (dialog->image_id);
-
-     dialog->frames      = NULL;
-     dialog->frames_lock = FALSE;
-
-      /* We default at full preview size. */
-     dialog->preview_width  = image_width;
-     dialog->preview_height = image_height;
-
-     /* Acceptable settings for the default. */
-     dialog->settings.frame_disposal = DISPOSE_COMBINE;
-     dialog->settings.frame_rate     = 24.0; /* fps */
-
-     /* Not yet initialized animation. */
-     dialog->settings.current_frame = 0;
-     dialog->settings.num_frames    = 0;
-     dialog->settings.frame_min     = 0;
-     dialog->settings.frame_max     = 0;
-     dialog->settings.start_frame   = 0;
-     dialog->settings.end_frame     = 0;
-
-     /* If we saved any settings globally, use the one from the last run. */
-     gimp_get_data (PLUG_IN_PROC, &dialog->settings);
-
-     /* If this image has specific settings already, override the global ones. */
-     parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-disposal");
-     if (parasite)
-       {
-         const DisposeType *mode = gimp_parasite_data (parasite);
-
-         dialog->settings.frame_disposal = *mode;
-         gimp_parasite_free (parasite);
-       }
-     parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-rate");
-     if (parasite)
-       {
-         const gdouble *rate = gimp_parasite_data (parasite);
-
-         dialog->settings.frame_rate = *rate;
-         gimp_parasite_free (parasite);
-       }
-
-     /* UI setup. */
-     dialog->window                    = NULL;
-
-     dialog->play_bar                  = NULL;
-     dialog->progress_bar              = NULL;
-     dialog->settings_bar              = NULL;
-     dialog->view_bar                  = NULL;
-     dialog->refresh                   = NULL;
-
-     dialog->fpscombo                  = NULL;
-     dialog->zoomcombo                 = NULL;
-     dialog->proxycombo                = NULL;
-
-     dialog->ui_manager                = NULL;
-     dialog->progress                  = NULL;
-     dialog->startframe_spin           = NULL;
-     dialog->endframe_spin             = NULL;
-     dialog->shape_window              = NULL;
-     dialog->shape_drawing_area        = NULL;
-     dialog->shape_drawing_area_data   = NULL;
-     dialog->drawing_area              = NULL;
-     dialog->drawing_area_data         = NULL;
-     dialog->drawing_area_width        = -1;
-     dialog->drawing_area_height       = -1;
-     dialog->shape_drawing_area_width  = -1;
-     dialog->shape_drawing_area_height = -1;
-
-     initialize (dialog);
-
-     gtk_main ();
-     gimp_set_data (PLUG_IN_PROC, &dialog->settings, sizeof (dialog->settings));
-
-     if (run_mode != GIMP_RUN_NONINTERACTIVE)
-       gimp_displays_flush ();
-   }
-
-  values[0].type = GIMP_PDB_STATUS;
-  values[0].data.d_status = status;
-
-  gegl_exit ();
-}
-
-static void
-initialize (AnimationPlayDialog *dialog)
-{
-  /* Freeing existing data after a refresh. */
-  /* Catch the case when the user has closed the image in the meantime. */
-  if (! gimp_image_is_valid (dialog->image_id))
-    {
-      gimp_message (_("Invalid image. Did you close it?"));
-      clean_exit (dialog);
-      return;
-    }
-
-  if (! dialog->window)
-    {
-      /* First run. */
-      gchar *name = gimp_image_get_name (dialog->image_id);
-      init_ui (dialog, name);
-      g_free (name);
-    }
-  refresh_dialog (dialog);
-
-  /* I want to make sure the progress bar is realized before init_frames()
-   * which may take quite a bit of time. */
-  if (!gtk_widget_get_realized (dialog->progress))
-    gtk_widget_realize (dialog->progress);
-
-  render_frame (dialog, init_frames (dialog));
-}
-
-/**
- * Set num_frames, which is not necessarily the number of layers, in
- * particular with the DISPOSE_TAGS disposal.
- * Will set 0 if no layer has frame tags in tags mode, or if there is
- * no layer in combine/replace.
- */
-static void
-init_frame_numbers (AnimationPlayDialog *dialog,
-                    gint                *layers,
-                    gint                 num_layers)
-{
-  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
-  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
-  gboolean       start_from_first = FALSE;
-  gboolean       end_at_last      = FALSE;
-
-  /* As a special exception, when we start or end at first or last frames,
-   * we want to stay that way, even with different first and last frames. */
-  if (dialog->settings.start_frame == dialog->settings.frame_min)
-    start_from_first = TRUE;
-  if (dialog->settings.end_frame == dialog->settings.frame_max)
-    end_at_last = TRUE;
-
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS)
-    {
-      dialog->settings.num_frames = num_layers;
-      dialog->settings.frame_min = 1;
-      dialog->settings.frame_max = dialog->settings.frame_min + dialog->settings.num_frames - 1;
-    }
-  else
-    {
-      gint i;
-      gint max = G_MININT;
-      gint min = G_MAXINT;
-
-      for (i = 0; i < num_layers; i++)
-        rec_set_total_frames (layers[i], &min, &max);
-
-      dialog->settings.num_frames = (max > min)? max + 1 - min : 0;
-      dialog->settings.frame_min = min;
-      dialog->settings.frame_max = max;
-    }
-
-  /* Keep the same frame number, unless it is now invalid. */
-  if (dialog->settings.current_frame > dialog->settings.end_frame ||
-      dialog->settings.current_frame < dialog->settings.start_frame)
-    {
-      dialog->settings.current_frame = dialog->settings.start_frame;
-    }
-
-  /* Update frame counting UI widgets. */
-  if (startframe_adjust)
-    {
-      dialog->settings.start_frame = gtk_adjustment_get_value (startframe_adjust);
-      if (start_from_first                        ||
-          dialog->settings.start_frame < dialog->settings.frame_min ||
-          dialog->settings.start_frame > dialog->settings.frame_max)
-        {
-          dialog->settings.start_frame = dialog->settings.frame_min;
-        }
-    }
-  else
-    {
-      dialog->settings.start_frame = dialog->settings.frame_min;
-    }
-
-  if (endframe_adjust)
-    {
-      dialog->settings.end_frame = gtk_adjustment_get_value (endframe_adjust);
-      if (end_at_last                           ||
-          dialog->settings.end_frame < dialog->settings.frame_min ||
-          dialog->settings.end_frame > dialog->settings.frame_max ||
-          dialog->settings.end_frame < dialog->settings.start_frame)
-        {
-          dialog->settings.end_frame = dialog->settings.frame_max;
-        }
-    }
-  else
-    {
-      dialog->settings.end_frame = dialog->settings.frame_max;
-    }
-}
-
-/* Initialize the frames, and return TRUE if render must be forced. */
-static gboolean
-init_frames (AnimationPlayDialog *dialog)
-{
-  /* Frames are associated to an unused image. */
-  static gint32    frames_image_id = 0;
-  /* We keep track of the frames in a separate structure to free drawable
-   * memory. We can't use easily dialog->frames because some of the
-   * drawables may be used in more than 1 frame. */
-  static GList    *previous_frames = NULL;
-
-  gint            *layers;
-  gint             num_layers;
-  gint             image_width;
-  gint             image_height;
-  gint32           new_frame, previous_frame, new_layer;
-  gint             duration = 0;
-  DisposeType      disposal = dialog->settings.frame_disposal;
-  gint             frame_spin_size;
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-
-  if (dialog->frames_lock)
-    {
-      return FALSE;
-    }
-  dialog->frames_lock = TRUE;
-
-  /* Block most UI during frame initialization. */
-  gtk_action_group_set_sensitive (dialog->play_actions, FALSE);
-  gtk_widget_set_sensitive (dialog->play_bar, FALSE);
-  gtk_widget_set_sensitive (dialog->progress_bar, FALSE);
-  gtk_action_group_set_sensitive (dialog->settings_actions, FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), FALSE);
-  gtk_action_group_set_sensitive (dialog->view_actions, FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->view_bar), FALSE);
-
-  /* Cleanup before re-generation. */
-  if (dialog->frames)
+  if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
     {
-      GList *idx;
-
-      gimp_image_delete (frames_image_id);
-      frames_image_id = 0;
-
-      /* Freeing previous frames only once. */
-      for (idx = g_list_first (previous_frames); idx != NULL; idx = g_list_next (idx))
-        {
-          Frame* frame = (Frame*) idx->data;
-
-          g_list_free (frame->indexes);
-          g_list_free (frame->updated_indexes);
-          g_free (frame);
-        }
-      g_list_free (previous_frames);
-      previous_frames = NULL;
-
-      g_free (dialog->frames);
-      dialog->frames = NULL;
-    }
-  if (! gimp_image_is_valid (dialog->image_id))
-    {
-      /* This is not necessarily an error. We may have simply wanted
-       * to clean up our GEGL buffers. */
-      return FALSE;
-    }
-
-  layers = gimp_image_get_layers (dialog->image_id, &num_layers);
-  init_frame_numbers (dialog, layers, num_layers);
-  if (dialog->settings.num_frames <= 0)
-    {
-      update_ui_sensitivity (dialog);
-      dialog->frames_lock = FALSE;
-      g_free (layers);
-      return FALSE;
-    }
-
-  dialog->frames = g_try_malloc0_n (dialog->settings.num_frames, sizeof (Frame*));
-  if (! dialog->frames)
-    {
-      dialog->frames_lock = FALSE;
-      gimp_message (_("Memory could not be allocated to the frame container."));
-      g_free (layers);
-      clean_exit (dialog);
-      return FALSE;
-    }
-
-  image_width  = gimp_image_width (dialog->image_id);
-  image_height = gimp_image_height (dialog->image_id);
-
-  /* We only use RGB images for display because indexed images would somehow
-     render terrible colors. Layers from other types will be automatically
-     converted. */
-  frames_image_id = gimp_image_new (dialog->preview_width, dialog->preview_height, GIMP_RGB);
-
-  /* Save processing time and memory by not saving history and merged frames. */
-  gimp_image_undo_disable (frames_image_id);
-
-  if (disposal == DISPOSE_TAGS)
-    {
-      gint        i;
-
-      for (i = 0; i < num_layers; i++)
-        {
-          show_loading_progress (dialog, i, num_layers);
-          rec_init_frames (dialog,
-                           frames_image_id,
-                           layers[num_layers - (i + 1)],
-                           previous_frames,
-                           image_width, image_height);
-        }
-
-      for (i = 0; i < dialog->settings.num_frames; i++)
-        {
-          /* If for some reason a frame is absent, use the previous one.
-           * We are ensured that there is at least a "first" frame for this. */
-          if (! dialog->frames[i])
-            {
-              dialog->frames[i] = dialog->frames[i - 1];
-              dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
-            }
-
-          /* A zero duration only means we use the global duration, whatever it is at the time. */
-          dialog->frames[i]->duration = 0;
-        }
+      /* This plugin is meaningless right now other than interactive. */
+      status = GIMP_PDB_CALLING_ERROR;
     }
   else
     {
-      gint   layer_offx;
-      gint   layer_offy;
-      gchar *layer_name;
-      gint   i;
-
-      for (i = 0; i < dialog->settings.num_frames; i++)
-        {
-          show_loading_progress (dialog, i, num_layers);
-
-          dialog->frames[i] = g_new (Frame, 1);
-          dialog->frames[i]->indexes = NULL;
-          dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
-          dialog->frames[i]->updated_indexes = NULL;
-
-          previous_frames = g_list_append (previous_frames, dialog->frames[i]);
+      Animation       *animation;
+      GtkWidget       *dialog;
+      GimpParasite    *parasite;
+      CachedSettings   cached_settings;
+      gint32           image_id;
 
-          layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
-          if (layer_name)
-            {
-              duration = parse_ms_tag (layer_name);
-              disposal = parse_disposal_tag (dialog, layer_name);
-              g_free (layer_name);
-            }
+      gimp_ui_init (PLUG_IN_BINARY, TRUE);
 
-          if (i > 0 && disposal != DISPOSE_REPLACE)
-            {
-              previous_frame = gimp_layer_copy (dialog->frames[i - 1]->drawable_id);
+      /***********************/
+      /* init the animation. */
+      image_id  = param[1].data.d_image;
+      animation = animation_new (image_id);
+      dialog    = animation_dialog_new (animation);
 
-              gimp_image_insert_layer (frames_image_id, previous_frame, 0, 0);
-              gimp_item_set_visible (previous_frame, TRUE);
-            }
+      gtk_widget_show_now (GTK_WIDGET (dialog));
 
-          new_layer = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)], frames_image_id);
+      gimp_help_connect (GTK_WIDGET (dialog), gimp_standard_help_func, PLUG_IN_PROC, NULL);
 
-          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
-          gimp_item_set_visible (new_layer, TRUE);
-          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_width) / image_width,
-                            (gimp_drawable_height (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_height) / image_height, FALSE);
-          gimp_drawable_offsets (layers[num_layers - (i + 1)], &layer_offx, &layer_offy);
-          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / image_width,
-                                  (layer_offy * (gint) dialog->preview_height) / image_height);
-          gimp_layer_resize_to_image_size (new_layer);
+      /* Acceptable default for cached settings. */
+      cached_settings.disposal  = DISPOSE_COMBINE;
+      cached_settings.framerate = 24.0;
 
-          if (gimp_item_is_group (new_layer))
-            {
-              gint    num_children;
-              gint32 *children;
-              gint    j;
+      /* If we saved any settings globally, use the one from the last run. */
+      gimp_get_data (PLUG_IN_PROC, &cached_settings);
 
-              /* I want to make all layers in the group visible, so that when I'll make
-               * the group visible too at render time, it will display everything in it. */
-              children = gimp_item_get_children (new_layer, &num_children);
-              for (j = 0; j < num_children; j++)
-                gimp_item_set_visible (children[j], TRUE);
-            }
-
-          new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
-          dialog->frames[i]->drawable_id = new_frame;
-          gimp_item_set_visible (new_frame, FALSE);
-
-          if (duration <= 0)
-            duration = 0;
-          dialog->frames[i]->duration = (guint) duration;
-        }
-    }
-
-  /* Update the UI. */
-  frame_spin_size = (gint) (log10 (dialog->settings.frame_max - (dialog->settings.frame_max % 10))) + 1;
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), frame_spin_size);
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), frame_spin_size);
-
-  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->startframe_spin)),
-                            dialog->settings.start_frame,
-                            dialog->settings.frame_min,
-                            dialog->settings.frame_max,
-                            1.0, 5.0, 0.0);
-  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin)),
-                            dialog->settings.end_frame,
-                            dialog->settings.start_frame,
-                            dialog->settings.frame_max,
-                            1.0, 5.0, 0.0);
-
-  update_ui_sensitivity (dialog);
-
-  dialog->frames_lock = FALSE;
-  g_free (layers);
-
-  return TRUE;
-}
-
-/**
- * Recursive call to generate frames in TAGS mode.
- **/
-static void
-rec_init_frames (AnimationPlayDialog *dialog,
-                 gint32               frames_image_id,
-                 gint32               layer,
-                 GList               *previous_frames,
-                 gint                 image_width,
-                 gint                 image_height)
-{
-  Frame        *empty_frame = NULL;
-  gchar        *layer_name;
-  gchar        *nospace_name;
-  GMatchInfo   *match_info;
-  gboolean      preview_quality;
-  gint32        new_layer;
-  gint          j, k;
-
-  if (gimp_item_is_group (layer))
-    {
-      gint    num_children;
-      gint32 *children;
-
-      children = gimp_item_get_children (layer, &num_children);
-      for (j = 0; j < num_children; j++)
-        rec_init_frames (dialog, frames_image_id,
-                         children[num_children - j - 1],
-                         previous_frames,
-                         image_width, image_height);
-
-      return;
-    }
-
-  layer_name = gimp_item_get_name (layer);
-  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
-  preview_quality = dialog->preview_width != image_width || dialog->preview_height != image_height;
-
-  if (g_regex_match (all_reg, nospace_name, 0, NULL))
-    {
-      for (j = 0; j < dialog->settings.num_frames; j++)
+      /* If this animation has specific settings already, override the global ones. */
+      parasite = gimp_image_get_parasite (image_id, PLUG_IN_PROC "/frame-disposal");
+      if (parasite)
         {
-          if (! dialog->frames[j])
-            {
-              if (! empty_frame)
-                {
-                  empty_frame = g_new (Frame, 1);
-                  empty_frame->indexes = NULL;
-                  empty_frame->updated_indexes = NULL;
-                  empty_frame->drawable_id = 0;
-
-                  previous_frames = g_list_append (previous_frames, empty_frame);
-                }
-
-              if (! g_list_find (empty_frame->updated_indexes, GINT_TO_POINTER (j)))
-                empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, GINT_TO_POINTER 
(j));
+          const DisposeType *mode = gimp_parasite_data (parasite);
 
-              dialog->frames[j] = empty_frame;
-            }
-          else if (! g_list_find (dialog->frames[j]->updated_indexes, GINT_TO_POINTER (j)))
-            dialog->frames[j]->updated_indexes = g_list_append (dialog->frames[j]->updated_indexes, 
GINT_TO_POINTER (j));
+          cached_settings.disposal = *mode;
+          gimp_parasite_free (parasite);
         }
-    }
-  else
-    {
-      g_regex_match (layers_reg, nospace_name, 0, &match_info);
-
-      while (g_match_info_matches (match_info))
-        {
-          gchar *tag = g_match_info_fetch (match_info, 1);
-          gchar** tokens = g_strsplit(tag, ",", 0);
-
-          for (j = 0; tokens[j] != NULL; j++)
-            {
-              gchar* hyphen = g_strrstr(tokens[j], "-");
-
-              if (hyphen != NULL)
-                {
-                  gint32 first = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
-                  gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
-
-                  for (k = first; k <= second; k++)
-                    {
-                      if (! dialog->frames[k - dialog->settings.frame_min])
-                        {
-                          if (! empty_frame)
-                            {
-                              empty_frame = g_new (Frame, 1);
-                              empty_frame->indexes = NULL;
-                              empty_frame->updated_indexes = NULL;
-                              empty_frame->drawable_id = 0;
-
-                              previous_frames = g_list_append (previous_frames, empty_frame);
-                            }
-
-                          if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
-                            empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (k - dialog->settings.frame_min));
-
-                          dialog->frames[k - dialog->settings.frame_min] = empty_frame;
-                        }
-                      else if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
-                        dialog->frames[k - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[k - dialog->settings.frame_min]->updated_indexes,
-                                                                                       GINT_TO_POINTER (k - 
dialog->settings.frame_min));
-                    }
-                }
-              else
-                {
-                  gint32 num = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
-
-                  if (! dialog->frames[num - dialog->settings.frame_min])
-                    {
-                      if (! empty_frame)
-                        {
-                          empty_frame = g_new (Frame, 1);
-                          empty_frame->indexes = NULL;
-                          empty_frame->updated_indexes = NULL;
-                          empty_frame->drawable_id = 0;
-
-                          previous_frames = g_list_append (previous_frames, empty_frame);
-                        }
-
-                      if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
-                        empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min));
-
-                      dialog->frames[num - dialog->settings.frame_min] = empty_frame;
-                    }
-                  else if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
-                    dialog->frames[num - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[num - dialog->settings.frame_min]->updated_indexes,
-                                                                                     GINT_TO_POINTER (num - 
dialog->settings.frame_min));
-                }
-            }
-          g_strfreev (tokens);
-          g_free (tag);
-          g_match_info_next (match_info, NULL);
-        }
-      g_match_info_free (match_info);
-    }
-
-  for (j = 0; j < dialog->settings.num_frames; j++)
-    {
-      /* Check which frame must be updated with the current layer. */
-      if (dialog->frames[j] && g_list_length (dialog->frames[j]->updated_indexes))
-        {
-          new_layer = gimp_layer_new_from_drawable (layer, frames_image_id);
-          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
-
-          if (preview_quality)
-            {
-              gint layer_offx, layer_offy;
-
-              gimp_layer_scale (new_layer,
-                                (gimp_drawable_width (layer) * (gint) dialog->preview_width) / (gint) 
image_width,
-                                (gimp_drawable_height (layer) * (gint) dialog->preview_height) / (gint) 
image_height,
-                                FALSE);
-              gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
-              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / (gint) 
image_width,
-                                      (layer_offy * (gint) dialog->preview_height) / (gint) image_height);
-            }
-          gimp_layer_resize_to_image_size (new_layer);
-
-          if (dialog->frames[j]->drawable_id == 0)
-            {
-              dialog->frames[j]->drawable_id = new_layer;
-              dialog->frames[j]->indexes = dialog->frames[j]->updated_indexes;
-              dialog->frames[j]->updated_indexes = NULL;
-            }
-          else if (g_list_length (dialog->frames[j]->indexes) == g_list_length 
(dialog->frames[j]->updated_indexes))
-            {
-              gimp_item_set_visible (new_layer, TRUE);
-              gimp_item_set_visible (dialog->frames[j]->drawable_id, TRUE);
-
-              dialog->frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, 
GIMP_CLIP_TO_IMAGE);
-              g_list_free (dialog->frames[j]->updated_indexes);
-              dialog->frames[j]->updated_indexes = NULL;
-            }
-          else
-            {
-              GList    *idx;
-              gboolean  move_j = FALSE;
-              Frame    *forked_frame = g_new (Frame, 1);
-              gint32    forked_drawable_id = gimp_layer_new_from_drawable (dialog->frames[j]->drawable_id, 
frames_image_id);
-
-              /* if part only of the dialog->frames are updated, we fork the existing frame. */
-              gimp_image_insert_layer (frames_image_id, forked_drawable_id, 0, 1);
-              gimp_item_set_visible (new_layer, TRUE);
-              gimp_item_set_visible (forked_drawable_id, TRUE);
-              forked_drawable_id = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
-
-              forked_frame->drawable_id = forked_drawable_id;
-              forked_frame->indexes = dialog->frames[j]->updated_indexes;
-              forked_frame->updated_indexes = NULL;
-              dialog->frames[j]->updated_indexes = NULL;
-
-              for (idx = g_list_first (forked_frame->indexes); idx != NULL; idx = g_list_next (idx))
-                {
-                  dialog->frames[j]->indexes = g_list_remove (dialog->frames[j]->indexes, idx->data);
-                  if (GPOINTER_TO_INT (idx->data) != j)
-                    dialog->frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
-                  else
-                    /* Frame j must also be moved to the forked frame, but only after the loop. */
-                    move_j = TRUE;
-                }
-              if (move_j)
-                dialog->frames[j] = forked_frame;
-
-              gimp_item_set_visible (forked_drawable_id, FALSE);
-
-              previous_frames = g_list_append (previous_frames, forked_frame);
-            }
-
-          gimp_item_set_visible (dialog->frames[j]->drawable_id, FALSE);
-        }
-    }
-
-  g_free (layer_name);
-  g_free (nospace_name);
-}
-
-static void
-init_ui (AnimationPlayDialog *dialog,
-         gchar               *imagename)
-{
-  GtkWidget     *upper_bar;
-  GtkWidget     *widget;
-  GtkAdjustment *adjust;
-  GtkWidget     *main_vbox;
-  GtkWidget     *vbox;
-  GtkWidget     *hbox;
-  GtkWidget     *abox;
-  GdkCursor     *cursor;
-  gchar         *text;
-  gint           index;
-
-  gimp_ui_init (PLUG_IN_BINARY, TRUE);
-
-  dialog->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_role (GTK_WINDOW (dialog->window), "animation-playback");
-
-  /* Some basic signals. */
-  g_signal_connect (dialog->window, "destroy",
-                    G_CALLBACK (window_destroy),
-                    dialog);
-  g_signal_connect (dialog->window, "popup-menu",
-                    G_CALLBACK (popup_menu),
-                    dialog);
-
-  /* Window Title */
-  text = g_strconcat (_("Animation Playback:"), " ", imagename, NULL);
-  gtk_window_set_title (GTK_WINDOW (dialog->window), text);
-  g_free (text);
-
-  gimp_help_connect (dialog->window, gimp_standard_help_func, PLUG_IN_PROC, NULL);
-
-  dialog->ui_manager = ui_manager_new (dialog);
-
-  /* Main vertical box. */
-  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
-  gtk_container_add (GTK_CONTAINER (dialog->window), main_vbox);
-  gtk_widget_show (main_vbox);
-
-  /* Upper Bar */
-  upper_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_box_pack_start (GTK_BOX (main_vbox), upper_bar, FALSE, FALSE, 0);
-  gtk_widget_show (upper_bar);
-
-  /**********************************/
-  /* The upper bar is itself paned. */
-  hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
-  gtk_box_pack_start (GTK_BOX (upper_bar), hbox, FALSE, FALSE, 0);
-  gtk_widget_show (hbox);
-
-  /*****************/
-  /* Settings box. */
-  /*****************/
-  dialog->settings_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_add1 (GTK_PANED (hbox), dialog->settings_bar);
-  gtk_widget_show (dialog->settings_bar);
-
-  /* Settings: expander to display the proxy settings. */
-  widget = gtk_expander_new_with_mnemonic (_("_Proxy Quality"));
-  gtk_expander_set_expanded (GTK_EXPANDER (widget), FALSE);
-
-  g_signal_connect (GTK_EXPANDER (widget),
-                    "notify::expanded",
-                    G_CALLBACK (proxy_checkbox_expanded),
-                    dialog);
-
-  gimp_help_set_help_data (widget, _("Degrade image quality for lower memory footprint"), NULL);
-
-  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Settings: proxy image. */
-  dialog->proxycombo = gtk_combo_box_text_new_with_entry ();
-
-  for (index = 0; index < 3; index++)
-    {
-      text = g_strdup_printf  (_("%.1f %%"), get_proxy (dialog, index) * 100.0);
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->proxycombo), text);
-      g_free (text);
-    }
-
-  /* By default, we are at normal resolution. */
-  gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->proxycombo), -1);
-  text = g_strdup_printf  (_("%.1f %%"), 100.0);
-  gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))), text);
-  g_free (text);
-
-  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
-                    "activate",
-                    G_CALLBACK (proxycombo_activated),
-                    dialog);
-  g_signal_connect (dialog->proxycombo, "changed",
-                    G_CALLBACK (proxycombo_changed),
-                    dialog);
-
-  gimp_help_set_help_data (dialog->proxycombo, _("Proxy resolution quality"), NULL);
-
-  gtk_widget_show (dialog->proxycombo);
-  gtk_container_add (GTK_CONTAINER (widget), dialog->proxycombo);
-
-  /* Settings: fps */
-  dialog->fpscombo = gtk_combo_box_text_new_with_entry ();
-
-  gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->fpscombo), -1);
-  for (index = 0; index < 5; index++)
-    {
-      /* list is given in "fps" - frames per second.
-       * We allow accurate fps (double) for special cases. */
-      text = g_strdup_printf  (_("%g fps"), get_fps (index));
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->fpscombo), text);
-      g_free (text);
-      if (get_fps (index) == dialog->settings.frame_rate)
-        gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->fpscombo), index);
-    }
-
-  if (gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->fpscombo)) == -1)
-    {
-      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
-      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
-      g_free (text);
-    }
-
-  g_signal_connect (dialog->fpscombo, "changed",
-                    G_CALLBACK (fpscombo_changed),
-                    dialog);
-  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
-                    "activate",
-                    G_CALLBACK (fpscombo_activated),
-                    dialog);
-
-  gimp_help_set_help_data (dialog->fpscombo, _("Frame Rate"), NULL);
-
-  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), dialog->fpscombo, FALSE, FALSE, 0);
-  gtk_widget_show (dialog->fpscombo);
-
-  /* Settings: frame mode. */
-  widget = gtk_combo_box_text_new ();
-
-  text = g_strdup (_("Cumulative layers (combine)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_COMBINE, text);
-  g_free (text);
-
-  text = g_strdup (_("One frame per layer (replace)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_REPLACE, text);
-  g_free (text);
-
-  text = g_strdup (_("Use layer tags (custom)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_TAGS, text);
-  g_free (text);
-
-  gtk_combo_box_set_active (GTK_COMBO_BOX (widget), dialog->settings.frame_disposal);
-
-  g_signal_connect (widget, "changed",
-                    G_CALLBACK (framecombo_changed),
-                    dialog);
-
-  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /*************/
-  /* View box. */
-  /*************/
-  dialog->view_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_pack2 (GTK_PANED (hbox), dialog->view_bar, FALSE, TRUE);
-  gtk_widget_show (dialog->view_bar);
-
-  /* View: zoom. */
-  dialog->zoomcombo = gtk_combo_box_text_new_with_entry ();
-  for (index = 0; index < 5; index++)
-    {
-      text = g_strdup_printf  (_("%.1f %%"), get_scale (dialog, index) * 100.0);
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->zoomcombo), text);
-      g_free (text);
-
-      if (get_scale (dialog, index) == 1.0)
-        gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->zoomcombo), index);
-    }
-
-  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo))),
-                    "activate",
-                    G_CALLBACK (zoomcombo_activated),
-                    dialog);
-  g_signal_connect (dialog->zoomcombo, "changed",
-                    G_CALLBACK (zoomcombo_changed),
-                    dialog);
-
-  gimp_help_set_help_data (dialog->zoomcombo, _("Zoom"), NULL);
-
-  gtk_box_pack_end (GTK_BOX (dialog->view_bar), dialog->zoomcombo, FALSE, FALSE, 0);
-  gtk_widget_show (dialog->zoomcombo);
-
-  /* View: detach. */
-  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
-  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (widget), GIMP_STOCK_DETACH);
-
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
-                                      gtk_action_group_get_action (dialog->view_actions, "detach"));
-
-  gtk_box_pack_end (GTK_BOX (dialog->view_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /***********/
-  /* Various */
-  /***********/
-
-  /* Various: separator for some spacing in the UI. */
-  widget = GTK_WIDGET (gtk_separator_tool_item_new ());
-  gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (widget), FALSE);
-  gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
-  gtk_box_pack_start (GTK_BOX (upper_bar), widget, TRUE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Various: refresh. */
-  dialog->refresh = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Reload the image")));
-
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (dialog->refresh),
-                                      gtk_action_group_get_action (dialog->settings_actions, "refresh"));
-
-  gtk_box_pack_start (GTK_BOX (upper_bar), dialog->refresh, FALSE, FALSE, 0);
-  gtk_widget_show (dialog->refresh);
-
-  /***********/
-  /* Drawing */
-  /***********/
-
-  /* Vbox for the preview window and lower option bar */
-  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
-  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
-  gtk_widget_show (vbox);
-
-  /* Alignment for the scrolling window, which can be resized by the user. */
-  abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
-  gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0);
-  gtk_widget_show (abox);
-
-  widget = gtk_scrolled_window_new (NULL, NULL);
-  gtk_container_add (GTK_CONTAINER (abox), widget);
-  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-  gtk_widget_show (widget);
-
-  /* I add the drawing area inside an alignment box to prevent it from being resized. */
-  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
-  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (widget), abox);
-  gtk_widget_show (abox);
-
-  /* Build a drawing area, with a default size same as the image */
-  dialog->drawing_area = gtk_drawing_area_new ();
-  gtk_widget_add_events (dialog->drawing_area, GDK_BUTTON_PRESS_MASK);
-  gtk_container_add (GTK_CONTAINER (abox), dialog->drawing_area);
-  gtk_widget_show (dialog->drawing_area);
-
-  g_signal_connect (dialog->drawing_area, "size-allocate",
-                    G_CALLBACK(da_size_callback),
-                    dialog);
-  g_signal_connect (dialog->drawing_area, "button-press-event",
-                    G_CALLBACK (da_button_press),
-                    dialog);
-
-  /*****************/
-  /* Play toolbar. */
-  /*****************/
-
-  hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
-  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
-  gtk_widget_show (hbox);
-
-  /* Play buttons. */
-  dialog->play_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_add1 (GTK_PANED (hbox), dialog->play_bar);
-  gtk_widget_show (dialog->play_bar);
-
-  /* Play: play. */
-  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
-                                      gtk_action_group_get_action (dialog->play_actions, "play"));
-
-  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Play: step backward. */
-  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step back to previous frame")));
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
-                                      gtk_action_group_get_action (dialog->play_actions, "step-back"));
-
-  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Play: step forward. */
-  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step to next frame")));
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
-                                      gtk_action_group_get_action (dialog->play_actions, "step"));
-
-  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Play: rewind. */
-  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Rewind the animation")));
-  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
-                                      gtk_action_group_get_action (dialog->play_actions, "rewind"));
-
-  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
-  gtk_widget_show (widget);
-
-  /* Progress box. */
-
-  dialog->progress_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_add2 (GTK_PANED (hbox), dialog->progress_bar);
-  gtk_widget_show (dialog->progress_bar);
-
-  /* End frame spin button. */
-
-  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
-  dialog->endframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), 2);
-
-  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->endframe_spin, FALSE, FALSE, 0);
-  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (dialog->endframe_spin), GTK_UPDATE_IF_VALID);
-  gtk_widget_show (dialog->endframe_spin);
-
-  g_signal_connect (adjust,
-                    "value-changed",
-                    G_CALLBACK (endframe_changed),
-                    dialog);
-
-  g_signal_connect (dialog->endframe_spin, "button-press-event",
-                    G_CALLBACK (adjustment_pressed),
-                    dialog);
-
-  gimp_help_set_help_data (dialog->endframe_spin, _("End frame"), NULL);
-
-  /* Progress bar. */
-
-  dialog->progress = gtk_progress_bar_new ();
-  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->progress, TRUE, TRUE, 0);
-  gtk_widget_show (dialog->progress);
-
-  gtk_widget_add_events (dialog->progress,
-                         GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
-                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
-  g_signal_connect (dialog->progress, "enter-notify-event",
-                    G_CALLBACK (progress_entered),
-                    dialog);
-  g_signal_connect (dialog->progress, "leave-notify-event",
-                    G_CALLBACK (progress_left),
-                    dialog);
-  g_signal_connect (dialog->progress, "motion-notify-event",
-                    G_CALLBACK (progress_motion),
-                    dialog);
-  g_signal_connect (dialog->progress, "button-press-event",
-                    G_CALLBACK (progress_button),
-                    dialog);
-
-  /* Start frame spin button. */
-
-  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
-  dialog->startframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), 2);
-
-  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->startframe_spin, FALSE, FALSE, 0);
-  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (dialog->startframe_spin), GTK_UPDATE_IF_VALID);
-  gtk_widget_show (dialog->startframe_spin);
-
-  g_signal_connect (adjust,
-                    "value-changed",
-                    G_CALLBACK (startframe_changed),
-                    dialog);
-
-  g_signal_connect (GTK_ENTRY (dialog->startframe_spin), "button-press-event",
-                    G_CALLBACK (adjustment_pressed),
-                    dialog);
-
-  gimp_help_set_help_data (dialog->startframe_spin, _("Start frame"), NULL);
-
-  /* Finalization. */
-  gtk_window_set_resizable (GTK_WINDOW (dialog->window), TRUE);
-  gtk_window_set_default_size (GTK_WINDOW (dialog->window), dialog->preview_width + 20, 
dialog->preview_height + 90);
-  gtk_widget_show (dialog->window);
-
-  /* shape_drawing_area for detached feature. */
-  dialog->shape_window = gtk_window_new (GTK_WINDOW_POPUP);
-  gtk_window_set_resizable (GTK_WINDOW (dialog->shape_window), FALSE);
-
-  dialog->shape_drawing_area = gtk_drawing_area_new ();
-  gtk_container_add (GTK_CONTAINER (dialog->shape_window), dialog->shape_drawing_area);
-  gtk_widget_show (dialog->shape_drawing_area);
-  gtk_widget_add_events (dialog->shape_drawing_area, GDK_BUTTON_PRESS_MASK);
-  gtk_widget_realize (dialog->shape_drawing_area);
-
-  gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_window), NULL, FALSE);
-
-  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (dialog->shape_window),
-                                       GDK_HAND2);
-  gdk_window_set_cursor (gtk_widget_get_window (dialog->shape_window), cursor);
-  gdk_cursor_unref (cursor);
-
-  g_signal_connect (dialog->shape_drawing_area, "size-allocate",
-                    G_CALLBACK(da_size_callback),
-                    dialog);
-  g_signal_connect (dialog->shape_window, "button-press-event",
-                    G_CALLBACK (shape_pressed),
-                    dialog);
-  g_signal_connect (dialog->shape_window, "button-release-event",
-                    G_CALLBACK (shape_released),
-                    NULL);
-  g_signal_connect (dialog->shape_window, "motion-notify-event",
-                    G_CALLBACK (shape_motion),
-                    NULL);
-
-  g_object_set_data (G_OBJECT (dialog->shape_window),
-                     "cursor-offset", g_new0 (CursorOffset, 1));
-
-  g_signal_connect (dialog->drawing_area, "expose-event",
-                    G_CALLBACK (repaint_da),
-                    dialog);
-
-  g_signal_connect (dialog->shape_drawing_area, "expose-event",
-                    G_CALLBACK (repaint_da),
-                    dialog);
-
-  /* We request a minimum size *after* having connecting the
-   * size-allocate signal for correct initialization. */
-  gtk_widget_set_size_request (dialog->drawing_area, dialog->preview_width, dialog->preview_height);
-  gtk_widget_set_size_request (dialog->shape_drawing_area, dialog->preview_width, dialog->preview_height);
-}
-
-static GtkUIManager *
-ui_manager_new (AnimationPlayDialog *dialog)
-{
-  static GtkActionEntry play_entries[] =
-  {
-    { "step-back", "media-skip-backward",
-      N_("Step _back"), "d", N_("Step back to previous frame"),
-      G_CALLBACK (step_back_callback) },
-
-    { "step", "media-skip-forward",
-      N_("_Step"), "f", N_("Step to next frame"),
-      G_CALLBACK (step_callback) },
-
-    { "rewind", "media-seek-backward",
-      NULL, "s", N_("Rewind the animation"),
-      G_CALLBACK (rewind_callback) },
-  };
-
-  static GtkToggleActionEntry play_toggle_entries[] =
-  {
-    { "play", "media-playback-start",
-      NULL, "space", N_("Start playback"),
-      G_CALLBACK (play_callback), FALSE },
-  };
-
-  static GtkActionEntry settings_entries[] =
-  {
-    /* Refresh is not really a settings, but it makes sense to be grouped
-     * as such, because we want to be able to refresh and set things in the
-     * same moments. */
-    { "refresh", GIMP_ICON_VIEW_REFRESH,
-      N_("Refresh"), "<control>R", N_("Reload the image"),
-      G_CALLBACK (refresh_callback) },
-
-    {
-      "speed-up", NULL,
-      N_("Faster"), "bracketright", N_("Increase the speed of the animation"),
-      G_CALLBACK (speed_up_callback)
-    },
-    {
-      "speed-down", NULL,
-      N_("Slower"), "bracketleft", N_("Decrease the speed of the animation"),
-      G_CALLBACK (speed_down_callback)
-    },
-  };
-
-  static GtkActionEntry view_entries[] =
-  {
-    { "zoom-in", GTK_STOCK_ZOOM_IN,
-      NULL, "plus", N_("Zoom in"),
-      G_CALLBACK (zoom_in_callback) },
-
-    { "zoom-in-accel", GTK_STOCK_ZOOM_IN,
-      NULL, "KP_Add", N_("Zoom in"),
-      G_CALLBACK (zoom_in_callback) },
-
-    { "zoom-out", GTK_STOCK_ZOOM_OUT,
-      NULL, "minus", N_("Zoom out"),
-      G_CALLBACK (zoom_out_callback) },
-
-    { "zoom-out-accel", GTK_STOCK_ZOOM_OUT,
-      NULL, "KP_Subtract", N_("Zoom out"),
-      G_CALLBACK (zoom_out_callback) },
-
-    { "zoom-reset", GTK_STOCK_ZOOM_OUT,
-      NULL, "equal", N_("Zoom out"),
-      G_CALLBACK (zoom_reset_callback) },
-
-    { "zoom-reset-accel", GTK_STOCK_ZOOM_OUT,
-      NULL, "KP_Equal", N_("Zoom out"),
-      G_CALLBACK (zoom_reset_callback) },
-  };
-
-  static GtkToggleActionEntry view_toggle_entries[] =
-  {
-    { "detach", GIMP_STOCK_DETACH,
-      N_("Detach"), NULL,
-      N_("Detach the animation from the dialog window"),
-      G_CALLBACK (detach_callback), FALSE }
-  };
-
-  static GtkActionEntry various_entries[] =
-  {
-    { "help", "help-browser",
-      N_("About the animation plug-in"), "question", NULL,
-      G_CALLBACK (help_callback) },
-
-    { "close", "window-close",
-      N_("Quit"), "<control>W", NULL,
-      G_CALLBACK (close_callback)
-    },
-    {
-      "quit", "application-quit",
-      N_("Quit"), "<control>Q", NULL,
-      G_CALLBACK (close_callback)
-    },
-  };
-
-  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
-  GError         *error      = NULL;
-
-  /* All playback related actions. */
-  dialog->play_actions = gtk_action_group_new ("playback");
-  gtk_action_group_set_translation_domain (dialog->play_actions, NULL);
-  gtk_action_group_add_actions (dialog->play_actions,
-                                play_entries,
-                                G_N_ELEMENTS (play_entries),
-                                dialog);
-  gtk_action_group_add_toggle_actions (dialog->play_actions,
-                                       play_toggle_entries,
-                                       G_N_ELEMENTS (play_toggle_entries),
-                                       dialog);
-  connect_accelerators (ui_manager, dialog->play_actions);
-  gtk_ui_manager_insert_action_group (ui_manager, dialog->play_actions, -1);
-
-  /* All settings related actions. */
-  dialog->settings_actions = gtk_action_group_new ("settings");
-  gtk_action_group_set_translation_domain (dialog->settings_actions, NULL);
-  gtk_action_group_add_actions (dialog->settings_actions,
-                                settings_entries,
-                                G_N_ELEMENTS (settings_entries),
-                                dialog);
-  connect_accelerators (ui_manager, dialog->settings_actions);
-  gtk_ui_manager_insert_action_group (ui_manager, dialog->settings_actions, -1);
-
-  /* All view actions. */
-  dialog->view_actions = gtk_action_group_new ("view");
-  gtk_action_group_set_translation_domain (dialog->view_actions, NULL);
-  gtk_action_group_add_actions (dialog->view_actions,
-                                view_entries,
-                                G_N_ELEMENTS (view_entries),
-                                dialog);
-  gtk_action_group_add_toggle_actions (dialog->view_actions,
-                                       view_toggle_entries,
-                                       G_N_ELEMENTS (view_toggle_entries),
-                                       dialog);
-  connect_accelerators (ui_manager, dialog->view_actions);
-  gtk_ui_manager_insert_action_group (ui_manager, dialog->view_actions, -1);
-
-  /* Remaining various actions. */
-  dialog->various_actions = gtk_action_group_new ("various");
-
-  gtk_action_group_set_translation_domain (dialog->various_actions, NULL);
-
-  gtk_action_group_add_actions (dialog->various_actions,
-                                various_entries,
-                                G_N_ELEMENTS (various_entries),
-                                dialog);
-  connect_accelerators (ui_manager, dialog->various_actions);
-  gtk_ui_manager_insert_action_group (ui_manager, dialog->various_actions, -1);
-
-  /* Finalize. */
-  gtk_window_add_accel_group (GTK_WINDOW (dialog->window),
-                              gtk_ui_manager_get_accel_group (ui_manager));
-  gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager));
-
-  /* Finally make some limited popup menu. */
-  gtk_ui_manager_add_ui_from_string (ui_manager,
-                                     "<ui>"
-                                     "  <popup name=\"anim-play-popup\" accelerators=\"true\">"
-                                     "    <menuitem action=\"refresh\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"zoom-in\" />"
-                                     "    <menuitem action=\"zoom-out\" />"
-                                     "    <menuitem action=\"zoom-reset\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"speed-up\" />"
-                                     "    <menuitem action=\"speed-down\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"help\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"close\" />"
-                                     "  </popup>"
-                                     "</ui>",
-                                     -1, &error);
-
-  if (error)
-    {
-      g_warning ("error parsing ui: %s", error->message);
-      g_clear_error (&error);
-    }
-
-  return ui_manager;
-}
-
-static void
-refresh_dialog (AnimationPlayDialog *dialog)
-{
-  GdkScreen *screen;
-  guint      screen_width, screen_height;
-  gint       window_width, window_height;
-
-  /* Update GUI size. */
-  screen = gtk_widget_get_screen (dialog->window);
-  screen_height = gdk_screen_get_height (screen);
-  screen_width = gdk_screen_get_width (screen);
-  gtk_window_get_size (GTK_WINDOW (dialog->window), &window_width, &window_height);
-
-  /* if the *window* size is bigger than the screen size,
-   * diminish the drawing area by as much, then compute the corresponding scale. */
-  if (window_width + 50 > screen_width || window_height + 50 > screen_height)
-  {
-      gint expected_drawing_area_width = MAX (1, dialog->preview_width - window_width + screen_width);
-      gint expected_drawing_area_height = MAX (1, dialog->preview_height - window_height + screen_height);
-
-      gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) dialog->preview_width,
-                                    (gdouble) expected_drawing_area_height / (gdouble) 
dialog->preview_height);
-      update_scale (dialog, expected_scale);
-
-      /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
-       * So we take a wild guess by making the window slightly smaller to fit into most case. */
-      gtk_window_set_default_size (GTK_WINDOW (dialog->window),
-                                   MIN (expected_drawing_area_width + 20, screen_width - 60),
-                                   MIN (expected_drawing_area_height + 90, screen_height - 60));
-
-      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->window));
-  }
-}
-
-/* Update the tool sensitivity for playing, depending on the number of frames. */
-static void
-update_ui_sensitivity (AnimationPlayDialog *dialog)
-{
-  gboolean animated;
-
-  animated = dialog->settings.end_frame - dialog->settings.start_frame > 1;
-  /* Play actions only if we selected several frames between start/end. */
-  gtk_action_group_set_sensitive (dialog->play_actions, animated);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->play_bar), animated);
-
-  /* We can access the progress bar if there are several frames. */
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->progress_bar),
-                            dialog->settings.num_frames > 1);
-
-  /* Settings are always changeable. */
-  gtk_action_group_set_sensitive (dialog->settings_actions, TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), TRUE);
-
-  /* View are always meaningfull with at least 1 frame. */
-  gtk_action_group_set_sensitive (dialog->view_actions,
-                                  dialog->settings.num_frames >= 1);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->view_bar),
-                            dialog->settings.num_frames >= 1);
-}
-
-/***************** CALLBACKS ********************/
-
-static void
-close_callback (GtkAction           *action,
-                AnimationPlayDialog *dialog)
-{
-  clean_exit (dialog);
-}
-
-static void
-help_callback (GtkAction           *action,
-               AnimationPlayDialog *dialog)
-{
-  gimp_standard_help_func (PLUG_IN_PROC, dialog->window);
-}
-
-static void
-window_destroy (GtkWidget           *widget,
-                AnimationPlayDialog *dialog)
-{
-  clean_exit (dialog);
-}
-
-static gboolean
-popup_menu (GtkWidget           *widget,
-            AnimationPlayDialog *dialog)
-{
-  GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
-
-  gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
-  gtk_menu_popup (GTK_MENU (menu),
-                  NULL, NULL, NULL, NULL,
-                  0, gtk_get_current_event_time ());
-
-  return TRUE;
-}
-
-static gboolean
-adjustment_pressed (GtkWidget           *widget,
-                    GdkEventButton      *event,
-                    AnimationPlayDialog *dialog)
-{
-  gboolean event_processed = FALSE;
-
-  if (event->type == GDK_BUTTON_PRESS &&
-      event->button == 2)
-    {
-      GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
-      GtkAdjustment *adj = gtk_spin_button_get_adjustment (spin);
-
-      gtk_adjustment_set_value (adj,
-                                (gdouble) dialog->settings.current_frame);
-
-      /* We don't want the middle click to have another usage (in
-       * particular, there is likely no need to copy-paste in these spin
-       * buttons). */
-      event_processed = TRUE;
-    }
-
-  return event_processed;
-}
-
-static gboolean
-da_button_press (GtkWidget           *widget,
-              GdkEventButton      *event,
-              AnimationPlayDialog *dialog)
-{
-  if (gdk_event_triggers_context_menu ((GdkEvent *) event))
-    {
-      GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
-
-      gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
-      gtk_menu_popup (GTK_MENU (menu),
-                      NULL, NULL, NULL, NULL,
-                      event->button,
-                      event->time);
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-/*
- * Update the actual drawing area metrics, which may be different from
- * requested, since there is no full control of the WM.
- */
-static void
-da_size_callback (GtkWidget           *drawing_area,
-                  GtkAllocation       *allocation,
-                  AnimationPlayDialog *dialog)
-{
-  guchar   **drawing_data;
-
-  if (drawing_area == dialog->shape_drawing_area)
-    {
-      if (allocation->width  == dialog->shape_drawing_area_width &&
-          allocation->height == dialog->shape_drawing_area_height)
-        return;
-
-      dialog->shape_drawing_area_width  = allocation->width;
-      dialog->shape_drawing_area_height = allocation->height;
-
-      g_free (dialog->shape_drawing_area_data);
-      drawing_data = &dialog->shape_drawing_area_data;
-    }
-  else
-    {
-      if (allocation->width  == dialog->drawing_area_width &&
-          allocation->height == dialog->drawing_area_height)
-        return;
-
-      dialog->drawing_area_width  = allocation->width;
-      dialog->drawing_area_height = allocation->height;
-
-      g_free (dialog->drawing_area_data);
-      drawing_data = &dialog->drawing_area_data;
-    }
-
-  dialog->settings.scale = MIN ((gdouble) allocation->width / (gdouble) dialog->preview_width,
-                                (gdouble) allocation->height / (gdouble) dialog->preview_height);
-
-  *drawing_data = g_malloc (allocation->width * allocation->height * 3);
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))) &&
-      drawing_area == dialog->drawing_area)
-    {
-      /* Set "alpha grid" background. */
-      total_alpha_preview (dialog->drawing_area_data,
-                           allocation->width,
-                           allocation->height);
-      repaint_da (dialog->drawing_area, NULL, dialog);
-    }
-  else 
-    {
-      /* Update the zoom information. */
-      GtkEntry *zoomcombo_text_child;
-
-      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo)));
-      if (zoomcombo_text_child)
+      parasite = gimp_image_get_parasite (image_id,
+                                          PLUG_IN_PROC "/framerate");
+      if (parasite)
         {
-          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), dialog->settings.scale * 100.0);
+          const gdouble *rate = gimp_parasite_data (parasite);
 
-          gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
-          g_free (new_entry_text);
+          cached_settings.framerate = *rate;
+          gimp_parasite_free (parasite);
         }
 
-      /* As we re-allocated the drawn data, let's render it again. */
-      if (dialog->settings.current_frame - dialog->settings.frame_min < dialog->settings.num_frames)
-        render_frame (dialog, TRUE);
-    }
-}
-
-static gboolean
-shape_pressed (GtkWidget           *widget,
-               GdkEventButton      *event,
-               AnimationPlayDialog *dialog)
-{
-  if (da_button_press (widget, event, dialog))
-    return TRUE;
-
-  /* ignore double and triple click */
-  if (event->type == GDK_BUTTON_PRESS)
-    {
-      CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset");
-
-      if (!p)
-        return FALSE;
-
-      p->x = (gint) event->x;
-      p->y = (gint) event->y;
-
-      gtk_grab_add (widget);
-      gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
-                        GDK_BUTTON_RELEASE_MASK |
-                        GDK_BUTTON_MOTION_MASK  |
-                        GDK_POINTER_MOTION_HINT_MASK,
-                        NULL, NULL, 0);
-      gdk_window_raise (gtk_widget_get_window (widget));
-    }
-
-  return FALSE;
-}
-
-static gboolean
-shape_released (GtkWidget *widget)
-{
-  gtk_grab_remove (widget);
-  gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0);
-  gdk_flush ();
-
-  return FALSE;
-}
-
-static gboolean
-shape_motion (GtkWidget      *widget,
-              GdkEventMotion *event)
-{
-  GdkModifierType  mask;
-  gint             xp, yp;
-  GdkWindow       *root_win;
-
-  root_win = gdk_get_default_root_window ();
-  gdk_window_get_pointer (root_win, &xp, &yp, &mask);
-
-  /* if a button is still held by the time we process this event... */
-  if (mask & GDK_BUTTON1_MASK)
-    {
-      CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset");
-
-      if (!p)
-        return FALSE;
+      animation_set_framerate (animation, cached_settings.framerate);
+      animation_load (animation, cached_settings.disposal, 1.0);
 
-      gtk_window_move (GTK_WINDOW (widget), xp  - p->x, yp  - p->y);
-    }
-  else /* the user has released all buttons */
-    {
-      shape_released (widget);
-    }
-
-  return FALSE;
-}
-
-static gboolean
-repaint_da (GtkWidget           *darea,
-            GdkEventExpose      *event,
-            AnimationPlayDialog *dialog)
-{
-  GtkStyle *style = gtk_widget_get_style (darea);
-  gint      da_width;
-  gint      da_height;
-  guchar   *da_data;
-
-  if (darea == dialog->drawing_area)
-    {
-      da_width  = dialog->drawing_area_width;
-      da_height = dialog->drawing_area_height;
-      da_data   = dialog->drawing_area_data;
-    }
-  else
-    {
-      da_width  = dialog->shape_drawing_area_width;
-      da_height = dialog->shape_drawing_area_height;
-      da_data   = dialog->shape_drawing_area_data;
-    }
-
-  gdk_draw_rgb_image (gtk_widget_get_window (darea),
-                      style->white_gc,
-                      (gint) ((da_width - dialog->settings.scale * dialog->preview_width) / 2),
-                      (gint) ((da_height - dialog->settings.scale * dialog->preview_height) / 2),
-                      da_width, da_height,
-                      (dialog->settings.num_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
-                      da_data, da_width * 3);
-
-  return TRUE;
-}
-
-static gboolean
-progress_button (GtkWidget           *widget,
-                 GdkEventButton      *event,
-                 AnimationPlayDialog *dialog)
-{
-  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
-  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
-
-  /* ignore double and triple click */
-  if (event->type == GDK_BUTTON_PRESS)
-    {
-      GtkAllocation  allocation;
-      guint          goto_frame;
-
-      gtk_widget_get_allocation (widget, &allocation);
+      gtk_main ();
 
-      goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
+      /* Save the current settings. */
+      save_settings (animation, image_id);
 
-      if (goto_frame < dialog->settings.start_frame)
-        gtk_adjustment_set_value (startframe_adjust, (gdouble) goto_frame);
+      /* Frames are associated to an unused GimpImage.
+       * We have to make sure the animation is freed properly so that
+       * we don't get leaked GEGL buffers. */
+      g_object_unref (animation);
 
-      if (goto_frame > dialog->settings.end_frame)
-        gtk_adjustment_set_value (endframe_adjust, (gdouble) goto_frame);
-
-      if (goto_frame >= dialog->settings.frame_min && goto_frame < dialog->settings.frame_min + 
dialog->settings.num_frames)
-        {
-          dialog->settings.current_frame = goto_frame;
-          render_frame (dialog, FALSE);
-        }
+      if (run_mode != GIMP_RUN_NONINTERACTIVE)
+        gimp_displays_flush ();
     }
 
-  return FALSE;
-}
-
-static gboolean
-progress_entered (GtkWidget           *widget,
-                  GdkEventCrossing    *event,
-                  AnimationPlayDialog *dialog)
-{
-  GtkAllocation  allocation;
-  guint          goto_frame;
-
-  gtk_widget_get_allocation (widget, &allocation);
-
-  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
-
-  show_goto_progress (goto_frame, dialog);
-
-  return FALSE;
-}
-
-static gboolean
-progress_motion (GtkWidget           *widget,
-                 GdkEventMotion      *event,
-                 AnimationPlayDialog *dialog)
-{
-  GtkAllocation  allocation;
-  guint          goto_frame;
-
-  gtk_widget_get_allocation (widget, &allocation);
-
-  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
-
-  show_goto_progress (goto_frame, dialog);
-
-  return FALSE;
-}
-
-static gboolean
-progress_left (GtkWidget           *widget,
-               GdkEventCrossing    *event,
-               AnimationPlayDialog *dialog)
-{
-  show_playing_progress (dialog);
-
-  return FALSE;
-}
-
-static void
-detach_callback (GtkToggleAction     *action,
-                 AnimationPlayDialog *dialog)
-{
-  gboolean detached = gtk_toggle_action_get_active (action);
-
-  if (detached)
-    {
-      gint x, y;
-
-      gtk_window_set_screen (GTK_WINDOW (dialog->shape_window),
-                             gtk_widget_get_screen (dialog->drawing_area));
-
-      gtk_widget_show (dialog->shape_window);
-
-      if (! gtk_widget_get_realized (dialog->drawing_area))
-        {
-          gtk_widget_realize (dialog->drawing_area);
-        }
-      if (! gtk_widget_get_realized (dialog->shape_drawing_area))
-        {
-          gtk_widget_realize (dialog->shape_drawing_area);
-        }
-
-      gdk_window_get_origin (gtk_widget_get_window (dialog->drawing_area), &x, &y);
-
-      gtk_window_move (GTK_WINDOW (dialog->shape_window), x + 6, y + 6);
-
-      gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_drawing_area), NULL, TRUE);
-
-      /* Set "alpha grid" background. */
-      total_alpha_preview (dialog->drawing_area_data,
-                           dialog->drawing_area_width,
-                           dialog->drawing_area_height);
-      repaint_da (dialog->drawing_area, NULL, dialog);
-    }
-  else
-    {
-      gtk_widget_hide (dialog->shape_window);
-    }
-
-  render_frame (dialog, TRUE);
-}
-
-static void
-play_callback (GtkToggleAction     *action,
-               AnimationPlayDialog *dialog)
-{
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
-    {
-      guint timer;
-      guint duration = dialog->frames[dialog->settings.current_frame - dialog->settings.frame_min]->duration;
-
-      if (duration <= 0)
-        duration = (guint) (1000.0 / dialog->settings.frame_rate);
-
-      timer = g_timeout_add (duration, (GSourceFunc) advance_frame_callback, dialog);
-      set_timer (timer);
-
-      gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause");
-    }
-  else
-    {
-      /* Remove any previous timer to stock playback. */
-      set_timer (0);
-
-      gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-start");
-    }
-
-  g_object_set (action,
-                "tooltip",
-                gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play"))) ?
-                  _("Pause playback") : _("Start playback"),
-                NULL);
-}
-
-static void
-step_back_callback (GtkAction           *action,
-                    AnimationPlayDialog *dialog)
-{
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-  do_back_step (dialog);
-}
-
-static void
-step_callback (GtkAction           *action,
-               AnimationPlayDialog *dialog)
-{
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-  do_step (dialog);
-}
-
-static void
-rewind_callback (GtkAction           *action,
-                 AnimationPlayDialog *dialog)
-{
-  gboolean was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
-
-  if (was_playing)
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-
-  dialog->settings.current_frame = dialog->settings.start_frame;
-  render_frame (dialog, FALSE);
-
-  /* If we were playing, start playing again. */
-  if (was_playing)
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-}
-
-static void
-refresh_callback (GtkAction           *action,
-                  AnimationPlayDialog *dialog)
-{
-  initialize (dialog);
-}
-
-static void
-zoom_in_callback (GtkAction           *action,
-                  AnimationPlayDialog *dialog)
-{
-  gdouble  scale = get_scale (dialog, -1);
-
-  g_signal_handlers_block_by_func (dialog->zoomcombo,
-                                   G_CALLBACK (zoomcombo_changed),
-                                   dialog);
-  update_scale (dialog, scale + 0.1);
-
-  g_signal_handlers_unblock_by_func (dialog->zoomcombo,
-                                     G_CALLBACK (zoomcombo_changed),
-                                     dialog);
-}
-
-static void
-zoom_out_callback (GtkAction           *action,
-                   AnimationPlayDialog *dialog)
-{
-  gdouble  scale = get_scale (dialog, -1);
-
-  if (scale > 0.1)
-    {
-      g_signal_handlers_block_by_func (dialog->zoomcombo,
-                                       G_CALLBACK (zoomcombo_changed),
-                                       dialog);
-      update_scale (dialog, scale - 0.1);
-      g_signal_handlers_unblock_by_func (dialog->zoomcombo,
-                                         G_CALLBACK (zoomcombo_changed),
-                                         dialog);
-    }
-}
-
-static void
-zoom_reset_callback (GtkAction           *action,
-                     AnimationPlayDialog *dialog)
-{
-  gdouble  scale = get_scale (dialog, -1);
-
-  if (scale != 1.0)
-    {
-      g_signal_handlers_block_by_func (dialog->zoomcombo,
-                                       G_CALLBACK (zoomcombo_changed),
-                                       dialog);
-      update_scale (dialog, 1.0);
-      g_signal_handlers_unblock_by_func (dialog->zoomcombo,
-                                         G_CALLBACK (zoomcombo_changed),
-                                         dialog);
-    }
-}
-
-static void
-speed_up_callback (GtkAction           *action,
-                   AnimationPlayDialog *dialog)
-{
-  if (dialog->settings.frame_rate <= MAX_FRAMERATE - 1)
-    {
-      gchar *text;
-
-      ++dialog->settings.frame_rate;
-
-      g_signal_handlers_block_by_func (dialog->fpscombo,
-                                       G_CALLBACK (fpscombo_changed),
-                                       dialog);
-      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
-                                       G_CALLBACK (fpscombo_activated),
-                                       dialog);
-
-      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
-      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
-      g_free (text);
-
-      g_signal_handlers_unblock_by_func (dialog->fpscombo,
-                                         G_CALLBACK (fpscombo_changed),
-                                         dialog);
-      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
-                                         G_CALLBACK (fpscombo_activated),
-                                         dialog);
-    }
-}
-
-static void
-speed_down_callback (GtkAction           *action,
-                     AnimationPlayDialog *dialog)
-{
-  if (dialog->settings.frame_rate > 1)
-    {
-      gchar *text;
-
-      --dialog->settings.frame_rate;
-
-      g_signal_handlers_block_by_func (dialog->fpscombo,
-                                       G_CALLBACK (fpscombo_changed),
-                                       dialog);
-      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
-                                       G_CALLBACK (fpscombo_activated),
-                                       dialog);
-
-      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
-      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
-      g_free (text);
-
-      g_signal_handlers_unblock_by_func (dialog->fpscombo,
-                                         G_CALLBACK (fpscombo_changed),
-                                         dialog);
-      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
-                                         G_CALLBACK (fpscombo_activated),
-                                         dialog);
-    }
-}
-
-static void
-framecombo_changed (GtkWidget           *combo,
-                    AnimationPlayDialog *dialog)
-{
-  gboolean was_playing;
-
-  dialog->settings.frame_disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-  was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
-
-  render_frame (dialog, init_frames (dialog));
-
-  if (was_playing)
-    {
-      /* Initializing frames stopped the playing. I restart it. */
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-}
-
-/*
- * Callback emitted when the user hits the Enter key of the fps combo.
- */
-static void
-fpscombo_activated (GtkEntry            *combo_entry,
-                    AnimationPlayDialog *dialog)
-{
-  const gchar *active_text;
-  gdouble      fps;
-  gchar       *text;
-
-  active_text = gtk_entry_get_text (combo_entry);
-  /* Try a text conversion, locale-aware. */
-  fps         = g_strtod (active_text, NULL);
-
-  if (fps >= MAX_FRAMERATE)
-    {
-      /* Let's avoid huge frame rates. */
-      fps = MAX_FRAMERATE;
-    }
-  else if (fps <= 0)
-    {
-      /* Null or negative framerates are impossible. */
-      fps = 0.1;
-    }
-
-  dialog->settings.frame_rate = fps;
-
-  /* Now let's format the text cleanly: "%g fps". */
-  text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
-  gtk_entry_set_text (combo_entry, text);
-  g_free (text);
-}
-
-static void
-fpscombo_changed (GtkWidget           *combo,
-                  AnimationPlayDialog *dialog)
-{
-  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-
-  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
-  if (index != -1)
-    {
-      dialog->settings.frame_rate = get_fps (index);
-    }
-}
-
-/*
- * Callback emitted when the user hits the Enter key of the zoom combo.
- */
-static void
-zoomcombo_activated (GtkEntry            *combo,
-                     AnimationPlayDialog *dialog)
-{
-  update_scale (dialog, get_scale (dialog, -1));
-}
-
-/*
- * Callback emitted when the user selects a zoom in the dropdown,
- * or edits the text entry.
- * We don't want to process manual edits because it greedily emits
- * signals after each character deleted or added.
- */
-static void
-zoomcombo_changed (GtkWidget           *combo,
-                   AnimationPlayDialog *dialog)
-{
-  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-
-  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
-  if (index != -1)
-    update_scale (dialog, get_scale (dialog, index));
-}
-
-static void
-startframe_changed (GtkAdjustment       *adjustment,
-                    AnimationPlayDialog *dialog)
-{
-  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
-  gdouble        value = gtk_adjustment_get_value (adjustment);
-
-  if (value < dialog->settings.frame_min || value > dialog->settings.frame_max)
-    {
-      value = dialog->settings.frame_min;
-      gtk_adjustment_set_value (adjustment, dialog->settings.frame_min);
-    }
-
-  dialog->settings.start_frame = (guint) value;
-
-  if (gtk_adjustment_get_value (endframe_adjust) < value)
-    {
-      gtk_adjustment_set_value (endframe_adjust, dialog->settings.frame_max);
-      dialog->settings.end_frame = (guint) dialog->settings.frame_max;
-    }
-
-  if (dialog->settings.current_frame < dialog->settings.start_frame)
-    {
-      dialog->settings.current_frame = dialog->settings.start_frame;
-      render_frame (dialog, FALSE);
-    }
-
-  update_ui_sensitivity (dialog);
-}
-
-static void
-endframe_changed (GtkAdjustment       *adjustment,
-                  AnimationPlayDialog *dialog)
-{
-  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
-  gdouble        value = gtk_adjustment_get_value (adjustment);
-
-  if (value < dialog->settings.frame_min || value > dialog->settings.frame_max)
-    {
-      value = dialog->settings.frame_max;
-      gtk_adjustment_set_value (adjustment, dialog->settings.frame_max);
-    }
-
-  dialog->settings.end_frame = (guint) value;
-
-  if (gtk_adjustment_get_value (startframe_adjust) > value)
-    {
-      gtk_adjustment_set_value (startframe_adjust, dialog->settings.frame_min);
-      dialog->settings.start_frame = (guint) dialog->settings.frame_min;
-    }
-
-  if (dialog->settings.current_frame > dialog->settings.end_frame)
-    {
-      dialog->settings.current_frame = dialog->settings.end_frame;
-      render_frame (dialog, FALSE);
-    }
-
-  update_ui_sensitivity (dialog);
-}
-
-/*
- * Callback emitted when the user toggle the proxy checkbox.
- */
-static void
-proxy_checkbox_expanded (GtkExpander         *expander,
-                         GParamSpec          *param_spec,
-                         AnimationPlayDialog *dialog)
-{
-  gint previous_preview_width  = dialog->preview_width;
-  gint previous_preview_height = dialog->preview_height;
-  gint image_width  = gimp_image_width (dialog->image_id);
-  gint image_height = gimp_image_height (dialog->image_id);
-
-  if (gtk_expander_get_expanded (expander))
-    {
-      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
-                            dialog);
-    }
-  else if (dialog->preview_width  != image_width ||
-           dialog->preview_height != image_height)
-    {
-      dialog->preview_width  = image_width;
-      dialog->preview_height = image_height;
-
-      if (previous_preview_width  != dialog->preview_width ||
-          previous_preview_height != dialog->preview_height)
-        {
-          gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->zoomcombo));
-
-          update_scale (dialog, get_scale (dialog, index));
-
-          init_frames (dialog);
-          render_frame (dialog, TRUE);
-        }
-    }
-}
-
-/*
- * Callback emitted when the user hits the Enter key of the fps combo.
- */
-static void
-proxycombo_activated (GtkEntry            *combo_entry,
-                      AnimationPlayDialog *dialog)
-{
-  const gchar *active_text;
-  gchar       *text;
-  gint         image_width;
-  gint         image_height;
-  gdouble      ratio;
-
-  active_text = gtk_entry_get_text (combo_entry);
-  /* Try a text conversion, locale-aware. */
-  ratio       = g_strtod (active_text, NULL);
-
-  ratio = ratio / 100.0;
-  if (ratio >= 1.0)
-    {
-      ratio = 1.0;
-    }
-  else if (ratio <= 0)
-    {
-      /* Null or negative ratio are impossible. */
-      ratio = 0.1;
-    }
-
-  image_width  = gimp_image_width (dialog->image_id);
-  image_height = gimp_image_height (dialog->image_id);
-
-  /* Now let's format the text cleanly: "%.1f %%". */
-  text = g_strdup_printf  (_("%.1f %%"), ratio * 100.0);
-  gtk_entry_set_text (combo_entry, text);
-  g_free (text);
-
-  /* Finally set the preview size, unless they were already good. */
-  if (dialog->preview_width  != image_width * ratio ||
-      dialog->preview_height != image_height * ratio)
-    {
-      gboolean     was_playing;
-
-      was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
-
-      dialog->preview_width  = image_width * ratio;
-      dialog->preview_height = image_height * ratio;
-      update_scale (dialog, get_scale (dialog, -1));
-
-      init_frames (dialog);
-      render_frame (dialog, TRUE);
-
-      if (was_playing)
-        {
-          /* Initializing frames stopped the playing. I restart it. */
-          gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-        }
-    }
-}
-
-static void
-proxycombo_changed (GtkWidget           *combo,
-                    AnimationPlayDialog *dialog)
-{
-  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-
-  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
-  if (index != -1)
-    {
-      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
-                            dialog);
-    }
-}
-
-/* Rendering Functions */
-
-static void
-render_frame (AnimationPlayDialog *dialog,
-              gboolean             force_render)
-{
-  static gint    last_frame_index        = -1;
-  static gchar  *shape_preview_mask      = NULL;
-  static guint   shape_preview_mask_size = 0;
-  static guchar *rawframe                = NULL;
-  static guint   rawframe_size           = 0;
-  GeglBuffer    *buffer;
-  gint           i, j, k;
-  guchar        *srcptr;
-  guchar        *destptr;
-  GtkWidget     *da;
-  guint          drawing_width;
-  guint          drawing_height;
-  guchar        *preview_data;
-
-  /* Do not try to update the drawing areas while init_frames() is still running. */
-  if (dialog->frames_lock)
-    return;
-
-  /* Unless we are in a case where we always want to redraw
-   * (after a zoom, preview mode change, reinitialization, and such),
-   * we don't redraw if the same frame was already drawn. */
-  if ((! force_render) && dialog->settings.num_frames > 0 && last_frame_index > -1 &&
-      g_list_find (dialog->frames[last_frame_index]->indexes,
-                   GINT_TO_POINTER (dialog->settings.current_frame - dialog->settings.frame_min)))
-    {
-      show_playing_progress (dialog);
-      return;
-    }
-
-  dialog->frames_lock = TRUE;
-
-  g_assert (dialog->settings.num_frames < 1 || (dialog->settings.current_frame >= 
dialog->settings.start_frame &&
-                                                dialog->settings.current_frame <= 
dialog->settings.end_frame));
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
-    {
-      da = dialog->shape_drawing_area;
-      preview_data = dialog->shape_drawing_area_data;
-      drawing_width = dialog->shape_drawing_area_width;
-      drawing_height = dialog->shape_drawing_area_height;
-
-      if (dialog->settings.num_frames < 1)
-        total_alpha_preview (preview_data, drawing_width, drawing_height);
-    }
-  else
-    {
-      da = dialog->drawing_area;
-      preview_data = dialog->drawing_area_data;
-      drawing_width = dialog->drawing_area_width;
-      drawing_height = dialog->drawing_area_height;
-
-      /* Set "alpha grid" background. */
-      total_alpha_preview (preview_data, drawing_width, drawing_height);
-    }
-
-  /* When there is no frame to show, we simply display the alpha background and return. */
-  if (dialog->settings.num_frames > 0)
-    {
-      /* Update the rawframe. */
-      if (rawframe_size < drawing_width * drawing_height * 4)
-        {
-          rawframe_size = drawing_width * drawing_height * 4;
-          g_free (rawframe);
-          rawframe = g_malloc (rawframe_size);
-        }
-
-      buffer = gimp_drawable_get_buffer (dialog->frames[dialog->settings.current_frame - 
dialog->settings.frame_min]->drawable_id);
-
-      /* Fetch and scale the whole raw new frame */
-      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
-                       dialog->settings.scale, babl_format ("R'G'B'A u8"),
-                       rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
-
-      /* Number of pixels. */
-      i = drawing_width * drawing_height;
-      destptr = preview_data;
-      srcptr  = rawframe;
-      while (i--)
-        {
-          if (! (srcptr[3] & 128))
-            {
-              srcptr  += 4;
-              destptr += 3;
-              continue;
-            }
-
-          *(destptr++) = *(srcptr++);
-          *(destptr++) = *(srcptr++);
-          *(destptr++) = *(srcptr++);
-
-          srcptr++;
-        }
-
-      /* calculate the shape mask */
-      if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->view_actions, "detach"))))
-        {
-          gint ideal_shape_size = (drawing_width * drawing_height) /
-                                   8 + 1 + drawing_height;
-
-          if (shape_preview_mask_size < ideal_shape_size)
-            {
-              shape_preview_mask_size = ideal_shape_size;
-              g_free (shape_preview_mask);
-              shape_preview_mask = g_malloc (ideal_shape_size);
-            }
-
-          memset (shape_preview_mask, 0,
-                  (drawing_width * drawing_height) / 8 + drawing_height);
-          srcptr = rawframe + 3;
-
-          for (j = 0; j < drawing_height; j++)
-            {
-              k = j * ((7 + drawing_width) / 8);
-
-              for (i = 0; i < drawing_width; i++)
-                {
-                  if ((*srcptr) & 128)
-                    shape_preview_mask[k + i/8] |= (1 << (i&7));
-
-                  srcptr += 4;
-                }
-            }
-          reshape_from_bitmap (dialog, shape_preview_mask);
-        }
-
-      /* clean up */
-      g_object_unref (buffer);
-
-      /* Update UI. */
-      show_playing_progress (dialog);
-
-      last_frame_index = dialog->settings.current_frame - dialog->settings.frame_min;
-    }
-
-  /* Display the preview buffer. */
-  gdk_draw_rgb_image (gtk_widget_get_window (da),
-                      (gtk_widget_get_style (da))->white_gc,
-                      (gint) ((drawing_width - dialog->settings.scale * dialog->preview_width) / 2),
-                      (gint) ((drawing_height - dialog->settings.scale * dialog->preview_height) / 2),
-                      drawing_width, drawing_height,
-                      (dialog->settings.num_frames == 1 ?
-                       GDK_RGB_DITHER_MAX : DITHERTYPE),
-                      preview_data, drawing_width * 3);
-
-  dialog->frames_lock = FALSE;
-}
-
-/* total_alpha_preview:
- * Fill the @drawing_data with an alpha (grey chess) pattern.
- * This uses a static array, copied over each line (with some shift to
- * reproduce the pattern), using `memcpy()`.
- * The reason why we keep the pattern in the statically allocated memory,
- * instead of simply looping through @drawing_data and recreating the
- * pattern is simply because `memcpy()` implementations are supposed to
- * be more efficient than loops over an array. */
-static void
-total_alpha_preview (guchar *drawing_data,
-                     guint   drawing_width,
-                     guint   drawing_height)
-{
-  static guint   alpha_line_width = 0;
-  static guchar *alpha_line       = NULL;
-  gint           i;
-
-  g_assert (drawing_width > 0);
-
-  /* If width change, we update the "alpha" line. */
-  if (alpha_line_width < drawing_width + 8)
-    {
-      alpha_line_width = drawing_width + 8;
-
-      g_free (alpha_line);
-
-      /* A full line + 8 pixels (1 square). */
-      alpha_line = g_malloc (alpha_line_width * 3);
-
-      for (i = 0; i < alpha_line_width; i++)
-        {
-          /* 8 pixels dark grey, 8 pixels light grey, and so on. */
-          if (i & 8)
-            {
-              alpha_line[i * 3 + 0] =
-                alpha_line[i * 3 + 1] =
-                alpha_line[i * 3 + 2] = 102;
-            }
-          else
-            {
-              alpha_line[i * 3 + 0] =
-                alpha_line[i * 3 + 1] =
-                alpha_line[i * 3 + 2] = 154;
-            }
-        }
-    }
-
-  for (i = 0; i < drawing_height; i++)
-    {
-      if (i & 8)
-        {
-          memcpy (&drawing_data[i * 3 * drawing_width],
-                  alpha_line,
-                  3 * drawing_width);
-        }
-      else
-        {
-          /* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
-          memcpy (&drawing_data[i * 3 * drawing_width],
-                  alpha_line + 24,
-                  3 * drawing_width);
-        }
-    }
-}
-
-static void
-reshape_from_bitmap (AnimationPlayDialog *dialog,
-                     const gchar         *bitmap)
-{
-  static gchar *prev_bitmap = NULL;
-  static guint  prev_width = -1;
-  static guint  prev_height = -1;
-
-  if ((! prev_bitmap)                                  ||
-      prev_width != dialog->shape_drawing_area_width   ||
-      prev_height != dialog->shape_drawing_area_height ||
-      (memcmp (prev_bitmap, bitmap,
-               (dialog->shape_drawing_area_width *
-                dialog->shape_drawing_area_height) / 8 +
-               dialog->shape_drawing_area_height)))
-    {
-      GdkBitmap *shape_mask;
-
-      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (dialog->shape_window),
-                                                bitmap,
-                                                dialog->shape_drawing_area_width, 
dialog->shape_drawing_area_height);
-      gtk_widget_shape_combine_mask (dialog->shape_window, shape_mask, 0, 0);
-      g_object_unref (shape_mask);
-
-      if (!prev_bitmap || prev_width != dialog->shape_drawing_area_width || prev_height != 
dialog->shape_drawing_area_height)
-        {
-          g_free(prev_bitmap);
-          prev_bitmap = g_malloc ((dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 8 
+ dialog->shape_drawing_area_height);
-          prev_width = dialog->shape_drawing_area_width;
-          prev_height = dialog->shape_drawing_area_height;
-        }
-
-      memcpy (prev_bitmap, bitmap, (dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 
8 + dialog->shape_drawing_area_height);
-    }
-}
-
-static void
-show_playing_progress (AnimationPlayDialog *dialog)
-{
-  gchar *text;
-
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
-                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
-                                  (gfloat) (dialog->settings.num_frames - 0.999)));
-
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
-    {
-      text = g_strdup_printf (_("Frame %d of %d"),
-                              dialog->settings.current_frame,
-                              dialog->settings.num_frames);
-    }
-  else
-    {
-      text = g_strdup_printf (_("Frame %d (%d) of %d"),
-                              dialog->settings.current_frame - dialog->settings.frame_min + 1,
-                              dialog->settings.current_frame, dialog->settings.num_frames);
-    }
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
-}
-
-static void
-show_loading_progress (AnimationPlayDialog *dialog,
-                       gint                 layer_nb,
-                       gint                 num_layers)
-{
-  gchar *text;
-  gfloat load_rate = (gfloat) layer_nb / ((gfloat) num_layers - 0.999);
-
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress), load_rate);
-
-  text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
-
-  /* Forcing the UI to update even with intensive computation. */
-  while (gtk_events_pending ())
-    gtk_main_iteration ();
-}
-
-static void
-do_back_step (AnimationPlayDialog *dialog)
-{
-  if (dialog->settings.current_frame == dialog->settings.start_frame)
-    {
-      dialog->settings.current_frame = dialog->settings.end_frame;
-    }
-  else
-    {
-      --dialog->settings.current_frame;
-    }
-  render_frame (dialog, FALSE);
-}
-
-static void
-do_step (AnimationPlayDialog *dialog)
-{
-  dialog->settings.current_frame = dialog->settings.start_frame +
-                                   ((dialog->settings.current_frame - dialog->settings.start_frame + 1) %
-                                    (dialog->settings.end_frame - dialog->settings.start_frame + 1));
-
-  render_frame (dialog, FALSE);
-}
-
-static void
-set_timer (guint new_timer)
-{
-  static guint timer = 0;
-
-  if (timer)
-    {
-      g_source_remove (timer);
-    }
-  timer = new_timer;
-}
-
-static gboolean
-advance_frame_callback (AnimationPlayDialog *dialog)
-{
-  guint duration;
-  guint timer;
-
-  duration = dialog->frames[(dialog->settings.current_frame - dialog->settings.frame_min + 1) %
-             dialog->settings.num_frames]->duration;
-  if (duration <= 0)
-    {
-      duration = (guint) (1000.0 / ((AnimationPlayDialog *) dialog)->settings.frame_rate);
-    }
-
-  timer = g_timeout_add (duration,
-                         (GSourceFunc) advance_frame_callback,
-                         (AnimationPlayDialog *) dialog);
-  set_timer (timer);
-
-  do_step (dialog);
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-show_goto_progress (guint                goto_frame,
-                    AnimationPlayDialog *dialog)
-{
-  gchar         *text;
-
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
-                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
-                                  (gfloat) (dialog->settings.num_frames - 0.999)));
+  values[0].type = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
 
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
-    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, dialog->settings.num_frames);
-  else
-    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - dialog->settings.frame_min + 1, 
goto_frame, dialog->settings.num_frames);
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
+  gegl_exit ();
+  gimp_quit ();
 }
 
 /************ UTILS ****************/
 
 static void
-connect_accelerators (GtkUIManager   *ui_manager,
-                      GtkActionGroup *group)
-{
-  GList          *action_list;
-  GList          *iter;
-
-  action_list = gtk_action_group_list_actions (group);
-  iter = action_list;
-  while (iter)
-    {
-      /* Make sure all the action's accelerator are correctly connected,
-       * even when there are no associated UI item. */
-      GtkAction *action = GTK_ACTION (iter->data);
-
-      gtk_action_set_accel_group (action,
-                                  gtk_ui_manager_get_accel_group (ui_manager));
-      gtk_action_connect_accelerator (action);
-
-      iter = iter->next;
-    }
-  g_list_free (action_list);
-}
-
-/* get_fps:
- * Frame rate proposed as default.
- * These are the most common framerates.
- */
-static gdouble
-get_fps (gint       index)
-{
-  gdouble fps;
-
-  switch (index)
-    {
-    case 0:
-      fps = 12.0;
-      break;
-    case 1:
-      fps = 24.0;
-      break;
-    case 2:
-      fps = 25.0;
-      break;
-    case 3:
-      fps = 30.0;
-      break;
-    case 4:
-      fps = 48.0;
-      break;
-    default:
-      fps = 24.0;
-      break;
-    }
-
-  return fps;
-}
-
-static gdouble
-get_proxy (AnimationPlayDialog *dialog,
-           gint                 index)
-{
-  switch (index)
-    {
-    case 0:
-      return 0.25;
-    case 1:
-      return 0.5;
-    case 2:
-      return 0.75;
-    default:
-      {
-        /* likely -1 returned if there is no active item from the list.
-         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
-        gchar   *active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dialog->proxycombo));
-        gdouble  proxy       = g_strtod (active_text, NULL);
-
-        /* Proxy over 100% are meaningless. */
-        if (proxy > 100.0)
-          {
-            proxy = 100.0;
-          }
-        else if (proxy <= 0.0)
-          {
-            proxy = 1.0;
-          }
-        g_free (active_text);
-        return proxy / 100.0;
-      }
-    }
-}
-
-static void
-update_scale (AnimationPlayDialog *dialog,
-              gdouble              scale)
-{
-  guint expected_drawing_area_width;
-  guint expected_drawing_area_height;
-
-  /* FIXME: scales under 0.5 are broken. See bug 690265. */
-  if (scale <= 0.5)
-    scale = 0.51;
-
-  expected_drawing_area_width  = dialog->preview_width * scale;
-  expected_drawing_area_height = dialog->preview_height * scale;
-
-  /* We don't update dialog->settings.scale directly because this might
-   * end up not being the real scale. Instead we request this size for
-   * the drawing areas, and the actual scale update will be done on the
-   * callback when size is actually allocated. */
-  gtk_widget_set_size_request (dialog->drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
-  gtk_widget_set_size_request (dialog->shape_drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
-  /* I force the shape window to a smaller size if we scale down. */
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
-    {
-      gint x, y;
-
-      gdk_window_get_origin (gtk_widget_get_window (dialog->shape_window), &x, &y);
-      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->shape_window));
-      gtk_window_move (GTK_WINDOW (dialog->shape_window), x, y);
-    }
-}
-
-static gdouble
-get_scale (AnimationPlayDialog *dialog,
-           gint                 index)
+save_settings (Animation       *animation,
+               gint32           image_id)
 {
-  switch (index)
-    {
-    case 0:
-      return 0.51;
-    case 1:
-      return 1.0;
-    case 2:
-      return 1.25;
-    case 3:
-      return 1.5;
-    case 4:
-      return 2.0;
-    default:
-      {
-        /* likely -1 returned if there is no active item from the list.
-         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
-        gchar   *active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dialog->zoomcombo));
-        gdouble  zoom        = g_strtod (active_text, NULL);
+  GimpParasite   *old_parasite;
+  gboolean        undo_step_started = FALSE;
+  CachedSettings  cached_settings;
 
-        /* Negative scales are inconsistent. And we want to avoid huge scaling. */
-        if (zoom > 300.0)
-          zoom = 300.0;
-        else if (zoom <= 50.0)
-          /* FIXME: scales under 0.5 are broken. See bug 690265. */
-          zoom = 50.1;
-        g_free (active_text);
-        return zoom / 100.0;
-      }
-    }
-}
+  /* First saving in cache for any image. */
+  cached_settings.disposal  = animation_get_disposal (animation);
+  cached_settings.framerate = animation_get_framerate (animation);
 
-static void
-save_settings (AnimationPlayDialog *dialog)
-{
-  GimpParasite *old_parasite;
-  gboolean      undo_step_started = FALSE;
-
-  /* First saving the settings globally. */
-  gimp_set_data (PLUG_IN_PROC, &dialog->settings, sizeof (&dialog->settings));
+  gimp_set_data (PLUG_IN_PROC, &cached_settings, sizeof (&cached_settings));
 
   /* Then as a parasite for the specific image.
    * If there was already parasites and they were all the same as the
    * current state, do not resave them.
    * This prevents setting the image in a dirty state while it stayed
    * the same. */
-  old_parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-disposal");
+  old_parasite = gimp_image_get_parasite (image_id, PLUG_IN_PROC "/frame-disposal");
   if (! old_parasite ||
-      *(DisposeType *) gimp_parasite_data (old_parasite) != dialog->settings.frame_disposal)
+      *(DisposeType *) gimp_parasite_data (old_parasite) != animation_get_disposal (animation))
     {
       GimpParasite *parasite;
+      DisposeType   disposal = animation_get_disposal (animation);
 
-      gimp_image_undo_group_start (dialog->image_id);
+      gimp_image_undo_group_start (image_id);
       undo_step_started = TRUE;
 
       parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-disposal",
                                     GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
-                                    sizeof (dialog->settings.frame_disposal),
-                                    &dialog->settings.frame_disposal);
-      gimp_image_attach_parasite (dialog->image_id, parasite);
+                                    sizeof (disposal),
+                                    &disposal);
+      gimp_image_attach_parasite (image_id, parasite);
       gimp_parasite_free (parasite);
     }
   gimp_parasite_free (old_parasite);
 
-  old_parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-rate");
+  old_parasite = gimp_image_get_parasite (image_id, PLUG_IN_PROC "/framerate");
   if (! old_parasite ||
-      *(gdouble *) gimp_parasite_data (old_parasite) != dialog->settings.frame_rate)
+      *(gdouble *) gimp_parasite_data (old_parasite) != cached_settings.framerate)
     {
       GimpParasite *parasite;
       if (! undo_step_started)
         {
-          gimp_image_undo_group_start (dialog->image_id);
+          gimp_image_undo_group_start (image_id);
           undo_step_started = TRUE;
         }
       parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-rate",
                                     GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
-                                    sizeof (dialog->settings.frame_rate),
-                                    &dialog->settings.frame_rate);
-      gimp_image_attach_parasite (dialog->image_id, parasite);
+                                    sizeof (cached_settings.framerate),
+                                    &cached_settings.framerate);
+      gimp_image_attach_parasite (image_id, parasite);
       gimp_parasite_free (parasite);
     }
   gimp_parasite_free (old_parasite);
 
   if (undo_step_started)
     {
-      gimp_image_undo_group_end (dialog->image_id);
-    }
-}
-
-static void
-clean_exit (AnimationPlayDialog *dialog)
-{
-  save_settings (dialog);
-
-  /* Frames are associated to an unused GimpImage.
-   * Initializing frames with a NULL image allows to free this image,
-   * otherwise we would leak GEGL buffers. */
-  dialog->image_id = 0;
-  init_frames (dialog);
-
-  if (dialog->shape_window)
-    gtk_widget_destroy (GTK_WIDGET (dialog->shape_window));
-
-  g_signal_handlers_disconnect_by_func (dialog->window,
-                                        G_CALLBACK (window_destroy),
-                                        dialog);
-  if (dialog->window)
-    gtk_widget_destroy (GTK_WIDGET (dialog->window));
-
-  if (dialog->frames)
-    g_free (dialog->frames);
-
-  g_free (dialog);
-
-  gegl_exit ();
-  gtk_main_quit ();
-  gimp_quit ();
-}
-
-/******* TAG UTILS **************/
-
-static gboolean
-is_disposal_tag (const gchar *str,
-                 DisposeType *disposal,
-                 gint        *taglength)
-{
-  if (strlen (str) != 9)
-    return FALSE;
-
-  if (strncmp (str, "(combine)", 9) == 0)
-    {
-      *taglength = 9;
-      *disposal = DISPOSE_COMBINE;
-      return TRUE;
-    }
-  else if (strncmp (str, "(replace)", 9) == 0)
-    {
-      *taglength = 9;
-      *disposal = DISPOSE_REPLACE;
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static DisposeType
-parse_disposal_tag (AnimationPlayDialog *dialog,
-                    const gchar         *str)
-{
-  gint i;
-  gint length = strlen (str);
-
-  for (i = 0; i < length; i++)
-    {
-      DisposeType rtn;
-      gint        dummy;
-
-      if (is_disposal_tag (&str[i], &rtn, &dummy))
-        return rtn;
-    }
-
-  return dialog->settings.frame_disposal;
-}
-
-static gboolean
-is_ms_tag (const gchar *str,
-           gint        *duration,
-           gint        *taglength)
-{
-  gint sum = 0;
-  gint offset;
-  gint length;
-
-  length = strlen(str);
-
-  if (str[0] != '(')
-    return FALSE;
-
-  offset = 1;
-
-  /* eat any spaces between open-parenthesis and number */
-  while ((offset < length) && (str[offset] == ' '))
-    offset++;
-
-  if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
-    return FALSE;
-
-  do
-    {
-      sum *= 10;
-      sum += str[offset] - '0';
-      offset++;
-    }
-  while ((offset<length) && (g_ascii_isdigit (str[offset])));
-
-  if (length - offset <= 2)
-    return FALSE;
-
-  /* eat any spaces between number and 'ms' */
-  while ((offset < length) && (str[offset] == ' '))
-    offset++;
-
-  if (length - offset <= 2                     ||
-      g_ascii_toupper (str[offset])     != 'M' ||
-      g_ascii_toupper (str[offset + 1]) != 'S')
-    return FALSE;
-
-  offset += 2;
-
-  /* eat any spaces between 'ms' and close-parenthesis */
-  while ((offset < length) && (str[offset] == ' '))
-    offset++;
-
-  if ((length - offset < 1) || (str[offset] != ')'))
-    return FALSE;
-
-  offset++;
-
-  *duration = sum;
-  *taglength = offset;
-
-  return TRUE;
-}
-
-static gint
-parse_ms_tag (const gchar *str)
-{
-  gint i;
-  gint length = strlen (str);
-
-  for (i = 0; i < length; i++)
-    {
-      gint rtn;
-      gint dummy;
-
-      if (is_ms_tag (&str[i], &rtn, &dummy))
-        return rtn;
-    }
-
-  return -1;
-}
-
-/**
- * A recursive call which will call itself for layer groups
- * and update the min/max progressively.
- **/
-static void
-rec_set_total_frames (const gint32  layer,
-                      gint         *min,
-                      gint         *max)
-{
-  gchar        *layer_name;
-  gchar        *nospace_name;
-  GMatchInfo   *match_info;
-  gint          i;
-
-  if (gimp_item_is_group (layer))
-    {
-      gint    num_children;
-      gint32 *children;
-
-      children = gimp_item_get_children (layer, &num_children);
-      for (i = 0; i < num_children; i++)
-        rec_set_total_frames (children[i], min, max);
-
-      return;
+      gimp_image_undo_group_end (image_id);
     }
-
-  /* TODO: use parasites instead! */
-  layer_name = gimp_item_get_name (layer);
-  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
-
-  g_regex_match (layers_reg, nospace_name, 0, &match_info);
-
-  while (g_match_info_matches (match_info))
-    {
-      gchar *tag = g_match_info_fetch (match_info, 1);
-      gchar** tokens = g_strsplit(tag, ",", 0);
-
-      for (i = 0; tokens[i] != NULL; i++)
-        {
-          gchar* hyphen;
-          hyphen = g_strrstr(tokens[i], "-");
-          if (hyphen != NULL)
-            {
-              gint32 first = (gint32) g_ascii_strtoll (tokens[i], NULL, 10);
-              gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
-              *max = (second > first && second > *max)? second : *max;
-              *min = (second > first && first < *min)? first : *min;
-            }
-          else
-            {
-              gint32 num = (gint32) g_ascii_strtoll (tokens[i], NULL, 10);
-              *max = (num > *max)? num : *max;
-              *min = (num < *min)? num : *min;
-            }
-        }
-      g_strfreev (tokens);
-      g_free (tag);
-      g_free (layer_name);
-      g_match_info_next (match_info, NULL);
-    }
-
-  g_free (nospace_name);
-  g_match_info_free (match_info);
 }
-
diff --git a/plug-ins/animation-play/animation-utils.c b/plug-ins/animation-play/animation-utils.c
new file mode 100755
index 0000000..333e0ce
--- /dev/null
+++ b/plug-ins/animation-play/animation-utils.c
@@ -0,0 +1,98 @@
+/*
+ * Animation Playback plug-in version 0.99.1
+ *
+ * (c) Jehan : 2012-2014 : jehan at girinstud.io
+ *
+ * GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#undef GDK_DISABLE_DEPRECATED
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include "animation-utils.h"
+
+/* total_alpha_preview:
+ * Fill the @drawing_data with an alpha (grey chess) pattern.
+ * This uses a static array, copied over each line (with some shift to
+ * reproduce the pattern), using `memcpy()`.
+ * The reason why we keep the pattern in the statically allocated memory,
+ * instead of simply looping through @drawing_data and recreating the
+ * pattern is simply because `memcpy()` implementations are supposed to
+ * be more efficient than loops over an array. */
+void
+total_alpha_preview (guchar *drawing_data,
+                     guint   drawing_width,
+                     guint   drawing_height)
+{
+  static guint   alpha_line_width = 0;
+  static guchar *alpha_line       = NULL;
+  gint           i;
+
+  g_assert (drawing_width > 0);
+
+  /* If width change, we update the "alpha" line. */
+  if (alpha_line_width < drawing_width + 8)
+    {
+      alpha_line_width = drawing_width + 8;
+
+      g_free (alpha_line);
+
+      /* A full line + 8 pixels (1 square). */
+      alpha_line = g_malloc (alpha_line_width * 3);
+
+      for (i = 0; i < alpha_line_width; i++)
+        {
+          /* 8 pixels dark grey, 8 pixels light grey, and so on. */
+          if (i & 8)
+            {
+              alpha_line[i * 3 + 0] =
+                alpha_line[i * 3 + 1] =
+                alpha_line[i * 3 + 2] = 102;
+            }
+          else
+            {
+              alpha_line[i * 3 + 0] =
+                alpha_line[i * 3 + 1] =
+                alpha_line[i * 3 + 2] = 154;
+            }
+        }
+    }
+
+  for (i = 0; i < drawing_height; i++)
+    {
+      if (i & 8)
+        {
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line,
+                  3 * drawing_width);
+        }
+      else
+        {
+          /* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line + 24,
+                  3 * drawing_width);
+        }
+    }
+}
diff --git a/plug-ins/animation-play/animation-utils.h b/plug-ins/animation-play/animation-utils.h
new file mode 100755
index 0000000..09e084b
--- /dev/null
+++ b/plug-ins/animation-play/animation-utils.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-utils.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ANIMATION_UTILS_H__
+#define __ANIMATION_UTILS_H__
+
+#define PLUG_IN_PROC   "plug-in-animationplay"
+#define PLUG_IN_BINARY "animation-play"
+#define PLUG_IN_ROLE   "gimp-animation-playback"
+
+void total_alpha_preview (guchar *drawing_data,
+                          guint   drawing_width,
+                          guint   drawing_height);
+
+#endif  /*  __ANIMATION_UTILS_H__  */
+
+
diff --git a/plug-ins/animation-play/animation.c b/plug-ins/animation-play/animation.c
new file mode 100644
index 0000000..7034d5f
--- /dev/null
+++ b/plug-ins/animation-play/animation.c
@@ -0,0 +1,1421 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/stdplugins-intl.h>
+
+#include "animation.h"
+
+enum
+{
+  LOADING_START,
+  LOADING,
+  LOADED,
+  DISPOSAL_CHANGED,
+  PROXY_CHANGED,
+  FRAMERATE_CHANGED,
+  PLAYBACK_RANGE,
+  RENDER,
+  LOW_FRAMERATE,
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_IMAGE
+};
+
+typedef struct
+{
+  gint32  drawable_id;
+  GList  *indexes;
+  GList  *updated_indexes;
+
+  guint  duration;
+}
+Frame;
+
+typedef struct _AnimationPrivate AnimationPrivate;
+
+struct _AnimationPrivate
+{
+  gint32       image_id;
+
+  Frame      **frames;
+  guint        preview_width;
+  guint        preview_height;
+  gdouble      framerate;
+  guint        playback_timer;
+
+  /* State of the currently loaded animation. */
+  gint32       num_frames;
+  gint         current_frame;
+  /* We want to allow animator to set any number as first frame,
+   * for frame export capability. */
+  guint        first_frame;
+
+  /* Playback can be a subset of frames. */
+  guint        playback_start;
+  guint        playback_stop;
+
+  /* These settings generate a reload. */
+  DisposeType  disposal;
+  gdouble      proxy_ratio;
+
+  /* Frames are associated to an unused image (used as backend for GEGL buffers). */
+  gboolean     loaded;
+  gint32       frames_image_id;
+  /* We keep track of the frames in a separate structure to free drawable
+   * memory. We can't use easily priv->frames because some of the
+   * drawables may be used in more than 1 frame. */
+  GList       *previous_frames;
+};
+
+
+#define ANIMATION_GET_PRIVATE(animation) \
+        G_TYPE_INSTANCE_GET_PRIVATE (animation, \
+                                     ANIMATION_TYPE_ANIMATION, \
+                                     AnimationPrivate)
+
+static void         animation_set_property       (GObject      *object,
+                                                  guint         property_id,
+                                                  const GValue *value,
+                                                  GParamSpec   *pspec);
+static void         animation_get_property       (GObject      *object,
+                                                  guint         property_id,
+                                                  GValue       *value,
+                                                  GParamSpec   *pspec);
+static void         animation_finalize           (GObject      *object);
+
+static void         cleanup_cache                (Animation    *animation);
+static void         animation_init_frame_numbers (Animation    *animation,
+                                                  gboolean      disposal_changed);
+static void         rec_set_total_frames         (Animation    *animation,
+                                                  const gint32  layer,
+                                                  gint         *min,
+                                                  gint         *max);
+static void         rec_init_frames              (Animation    *animation,
+                                                  gint32        frames_image_id,
+                                                  gint          image_width,
+                                                  gint          image_height,
+                                                  gint32        layer,
+                                                  GList        *previous_frames);
+static gint         parse_ms_tag                 (const gchar  *str);
+static DisposeType  parse_disposal_tag           (Animation    *animation,
+                                                  const gchar  *str);
+
+static gboolean     is_ms_tag                    (const gchar  *str,
+                                                  gint         *duration,
+                                                  gint         *taglength);
+static gboolean     is_disposal_tag              (const gchar  *str,
+                                                  DisposeType  *disposal,
+                                                  gint         *taglength);
+static gint         animation_time_to_next       (Animation    *animation,
+                                                  gboolean      reset);
+static gboolean     advance_frame_callback       (Animation    *animation);
+static gboolean     animation_must_redraw        (Animation    *animation,
+                                                  gint          previous_frame,
+                                                  gint          next_frame);
+
+
+G_DEFINE_TYPE (Animation, animation, G_TYPE_OBJECT)
+
+#define parent_class animation_parent_class
+
+static GRegex *nospace_reg;
+static GRegex *layers_reg;
+static GRegex *all_reg;
+
+static guint   animation_signals[LAST_SIGNAL] = { 0 };
+
+static void
+animation_class_init (AnimationClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    /* Loading in progress. The parameter is load percentage. */
+    animation_signals[LOADING] =
+      g_signal_new ("loading",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    1,
+                    G_TYPE_DOUBLE);
+    animation_signals[LOADING_START] =
+      g_signal_new ("loading-start",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    0);
+    /* Loading ended. Parameters are:
+     * - first_frame
+     * - length
+     * - playback_start
+     * - playback_stop
+     * - width
+     * - height
+     */
+    animation_signals[LOADED] =
+      g_signal_new ("loaded",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    6,
+                    G_TYPE_INT,
+                    G_TYPE_INT,
+                    G_TYPE_INT,
+                    G_TYPE_INT,
+                    G_TYPE_INT,
+                    G_TYPE_INT);
+    animation_signals[DISPOSAL_CHANGED] =
+      g_signal_new ("disposal-changed",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    1,
+                    G_TYPE_INT);
+    animation_signals[PROXY_CHANGED] =
+      g_signal_new ("proxy-changed",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    1,
+                    G_TYPE_DOUBLE);
+    animation_signals[FRAMERATE_CHANGED] =
+      g_signal_new ("framerate-changed",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    1,
+                    G_TYPE_DOUBLE);
+    animation_signals[PLAYBACK_RANGE] =
+      g_signal_new ("playback-range",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    2,
+                    G_TYPE_INT,
+                    G_TYPE_INT);
+    animation_signals[RENDER] =
+      g_signal_new ("render",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    2,
+                    G_TYPE_INT,
+                    GEGL_TYPE_BUFFER);
+    animation_signals[LOW_FRAMERATE] =
+      g_signal_new ("low-framerate-playback",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_FIRST,
+                    0,
+                    NULL, NULL,
+                    NULL,
+                    G_TYPE_NONE,
+                    1,
+                    G_TYPE_DOUBLE);
+
+    object_class->finalize     = animation_finalize;
+    object_class->get_property = animation_get_property;
+    object_class->set_property = animation_set_property;
+
+    /**
+     * Animation:image:
+     *
+     * The attached image id.
+     */
+    g_object_class_install_property (object_class, PROP_IMAGE,
+                                     g_param_spec_int ("image", NULL, NULL,
+                                                       0, G_MAXINT, 0,
+                                                       G_PARAM_READWRITE |
+                                                       G_PARAM_CONSTRUCT));
+
+    /* Init the global variable: frame parsing. */
+    nospace_reg = g_regex_new("[ \t]*", G_REGEX_OPTIMIZE, 0, NULL);
+    layers_reg  = g_regex_new("\\[(([0-9]+(-[0-9]+)?)(,[0-9]+(-[0-9]+)?)*)\\]", G_REGEX_OPTIMIZE, 0, NULL);
+    all_reg     = g_regex_new("\\[\\*\\]", G_REGEX_OPTIMIZE, 0, NULL);
+
+    g_type_class_add_private (klass, sizeof (AnimationPrivate));
+}
+
+static void
+animation_init (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  /* Acceptable settings for the default. */
+  priv->disposal    = DISPOSE_COMBINE;
+  priv->proxy_ratio = 1.0;
+  priv->framerate   = 24.0; /* fps */
+}
+
+/************ Public Functions ****************/
+
+Animation *
+animation_new (gint32 image_id)
+{
+  Animation *animation;
+
+  animation = g_object_new (ANIMATION_TYPE_ANIMATION,
+                            "image", image_id,
+                            NULL);
+
+  return animation;
+}
+
+void
+animation_load (Animation   *animation,
+                DisposeType  disposal,
+                gdouble      proxy_ratio)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  gint             *layers;
+  GeglBuffer       *buffer;
+  gint              num_layers;
+  gint32            new_frame, previous_frame, new_layer;
+  gint              duration = 0;
+  gint              image_width;
+  gint              image_height;
+
+  /* In case we were playing. */
+  animation_stop (animation);
+
+  priv->loaded = FALSE;
+
+  if (! gimp_image_is_valid (priv->image_id))
+    {
+      gimp_message (_("Invalid image. Did you close it?"));
+      return;
+    }
+
+  /* Block most UI during frame initialization. */
+  g_signal_emit (animation, animation_signals[LOADING_START],
+                 0, NULL);
+
+  /* Cleanup before re-generation. */
+  cleanup_cache (animation);
+
+  if (disposal != DISPOSE_KEEP)
+    {
+      priv->disposal = disposal;
+    }
+
+  animation_init_frame_numbers (animation, disposal != DISPOSE_KEEP);
+  layers = gimp_image_get_layers (priv->image_id, &num_layers);
+
+  if (priv->num_frames <= 0)
+    {
+      goto animation_loaded;
+    }
+
+  priv->frames = g_try_malloc0_n (priv->num_frames, sizeof (Frame*));
+  if (! priv->frames)
+    {
+      gimp_message (_("Memory could not be allocated to the frame container."));
+      goto animation_loaded;
+    }
+
+  /* We default at full preview size. */
+  image_width = gimp_image_width (priv->image_id);
+  image_height = gimp_image_height (priv->image_id);
+
+  priv->preview_width = image_width;
+  priv->preview_height = image_height;
+
+  if (proxy_ratio != PROXY_KEEP)
+    {
+      priv->proxy_ratio = proxy_ratio;
+    }
+  priv->preview_width  *= priv->proxy_ratio;
+  priv->preview_height *= priv->proxy_ratio;
+
+  /* We only use RGB images for display because indexed images would somehow
+     render terrible colors. Layers from other types will be automatically
+     converted. */
+  priv->frames_image_id = gimp_image_new (priv->preview_width, priv->preview_height, GIMP_RGB);
+
+  /* Save processing time and memory by not saving history and merged frames. */
+  gimp_image_undo_disable (priv->frames_image_id);
+
+  if (priv->disposal == DISPOSE_TAGS)
+    {
+      gint        i;
+
+      for (i = 0; i < num_layers; i++)
+        {
+          g_signal_emit (animation, animation_signals[LOADING],
+                         0, (gdouble) i / ((gdouble) num_layers - 0.999), NULL);
+          rec_init_frames (animation,
+                           priv->frames_image_id,
+                           image_width, image_height,
+                           layers[num_layers - (i + 1)],
+                           priv->previous_frames);
+        }
+
+      for (i = 0; i < priv->num_frames; i++)
+        {
+          /* If for some reason a frame is absent, use the previous one.
+           * We are ensured that there is at least a "first" frame for this. */
+          if (! priv->frames[i])
+            {
+              priv->frames[i] = priv->frames[i - 1];
+              priv->frames[i]->indexes = g_list_append (priv->frames[i]->indexes, GINT_TO_POINTER (i));
+            }
+
+          /* A zero duration only means we use the global duration, whatever it is at the time. */
+          priv->frames[i]->duration = 0;
+        }
+    }
+  else
+    {
+      gint   layer_offx;
+      gint   layer_offy;
+      gchar *layer_name;
+      gint   i;
+
+      for (i = 0; i < priv->num_frames; i++)
+        {
+          DisposeType tmp_disposal = priv->disposal;
+
+          g_signal_emit (animation, animation_signals[LOADING],
+                         0, (gdouble) i / ((gdouble) num_layers - 0.999), NULL);
+
+          priv->frames[i] = g_new (Frame, 1);
+          priv->frames[i]->indexes = NULL;
+          priv->frames[i]->indexes = g_list_append (priv->frames[i]->indexes, GINT_TO_POINTER (i));
+          priv->frames[i]->updated_indexes = NULL;
+
+          priv->previous_frames = g_list_append (priv->previous_frames, priv->frames[i]);
+
+          layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
+          if (layer_name)
+            {
+              duration = parse_ms_tag (layer_name);
+              tmp_disposal = parse_disposal_tag (animation, layer_name);
+              g_free (layer_name);
+            }
+
+          if (i > 0 && tmp_disposal != DISPOSE_REPLACE)
+            {
+              previous_frame = gimp_layer_copy (priv->frames[i - 1]->drawable_id);
+
+              gimp_image_insert_layer (priv->frames_image_id, previous_frame, 0, 0);
+              gimp_item_set_visible (previous_frame, TRUE);
+            }
+
+          new_layer = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)], priv->frames_image_id);
+
+          gimp_image_insert_layer (priv->frames_image_id, new_layer, 0, 0);
+          gimp_item_set_visible (new_layer, TRUE);
+          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[num_layers - (i + 1)]) * (gint) 
priv->preview_width) / image_width,
+                            (gimp_drawable_height (layers[num_layers - (i + 1)]) * (gint) 
priv->preview_height) / image_height, FALSE);
+          gimp_drawable_offsets (layers[num_layers - (i + 1)], &layer_offx, &layer_offy);
+          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) priv->preview_width) / image_width,
+                                  (layer_offy * (gint) priv->preview_height) / image_height);
+          gimp_layer_resize_to_image_size (new_layer);
+
+          if (gimp_item_is_group (new_layer))
+            {
+              gint    num_children;
+              gint32 *children;
+              gint    j;
+
+              /* I want to make all layers in the group visible, so that when I'll make
+               * the group visible too at render time, it will display everything in it. */
+              children = gimp_item_get_children (new_layer, &num_children);
+              for (j = 0; j < num_children; j++)
+                gimp_item_set_visible (children[j], TRUE);
+            }
+
+          new_frame = gimp_image_merge_visible_layers (priv->frames_image_id, GIMP_CLIP_TO_IMAGE);
+          priv->frames[i]->drawable_id = new_frame;
+          gimp_item_set_visible (new_frame, FALSE);
+
+          if (duration <= 0)
+            duration = 0;
+          priv->frames[i]->duration = (guint) duration;
+        }
+    }
+
+animation_loaded:
+  priv->loaded = TRUE;
+  g_signal_emit (animation, animation_signals[DISPOSAL_CHANGED], 0,
+                 priv->disposal);
+  g_signal_emit (animation, animation_signals[PROXY_CHANGED], 0,
+                 priv->proxy_ratio);
+  g_signal_emit (animation, animation_signals[LOADED], 0,
+                 priv->first_frame,
+                 priv->num_frames,
+                 animation_get_playback_start (animation),
+                 animation_get_playback_stop (animation),
+                 priv->preview_width,
+                 priv->preview_height,
+                 NULL);
+  buffer = animation_get_frame (animation, priv->current_frame);
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->current_frame, buffer);
+  g_free (layers);
+}
+
+void
+animation_play (Animation   *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  gdouble fps = animation_get_framerate (animation);
+  guint duration = animation_time_to_next (animation, TRUE);
+
+  if (priv->playback_timer)
+    {
+      /* It means we are already playing, so we should not need to play
+       * again.
+       * Still be liberal and simply remove the timer before creating a
+       * new one. */
+      g_source_remove (priv->playback_timer);
+    }
+
+  if (duration <= 0)
+    duration = (guint) (1000.0 / fps);
+
+  priv->playback_timer = g_timeout_add (duration, (GSourceFunc) advance_frame_callback, animation);
+}
+
+void
+animation_stop (Animation   *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  if (priv->playback_timer)
+    {
+      /* Stop playing by removing any playback timer. */
+      g_source_remove (priv->playback_timer);
+      priv->playback_timer = 0;
+    }
+}
+
+gchar *
+animation_get_name (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return gimp_image_get_name (priv->image_id);
+}
+
+DisposeType
+animation_get_disposal (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->disposal;
+}
+
+gdouble
+animation_get_proxy (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->proxy_ratio;
+}
+
+void
+animation_get_size (Animation *animation,
+                    gint      *width,
+                    gint      *height)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  *width = priv->preview_width;
+  *height = priv->preview_height;
+}
+
+gint
+animation_get_length (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->num_frames;
+}
+
+gint animation_get_first_frame (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->first_frame;
+}
+
+gint
+animation_get_position (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->current_frame;
+}
+
+void
+animation_set_playback_start (Animation *animation,
+                              gint       frame_number)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  if (frame_number < priv->first_frame ||
+      frame_number >= priv->first_frame + priv->num_frames)
+    {
+      priv->playback_start = priv->first_frame;
+    }
+  else
+    {
+      priv->playback_start = frame_number;
+    }
+  if (priv->playback_stop < priv->playback_start)
+    {
+      priv->playback_stop = priv->first_frame + priv->num_frames - 1;
+    }
+
+  g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
+                 priv->playback_start, priv->playback_stop);
+
+  if (priv->current_frame < priv->playback_start ||
+      priv->current_frame > priv->playback_stop)
+    {
+      animation_jump (animation, priv->playback_start);
+    }
+}
+
+gint
+animation_get_playback_start (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->playback_start;
+}
+
+void
+animation_set_playback_stop (Animation *animation,
+                             gint       frame_number)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  if (frame_number < priv->first_frame ||
+      frame_number >= priv->first_frame + priv->num_frames)
+    {
+      priv->playback_stop = priv->first_frame + priv->num_frames - 1;
+    }
+  else
+    {
+      priv->playback_stop = frame_number;
+    }
+  if (priv->playback_stop < priv->playback_start)
+    {
+      priv->playback_start = priv->first_frame;
+    }
+  g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
+                 priv->playback_start, priv->playback_stop);
+
+  if (priv->current_frame < priv->playback_start ||
+      priv->current_frame > priv->playback_stop)
+    {
+      animation_jump (animation, priv->playback_start);
+    }
+}
+
+gint
+animation_get_playback_stop (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->playback_stop;
+}
+
+void
+animation_set_framerate (Animation *animation,
+                         gdouble    fps)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  priv->framerate = fps;
+
+  g_signal_emit (animation, animation_signals[FRAMERATE_CHANGED], 0,
+                 fps);
+}
+
+gdouble
+animation_get_framerate (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->framerate;
+}
+
+gboolean
+animation_loaded (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->loaded;
+}
+
+/* @return the GeglBuffer for the current frame.
+ * The returned buffer must be unref-ed after use. */
+GeglBuffer *
+animation_get_frame (Animation *animation,
+                     gint       frame_number)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer = NULL;
+
+  if (priv->loaded && priv->num_frames > 0)
+    buffer = gimp_drawable_get_buffer (priv->frames[frame_number - priv->first_frame]->drawable_id);
+
+  return buffer;
+}
+
+void
+animation_next (Animation *animation)
+{
+  AnimationPrivate *priv           = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer         = NULL;
+  gint              previous_frame = priv->current_frame;
+
+  priv->current_frame = animation_get_playback_start (animation) +
+                        ((priv->current_frame - animation_get_playback_start (animation) + 1) %
+                        (animation_get_playback_stop (animation) - animation_get_playback_start (animation) 
+ 1));
+
+  if (animation_must_redraw (animation, 
+                             previous_frame,
+                             priv->current_frame))
+    {
+      buffer = animation_get_frame (animation, priv->current_frame);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->current_frame, buffer);
+}
+
+void
+animation_prev (Animation *animation)
+{
+  AnimationPrivate *priv           = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer         = NULL;
+  gint              previous_frame = priv->current_frame;
+
+  if (priv->current_frame == animation_get_playback_start (animation))
+    {
+      priv->current_frame = animation_get_playback_stop (animation);
+    }
+  else
+    {
+      --priv->current_frame;
+    }
+
+  if (animation_must_redraw (animation, 
+                             previous_frame,
+                             priv->current_frame))
+    {
+      buffer = animation_get_frame (animation, priv->current_frame);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->current_frame, buffer);
+}
+
+void
+animation_jump (Animation *animation,
+                gint       index)
+{
+  AnimationPrivate *priv           = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer         = NULL;
+  gint              previous_frame = priv->current_frame;
+
+  g_assert (index >= animation_get_playback_start (animation) &&
+            index <= animation_get_playback_stop (animation));
+
+  if (index < priv->playback_start ||
+      index > priv->playback_stop)
+    priv->current_frame = priv->playback_start;
+  else
+    priv->current_frame = index;
+
+  if (animation_must_redraw (animation, 
+                             previous_frame,
+                             priv->current_frame))
+    {
+      buffer = animation_get_frame (animation, priv->current_frame);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->current_frame, buffer);
+}
+
+/************ Private Functions ****************/
+
+static void
+animation_set_property (GObject      *object,
+                        guint         property_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  Animation        *animation = ANIMATION (object);
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      priv->image_id       = g_value_get_int (value);
+      priv->preview_width  = gimp_image_width (priv->image_id);
+      priv->preview_height = gimp_image_height (priv->image_id);
+
+      if (priv->proxy_ratio != 1.0)
+        {
+          priv->preview_width  *= priv->proxy_ratio;
+          priv->preview_height *= priv->proxy_ratio;
+        }
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_get_property (GObject      *object,
+                        guint         property_id,
+                        GValue       *value,
+                        GParamSpec   *pspec)
+{
+  Animation        *animation = ANIMATION (object);
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      g_value_set_int (value, priv->image_id);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_finalize (GObject *object)
+{
+  Animation *animation = ANIMATION (object);
+
+  cleanup_cache (animation);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+cleanup_cache (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  if (priv->frames)
+    {
+      GList *idx;
+
+      gimp_image_delete (priv->frames_image_id);
+      priv->frames_image_id = 0;
+
+      /* Freeing previous frames only once. */
+      for (idx = g_list_first (priv->previous_frames); idx != NULL; idx = g_list_next (idx))
+        {
+          Frame* frame = (Frame*) idx->data;
+
+          g_list_free (frame->indexes);
+          g_list_free (frame->updated_indexes);
+          g_free (frame);
+        }
+      g_list_free (priv->previous_frames);
+      priv->previous_frames = NULL;
+
+      g_free (priv->frames);
+      priv->frames = NULL;
+    }
+}
+
+/**
+ * Set num_frames, which is not necessarily the number of layers, in
+ * particular with the DISPOSE_TAGS disposal.
+ * Will set 0 if no layer has frame tags in tags mode, or if there is
+ * no layer in combine/replace.
+ */
+static void
+animation_init_frame_numbers (Animation   *animation,
+                              gboolean     disposal_changed)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  gint             *layers;
+  gint              num_layers;
+
+  layers = gimp_image_get_layers (priv->image_id, &num_layers);
+
+  if (priv->disposal != DISPOSE_TAGS)
+    {
+      priv->num_frames  = num_layers;
+      priv->first_frame = 1;
+    }
+  else
+    {
+      gint i;
+      gint max = G_MININT;
+      gint min = G_MAXINT;
+
+      for (i = 0; i < num_layers; i++)
+        rec_set_total_frames (animation, layers[i], &min, &max);
+
+      priv->num_frames  = (max > min)? max + 1 - min : 0;
+      priv->first_frame = min;
+    }
+  if (priv->num_frames == 0)
+    {
+      priv->first_frame = 0;
+      priv->playback_start = priv->playback_stop = 0;
+    }
+
+  if (disposal_changed)
+    {
+      /* Reset the playback range to first and last frame. */
+      animation_set_playback_start (animation, priv->first_frame);
+      animation_set_playback_stop (animation, priv->first_frame + priv->num_frames - 1);
+      priv->current_frame = priv->first_frame;
+    }
+  else
+    {
+      /* Keep the same play state, unless it is now invalid. */
+      animation_set_playback_start (animation, priv->playback_start);
+      animation_set_playback_stop (animation, priv->playback_stop);
+      if (priv->current_frame > animation_get_playback_stop (animation) ||
+          priv->current_frame < animation_get_playback_start (animation))
+        {
+          priv->current_frame = priv->first_frame;
+        }
+    }
+}
+
+/**
+ * A recursive call which will call itself for layer groups
+ * and update the min/max progressively.
+ **/
+static void
+rec_set_total_frames (Animation    *animation,
+                      const gint32  layer,
+                      gint         *min,
+                      gint         *max)
+{
+  gchar        *layer_name;
+  gchar        *nospace_name;
+  GMatchInfo   *match_info;
+  gint          i;
+
+  if (gimp_item_is_group (layer))
+    {
+      gint    num_children;
+      gint32 *children;
+
+      children = gimp_item_get_children (layer, &num_children);
+      for (i = 0; i < num_children; i++)
+        rec_set_total_frames (animation, children[i], min, max);
+
+      return;
+    }
+
+  layer_name   = gimp_item_get_name (layer);
+  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
+
+  g_regex_match (layers_reg, nospace_name, 0, &match_info);
+
+  while (g_match_info_matches (match_info))
+    {
+      gchar *tag = g_match_info_fetch (match_info, 1);
+      gchar** tokens = g_strsplit(tag, ",", 0);
+
+      for (i = 0; tokens[i] != NULL; i++)
+        {
+          gchar* hyphen;
+          hyphen = g_strrstr(tokens[i], "-");
+          if (hyphen != NULL)
+            {
+              gint32 first = (gint32) g_ascii_strtoll (tokens[i], NULL, 10);
+              gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
+              *max = (second > first && second > *max)? second : *max;
+              *min = (second > first && first < *min)? first : *min;
+            }
+          else
+            {
+              gint32 num = (gint32) g_ascii_strtoll (tokens[i], NULL, 10);
+              *max = (num > *max)? num : *max;
+              *min = (num < *min)? num : *min;
+            }
+        }
+      g_strfreev (tokens);
+      g_free (tag);
+      g_free (layer_name);
+      g_match_info_next (match_info, NULL);
+    }
+
+  g_free (nospace_name);
+  g_match_info_free (match_info);
+}
+
+/**
+ * Recursive call to generate frames in TAGS mode.
+ **/
+static void
+rec_init_frames (Animation *animation,
+                 gint32     frames_image_id,
+                 gint       image_width,
+                 gint       image_height,
+                 gint32     layer,
+                 GList     *previous_frames)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  Frame            *empty_frame = NULL;
+  gchar            *layer_name;
+  gchar            *nospace_name;
+  GMatchInfo       *match_info;
+  gboolean          preview_quality;
+  gint32            new_layer;
+  gint              j, k;
+
+  if (gimp_item_is_group (layer))
+    {
+      gint    num_children;
+      gint32 *children;
+
+      children = gimp_item_get_children (layer, &num_children);
+      for (j = 0; j < num_children; j++)
+        rec_init_frames (animation, frames_image_id,
+                         image_width, image_height,
+                         children[num_children - j - 1],
+                         previous_frames);
+
+      return;
+    }
+
+  layer_name = gimp_item_get_name (layer);
+  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
+  preview_quality = priv->proxy_ratio != 1.0;
+
+  if (g_regex_match (all_reg, nospace_name, 0, NULL))
+    {
+      for (j = 0; j < priv->num_frames; j++)
+        {
+          if (! priv->frames[j])
+            {
+              if (! empty_frame)
+                {
+                  empty_frame = g_new (Frame, 1);
+                  empty_frame->indexes = NULL;
+                  empty_frame->updated_indexes = NULL;
+                  empty_frame->drawable_id = 0;
+
+                  previous_frames = g_list_append (previous_frames, empty_frame);
+                }
+
+              if (! g_list_find (empty_frame->updated_indexes, GINT_TO_POINTER (j)))
+                empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, GINT_TO_POINTER 
(j));
+
+              priv->frames[j] = empty_frame;
+            }
+          else if (! g_list_find (priv->frames[j]->updated_indexes, GINT_TO_POINTER (j)))
+            priv->frames[j]->updated_indexes = g_list_append (priv->frames[j]->updated_indexes, 
GINT_TO_POINTER (j));
+        }
+    }
+  else
+    {
+      g_regex_match (layers_reg, nospace_name, 0, &match_info);
+
+      while (g_match_info_matches (match_info))
+        {
+          gchar *tag = g_match_info_fetch (match_info, 1);
+          gchar** tokens = g_strsplit(tag, ",", 0);
+
+          for (j = 0; tokens[j] != NULL; j++)
+            {
+              gchar* hyphen = g_strrstr(tokens[j], "-");
+
+              if (hyphen != NULL)
+                {
+                  gint32 first = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
+                  gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
+
+                  for (k = first; k <= second; k++)
+                    {
+                      if (! priv->frames[k - priv->first_frame])
+                        {
+                          if (! empty_frame)
+                            {
+                              empty_frame = g_new (Frame, 1);
+                              empty_frame->indexes = NULL;
+                              empty_frame->updated_indexes = NULL;
+                              empty_frame->drawable_id = 0;
+
+                              previous_frames = g_list_append (previous_frames, empty_frame);
+                            }
+
+                          if (! g_list_find (priv->frames[k - priv->first_frame]->updated_indexes, 
GINT_TO_POINTER (k - priv->first_frame)))
+                            empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (k - priv->first_frame));
+
+                          priv->frames[k - priv->first_frame] = empty_frame;
+                        }
+                      else if (! g_list_find (priv->frames[k - priv->first_frame]->updated_indexes, 
GINT_TO_POINTER (k - priv->first_frame)))
+                        priv->frames[k - priv->first_frame]->updated_indexes = g_list_append (priv->frames[k 
- priv->first_frame]->updated_indexes,
+                                                                                       GINT_TO_POINTER (k - 
priv->first_frame));
+                    }
+                }
+              else
+                {
+                  gint32 num = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
+
+                  if (! priv->frames[num - priv->first_frame])
+                    {
+                      if (! empty_frame)
+                        {
+                          empty_frame = g_new (Frame, 1);
+                          empty_frame->indexes = NULL;
+                          empty_frame->updated_indexes = NULL;
+                          empty_frame->drawable_id = 0;
+
+                          previous_frames = g_list_append (previous_frames, empty_frame);
+                        }
+
+                      if (! g_list_find (priv->frames[num - priv->first_frame]->updated_indexes, 
GINT_TO_POINTER (num - priv->first_frame)))
+                        empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (num - priv->first_frame));
+
+                      priv->frames[num - priv->first_frame] = empty_frame;
+                    }
+                  else if (! g_list_find (priv->frames[num - priv->first_frame]->updated_indexes, 
GINT_TO_POINTER (num - priv->first_frame)))
+                    priv->frames[num - priv->first_frame]->updated_indexes = g_list_append (priv->frames[num 
- priv->first_frame]->updated_indexes,
+                                                                                     GINT_TO_POINTER (num - 
priv->first_frame));
+                }
+            }
+          g_strfreev (tokens);
+          g_free (tag);
+          g_match_info_next (match_info, NULL);
+        }
+      g_match_info_free (match_info);
+    }
+
+  for (j = 0; j < priv->num_frames; j++)
+    {
+      /* Check which frame must be updated with the current layer. */
+      if (priv->frames[j] && g_list_length (priv->frames[j]->updated_indexes))
+        {
+          new_layer = gimp_layer_new_from_drawable (layer, frames_image_id);
+          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
+
+          if (preview_quality)
+            {
+              gint layer_offx, layer_offy;
+
+              gimp_layer_scale (new_layer,
+                                (gimp_drawable_width (layer) * (gint) priv->preview_width) / image_width,
+                                (gimp_drawable_height (layer) * (gint) priv->preview_height) / image_height,
+                                FALSE);
+              gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
+              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) priv->preview_width) / image_width,
+                                      (layer_offy * (gint) priv->preview_height) / image_height);
+            }
+          gimp_layer_resize_to_image_size (new_layer);
+
+          if (priv->frames[j]->drawable_id == 0)
+            {
+              priv->frames[j]->drawable_id = new_layer;
+              priv->frames[j]->indexes = priv->frames[j]->updated_indexes;
+              priv->frames[j]->updated_indexes = NULL;
+            }
+          else if (g_list_length (priv->frames[j]->indexes) == g_list_length 
(priv->frames[j]->updated_indexes))
+            {
+              gimp_item_set_visible (new_layer, TRUE);
+              gimp_item_set_visible (priv->frames[j]->drawable_id, TRUE);
+
+              priv->frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, 
GIMP_CLIP_TO_IMAGE);
+              g_list_free (priv->frames[j]->updated_indexes);
+              priv->frames[j]->updated_indexes = NULL;
+            }
+          else
+            {
+              GList    *idx;
+              gboolean  move_j = FALSE;
+              Frame    *forked_frame = g_new (Frame, 1);
+              gint32    forked_drawable_id = gimp_layer_new_from_drawable (priv->frames[j]->drawable_id, 
frames_image_id);
+
+              /* if part only of the priv->frames are updated, we fork the existing frame. */
+              gimp_image_insert_layer (frames_image_id, forked_drawable_id, 0, 1);
+              gimp_item_set_visible (new_layer, TRUE);
+              gimp_item_set_visible (forked_drawable_id, TRUE);
+              forked_drawable_id = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
+
+              forked_frame->drawable_id = forked_drawable_id;
+              forked_frame->indexes = priv->frames[j]->updated_indexes;
+              forked_frame->updated_indexes = NULL;
+              priv->frames[j]->updated_indexes = NULL;
+
+              for (idx = g_list_first (forked_frame->indexes); idx != NULL; idx = g_list_next (idx))
+                {
+                  priv->frames[j]->indexes = g_list_remove (priv->frames[j]->indexes, idx->data);
+                  if (GPOINTER_TO_INT (idx->data) != j)
+                    priv->frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
+                  else
+                    /* Frame j must also be moved to the forked frame, but only after the loop. */
+                    move_j = TRUE;
+                }
+              if (move_j)
+                priv->frames[j] = forked_frame;
+
+              gimp_item_set_visible (forked_drawable_id, FALSE);
+
+              previous_frames = g_list_append (previous_frames, forked_frame);
+            }
+
+          gimp_item_set_visible (priv->frames[j]->drawable_id, FALSE);
+        }
+    }
+
+  g_free (layer_name);
+  g_free (nospace_name);
+}
+
+static gint
+parse_ms_tag (const gchar *str)
+{
+  gint i;
+  gint length = strlen (str);
+
+  for (i = 0; i < length; i++)
+    {
+      gint rtn;
+      gint dummy;
+
+      if (is_ms_tag (&str[i], &rtn, &dummy))
+        return rtn;
+    }
+
+  return -1;
+}
+
+static DisposeType
+parse_disposal_tag (Animation* animation,
+                    const gchar         *str)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  gint             length = strlen (str);
+  gint             i;
+
+  for (i = 0; i < length; i++)
+    {
+      DisposeType rtn;
+      gint        dummy;
+
+      if (is_disposal_tag (&str[i], &rtn, &dummy))
+        return rtn;
+    }
+
+  return priv->disposal;
+}
+
+/******* TAG UTILS **************/
+
+static gboolean
+is_ms_tag (const gchar *str,
+           gint        *duration,
+           gint        *taglength)
+{
+  gint sum = 0;
+  gint offset;
+  gint length;
+
+  length = strlen(str);
+
+  if (str[0] != '(')
+    return FALSE;
+
+  offset = 1;
+
+  /* eat any spaces between open-parenthesis and number */
+  while ((offset < length) && (str[offset] == ' '))
+    offset++;
+
+  if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
+    return FALSE;
+
+  do
+    {
+      sum *= 10;
+      sum += str[offset] - '0';
+      offset++;
+    }
+  while ((offset<length) && (g_ascii_isdigit (str[offset])));
+
+  if (length - offset <= 2)
+    return FALSE;
+
+  /* eat any spaces between number and 'ms' */
+  while ((offset < length) && (str[offset] == ' '))
+    offset++;
+
+  if (length - offset <= 2                     ||
+      g_ascii_toupper (str[offset])     != 'M' ||
+      g_ascii_toupper (str[offset + 1]) != 'S')
+    return FALSE;
+
+  offset += 2;
+
+  /* eat any spaces between 'ms' and close-parenthesis */
+  while ((offset < length) && (str[offset] == ' '))
+    offset++;
+
+  if ((length - offset < 1) || (str[offset] != ')'))
+    return FALSE;
+
+  offset++;
+
+  *duration = sum;
+  *taglength = offset;
+
+  return TRUE;
+}
+
+static gboolean
+is_disposal_tag (const gchar *str,
+                 DisposeType *disposal,
+                 gint        *taglength)
+{
+  if (strlen (str) != 9)
+    return FALSE;
+
+  if (strncmp (str, "(combine)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_COMBINE;
+      return TRUE;
+    }
+  else if (strncmp (str, "(replace)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_REPLACE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gint
+animation_time_to_next (Animation *animation,
+                        gboolean   reset)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  static gint64     start_time = -1;
+  static gint       frames = 0;
+  gdouble           expected_time_from_start = 0.0;
+  gdouble           duration;
+
+  duration = priv->frames[priv->current_frame - priv->first_frame]->duration;
+  if (duration <= 0)
+    {
+      duration = (guint) (1000.0 / priv->framerate);
+    }
+
+  if (priv->disposal != DISPOSE_COMBINE && priv->disposal != DISPOSE_REPLACE)
+    {
+      /* Replace and Combine disposals can have different duration for each
+       * frame (by tagging) making it harder to compute any framerate check. */
+      if (reset || start_time == -1)
+        {
+          start_time = g_get_monotonic_time ();
+          frames = 0;
+        }
+
+      expected_time_from_start = (gdouble) frames * 1000.0 / priv->framerate;
+      frames++;
+
+      if (frames > 1)
+        duration = duration - (((gdouble) (g_get_monotonic_time () - start_time)) / 1000.0 - 
expected_time_from_start);
+
+      if (duration < 1.0)
+        {
+          duration = 1.0;
+        }
+    }
+
+  return duration;
+}
+
+static gboolean
+advance_frame_callback (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  guint duration;
+  gboolean reset;
+
+  reset = (animation_get_position (animation) == animation_get_playback_stop (animation));
+  animation_next (animation);
+  duration = animation_time_to_next (animation, reset);
+
+  priv->playback_timer = g_timeout_add (duration,
+                                        (GSourceFunc) advance_frame_callback,
+                                        (Animation *) animation);
+
+  return G_SOURCE_REMOVE;
+}
+
+gboolean
+animation_must_redraw (Animation *animation,
+                       gint       previous_frame,
+                       gint       next_frame)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return (previous_frame < priv->first_frame ||
+          previous_frame >= priv->first_frame + priv->num_frames ||
+          (priv->num_frames > 0 &&
+          NULL == g_list_find (priv->frames[previous_frame - priv->first_frame]->indexes,
+                               GINT_TO_POINTER (next_frame - priv->first_frame))));
+}
diff --git a/plug-ins/animation-play/animation.h b/plug-ins/animation-play/animation.h
new file mode 100644
index 0000000..ed4210a
--- /dev/null
+++ b/plug-ins/animation-play/animation.h
@@ -0,0 +1,101 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.h
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ANIMATION_H__
+#define __ANIMATION_H__
+
+#define ANIMATION_TYPE_ANIMATION            (animation_get_type ())
+#define ANIMATION(obj)                      (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_ANIMATION, 
Animation))
+#define ANIMATION_CLASS(klass)              (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_ANIMATION, 
AnimationClass))
+#define ANIMATION_IS_ANIMATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_ANIMATION))
+#define ANIMATION_IS_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_ANIMATION))
+#define ANIMATION_GET_CLASS(obj)            (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_ANIMATION, 
AnimationClass))
+
+typedef struct _Animation      Animation;
+typedef struct _AnimationClass AnimationClass;
+
+#define PROXY_KEEP 0.0
+
+typedef enum
+{
+  /* Each layer is combined over the previous frame. */
+  DISPOSE_COMBINE = 0x00,
+  /* Each layer is a frame. */
+  DISPOSE_REPLACE = 0x01,
+  /* Custom disposal through tagging. */
+  DISPOSE_TAGS    = 0x02,
+  /* Keep the current disposal */
+  DISPOSE_KEEP    = 0x03,
+} DisposeType;
+
+struct _Animation
+{
+  GObject      parent_instance;
+};
+
+struct _AnimationClass
+{
+  GObjectClass  parent_class;
+};
+
+GType         animation_get_type (void);
+
+Animation   * animation_new                (gint32       image_id);
+
+void          animation_load               (Animation   *animation,
+                                            DisposeType  disposal,
+                                            gdouble      proxy_ratio);
+
+void          animation_play               (Animation   *animation);
+void          animation_stop               (Animation   *animation);
+
+gchar       * animation_get_name           (Animation   *animation);
+DisposeType   animation_get_disposal       (Animation   *animation);
+gdouble       animation_get_proxy          (Animation   *animation);
+
+void          animation_get_size           (Animation   *animation,
+                                            gint        *width,
+                                            gint        *height);
+gint          animation_get_length         (Animation   *animation);
+gint          animation_get_first_frame    (Animation   *animation);
+gint          animation_get_position       (Animation   *animation);
+
+void          animation_set_playback_start (Animation   *animation,
+                                            gint         frame_number);
+gint          animation_get_playback_start (Animation   *animation);
+void          animation_set_playback_stop  (Animation   *animation,
+                                            gint         frame_number);
+gint          animation_get_playback_stop  (Animation   *animation);
+
+void          animation_set_framerate      (Animation   *animation,
+                                            gdouble      fps);
+gdouble       animation_get_framerate      (Animation   *animation);
+
+gboolean      animation_loaded             (Animation   *animation);
+
+GeglBuffer  * animation_get_frame          (Animation   *animation,
+                                            gint         frame_number);
+
+void          animation_next               (Animation   *animation);
+void          animation_prev               (Animation   *animation);
+void          animation_jump               (Animation   *animation,
+                                            gint         index);
+
+#endif  /*  __ANIMATION_H__  */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]