[gimp/wip/animation: 228/373] plug-ins: complete code reorganization of animation-play.



commit 09fb797c5471aecf87b32a89edcdefd5d629b1db
Author: Jehan <jehan girinstud io>
Date:   Fri Aug 21 13:10:53 2015 +0200

    plug-ins: complete code reorganization of animation-play.
    
    We temporary lost the tags feature which will be back soon with a
    complete UI, as well as the framerate check.

 plug-ins/animation-play/.gitignore                 |    2 +
 plug-ins/animation-play/Makefile.am                |   16 +-
 plug-ins/animation-play/animation-play.c           |  137 +--
 plug-ins/animation-play/animation-utils.c          |    2 +-
 plug-ins/animation-play/animation.c                | 1435 --------------------
 plug-ins/animation-play/core/animation.c           |  769 +++++++++++
 plug-ins/animation-play/{ => core}/animation.h     |   79 +-
 plug-ins/animation-play/core/animationlegacy.c     |  473 +++++++
 plug-ins/animation-play/core/animationlegacy.h     |   48 +
 .../{ => widgets}/animation-dialog.c               | 1005 +++++++++-----
 .../{ => widgets}/animation-dialog.h               |    6 +-
 11 files changed, 2029 insertions(+), 1943 deletions(-)
---
diff --git a/plug-ins/animation-play/.gitignore b/plug-ins/animation-play/.gitignore
new file mode 100644
index 0000000..9ee6454
--- /dev/null
+++ b/plug-ins/animation-play/.gitignore
@@ -0,0 +1,2 @@
+/Makefile.in
+/Makefile
diff --git a/plug-ins/animation-play/Makefile.am b/plug-ins/animation-play/Makefile.am
index 503793f..763be80 100644
--- a/plug-ins/animation-play/Makefile.am
+++ b/plug-ins/animation-play/Makefile.am
@@ -1,3 +1,5 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
 if OS_WIN32
 mwindows = -mwindows
 else
@@ -41,10 +43,12 @@ LDADD = \
        $(animation_play_RC)
 
 animation_play_SOURCES = \
-       animation-utils.h       \
-       animation-utils.c       \
-       animation-dialog.h      \
-       animation-dialog.c      \
-       animation.h             \
-       animation.c             \
+       core/animation.h                                \
+       core/animation.c                                \
+       core/animationlegacy.h                          \
+       core/animationlegacy.c                          \
+       widgets/animation-dialog.h              \
+       widgets/animation-dialog.c              \
+       animation-utils.h                               \
+       animation-utils.c                               \
        animation-play.c
diff --git a/plug-ins/animation-play/animation-play.c b/plug-ins/animation-play/animation-play.c
index f5d5b17..af76930 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-2015 : jehan at girinstud.io
+ * (c) Jehan : 2012-2016 : jehan at girinstud.io
  *
  * GIMP - The GNU Image Manipulation Program
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
@@ -24,17 +24,14 @@
 
 #include "config.h"
 
-#include <string.h>
-
 #include <libgimp/gimp.h>
 #undef GDK_DISABLE_DEPRECATED
 #include <libgimp/gimpui.h>
-
 #include "libgimp/stdplugins-intl.h"
 
+#include "widgets/animation-dialog.h"
+
 #include "animation-utils.h"
-#include "animation.h"
-#include "animation-dialog.h"
 
 static void        query                     (void);
 static void        run                       (const gchar      *name,
@@ -43,19 +40,6 @@ static void        run                       (const gchar      *name,
                                               gint             *nreturn_vals,
                                               GimpParam       **return_vals);
 
-/* 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
-{
-  DisposeType disposal;
-  gdouble     framerate;
-}
-CachedSettings;
-
 const GimpPlugInInfo PLUG_IN_INFO =
 {
   NULL,  /* init_proc  */
@@ -125,63 +109,19 @@ run (const gchar      *name,
     }
   else
     {
-      Animation       *animation;
       GtkWidget       *dialog;
-      GimpParasite    *parasite;
-      CachedSettings   cached_settings;
       gint32           image_id;
 
       gimp_ui_init (PLUG_IN_BINARY, TRUE);
 
-      /***********************/
-      /* init the animation. */
-      image_id  = param[1].data.d_image;
-      animation = animation_new (image_id);
-      dialog    = animation_dialog_new (animation);
-
-      gtk_widget_show_now (GTK_WIDGET (dialog));
+      image_id = param[1].data.d_image;
+      dialog   = animation_dialog_new (image_id);
 
       gimp_help_connect (GTK_WIDGET (dialog), gimp_standard_help_func, PLUG_IN_PROC, NULL);
-
-      /* Acceptable default for cached settings. */
-      cached_settings.disposal  = DISPOSE_COMBINE;
-      cached_settings.framerate = 24.0;
-
-      /* If we saved any settings globally, use the one from the last run. */
-      gimp_get_data (PLUG_IN_PROC, &cached_settings);
-
-      /* 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)
-        {
-          const DisposeType *mode = gimp_parasite_data (parasite);
-
-          cached_settings.disposal = *mode;
-          gimp_parasite_free (parasite);
-        }
-      parasite = gimp_image_get_parasite (image_id,
-                                          PLUG_IN_PROC "/framerate");
-      if (parasite)
-        {
-          const gdouble *rate = gimp_parasite_data (parasite);
-
-          cached_settings.framerate = *rate;
-          gimp_parasite_free (parasite);
-        }
-
-      animation_set_framerate (animation, cached_settings.framerate);
-      animation_load (animation, cached_settings.disposal, 1.0);
+      gtk_widget_show_now (GTK_WIDGET (dialog));
 
       gtk_main ();
 
-      /* Save the current settings. */
-      save_settings (animation, image_id);
-
-      /* 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 (run_mode != GIMP_RUN_NONINTERACTIVE)
         gimp_displays_flush ();
     }
@@ -192,68 +132,3 @@ run (const gchar      *name,
   gegl_exit ();
   gimp_quit ();
 }
-
-/************ UTILS ****************/
-
-static void
-save_settings (Animation       *animation,
-               gint32           image_id)
-{
-  GimpParasite   *old_parasite;
-  gboolean        undo_step_started = FALSE;
-  CachedSettings  cached_settings;
-
-  /* First saving in cache for any image. */
-  cached_settings.disposal  = animation_get_disposal (animation);
-  cached_settings.framerate = animation_get_framerate (animation);
-
-  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 (image_id, PLUG_IN_PROC "/frame-disposal");
-  if (! old_parasite ||
-      *(DisposeType *) gimp_parasite_data (old_parasite) != animation_get_disposal (animation))
-    {
-      GimpParasite *parasite;
-      DisposeType   disposal = animation_get_disposal (animation);
-
-      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 (disposal),
-                                    &disposal);
-      gimp_image_attach_parasite (image_id, parasite);
-      gimp_parasite_free (parasite);
-    }
-  gimp_parasite_free (old_parasite);
-
-  old_parasite = gimp_image_get_parasite (image_id, PLUG_IN_PROC "/framerate");
-  if (! old_parasite ||
-      *(gdouble *) gimp_parasite_data (old_parasite) != cached_settings.framerate)
-    {
-      GimpParasite *parasite;
-      if (! undo_step_started)
-        {
-          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 (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 (image_id);
-    }
-}
diff --git a/plug-ins/animation-play/animation-utils.c b/plug-ins/animation-play/animation-utils.c
index 333e0ce..f5f51d0 100755
--- a/plug-ins/animation-play/animation-utils.c
+++ b/plug-ins/animation-play/animation-utils.c
@@ -1,7 +1,7 @@
 /*
  * Animation Playback plug-in version 0.99.1
  *
- * (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
diff --git a/plug-ins/animation-play/core/animation.c b/plug-ins/animation-play/core/animation.c
new file mode 100644
index 0000000..6fe1758
--- /dev/null
+++ b/plug-ins/animation-play/core/animation.c
@@ -0,0 +1,769 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.c
+ * Copyright (C) 2015-2016 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-utils.h"
+
+#include "animation.h"
+#include "animationlegacy.h"
+
+enum
+{
+  LOADING_START,
+  LOADING,
+  LOADED,
+  START,
+  STOP,
+  FRAMERATE_CHANGED,
+  PLAYBACK_RANGE,
+  RENDER,
+  LOW_FRAMERATE,
+  PROXY,
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_IMAGE
+};
+
+typedef struct _AnimationPrivate AnimationPrivate;
+
+struct _AnimationPrivate
+{
+  gint32       image_id;
+
+  gdouble      framerate;
+
+  guint        playback_timer;
+
+  /* State of the currently loaded animation. */
+  gint         position;
+  gint32       length;
+  /* We want to allow animator to set any number as first frame,
+   * for frame export capability. */
+  guint        start_pos;
+  /* Playback can be a subset of frames. */
+  guint        playback_start;
+  guint        playback_stop;
+
+  /* Proxy settings generates a reload. */
+  gdouble      proxy_ratio;
+
+  /* Frames are associated to an unused image (used as backend for GEGL buffers). */
+  gboolean     loaded;
+};
+
+
+#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);
+
+/* Base implementation of virtual methods. */
+static gint       animation_real_get_start_position (Animation *animation);
+static gboolean   animation_real_identical_frames   (Animation *animation,
+                                                     gint       prev_pos,
+                                                     gint       next_pos);
+
+/* Timer callback for playback. */
+static gboolean   animation_advance_frame_callback  (Animation *animation);
+
+G_DEFINE_TYPE (Animation, animation, G_TYPE_OBJECT)
+
+#define parent_class animation_parent_class
+
+static guint animation_signals[LAST_SIGNAL] = { 0 };
+
+static void
+animation_class_init (AnimationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  /**
+   * Animation::loading-start:
+   * @animation: the animation loading.
+   *
+   * The ::loading-start signal is emitted when loading starts.
+   * GUI widgets depending on a consistent state of @animation should
+   * become unresponsive.
+   */
+  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);
+  /**
+   * Animation::loading:
+   * @animation: the animation loading.
+   * @ratio: fraction loaded [0-1].
+   *
+   * The ::loading signal must be emitted by subclass of Animation.
+   * It can be used by a GUI to display a progress bar during loading.
+   */
+  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::loaded:
+   * @animation: the animation loading.
+   * @start_pos: the first frame.
+   * @length: number of frames.
+   * @playback_start: the playback start frame.
+   * @playback_stop: the playback last frame.
+   * @width: display width in pixels.
+   * @height: display height in pixels.
+   *
+   * The ::loaded signal is emitted when @animation is fully loaded.
+   * GUI widgets depending on a consistent state of @animation can
+   * now become responsive to user interaction.
+   */
+  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::render:
+   * @animation: the animation.
+   * @position: current position to be rendered.
+   * @buffer: the #GeglBuffer for the frame at @position.
+   * @must_draw_null: meaning of a %NULL @buffer.
+   * %TRUE means we have to draw an empty frame.
+   * %FALSE means the new frame is same as the current frame.
+   *
+   * Sends a request for render to the GUI.
+   */
+  animation_signals[RENDER] =
+    g_signal_new ("render",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  3,
+                  G_TYPE_INT,
+                  GEGL_TYPE_BUFFER,
+                  G_TYPE_BOOLEAN);
+  /**
+   * Animation::start:
+   * @animation: the animation.
+   *
+   * The @animation starts to play.
+   */
+  animation_signals[START] =
+    g_signal_new ("start",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
+  /**
+   * Animation::stops:
+   * @animation: the animation.
+   *
+   * The @animation stops playing.
+   */
+  animation_signals[STOP] =
+    g_signal_new ("stop",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
+  /**
+   * Animation::framerate-changed:
+   * @animation: the animation.
+   * @framerate: the new framerate in frames per second.
+   *
+   * The ::framerate-changed signal is emitted when framerate has
+   * changed.
+   */
+  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::playback-range:
+   * @animation: the animation.
+   * @playback_start: the playback start frame.
+   * @playback_stop: the playback last frame.
+   *
+   * The ::playback-range signal is emitted when the playback range is
+   * updated.
+   */
+  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::low-framerate:
+   * @animation: the animation.
+   * @actual_fps: the current playback framerate in fps.
+   *
+   * The ::low-framerate signal is emitted when the playback framerate
+   * is lower than expected.
+   * The @actual_fps is not necessarily meaningful for animation with
+   * various frame duration.
+   */
+  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);
+  /**
+   * Animation::proxy:
+   * @animation: the animation.
+   * @ratio: the current proxy ratio [0-1.0].
+   *
+   * The ::proxy signal is emitted to announce a change of proxy size.
+   */
+  animation_signals[PROXY] =
+    g_signal_new ("proxy",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_DOUBLE);
+
+  object_class->set_property = animation_set_property;
+  object_class->get_property = animation_get_property;
+
+  klass->get_start_position  = animation_real_get_start_position;
+  klass->identical_frames    = animation_real_identical_frames;
+
+  /**
+   * 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_ONLY));
+
+  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->framerate   = 24.0; /* fps */
+  priv->proxy_ratio = 1.0;
+}
+
+/************ Public Functions ****************/
+
+Animation *
+animation_new (gint32      image_id,
+               DisposeType disposal)
+{
+  Animation *animation;
+
+  /* Right now, only legacy animation type exists. */
+  animation = g_object_new (ANIMATION_TYPE_ANIMATION_LEGACY,
+                            "image", image_id,
+                            "disposal", disposal,
+                            NULL);
+  return animation;
+}
+
+gint32
+animation_get_image_id (Animation   *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->image_id;
+}
+
+void
+animation_load (Animation *animation,
+                gdouble    proxy_ratio)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer;
+  gint              width, height;
+
+  priv->loaded = FALSE;
+  g_signal_emit (animation, animation_signals[LOADING_START], 0);
+
+  priv->proxy_ratio = proxy_ratio;
+  g_signal_emit (animation, animation_signals[PROXY], 0, proxy_ratio);
+
+  ANIMATION_GET_CLASS (animation)->load (animation, proxy_ratio);
+
+  priv->start_pos = ANIMATION_GET_CLASS (animation)->get_start_position (animation);
+  priv->position  = priv->start_pos;
+  priv->length    = ANIMATION_GET_CLASS (animation)->get_length (animation);
+
+  /* Default playback is the full range of frames. */
+  priv->playback_start = priv->position;
+  priv->playback_stop  = priv->position + priv->length - 1;
+
+  priv->loaded = TRUE;
+
+  animation_get_size (animation, &width, &height);
+  g_signal_emit (animation, animation_signals[LOADED], 0,
+                 1, priv->length,
+                 priv->playback_start,
+                 priv->playback_stop,
+                 width,
+                 height);
+
+  /* Once loaded, let's ask render. */
+  buffer = animation_get_frame (animation, priv->position);
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->position, buffer, TRUE);
+
+  if (buffer)
+    g_object_unref (buffer);
+}
+
+DisposeType
+animation_get_disposal (Animation *animation)
+{
+  return ANIMATION_GET_CLASS (animation)->get_disposal (animation);
+}
+
+gint animation_get_start_position (Animation *animation)
+{
+  return ANIMATION_GET_CLASS (animation)->get_start_position (animation);
+}
+
+gint
+animation_get_position (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->position;
+}
+
+gint
+animation_get_length (Animation *animation)
+{
+  return ANIMATION_GET_CLASS (animation)->get_length (animation);
+}
+
+void
+animation_get_size (Animation *animation,
+                    gint      *width,
+                    gint      *height)
+{
+  ANIMATION_GET_CLASS (animation)->get_size (animation, width, height);
+}
+
+gdouble
+animation_get_proxy (Animation   *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return priv->proxy_ratio;
+}
+
+GeglBuffer *
+animation_get_frame (Animation *animation,
+                     gint       pos)
+{
+  return ANIMATION_GET_CLASS (animation)->get_frame (animation, pos);
+}
+
+gboolean
+animation_is_playing (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  return (priv->playback_timer != 0);
+}
+
+void
+animation_play (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  gint              duration;
+
+  duration = ANIMATION_GET_CLASS (animation)->time_to_next (animation);
+
+  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)
+    {
+      gdouble fps = animation_get_framerate (animation);
+      duration = (gint) (1000.0 / fps);
+    }
+
+  priv->playback_timer = g_timeout_add ((guint) duration,
+                                        (GSourceFunc) animation_advance_frame_callback,
+                                        animation);
+  g_signal_emit (animation, animation_signals[START], 0);
+}
+
+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;
+      g_signal_emit (animation, animation_signals[STOP], 0);
+    }
+}
+
+void
+animation_next (Animation *animation)
+{
+  AnimationPrivate *priv         = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer       = NULL;
+  gint              previous_pos = priv->position;
+
+  priv->position = animation_get_playback_start (animation) +
+                   ((priv->position - animation_get_playback_start (animation) + 1) %
+                    (animation_get_playback_stop (animation) - animation_get_playback_start (animation) + 
1));
+
+  if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
+                                                           previous_pos,
+                                                           priv->position))
+    {
+      buffer = animation_get_frame (animation, priv->position);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->position, buffer, FALSE);
+  if (buffer != NULL)
+    {
+      g_object_unref (buffer);
+    }
+}
+
+void
+animation_prev (Animation *animation)
+{
+  AnimationPrivate *priv     = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer   = NULL;
+  gint              prev_pos = priv->position;
+
+  if (priv->position == animation_get_playback_start (animation))
+    {
+      priv->position = animation_get_playback_stop (animation);
+    }
+  else
+    {
+      --priv->position;
+    }
+
+  if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
+                                                           prev_pos,
+                                                           priv->position))
+    {
+      buffer = animation_get_frame (animation, priv->position);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->position, buffer, FALSE);
+  if (buffer)
+    g_object_unref (buffer);
+}
+
+void
+animation_jump (Animation *animation,
+                gint       index)
+{
+  AnimationPrivate *priv           = ANIMATION_GET_PRIVATE (animation);
+  GeglBuffer       *buffer         = NULL;
+  gint              prev_pos = priv->position;
+
+  if (index < priv->playback_start ||
+      index > priv->playback_stop)
+    return;
+  else
+    priv->position = index;
+
+  if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
+                                                           prev_pos,
+                                                           priv->position))
+    {
+      buffer = animation_get_frame (animation, priv->position);
+    }
+  g_signal_emit (animation, animation_signals[RENDER], 0,
+                 priv->position, buffer, FALSE);
+  if (buffer)
+    g_object_unref (buffer);
+}
+
+void
+animation_set_playback_start (Animation *animation,
+                              gint       frame_number)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+
+  if (frame_number < priv->start_pos ||
+      frame_number >= priv->start_pos + priv->length)
+    {
+      priv->playback_start = priv->start_pos;
+    }
+  else
+    {
+      priv->playback_start = frame_number;
+    }
+  if (priv->playback_stop < priv->playback_start)
+    {
+      priv->playback_stop = priv->start_pos + priv->length - 1;
+    }
+
+  g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
+                 priv->playback_start, priv->playback_stop);
+
+  if (priv->position < priv->playback_start ||
+      priv->position > 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->start_pos ||
+      frame_number >= priv->start_pos + priv->length)
+    {
+      priv->playback_stop = priv->start_pos + priv->length - 1;
+    }
+  else
+    {
+      priv->playback_stop = frame_number;
+    }
+  if (priv->playback_stop < priv->playback_start)
+    {
+      priv->playback_start = priv->start_pos;
+    }
+  g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
+                 priv->playback_start, priv->playback_stop);
+
+  if (priv->position < priv->playback_start ||
+      priv->position > 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);
+
+  g_return_if_fail (fps > 0.0);
+
+  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;
+}
+
+/************ 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);
+      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
+gint animation_real_get_start_position (Animation *animation)
+{
+  /* By default, the first frame is numbered 1. */
+  return 1;
+}
+
+static gboolean
+animation_real_identical_frames (Animation *animation,
+                                 gint       previous_pos,
+                                 gint       next_pos)
+{
+  /* By default all frames are supposed different. */
+  return (previous_pos == next_pos);
+}
+
+static gboolean
+animation_advance_frame_callback (Animation *animation)
+{
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  gint              duration;
+
+  animation_next (animation);
+  duration = ANIMATION_GET_CLASS (animation)->time_to_next (animation);
+
+  if (duration <= 0)
+    {
+      gdouble fps = animation_get_framerate (animation);
+      duration = (gint) (1000.0 / fps);
+    }
+
+  priv->playback_timer = g_timeout_add ((guint) duration,
+                                        (GSourceFunc) animation_advance_frame_callback,
+                                        (Animation *) animation);
+
+  return G_SOURCE_REMOVE;
+}
diff --git a/plug-ins/animation-play/animation.h b/plug-ins/animation-play/core/animation.h
similarity index 72%
rename from plug-ins/animation-play/animation.h
rename to plug-ins/animation-play/core/animation.h
index ed4210a..9b701f8 100644
--- a/plug-ins/animation-play/animation.h
+++ b/plug-ins/animation-play/core/animation.h
@@ -2,7 +2,7 @@
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  *
  * animation.h
- * Copyright (C) 2015 Jehan <jehan gimp org>
+ * Copyright (C) 2015-2016 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
@@ -31,18 +31,16 @@
 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,
+  DISPOSE_COMBINE  = 0x00,
   /* Each layer is a frame. */
-  DISPOSE_REPLACE = 0x01,
-  /* Custom disposal through tagging. */
-  DISPOSE_TAGS    = 0x02,
+  DISPOSE_REPLACE  = 0x01,
+  /* Custom disposal through timeline. */
+  DISPOSE_XSHEET   = 0x02,
   /* Keep the current disposal */
-  DISPOSE_KEEP    = 0x03,
+  DISPOSE_KEEP     = 0x03,
 } DisposeType;
 
 struct _Animation
@@ -53,29 +51,64 @@ struct _Animation
 struct _AnimationClass
 {
   GObjectClass  parent_class;
+
+  /* Defaults to position 1. */
+  gint         (*get_start_position) (Animation *animation);
+
+  /* Defaults to returning FALSE for any different position. */
+  gboolean     (*identical_frames) (Animation *animation,
+                                    gint       prev_pos,
+                                    gint       next_pos);
+
+  /* These virtual methods must be implemented by any subclass. */
+  void         (*load)          (Animation  *animation,
+                                 gdouble     proxy_ratio);
+  DisposeType  (*get_disposal)  (Animation   *animation);
+  gint         (*get_length)    (Animation   *animation);
+  gint         (*next)          (Animation   *animation);
+  gint         (*prev)          (Animation   *animation);
+
+
+  void         (*get_size)      (Animation   *animation,
+                                 gint        *width,
+                                 gint        *height);
+
+  gint         (*time_to_next)  (Animation   *animation);
+
+  GeglBuffer * (*get_frame)     (Animation   *animation,
+                                 gint         pos);
 };
 
 GType         animation_get_type (void);
 
-Animation   * animation_new                (gint32       image_id);
+Animation   * animation_new                (gint32       image_id,
+                                            DisposeType  disposal);
+gint32        animation_get_image_id       (Animation   *animation);
 
 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);
+
+gint          animation_get_start_position (Animation   *animation);
+gint          animation_get_position       (Animation   *animation);
+gint          animation_get_length         (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);
+gdouble       animation_get_proxy          (Animation   *animation);
+
+GeglBuffer  * animation_get_frame          (Animation   *animation,
+                                            gint         frame_number);
+
+
+gboolean      animation_is_playing         (Animation   *animation);
+void          animation_play               (Animation   *animation);
+void          animation_stop               (Animation   *animation);
+void          animation_next               (Animation   *animation);
+void          animation_prev               (Animation   *animation);
+void          animation_jump               (Animation   *animation,
+                                            gint         index);
 
 void          animation_set_playback_start (Animation   *animation,
                                             gint         frame_number);
@@ -90,12 +123,4 @@ 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__  */
diff --git a/plug-ins/animation-play/core/animationlegacy.c b/plug-ins/animation-play/core/animationlegacy.c
new file mode 100644
index 0000000..abda703
--- /dev/null
+++ b/plug-ins/animation-play/core/animationlegacy.c
@@ -0,0 +1,473 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.c
+ * Copyright (C) 2016 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 <libgimp/gimp.h>
+#include <libgimp/stdplugins-intl.h>
+
+#include "animationlegacy.h"
+
+enum
+{
+  PROP_0,
+  PROP_DISPOSAL
+};
+
+typedef struct _AnimationLegacyPrivate AnimationLegacyPrivate;
+
+struct _AnimationLegacyPrivate
+{
+  /* Accepts 2 disposals: combine and replace. */
+  DisposeType  disposal;
+
+  gint         preview_width;
+  gint         preview_height;
+
+  /* Frames are associated to an unused GimpImage. */
+  gint32       frames;
+  gint        *durations;
+};
+
+#define GET_PRIVATE(animation) \
+        G_TYPE_INSTANCE_GET_PRIVATE (animation, \
+                                     ANIMATION_TYPE_ANIMATION_LEGACY, \
+                                     AnimationLegacyPrivate)
+
+static void         animation_legacy_set_property (GObject      *object,
+                                                   guint         property_id,
+                                                   const GValue *value,
+                                                   GParamSpec   *pspec);
+static void         animation_legacy_get_property (GObject      *object,
+                                                   guint         property_id,
+                                                   GValue       *value,
+                                                   GParamSpec   *pspec);
+static void         animation_legacy_finalize     (GObject      *object);
+
+/* Virtual methods */
+
+static DisposeType  animation_legacy_get_disposal (Animation *animation);
+static gint         animation_legacy_get_length   (Animation *animation);
+static void         animation_legacy_get_size     (Animation *animation,
+                                                   gint      *width,
+                                                   gint      *height);
+
+static void         animation_legacy_load         (Animation *animation,
+                                                   gdouble    proxy_ratio);
+static GeglBuffer * animation_legacy_get_frame    (Animation *animation,
+                                                   gint       pos);
+static gint         animation_legacy_time_to_next (Animation *animation);
+
+/* Tag handling (from layer names) */
+
+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);
+
+G_DEFINE_TYPE (AnimationLegacy, animation_legacy, ANIMATION_TYPE_ANIMATION)
+
+#define parent_class animation_legacy_parent_class
+
+static void
+animation_legacy_class_init (AnimationLegacyClass *klass)
+{
+  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+  AnimationClass *anim_class   = ANIMATION_CLASS (klass);
+
+  object_class->finalize     = animation_legacy_finalize;
+  object_class->set_property = animation_legacy_set_property;
+  object_class->get_property = animation_legacy_get_property;
+
+  anim_class->get_disposal   = animation_legacy_get_disposal;
+  anim_class->get_length     = animation_legacy_get_length;
+  anim_class->get_size       = animation_legacy_get_size;
+  anim_class->load           = animation_legacy_load;
+  anim_class->get_frame      = animation_legacy_get_frame;
+  anim_class->time_to_next   = animation_legacy_time_to_next;
+
+  g_object_class_install_property (object_class, PROP_DISPOSAL,
+                                   g_param_spec_int ("disposal", NULL, NULL,
+                                                     0, DISPOSE_REPLACE,
+                                                     DISPOSE_COMBINE,
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT_ONLY));
+
+  g_type_class_add_private (klass, sizeof (AnimationLegacyPrivate));
+}
+
+static void
+animation_legacy_init (AnimationLegacy *animation)
+{
+}
+
+static void
+animation_legacy_set_property (GObject      *object,
+                               guint         property_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_DISPOSAL:
+      priv->disposal = g_value_get_int (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_legacy_get_property (GObject      *object,
+                               guint         property_id,
+                               GValue       *value,
+                               GParamSpec   *pspec)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_DISPOSAL:
+      g_value_set_int (value, priv->disposal);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_legacy_finalize (GObject *object)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (object);
+
+  gimp_image_delete (priv->frames);
+  g_free (priv->durations);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* Virtual methods */
+
+static DisposeType
+animation_legacy_get_disposal (Animation *animation)
+{
+  return GET_PRIVATE (animation)->disposal;
+}
+
+static gint
+animation_legacy_get_length (Animation *animation)
+{
+  gint   *layers;
+  gint    num_layers;
+  gint32  image_id;
+
+  image_id = animation_get_image_id (animation);
+  layers   = gimp_image_get_layers (image_id, &num_layers);
+  g_free (layers);
+
+  return num_layers;
+}
+
+static void
+animation_legacy_get_size (Animation *animation,
+                           gint      *width,
+                           gint      *height)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (animation);
+
+  *width  = priv->preview_width;
+  *height = priv->preview_height;
+}
+
+static void
+animation_legacy_load (Animation *animation,
+                       gdouble    proxy_ratio)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (animation);
+  gint32                 *layers;
+  gint32                  image_id;
+  gint32                  num_layers;
+  gint32                  new_frame;
+  gint32                  previous_frame = 0;
+  gint                    image_width;
+  gint                    image_height;
+  gint                    i;
+
+  g_return_if_fail (proxy_ratio > 0.0 && proxy_ratio <= 1.0);
+
+  /* Cleaning. */
+  if (gimp_image_is_valid (priv->frames))
+    {
+      gimp_image_delete (priv->frames);
+      g_free (priv->durations);
+    }
+
+  image_id = animation_get_image_id (animation);
+  layers   = gimp_image_get_layers (image_id, &num_layers);
+
+  priv->durations = g_try_malloc0_n (num_layers, sizeof (gint));
+  if (! priv->durations)
+    {
+      gimp_message (_("Memory could not be allocated to the frame container."));
+      gimp_quit ();
+      return;
+    }
+
+  /* We default at full preview size. */
+  image_width = gimp_image_width (image_id);
+  image_height = gimp_image_height (image_id);
+
+  priv->preview_width = image_width;
+  priv->preview_height = image_height;
+
+  /* Apply proxy ratio. */
+  priv->preview_width  *= proxy_ratio;
+  priv->preview_height *= proxy_ratio;
+
+  priv->frames = gimp_image_new (priv->preview_width,
+                                 priv->preview_width,
+                                 GIMP_RGB);
+
+  gimp_image_undo_disable (priv->frames);
+
+  for (i = 0; i < num_layers; i++)
+    {
+      gchar       *layer_name;
+      gint         duration;
+      DisposeType  disposal;
+      gint         layer_offx;
+      gint         layer_offy;
+
+      g_signal_emit_by_name (animation, "loading",
+                             (gdouble) i / ((gdouble) num_layers - 0.999));
+
+      layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
+
+      duration = parse_ms_tag (layer_name);
+      disposal = parse_disposal_tag (animation, layer_name);
+      g_free (layer_name);
+
+      /* Frame duration. */
+      priv->durations[i] = duration;
+
+      /* Frame disposal. */
+      if (i > 0 && disposal == DISPOSE_COMBINE)
+        {
+          previous_frame = gimp_layer_copy (previous_frame);
+          gimp_image_insert_layer (priv->frames, previous_frame, 0, 0);
+          gimp_item_set_visible (previous_frame, TRUE);
+        }
+
+      new_frame = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)],
+                                                priv->frames);
+      gimp_image_insert_layer (priv->frames, new_frame, 0, 0);
+      gimp_layer_scale (new_frame,
+                        (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_frame,
+                              (layer_offx * (gint) priv->preview_width) / image_width,
+                              (layer_offy * (gint) priv->preview_height) / image_height);
+      gimp_layer_resize_to_image_size (new_frame);
+      gimp_item_set_visible (new_frame, TRUE);
+      new_frame = gimp_image_merge_visible_layers (priv->frames, GIMP_CLIP_TO_IMAGE);
+      gimp_item_set_visible (new_frame, FALSE);
+
+      previous_frame = new_frame;
+    }
+  g_free (layers);
+}
+
+static GeglBuffer *
+animation_legacy_get_frame (Animation *animation,
+                            gint       pos)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (animation);
+  GeglBuffer             *buffer = NULL;
+
+  if (priv->frames)
+    {
+      gint32 *layers;
+      gint32  num_layers;
+
+      layers = gimp_image_get_layers (priv->frames, &num_layers);
+
+      if (num_layers > 0 &&
+          pos >= 1 && pos <= num_layers)
+        {
+          buffer = gimp_drawable_get_buffer (layers[num_layers - pos]);
+        }
+
+      g_free (layers);
+    }
+
+  return buffer;
+}
+
+static gint
+animation_legacy_time_to_next (Animation *animation)
+{
+  return GET_PRIVATE (animation)->durations[animation_get_position (animation)];
+}
+
+/****** TAG UTILS ******/
+
+static gint
+parse_ms_tag (const gchar *str)
+{
+  if (str != NULL)
+    {
+      gint length = strlen (str);
+      gint i;
+
+      for (i = 0; i < length; i++)
+        {
+          gint rtn;
+          gint dummy;
+
+          if (is_ms_tag (&str[i], &rtn, &dummy))
+            return rtn;
+        }
+    }
+
+  /* -1 means using whatever is the default framerate. */
+  return -1;
+}
+
+static DisposeType
+parse_disposal_tag (Animation   *animation,
+                    const gchar *str)
+{
+  AnimationLegacyPrivate *priv = GET_PRIVATE (animation);
+
+  if (str != NULL)
+    {
+      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;
+}
+
+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;
+}
diff --git a/plug-ins/animation-play/core/animationlegacy.h b/plug-ins/animation-play/core/animationlegacy.h
new file mode 100644
index 0000000..37761a7
--- /dev/null
+++ b/plug-ins/animation-play/core/animationlegacy.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.h
+ * Copyright (C) 2016 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_LEGACY_H__
+#define __ANIMATION_LEGACY_H__
+
+#include "animation.h"
+
+#define ANIMATION_TYPE_ANIMATION_LEGACY            (animation_legacy_get_type ())
+#define ANIMATION_LEGACY(obj)                      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
ANIMATION_TYPE_ANIMATION_LEGACY, Animation))
+#define ANIMATION_LEGACY_CLASS(klass)              (G_TYPE_CHECK_CLASS_CAST ((klass), 
ANIMATION_TYPE_ANIMATION_LEGACY, AnimationClass))
+#define ANIMATION_IS_ANIMATION_LEGACY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
ANIMATION_TYPE_ANIMATION_LEGACY))
+#define ANIMATION_IS_ANIMATION_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
ANIMATION_TYPE_ANIMATION_LEGACY))
+#define ANIMATION_LEGACY_GET_CLASS(obj)            (G_TYPE_INSTANCE_GET_CLASS ((obj), 
ANIMATION_TYPE_ANIMATION_LEGACY, AnimationClass))
+
+typedef struct _AnimationLegacy      AnimationLegacy;
+typedef struct _AnimationLegacyClass AnimationLegacyClass;
+
+struct _AnimationLegacy
+{
+  Animation       parent_instance;
+};
+
+struct _AnimationLegacyClass
+{
+  AnimationClass  parent_class;
+};
+
+GType         animation_legacy_get_type (void);
+
+#endif  /*  __ANIMATION_LEGACY_H__  */
diff --git a/plug-ins/animation-play/animation-dialog.c b/plug-ins/animation-play/widgets/animation-dialog.c
similarity index 78%
rename from plug-ins/animation-play/animation-dialog.c
rename to plug-ins/animation-play/widgets/animation-dialog.c
index 98af5e4..1cef10c 100755
--- a/plug-ins/animation-play/animation-dialog.c
+++ b/plug-ins/animation-play/widgets/animation-dialog.c
@@ -2,7 +2,7 @@
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  *
  * animation-dialog.c
- * Copyright (C) 2015 Jehan <jehan gimp org>
+ * Copyright (C) 2015-2016 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
@@ -31,7 +31,7 @@
 #include <libgimp/gimpui.h>
 
 #include "animation-utils.h"
-#include "animation.h"
+#include "core/animation.h"
 #include "animation-dialog.h"
 
 #include "libgimp/stdplugins-intl.h"
@@ -39,6 +39,16 @@
 #define MAX_FRAMERATE  300.0
 #define DITHERTYPE     GDK_RGB_DITHER_NORMAL
 
+/* Settings we cache assuming they may be the user's
+ * favorite, like a framerate, or a type of frame disposal.
+ * These will be used only for image without stored animation. */
+typedef struct
+{
+  DisposeType disposal;
+  gdouble     framerate;
+}
+CachedSettings;
+
 /* for shaping */
 typedef struct
 {
@@ -48,13 +58,15 @@ typedef struct
 enum
 {
   PROP_0,
-  PROP_ANIMATION
+  PROP_IMAGE
 };
 
 typedef struct _AnimationDialogPrivate AnimationDialogPrivate;
 
 struct _AnimationDialogPrivate
 {
+  gint32          image_id;
+
   Animation      *animation;
   gdouble         zoom;
 
@@ -66,6 +78,7 @@ struct _AnimationDialogPrivate
   GtkWidget      *disposalcombo;
   GtkWidget      *fpscombo;
   GtkWidget      *zoomcombo;
+  GtkWidget      *proxycheckbox;
   GtkWidget      *proxycombo;
 
   GtkWidget      *progress;
@@ -73,7 +86,7 @@ struct _AnimationDialogPrivate
   GtkWidget      *endframe_spin;
 
   GtkWidget      *view_bar;
-  GtkWidget      *refresh; /* refresh button */
+  GtkWidget      *refresh;
 
   GtkWidget      *drawing_area;
   guchar         *drawing_area_data;
@@ -86,6 +99,9 @@ struct _AnimationDialogPrivate
   guint           shape_drawing_area_width;
   guint           shape_drawing_area_height;
 
+  /* The vpaned (bottom is timeline, above is preview). */
+  GtkWidget      *vpaned;
+
   /* Actions */
   GtkUIManager   *ui_manager;
 
@@ -111,12 +127,17 @@ static void        animation_dialog_get_property (GObject      *object,
                                                   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_set_animation (AnimationDialog *dialog,
+                                                   Animation       *animation);
+/* Finalization. */
+static void        animation_dialog_save_settings (AnimationDialog *dialog);
+
 static void        animation_dialog_refresh  (AnimationDialog  *dialog);
 
 static void        update_ui_sensitivity     (AnimationDialog  *dialog);
@@ -133,13 +154,7 @@ 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,
@@ -150,6 +165,15 @@ static void        zoom_out_callback         (GtkAction        *action,
                                               AnimationDialog  *dialog);
 static void        zoom_reset_callback       (GtkAction        *action,
                                               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        speed_up_callback         (GtkAction        *action,
                                               AnimationDialog  *dialog);
 static void        speed_down_callback       (GtkAction        *action,
@@ -196,11 +220,11 @@ static void        playback_range_changed    (Animation        *animation,
                                               gint              playback_start,
                                               gint              playback_stop,
                                               AnimationDialog  *dialog);
-static void        framerate_changed         (Animation        *animation,
+static void        proxy_changed             (Animation        *animation,
                                               gdouble           fps,
                                               AnimationDialog  *dialog);
-static void        disposal_changed          (Animation        *animation,
-                                              gint              disposal,
+static void        framerate_changed         (Animation        *animation,
+                                              gdouble           fps,
                                               AnimationDialog  *dialog);
 static void        low_framerate_cb          (Animation        *animation,
                                               gdouble           real_framerate,
@@ -226,11 +250,13 @@ static gboolean    shape_motion              (GtkWidget        *widget,
 static void        render_callback           (Animation        *animation,
                                               gint              frame_number,
                                               GeglBuffer       *buffer,
+                                              gboolean          must_draw_null,
                                               AnimationDialog  *dialog);
 static void        render_on_realize         (GtkWidget        *drawing_area,
                                               AnimationDialog  *dialog);
 static void        render_frame              (AnimationDialog  *dialog,
-                                              GeglBuffer       *buffer);
+                                              GeglBuffer       *buffer,
+                                              gboolean          must_draw_null);
 static void        reshape_from_bitmap       (AnimationDialog  *dialog,
                                               const gchar      *bitmap);
 
@@ -248,18 +274,17 @@ static gboolean    progress_left             (GtkWidget        *widget,
                                               GdkEventCrossing *event,
                                               AnimationDialog  *dialog);
 
-static void        show_goto_progress        (gint              frame_nb,
-                                              AnimationDialog  *dialog);
+static void        show_goto_progress        (AnimationDialog  *dialog,
+                                              gint              frame_nb);
 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 gdouble     get_zoom                  (AnimationDialog   *dialog,
+                                              gint               index);
 static void        update_scale              (AnimationDialog  *dialog,
                                               gdouble           scale);
 
@@ -278,17 +303,12 @@ animation_dialog_class_init (AnimationDialogClass *klass)
   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_object_class_install_property (object_class, PROP_IMAGE,
+                                   g_param_spec_int ("image", NULL,
+                                                     "GIMP imag id",
+                                                     G_MININT, G_MAXINT, 0,
+                                                     GIMP_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT_ONLY));
 
   g_type_class_add_private (klass, sizeof (AnimationDialogPrivate));
 }
@@ -307,14 +327,50 @@ animation_dialog_init (AnimationDialog *dialog)
 /************ Public Functions ****************/
 
 GtkWidget *
-animation_dialog_new (Animation *animation)
+animation_dialog_new (gint32 image_id)
 {
-  GtkWidget *dialog;
+  GtkWidget      *dialog;
+  Animation      *animation;
+  GimpParasite   *parasite;
+  CachedSettings  settings;
+
+  /* Acceptable default settings. */
+  settings.disposal  = DISPOSE_COMBINE;
+  settings.framerate = 24.0;
+
+  /* If we saved any settings globally, use the one from the last run. */
+  gimp_get_data (PLUG_IN_PROC, &settings);
+
+  /* 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)
+    {
+      const DisposeType *mode = gimp_parasite_data (parasite);
+
+      settings.disposal = *mode;
+      gimp_parasite_free (parasite);
+    }
+  parasite = gimp_image_get_parasite (image_id,
+                                      PLUG_IN_PROC "/framerate");
+  if (parasite)
+    {
+      const gdouble *rate = gimp_parasite_data (parasite);
+
+      settings.framerate = *rate;
+      gimp_parasite_free (parasite);
+    }
+
+  animation = animation_new (image_id, settings.disposal);
 
   dialog = g_object_new (ANIMATION_TYPE_DIALOG,
-                         "type",      GTK_WINDOW_TOPLEVEL,
-                         "animation", animation,
+                         "type",  GTK_WINDOW_TOPLEVEL,
+                         "image", image_id,
                          NULL);
+  animation_dialog_set_animation (ANIMATION_DIALOG (dialog),
+                                  animation);
+
+  animation_set_framerate (animation, settings.framerate);
+  animation_load (animation, 1.0);
 
   return dialog;
 }
@@ -324,22 +380,24 @@ animation_dialog_new (Animation *animation)
 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);
+  AnimationDialog        *dialog = ANIMATION_DIALOG (object);
+  AnimationDialogPrivate *priv   = GET_PRIVATE (dialog);
+  GtkWidget              *hpaned;
+  GtkAdjustment          *adjust;
+  GtkWidget              *main_vbox;
+  GtkWidget              *upper_bar;
+  GtkWidget              *abox;
+  GtkWidget              *vbox;
+  GtkWidget              *hbox;
+  GtkWidget              *widget;
+  gchar                  *image_name;
+  gchar                  *text;
+  GdkCursor              *cursor;
+  GdkScreen              *screen;
+  guint                   screen_width, screen_height;
+  gint                    preview_width;
+  gint                    preview_height;
+  gint                    index;
 
   G_OBJECT_CLASS (parent_class)->constructed (object);
 
@@ -351,18 +409,26 @@ animation_dialog_constructed (GObject *object)
                     dialog);
 
   /* Window Title */
-  image_name = animation_get_name (priv->animation);
-  text = g_strconcat (_("Animation Playback:"),
-                      " ", image_name, NULL);
+  image_name = gimp_image_get_name (priv->image_id);
+  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. */
+  /* Window paned. */
+  priv->vpaned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
+  gtk_container_add (GTK_CONTAINER (dialog), priv->vpaned);
+  gtk_widget_show (priv->vpaned);
+
+  hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_paned_pack1 (GTK_PANED (priv->vpaned), hpaned, TRUE, TRUE);
+  gtk_widget_show (hpaned);
+
+  /* Playback vertical box. */
   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
-  gtk_container_add (GTK_CONTAINER (dialog), main_vbox);
+  gtk_paned_pack1 (GTK_PANED (hpaned), main_vbox, TRUE, TRUE);
   gtk_widget_show (main_vbox);
 
   /* Upper Bar */
@@ -383,18 +449,21 @@ animation_dialog_constructed (GObject *object)
   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);
+  priv->proxycheckbox = gtk_expander_new_with_mnemonic (_("_Proxy Quality"));
+  gtk_expander_set_expanded (GTK_EXPANDER (priv->proxycheckbox), FALSE);
 
-  g_signal_connect (GTK_EXPANDER (widget),
+  g_signal_connect (GTK_EXPANDER (priv->proxycheckbox),
                     "notify::expanded",
                     G_CALLBACK (proxy_checkbox_expanded),
                     dialog);
 
-  gimp_help_set_help_data (widget, _("Degrade image quality for lower memory footprint"), NULL);
+  gimp_help_set_help_data (priv->proxycheckbox,
+                           _("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);
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar), priv->proxycheckbox,
+                    FALSE, FALSE, 0);
+  gtk_widget_show (priv->proxycheckbox);
 
   /* Settings: proxy image. */
   priv->proxycombo = gtk_combo_box_text_new_with_entry ();
@@ -407,7 +476,6 @@ animation_dialog_constructed (GObject *object)
                                   "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);
 
@@ -422,28 +490,20 @@ animation_dialog_constructed (GObject *object)
   gimp_help_set_help_data (priv->proxycombo, _("Proxy resolution quality"), NULL);
 
   gtk_widget_show (priv->proxycombo);
-  gtk_container_add (GTK_CONTAINER (widget), priv->proxycombo);
+  gtk_container_add (GTK_CONTAINER (priv->proxycheckbox),
+                     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. */
+       * Not that fps are double since some common framerates are not
+       * integer (in particular 23.976 in NTSC). */
       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",
@@ -453,9 +513,6 @@ animation_dialog_constructed (GObject *object)
                     "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);
 
@@ -463,32 +520,25 @@ animation_dialog_constructed (GObject *object)
   gtk_widget_show (priv->fpscombo);
 
   /* Settings: frame mode. */
-  widget = gtk_combo_box_text_new ();
+  priv->disposalcombo = 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);
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (priv->disposalcombo),
+                                  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);
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (priv->disposalcombo),
+                                  DISPOSE_REPLACE, 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_signal_connect (priv->disposalcombo, "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);
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar),
+                    priv->disposalcombo, FALSE, FALSE, 0);
+  gtk_widget_show (priv->disposalcombo);
 
   /*************/
   /* View box. */
@@ -568,7 +618,9 @@ animation_dialog_constructed (GObject *object)
 
   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_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. */
@@ -635,13 +687,11 @@ animation_dialog_constructed (GObject *object)
   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);
@@ -662,7 +712,6 @@ animation_dialog_constructed (GObject *object)
   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);
@@ -684,7 +733,6 @@ animation_dialog_constructed (GObject *object)
                     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);
@@ -704,18 +752,22 @@ animation_dialog_constructed (GObject *object)
 
   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);
+
+  /* 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);
+
+  preview_width = gimp_image_width (priv->image_id);
+  preview_height = gimp_image_height (priv->image_id);
+  /*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));
+                               MAX (preview_width + 20,
+                                    screen_width - 20),
+                               MAX (preview_height + 90,
+                                    screen_height - 20));
 
   /* shape_drawing_area for detached feature. */
   priv->shape_window = gtk_window_new (GTK_WINDOW_POPUP);
@@ -758,26 +810,10 @@ animation_dialog_constructed (GObject *object)
                     G_CALLBACK (repaint_da),
                     dialog);
 
-  /* We request a minimum size *after* having connecting the
+  /* We request a minimum size *after* having connected 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);
-  g_signal_connect (priv->animation, "low-framerate-playback",
-                    G_CALLBACK (low_framerate_cb),
-                    dialog);
 }
 
 static void
@@ -786,13 +822,13 @@ animation_dialog_set_property (GObject      *object,
                                const GValue *value,
                                GParamSpec   *pspec)
 {
-  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialog        *dialog = ANIMATION_DIALOG (object);
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
   switch (property_id)
     {
-    case PROP_ANIMATION:
-      priv->animation = g_value_dup_object (value);
+    case PROP_IMAGE:
+      priv->image_id = g_value_get_int (value);
       break;
 
     default:
@@ -807,13 +843,13 @@ animation_dialog_get_property (GObject      *object,
                                GValue       *value,
                                GParamSpec   *pspec)
 {
-  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialog        *dialog = ANIMATION_DIALOG (object);
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
   switch (property_id)
     {
-    case PROP_ANIMATION:
-      g_value_set_object (value, priv->animation);
+    case PROP_IMAGE:
+      g_value_set_int (value, priv->image_id);
       break;
 
     default:
@@ -822,13 +858,14 @@ animation_dialog_get_property (GObject      *object,
     }
 }
 
-
 static void
 animation_dialog_finalize (GObject *object)
 {
-  AnimationDialog *dialog = ANIMATION_DIALOG (object);
+  AnimationDialog        *dialog = ANIMATION_DIALOG (object);
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
+  animation_dialog_save_settings (ANIMATION_DIALOG (dialog));
+
   if (priv->shape_window)
     gtk_widget_destroy (GTK_WIDGET (priv->shape_window));
 
@@ -1031,11 +1068,11 @@ static void
 connect_accelerators (GtkUIManager   *ui_manager,
                       GtkActionGroup *group)
 {
-  GList          *action_list;
-  GList          *iter;
+  GList *action_list;
+  GList *iter;
 
   action_list = gtk_action_group_list_actions (group);
-  iter = action_list;
+  iter        = action_list;
   while (iter)
     {
       /* Make sure all the action's accelerator are correctly connected,
@@ -1052,6 +1089,253 @@ connect_accelerators (GtkUIManager   *ui_manager,
 }
 
 static void
+animation_dialog_set_animation (AnimationDialog *dialog,
+                                Animation       *animation)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar                  *text;
+  gdouble                 fps;
+  gint                    index;
+
+  /* Disconnect all handlers on the previous animation. */
+  if (priv->animation)
+    {
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            G_CALLBACK (proxy_changed),
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            G_CALLBACK (framerate_changed),
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            G_CALLBACK (playback_range_changed),
+                                            dialog);
+
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            (GCallback) show_loading_progress,
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            (GCallback) block_ui,
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            (GCallback) unblock_ui,
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            G_CALLBACK (render_callback),
+                                            dialog);
+      g_signal_handlers_disconnect_by_func (priv->animation,
+                                            G_CALLBACK (low_framerate_cb),
+                                            dialog);
+    }
+
+  /* Block all handlers on UI widgets. */
+  g_signal_handlers_block_by_func (GTK_EXPANDER (priv->proxycheckbox),
+                                   G_CALLBACK (proxy_checkbox_expanded),
+                                   dialog);
+  g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                                   G_CALLBACK (proxycombo_activated),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->proxycombo,
+                                   G_CALLBACK (proxycombo_changed),
+                                   dialog);
+
+  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);
+
+  g_signal_handlers_block_by_func (priv->disposalcombo,
+                                   G_CALLBACK (disposalcombo_changed),
+                                   dialog);
+
+  g_signal_handlers_block_by_func (gtk_bin_get_child (GTK_BIN (priv->zoomcombo)),
+                                   G_CALLBACK (zoomcombo_activated),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->zoomcombo,
+                                   G_CALLBACK (zoomcombo_changed),
+                                   dialog);
+
+  g_signal_handlers_block_by_func (priv->progress,
+                                   G_CALLBACK (progress_entered),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->progress,
+                                   G_CALLBACK (progress_left),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->progress,
+                                   G_CALLBACK (progress_motion),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->progress,
+                                   G_CALLBACK (progress_button),
+                                   dialog);
+
+  g_clear_object (&priv->animation);
+  priv->animation = animation;
+
+  /* Settings: proxy image. */
+  g_signal_handlers_unblock_by_func (GTK_EXPANDER (priv->proxycheckbox),
+                                     G_CALLBACK (proxy_checkbox_expanded),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                                   G_CALLBACK (proxycombo_activated),
+                                   dialog);
+  g_signal_handlers_unblock_by_func (priv->proxycombo,
+                                   G_CALLBACK (proxycombo_changed),
+                                   dialog);
+  /* Settings: fps */
+  fps = animation_get_framerate (animation);
+
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->fpscombo), -1);
+  for (index = 0; index < 5; index++)
+    {
+      if (get_fps (index) == fps)
+        {
+          gtk_combo_box_set_active (GTK_COMBO_BOX (priv->fpscombo),
+                                    index);
+          break;
+        }
+    }
+
+  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_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);
+
+  /* Settings: frame mode. */
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->disposalcombo),
+                            animation_get_disposal (priv->animation));
+
+  g_signal_handlers_unblock_by_func (priv->disposalcombo,
+                                     G_CALLBACK (disposalcombo_changed),
+                                     dialog);
+
+  /* View: zoom. */
+  g_signal_handlers_unblock_by_func (gtk_bin_get_child (GTK_BIN (priv->zoomcombo)),
+                                     G_CALLBACK (zoomcombo_activated),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (priv->zoomcombo,
+                                     G_CALLBACK (zoomcombo_changed),
+                                     dialog);
+
+
+  /* Progress bar. */
+  g_signal_handlers_unblock_by_func (priv->progress,
+                                     G_CALLBACK (progress_entered),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (priv->progress,
+                                     G_CALLBACK (progress_left),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (priv->progress,
+                                     G_CALLBACK (progress_motion),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (priv->progress,
+                                     G_CALLBACK (progress_button),
+                                     dialog);
+
+  /* Animation. */
+  g_signal_connect (priv->animation, "proxy",
+                    G_CALLBACK (proxy_changed),
+                    dialog);
+  g_signal_connect (priv->animation, "framerate-changed",
+                    G_CALLBACK (framerate_changed),
+                    dialog);
+  g_signal_connect (priv->animation,
+                    "playback-range",
+                    G_CALLBACK (playback_range_changed),
+                    dialog);
+
+  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);
+  g_signal_connect (priv->animation, "low-framerate-playback",
+                    G_CALLBACK (low_framerate_cb),
+                    dialog);
+
+}
+
+static void
+animation_dialog_save_settings (AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GimpParasite           *old_parasite;
+  gboolean                undo_step_started = FALSE;
+  CachedSettings          cached_settings;
+
+  /* First saving in cache for any image. */
+  cached_settings.disposal  = animation_get_disposal (priv->animation);
+  cached_settings.framerate = animation_get_framerate (priv->animation);
+
+  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 (priv->image_id,
+                                          PLUG_IN_PROC "/frame-disposal");
+  if (! old_parasite ||
+      *(DisposeType *) gimp_parasite_data (old_parasite) != cached_settings.disposal)
+    {
+      GimpParasite *parasite;
+
+      gimp_image_undo_group_start (priv->image_id);
+      undo_step_started = TRUE;
+
+      parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-disposal",
+                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
+                                    sizeof (cached_settings.disposal),
+                                    &cached_settings.disposal);
+      gimp_image_attach_parasite (priv->image_id, parasite);
+      gimp_parasite_free (parasite);
+    }
+  gimp_parasite_free (old_parasite);
+
+  old_parasite = gimp_image_get_parasite (priv->image_id, PLUG_IN_PROC "/framerate");
+  if (! old_parasite ||
+      *(gdouble *) gimp_parasite_data (old_parasite) != cached_settings.framerate)
+    {
+      GimpParasite *parasite;
+      if (! undo_step_started)
+        {
+          gimp_image_undo_group_start (priv->image_id);
+          undo_step_started = TRUE;
+        }
+      parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-rate",
+                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
+                                    sizeof (cached_settings.framerate),
+                                    &cached_settings.framerate);
+      gimp_image_attach_parasite (priv->image_id, parasite);
+      gimp_parasite_free (parasite);
+    }
+  gimp_parasite_free (old_parasite);
+
+  if (undo_step_started)
+    {
+      gimp_image_undo_group_end (priv->image_id);
+    }
+}
+
+static void
 animation_dialog_refresh (AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
@@ -1093,7 +1377,7 @@ static void
 update_ui_sensitivity (AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gboolean animated;
+  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. */
@@ -1142,18 +1426,27 @@ disposalcombo_changed (GtkWidget       *combo,
                        AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gboolean    was_playing;
-  DisposeType disposal;
+  Animation              *animation;
+  DisposeType             disposal;
 
-  disposal    = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-  was_playing = is_playing (dialog);
+  disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
 
-  animation_load (priv->animation, disposal, PROXY_KEEP);
-
-  if (was_playing)
+  if (! priv->animation ||
+      animation_get_disposal (priv->animation) != disposal)
     {
-      /* Initializing frames stopped the playing. I restart it. */
-      play_pause (dialog);
+      gdouble proxy;
+
+      if (priv->animation &&
+          animation_is_playing (priv->animation))
+        animation_stop (priv->animation);
+
+      /* Keep same proxy ratio as previously. */
+      proxy = animation_get_proxy (priv->animation);
+
+      animation = animation_new (priv->image_id, disposal);
+      animation_dialog_set_animation (dialog,
+                                      animation);
+      animation_load (animation, proxy);
     }
 }
 
@@ -1165,9 +1458,12 @@ fpscombo_activated (GtkEntry        *combo_entry,
                     AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  const gchar *active_text;
-  gdouble      fps;
-  gchar       *text;
+  const gchar            *active_text;
+  gdouble                 fps;
+
+  if (! priv->animation ||
+      ! animation_loaded (priv->animation))
+    return;
 
   active_text = gtk_entry_get_text (combo_entry);
   /* Try a text conversion, locale-aware. */
@@ -1181,15 +1477,10 @@ fpscombo_activated (GtkEntry        *combo_entry,
   else if (fps <= 0)
     {
       /* Null or negative framerates are impossible. */
-      fps = 0.1;
+      fps = 0.5;
     }
 
   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
@@ -1197,103 +1488,18 @@ 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);
-        }
-    }
-}
+  gint                    index;
 
+  index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
 
-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 (! priv->animation ||
+      ! animation_loaded (priv->animation))
+    return;
 
   /* 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);
+      animation_set_framerate (priv->animation, get_fps (index));
     }
 }
 
@@ -1379,6 +1585,96 @@ zoom_reset_callback (GtkAction       *action,
     }
 }
 
+/*
+ * 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, 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 = animation_is_playing (priv->animation);
+
+      animation_load (priv->animation, 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);
+    }
+}
+
 static void
 speed_up_callback (GtkAction       *action,
                    AnimationDialog *dialog)
@@ -1479,7 +1775,10 @@ startframe_changed (GtkAdjustment   *adjustment,
                     AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gdouble value = gtk_adjustment_get_value (adjustment);
+  gdouble                 value = gtk_adjustment_get_value (adjustment);
+
+  if (! priv->animation)
+    return;
 
   animation_set_playback_start (priv->animation, (gint) value);
 
@@ -1491,7 +1790,10 @@ endframe_changed (GtkAdjustment   *adjustment,
                   AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gdouble value = gtk_adjustment_get_value (adjustment);
+  gdouble                 value = gtk_adjustment_get_value (adjustment);
+
+  if (! priv->animation)
+    return;
 
   animation_set_playback_stop (priv->animation, (gint) value);
 
@@ -1504,7 +1806,7 @@ play_callback (GtkToggleAction *action,
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  if (is_playing (dialog))
+  if (! animation_is_playing (priv->animation))
     {
       gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause");
       animation_play (priv->animation);
@@ -1523,7 +1825,7 @@ play_callback (GtkToggleAction *action,
 
   g_object_set (action,
                 "tooltip",
-                is_playing (dialog) ?  _("Pause playback") : _("Start playback"),
+                animation_is_playing (priv->animation) ?  _("Pause playback") : _("Start playback"),
                 NULL);
 }
 
@@ -1533,7 +1835,7 @@ step_back_callback (GtkAction           *action,
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  if (is_playing (dialog))
+  if (animation_is_playing (priv->animation))
     play_pause (dialog);
   animation_prev (priv->animation);
 }
@@ -1544,8 +1846,8 @@ step_callback (GtkAction       *action,
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  if (is_playing (dialog))
-    play_pause (dialog);
+  if (animation_is_playing (priv->animation))
+    animation_stop (priv->animation);
   animation_next (priv->animation);
 }
 
@@ -1554,7 +1856,9 @@ rewind_callback (GtkAction       *action,
                  AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gboolean was_playing = is_playing (dialog);
+  gboolean                was_playing;
+
+  was_playing = animation_is_playing (priv->animation);
 
   if (was_playing)
     play_pause (dialog);
@@ -1573,7 +1877,8 @@ refresh_callback (GtkAction       *action,
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
   animation_dialog_refresh (dialog);
-  animation_load (priv->animation, DISPOSE_KEEP, PROXY_KEEP);
+  animation_load (priv->animation,
+                  animation_get_proxy (priv->animation));
 }
 
 static void
@@ -1622,7 +1927,10 @@ detach_callback (GtkToggleAction *action,
   /* Force a refresh after detachment/attachment. */
   buffer = animation_get_frame (priv->animation,
                                 animation_get_position (priv->animation));
-  render_frame (dialog, buffer);
+  render_frame (dialog, buffer, TRUE);
+  /* clean up */
+  if (buffer != NULL)
+    g_object_unref (buffer);
 }
 
 static gboolean
@@ -1668,7 +1976,7 @@ block_ui (Animation       *animation,
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  if (is_playing (dialog))
+  if (animation_is_playing (priv->animation))
     play_pause (dialog);
 
   gtk_action_group_set_sensitive (priv->play_actions, FALSE);
@@ -1744,6 +2052,44 @@ playback_range_changed (Animation       *animation,
 }
 
 static void
+proxy_changed (Animation       *animation,
+               gdouble          proxy,
+               AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  gchar                  *text;
+
+  g_signal_handlers_block_by_func (GTK_EXPANDER (priv->proxycheckbox),
+                                   G_CALLBACK (proxy_checkbox_expanded),
+                                   dialog);
+  g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                                   G_CALLBACK (proxycombo_activated),
+                                   dialog);
+  g_signal_handlers_block_by_func (priv->proxycombo,
+                                   G_CALLBACK (proxycombo_changed),
+                                   dialog);
+
+  text = g_strdup_printf  (_("%g %%"), proxy * 100.0);
+  gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))), text);
+  g_free (text);
+
+  if (proxy == 1.0)
+    gtk_expander_set_expanded (GTK_EXPANDER (priv->proxycheckbox), FALSE);
+  else
+    gtk_expander_set_expanded (GTK_EXPANDER (priv->proxycheckbox), TRUE);
+
+  g_signal_handlers_unblock_by_func (GTK_EXPANDER (priv->proxycheckbox),
+                                     G_CALLBACK (proxy_checkbox_expanded),
+                                     dialog);
+  g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
+                                   G_CALLBACK (proxycombo_activated),
+                                   dialog);
+  g_signal_handlers_unblock_by_func (priv->proxycombo,
+                                   G_CALLBACK (proxycombo_changed),
+                                   dialog);
+}
+
+static void
 framerate_changed (Animation        *animation,
                    gdouble           fps,
                    AnimationDialog  *dialog)
@@ -1759,6 +2105,7 @@ framerate_changed (Animation        *animation,
                                    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);
@@ -1768,22 +2115,6 @@ framerate_changed (Animation        *animation,
 }
 
 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);
-}
-
-static void
 low_framerate_cb (Animation       *animation,
                   gdouble          real_framerate,
                   AnimationDialog *dialog)
@@ -1917,7 +2248,7 @@ da_size_callback (GtkWidget       *drawing_area,
 
   animation_get_size (priv->animation, &preview_width, &preview_height);
   priv->zoom = MIN ((gdouble) allocation->width / (gdouble) preview_width,
-                                (gdouble) allocation->height / (gdouble) preview_height);
+                    (gdouble) allocation->height / (gdouble) preview_height);
 
   *drawing_data = g_malloc (allocation->width * allocation->height * 3);
 
@@ -1946,7 +2277,7 @@ da_size_callback (GtkWidget       *drawing_area,
         }
 
       /* As we re-allocated the drawn data, let's render it again. */
-      if (animation_loaded (priv->animation))
+      if (priv->animation && animation_loaded (priv->animation))
         {
           if (! gtk_widget_get_realized (drawing_area))
             {
@@ -1962,15 +2293,18 @@ da_size_callback (GtkWidget       *drawing_area,
 
               buffer = animation_get_frame (priv->animation,
                                             animation_get_position (priv->animation));
-              render_frame (dialog, buffer);
+              render_frame (dialog, buffer, TRUE);
+              /* clean up */
+              if (buffer != NULL)
+                g_object_unref (buffer);
             }
         }
     }
 }
 
 static gboolean
-shape_pressed (GtkWidget           *widget,
-               GdkEventButton      *event,
+shape_pressed (GtkWidget       *widget,
+               GdkEventButton  *event,
                AnimationDialog *dialog)
 {
   if (da_button_press (widget, event, dialog))
@@ -2042,9 +2376,10 @@ static void
 render_callback (Animation       *animation,
                  gint             frame_number,
                  GeglBuffer      *buffer,
+                 gboolean         must_draw_null,
                  AnimationDialog *dialog)
 {
-  render_frame (dialog, buffer);
+  render_frame (dialog, buffer, must_draw_null);
 
   /* Update UI. */
   show_playing_progress (dialog);
@@ -2059,7 +2394,10 @@ render_on_realize (GtkWidget       *drawing_area,
 
   buffer = animation_get_frame (priv->animation,
                                 animation_get_position (priv->animation));
-  render_frame (dialog, buffer);
+  render_frame (dialog, buffer, TRUE);
+  /* clean up */
+  if (buffer != NULL)
+    g_object_unref (buffer);
 
   g_signal_handlers_disconnect_by_func (drawing_area,
                                         G_CALLBACK (render_on_realize),
@@ -2068,7 +2406,8 @@ render_on_realize (GtkWidget       *drawing_area,
 
 static void
 render_frame (AnimationDialog *dialog,
-              GeglBuffer      *buffer)
+              GeglBuffer      *buffer,
+              gboolean         must_draw_null)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
   static gchar  *shape_preview_mask      = NULL;
@@ -2086,13 +2425,13 @@ render_frame (AnimationDialog *dialog,
 
   /* If the animation returns a NULL buffer, it means the image to
    * display hasn't changed. */
-  if (buffer == NULL || ! animation_loaded (priv->animation))
+  if ((!must_draw_null && buffer == NULL)                ||
+      ! animation_loaded (priv->animation)               ||
+      /* Do not try to render on unrealized drawing areas. */
+      ! gtk_widget_get_realized (priv->shape_drawing_area) ||
+      ! gtk_widget_get_realized (priv->drawing_area))
     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;
@@ -2114,6 +2453,9 @@ render_frame (AnimationDialog *dialog,
       total_alpha_preview (preview_data, drawing_width, drawing_height);
     }
 
+  if (buffer == NULL)
+    return;
+
   /* When there is no frame to show, we simply display the alpha background and return. */
   if (animation_get_length (priv->animation) > 0)
     {
@@ -2181,9 +2523,6 @@ render_frame (AnimationDialog *dialog,
             }
           reshape_from_bitmap (dialog, shape_preview_mask);
         }
-
-      /* clean up */
-      g_object_unref (buffer);
     }
 
   /* Display the preview buffer. */
@@ -2241,8 +2580,6 @@ progress_button (GtkWidget       *widget,
                  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)
@@ -2252,18 +2589,10 @@ progress_button (GtkWidget       *widget,
 
       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);
+      goto_frame = animation_get_start_position (priv->animation) +
+                  (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
 
-      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);
-        }
+      animation_jump (priv->animation, goto_frame);
     }
 
   return FALSE;
@@ -2275,14 +2604,15 @@ progress_entered (GtkWidget        *widget,
                   AnimationDialog  *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  GtkAllocation  allocation;
-  guint          goto_frame;
+  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)));
+  goto_frame = animation_get_start_position (priv->animation) +
+               (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
 
-  show_goto_progress (goto_frame, dialog);
+  show_goto_progress (dialog, goto_frame);
 
   return FALSE;
 }
@@ -2298,9 +2628,10 @@ progress_motion (GtkWidget       *widget,
 
   gtk_widget_get_allocation (widget, &allocation);
 
-  goto_frame = animation_get_first_frame (priv->animation) + (gint) (event->x / (allocation.width / 
animation_get_length (priv->animation)));
+  goto_frame = animation_get_start_position (priv->animation) +
+               (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
 
-  show_goto_progress (goto_frame, dialog);
+  show_goto_progress (dialog, goto_frame);
 
   return FALSE;
 }
@@ -2316,22 +2647,26 @@ progress_left (GtkWidget        *widget,
 }
 
 static void
-show_goto_progress (gint             goto_frame,
-                    AnimationDialog *dialog)
+show_goto_progress (AnimationDialog *dialog,
+                    gint             goto_frame)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gchar         *text;
+  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) (goto_frame - animation_get_start_position (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));
+  if (animation_get_start_position (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));
+    text = g_strdup_printf (_("Go to frame %d [%d-%d]"),
+                            goto_frame, 
+                            animation_get_start_position (priv->animation),
+                            animation_get_start_position (priv->animation) + animation_get_length 
(priv->animation) - 1);
   gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progress), text);
   g_free (text);
 }
@@ -2345,10 +2680,10 @@ show_playing_progress (AnimationDialog *dialog)
   /* 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_position (priv->animation) - 
animation_get_start_position (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)
+  if (animation_get_start_position (priv->animation) == 1)
     {
       text = g_strdup_printf (_("Frame %d of %d"),
                               animation_get_position (priv->animation),
@@ -2356,23 +2691,16 @@ show_playing_progress (AnimationDialog *dialog)
     }
   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));
+      text = g_strdup_printf (_("Frame %d [%d-%d]"),
+                              animation_get_position (priv->animation),
+                              animation_get_start_position (priv->animation),
+                              animation_get_start_position (priv->animation) + animation_get_length 
(priv->animation) - 1);
     }
   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);
@@ -2447,14 +2775,12 @@ get_zoom (AnimationDialog *dialog,
          * 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);
+        g_free (active_text);
 
         /* 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;
       }
     }
@@ -2465,13 +2791,12 @@ 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;
+  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;
+  if (priv->animation == NULL)
+    return;
 
   animation_get_size (priv->animation, &preview_width, &preview_height);
 
diff --git a/plug-ins/animation-play/animation-dialog.h b/plug-ins/animation-play/widgets/animation-dialog.h
similarity index 90%
rename from plug-ins/animation-play/animation-dialog.h
rename to plug-ins/animation-play/widgets/animation-dialog.h
index 6fa08d4..a1397d6 100755
--- a/plug-ins/animation-play/animation-dialog.h
+++ b/plug-ins/animation-play/widgets/animation-dialog.h
@@ -2,7 +2,7 @@
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  *
  * animation-dialog.c
- * Copyright (C) 2015 Jehan <jehan gimp org>
+ * Copyright (C) 2015-2016 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
@@ -41,8 +41,8 @@ struct _AnimationDialogClass
   GtkWindowClass  parent_class;
 };
 
-GType       animation_dialog_get_type (void) G_GNUC_CONST;
+GType       animation_dialog_get_type             (void) G_GNUC_CONST;
 
-GtkWidget * animation_dialog_new      (Animation *animation);
+GtkWidget * animation_dialog_new                  (gint32 image_id);
 
 #endif  /*  __ANIMATION_DIALOG_H__  */


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