[gimp/wip/animation: 45/197] plug-ins: major cleanup of animation-play.



commit 0beb32299389a82a3a916daa9ae2748190586630
Author: Jehan <jehan girinstud io>
Date:   Fri Aug 29 04:34:32 2014 +0200

    plug-ins: major cleanup of animation-play.
    
    - Got rid of all the global variables, but the regexp;
    - much nicer UI, with all play tools at the bottom, animation settings
    at top left, and view settings at top right.
    - got rid of the duration_factor, but allow now fps entry, and shortcut
    to easily in|decrement fps.
    - fine proxy quality instead of just a rough checkbox.
    - Better grouped actions, and menus only filled with some actions which
    are not available as UI, with visible accelerator shown alongside.

 plug-ins/common/animation-play.c | 3446 ++++++++++++++++++++++----------------
 1 files changed, 2007 insertions(+), 1439 deletions(-)
---
diff --git a/plug-ins/common/animation-play.c b/plug-ins/common/animation-play.c
index 590506a..a4c5fc4 100644
--- a/plug-ins/common/animation-play.c
+++ b/plug-ins/common/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-2013 : jehan at girinstud.io
+ * (c) Jehan : 2012-2014 : jehan at girinstud.io
  *
  * GIMP - The GNU Image Manipulation Program
  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
@@ -32,12 +32,21 @@
 
 #include "libgimp/stdplugins-intl.h"
 
-
 #define PLUG_IN_PROC   "plug-in-animationplay"
 #define PLUG_IN_BINARY "animation-play"
 #define PLUG_IN_ROLE   "gimp-animation-play"
+
 #define DITHERTYPE     GDK_RGB_DITHER_NORMAL
 
+#define MAX_FRAMERATE  300.0
+
+/* Some regexp used to parse layer tags.
+ * A little ugly to have these here, but since the regexp will be
+ * used many times, it is more efficient to store them globally .*/
+static const GRegex *nospace_reg;
+static const GRegex *layers_reg;
+static const GRegex *all_reg;
+
 typedef enum
 {
   DISPOSE_COMBINE   = 0x00,
@@ -50,106 +59,208 @@ typedef struct
   gint32  drawable_id;
   GList  *indexes;
   GList  *updated_indexes;
+
+  guint  duration;
 }
 Frame;
 
 typedef struct
 {
-  gint        duration_index;
-  DisposeType default_frame_disposal;
-  guint32     default_frame_duration;
+  /* Settings saved as parasites. */
+  DisposeType frame_disposal;
+  gdouble     frame_rate;
+
+  /* Animation: the frame_min and frame_max are the first and last set
+   * frames, whereas the (start|end)_frame are the range for playback.
+   * These settings are computed at startup, and not saved. */
+  guint       current_frame;
+  gint32      num_frames;
+  guint       frame_min;
+  guint       frame_max;
+  guint       start_frame;
+  guint       end_frame;
+
+  gdouble     scale; /* Zoom. 1.0 is 100%. */
 }
 AnimationSettings;
 
+typedef struct
+{
+  /* Image */
+  gint32             image_id;
+
+  /* GUI */
+  GtkWidget         *window;
+  GtkWidget         *shape_window;
+
+  GtkWidget         *play_bar;
+  GtkWidget         *progress_bar;
+  GtkWidget         *settings_bar;
+  GtkWidget         *view_bar;
+  GtkWidget         *refresh;
+
+  GtkWidget         *fpscombo;
+  GtkWidget         *zoomcombo;
+  GtkWidget         *proxycombo;
+
+  GtkWidget         *progress;
+
+  GtkWidget         *startframe_spin;
+  GtkWidget         *endframe_spin;
+
+  GtkWidget         *drawing_area;
+  guchar            *drawing_area_data;
+  guint              drawing_area_width;
+  guint              drawing_area_height;
+
+  GtkWidget         *shape_drawing_area;
+  guchar            *shape_drawing_area_data;
+  guint              shape_drawing_area_width;
+  guint              shape_drawing_area_height;
+
+  guint              preview_width;
+  guint              preview_height;
+
+  /* Animations */
+  AnimationSettings  settings;
+  /* We don't associate the frames to the AnimationSettings, but to the
+   * dialog box, because it takes quite a bit of memory. So we want to
+   * allocate only the currently running drawables. */
+  Frame            **frames;
+  /* Since the application is single-threaded, a boolean is enough.
+   * It may become a mutex in the future with multi-thread support. */
+  gboolean           frames_lock;
+
+  /* Actions */
+  GtkUIManager      *ui_manager;
+
+  GtkActionGroup    *play_actions;
+  GtkActionGroup    *settings_actions;
+  GtkActionGroup    *view_actions;
+  GtkActionGroup    *various_actions;
+}
+AnimationPlayDialog;
+
 /* for shaping */
 typedef struct
 {
   gint x, y;
 } CursorOffset;
 
-/* Declare local functions. */
 static void        query                     (void);
-static void        run                       (const gchar      *name,
-                                              gint              nparams,
-                                              const GimpParam  *param,
-                                              gint             *nreturn_vals,
-                                              GimpParam       **return_vals);
-
-static void        initialize                (void);
-static void        build_dialog              (gchar           *imagename);
-static void        refresh_dialog            (gchar           *imagename);
-
-static void        da_size_callback          (GtkWidget *widget,
-                                              GtkAllocation *allocation, void *data);
-static void        sda_size_callback         (GtkWidget *widget,
-                                              GtkAllocation *allocation, void *data);
-
-static void        window_destroy            (GtkWidget       *widget);
-static void        play_callback             (GtkToggleAction *action);
-static void        step_back_callback        (GtkAction       *action);
-static void        step_callback             (GtkAction       *action);
-static void        refresh_callback          (GtkAction       *action);
-static void        rewind_callback           (GtkAction       *action);
-static void        speed_up_callback         (GtkAction       *action);
-static void        speed_down_callback       (GtkAction       *action);
-static void        speed_reset_callback      (GtkAction       *action);
-static void        framecombo_changed        (GtkWidget       *combo,
-                                              gpointer         data);
-static void        speedcombo_changed        (GtkWidget       *combo,
-                                              gpointer         data);
-static void        fpscombo_changed          (GtkWidget       *combo,
-                                              gpointer         data);
-static void        zoomcombo_activated       (GtkEntry        *combo,
-                                              gpointer         data);
-static void        zoomcombo_changed         (GtkWidget       *combo,
-                                              gpointer         data);
-static void        startframe_changed        (GtkAdjustment   *adjustment,
-                                              gpointer         user_data);
-static void        endframe_changed          (GtkAdjustment   *adjustment,
-                                              gpointer         user_data);
-static void        quality_checkbox_toggled  (GtkToggleButton *button,
-                                              gpointer         data);
-static gboolean    repaint_sda               (GtkWidget       *darea,
-                                              GdkEventExpose  *event,
-                                              gpointer         data);
-static gboolean    repaint_da                (GtkWidget       *darea,
-                                              GdkEventExpose  *event,
-                                              gpointer         data);
-
-static void        init_quality_checkbox     (void);
-static void        init_frames               (void);
-static void        render_frame              (guint            whichframe);
-static void        show_playing_progress     (void);
-static void        show_loading_progress     (gint             layer_nb);
-static void        show_goto_progress        (guint            frame_nb);
-static void        total_alpha_preview       (guchar *da_data,
-                                              guint da_width,
-                                              guint da_height);
-static void        update_alpha_preview      (void);
-static void        update_combobox           (void);
-static gdouble     get_duration_factor       (gint             index);
-static gint        get_fps                   (gint             index);
-static gdouble     get_scale                 (gint             index);
-static void        update_scale              (gdouble          scale);
+static void        run                       (const gchar          *name,
+                                              gint                  nparams,
+                                              const GimpParam      *param,
+                                              gint                 *nreturn_vals,
+                                              GimpParam           **return_vals);
+
+/* Initialization. */
+static void        initialize                (AnimationPlayDialog  *dialog);
+static void        init_frame_numbers        (AnimationPlayDialog  *dialog,
+                                              gint                 *layers,
+                                              gint                  num_layers);
+static gboolean    init_frames               (AnimationPlayDialog  *dialog);
+static void        init_ui                   (AnimationPlayDialog  *dialog,
+                                              gchar                *imagename);
+
+static void        refresh_dialog            (AnimationPlayDialog  *dialog);
+static void        update_ui_sensitivity     (AnimationPlayDialog  *dialog);
+static void        update_scale              (AnimationPlayDialog  *dialog,
+                                              gdouble               scale);
+
+/* All callbacks. */
+static gboolean    popup_menu                (GtkWidget            *widget,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    button_press              (GtkWidget            *widget,
+                                              GdkEventButton       *event,
+                                              AnimationPlayDialog  *dialog);
+static void        da_size_callback          (GtkWidget            *widget,
+                                              GtkAllocation        *allocation,
+                                              AnimationPlayDialog  *dialog);
+
+static void        window_destroy            (GtkWidget            *widget,
+                                              AnimationPlayDialog  *dialog);
+static void        play_callback             (GtkToggleAction      *action,
+                                              AnimationPlayDialog  *dialog);
+static void        step_back_callback        (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        step_callback             (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        rewind_callback           (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        refresh_callback          (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        zoom_in_callback          (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        zoom_out_callback         (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        zoom_reset_callback       (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        speed_up_callback         (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        speed_down_callback       (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        framecombo_changed        (GtkWidget             *combo,
+                                              AnimationPlayDialog  *dialog);
+static void        fpscombo_activated        (GtkEntry            *combo,
+                                              AnimationPlayDialog *dialog);
+static void        fpscombo_changed          (GtkWidget             *combo,
+                                              AnimationPlayDialog  *dialog);
+static void        zoomcombo_activated       (GtkEntry             *combo,
+                                              AnimationPlayDialog  *dialog);
+static void        zoomcombo_changed         (GtkWidget            *combo,
+                                              AnimationPlayDialog  *dialog);
+static void        startframe_changed        (GtkAdjustment        *adjustment,
+                                              AnimationPlayDialog  *dialog);
+static void        endframe_changed          (GtkAdjustment        *adjustment,
+                                              AnimationPlayDialog  *dialog);
+static void        proxy_checkbox_expanded   (GtkExpander          *expander,
+                                              GParamSpec           *param_spec,
+                                              AnimationPlayDialog  *dialog);
+static void        proxycombo_activated      (GtkEntry             *combo_entry,
+                                              AnimationPlayDialog  *dialog);
+static void        proxycombo_changed        (GtkWidget            *combo,
+                                              AnimationPlayDialog  *dialog);
+
+static gboolean    repaint_da                (GtkWidget            *darea,
+                                              GdkEventExpose       *event,
+                                              AnimationPlayDialog  *dialog);
+
+static void        render_frame              (AnimationPlayDialog  *dialog,
+                                              gboolean              force);
+static void        show_playing_progress     (AnimationPlayDialog  *dialog);
+static void        show_loading_progress     (AnimationPlayDialog  *dialog,
+                                              gint                  layer_nb,
+                                              gint                  num_layers);
+static void        show_goto_progress        (guint                 frame_nb,
+                                              AnimationPlayDialog  *dialog);
+static void        total_alpha_preview       (guchar               *drawing_data,
+                                              guint                 drawing_width,
+                                              guint                 drawing_height);
 
 /* Utils */
-static void        remove_timer              (void);
-static void        clean_exit                (void);
+static gdouble     get_fps                   (gint                  index);
+static gdouble     get_proxy                 (AnimationPlayDialog  *dialog,
+                                              gint                  index);
+static gdouble     get_scale                 (AnimationPlayDialog  *dialog,
+                                              gint                  index);
+static void        set_timer                 (guint                 new_timer);
+static void        save_settings             (AnimationPlayDialog  *dialog);
+static void        clean_exit                (AnimationPlayDialog *dialog);
 
 /* tag util functions*/
-static gint        parse_ms_tag              (const gchar     *str);
-static void        rec_set_total_frames      (const gint32     layer,
-                                              gint *min,
-                                              gint *max);
-static void        set_total_frames          (void);
-static DisposeType parse_disposal_tag        (const gchar     *str);
-static gboolean    is_disposal_tag           (const gchar     *str,
-                                              DisposeType     *disposal,
-                                              gint            *taglength);
-static gboolean    is_ms_tag                 (const gchar     *str,
-                                              gint            *duration,
-                                              gint            *taglength);
-
+static gint        parse_ms_tag              (const gchar          *str);
+static void        rec_set_total_frames      (const gint32          layer,
+                                              gint                 *min,
+                                              gint                 *max);
+static DisposeType parse_disposal_tag        (AnimationPlayDialog  *dialog,
+                                              const gchar          *str);
+static gboolean    is_disposal_tag           (const gchar          *str,
+                                              DisposeType          *disposal,
+                                              gint                 *taglength);
+static gboolean    is_ms_tag                 (const gchar          *str,
+                                              gint                 *duration,
+                                              gint                 *taglength);
 
 const GimpPlugInInfo PLUG_IN_INFO =
 {
@@ -159,107 +270,6 @@ const GimpPlugInInfo PLUG_IN_INFO =
   run,   /* run_proc   */
 };
 
-
-/* Global widgets'n'stuff */
-static GtkWidget         *window                    = NULL;
-static gulong             destroy_handler;
-static GdkWindow         *root_win                  = NULL;
-static GtkUIManager      *ui_manager                = NULL;
-static GtkWidget         *progress;
-static GtkWidget         *speedcombo                = NULL;
-static GtkWidget         *fpscombo                  = NULL;
-static GtkWidget         *zoomcombo                 = NULL;
-static GtkWidget         *quality_checkbox          = NULL;
-static GtkWidget         *frame_disposal_combo      = NULL;
-static GtkAdjustment     *startframe_adjust         = NULL;
-static GtkWidget         *startframe_spin           = NULL;
-static GtkAdjustment     *endframe_adjust           = NULL;
-static GtkWidget         *endframe_spin             = NULL;
-
-static gint32             image_id;
-static guint              width                     = -1,
-                          height                    = -1,
-                          preview_width, preview_height;
-static gint32            *layers                    = NULL;
-static gint32             total_layers              = 0;
-
-static GtkWidget         *drawing_area              = NULL;
-static guchar            *drawing_area_data         = NULL;
-static guint              drawing_area_width        = -1,
-                          drawing_area_height       = -1;
-static guchar            *preview_alpha1_data       = NULL;
-static guchar            *preview_alpha2_data       = NULL;
-
-static GtkWidget         *shape_window              = NULL;
-static GtkWidget         *shape_drawing_area        = NULL;
-static guchar            *shape_drawing_area_data   = NULL;
-static guint              shape_drawing_area_width  = -1,
-                          shape_drawing_area_height = -1;
-static gchar             *shape_preview_mask        = NULL;
-
-static gint32             frames_image_id;
-static gint32             total_frames              = 0;
-static Frame            **frames                    = NULL;
-static guchar            *rawframe                  = NULL;
-static guint32           *frame_durations           = NULL;
-static guint              frame_number;
-static guint              frame_number_min, frame_number_max;
-static guint              start_frame, end_frame;
-/* Since the application is single-thread, a boolean is enough.
- * It may become a mutex in the future with multi-thread support. */
-static gboolean           frames_lock               = FALSE;
-
-static gboolean           playing                   = FALSE;
-static gboolean           force_render              = TRUE;
-static gboolean           initialized_once          = FALSE;
-static guint              timer                     = 0;
-static gboolean           detached                  = FALSE;
-static gdouble            scale, shape_scale;
-static gulong             progress_entered_handler, progress_motion_handler,
-                          progress_left_handler, progress_button_handler;
-
-/* Some regexp used to parse layer tags. */
-static GRegex* nospace_reg = NULL;
-static GRegex* layers_reg = NULL;
-static GRegex* all_reg = NULL;
-
-/* Default settings. */
-static AnimationSettings settings =
-{
-  3,
-  DISPOSE_COMBINE,
-  100 /* ms */
-};
-
-static gint32 frames_image_id = 0;
-
-static void
-clean_exit (void)
-{
-  if (playing)
-    remove_timer ();
-
-  if (frames_image_id)
-    gimp_image_delete (frames_image_id);
-  frames_image_id = 0;
-
-  if (shape_window)
-    gtk_widget_destroy (GTK_WIDGET (shape_window));
-
-  g_signal_handler_disconnect (window, destroy_handler);
-  if (window)
-    gtk_widget_destroy (GTK_WIDGET (window));
-
-  if (frames)
-    g_free (frames);
-  if (frame_durations)
-    g_free (frame_durations);
-
-  gegl_exit ();
-  gtk_main_quit ();
-  gimp_quit ();
-}
-
 MAIN ()
 
 static void
@@ -273,12 +283,12 @@ query (void)
   };
 
   gimp_install_procedure (PLUG_IN_PROC,
-                          N_("Preview a GIMP layer-based animation"),
+                          N_("Preview a GIMP animation"),
                           "",
                           "Adam D. Moss <adam gimp org>",
                           "Adam D. Moss <adam gimp org>",
                           "1997, 1998...",
-                          N_("_Playback..."),
+                          N_("Animation _Playback..."),
                           "RGB*, INDEXED*, GRAY*",
                           GIMP_PLUGIN,
                           G_N_ELEMENTS (args), 0,
@@ -296,17 +306,19 @@ run (const gchar      *name,
      gint             *nreturn_vals,
      GimpParam       **return_vals)
 {
-  static GimpParam  values[1];
-  GimpRunMode       run_mode;
-  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
-  GeglConfig       *config;
+  static GimpParam   values[1];
+  GimpPDBStatusType  status;
+  GimpRunMode        run_mode;
+  GeglConfig        *config;
 
   INIT_I18N ();
+
   gegl_init (NULL, NULL);
   config = gegl_config ();
   /* For preview, we want fast (0.0) over high quality (1.0). */
   g_object_set (config, "quality", 0.0, NULL);
 
+  status        = GIMP_PDB_SUCCESS;
   *nreturn_vals = 1;
   *return_vals  = values;
 
@@ -314,198 +326,260 @@ run (const gchar      *name,
 
  if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
    {
+     /* This plugin is meaningless right now other than interactive. */
      status = GIMP_PDB_CALLING_ERROR;
    }
-
-  if (status == GIMP_PDB_SUCCESS)
-    {
-      gimp_get_data (PLUG_IN_PROC, &settings);
-      image_id = param[1].data.d_image;
-
-      /* Frame parsing. */
-      nospace_reg = g_regex_new("[ \t]*", G_REGEX_OPTIMIZE, 0, NULL);
-      layers_reg = g_regex_new("\\[(([0-9]+(-[0-9]+)?)(,[0-9]+(-[0-9]+)?)*)\\]", G_REGEX_OPTIMIZE, 0, NULL);
-      all_reg = g_regex_new("\\[\\*\\]", G_REGEX_OPTIMIZE, 0, NULL);
-
-      initialize ();
-
-      /* At least one full initialization finished. */
-      initialized_once = TRUE;
-
-      gtk_main ();
-      gimp_set_data (PLUG_IN_PROC, &settings, sizeof (settings));
-
-      if (run_mode != GIMP_RUN_NONINTERACTIVE)
-        gimp_displays_flush ();
-    }
+ else
+   {
+     AnimationPlayDialog *dialog;
+     GimpParasite        *parasite;
+     gint                 image_width;
+     gint                 image_height;
+
+     /********************************************/
+     /* Init the global variable: frame parsing. */
+     nospace_reg = g_regex_new("[ \t]*", G_REGEX_OPTIMIZE, 0, NULL);
+     layers_reg  = g_regex_new("\\[(([0-9]+(-[0-9]+)?)(,[0-9]+(-[0-9]+)?)*)\\]", G_REGEX_OPTIMIZE, 0, NULL);
+     all_reg     = g_regex_new("\\[\\*\\]", G_REGEX_OPTIMIZE, 0, NULL);
+
+     /**********************************************************************/
+     /* Now init the struct for the animation, which is its current state. */
+     dialog = g_new (AnimationPlayDialog, 1);
+
+     dialog->image_id    = param[1].data.d_image;
+     image_width  = gimp_image_width (dialog->image_id);
+     image_height = gimp_image_height (dialog->image_id);
+
+     dialog->frames      = NULL;
+     dialog->frames_lock = FALSE;
+
+      /* We default at full preview size. */
+     dialog->preview_width  = image_width;
+     dialog->preview_height = image_height;
+
+     /* Acceptable settings for the default. */
+     dialog->settings.frame_disposal = DISPOSE_COMBINE;
+     dialog->settings.frame_rate     = 24.0; /* fps */
+
+     /* Not yet initialized animation. */
+     dialog->settings.current_frame = 0;
+     dialog->settings.num_frames    = 0;
+     dialog->settings.frame_min     = 0;
+     dialog->settings.frame_max     = 0;
+     dialog->settings.start_frame   = 0;
+     dialog->settings.end_frame     = 0;
+
+     /* If we saved any settings globally, use the one from the last run. */
+     gimp_get_data (PLUG_IN_PROC, &dialog->settings);
+
+     /* If this image has specific settings already, override the global ones. */
+     parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-disposal");
+     if (parasite)
+       {
+         const DisposeType *mode = gimp_parasite_data (parasite);
+
+         dialog->settings.frame_disposal = *mode;
+         gimp_parasite_free (parasite);
+       }
+     parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-rate");
+     if (parasite)
+       {
+         const gdouble *rate = gimp_parasite_data (parasite);
+
+         dialog->settings.frame_rate = *rate;
+         gimp_parasite_free (parasite);
+       }
+
+     /* UI setup. */
+     dialog->window       = NULL;
+
+     dialog->play_bar     = NULL;
+     dialog->progress_bar = NULL;
+     dialog->settings_bar = NULL;
+     dialog->view_bar     = NULL;
+     dialog->refresh      = NULL;
+
+     dialog->fpscombo     = NULL;
+     dialog->zoomcombo    = NULL;
+     dialog->proxycombo   = NULL;
+
+     dialog->ui_manager   = NULL;
+     dialog->progress     = NULL;
+     dialog->startframe_spin = NULL;
+     dialog->endframe_spin = NULL;
+     dialog->shape_window = NULL;
+     dialog->shape_drawing_area = NULL;
+     dialog->shape_drawing_area_data = NULL;
+     dialog->drawing_area = NULL;
+     dialog->drawing_area_data = NULL;
+     dialog->drawing_area_width = -1;
+     dialog->drawing_area_height = -1;
+     dialog->shape_drawing_area_width = -1;
+     dialog->shape_drawing_area_height = -1;
+
+     initialize (dialog);
+
+     gtk_main ();
+     gimp_set_data (PLUG_IN_PROC, &dialog->settings, sizeof (dialog->settings));
+
+     if (run_mode != GIMP_RUN_NONINTERACTIVE)
+       gimp_displays_flush ();
+   }
 
   values[0].type = GIMP_PDB_STATUS;
   values[0].data.d_status = status;
 
-  gimp_image_delete (frames_image_id);
   gegl_exit ();
 }
 
 static void
-reshape_from_bitmap (const gchar *bitmap)
+reshape_from_bitmap (AnimationPlayDialog *dialog,
+                     const gchar         *bitmap)
 {
   static gchar *prev_bitmap = NULL;
   static guint  prev_width = -1;
   static guint  prev_height = -1;
 
-  if ((!prev_bitmap) ||
-      prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height ||
-      (memcmp (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + 
shape_drawing_area_height)))
+  if ((! prev_bitmap)                                  ||
+      prev_width != dialog->shape_drawing_area_width   ||
+      prev_height != dialog->shape_drawing_area_height ||
+      (memcmp (prev_bitmap, bitmap,
+               (dialog->shape_drawing_area_width *
+                dialog->shape_drawing_area_height) / 8 +
+               dialog->shape_drawing_area_height)))
     {
       GdkBitmap *shape_mask;
 
-      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (shape_window),
+      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (dialog->shape_window),
                                                 bitmap,
-                                                shape_drawing_area_width, shape_drawing_area_height);
-      gtk_widget_shape_combine_mask (shape_window, shape_mask, 0, 0);
+                                                dialog->shape_drawing_area_width, 
dialog->shape_drawing_area_height);
+      gtk_widget_shape_combine_mask (dialog->shape_window, shape_mask, 0, 0);
       g_object_unref (shape_mask);
 
-      if (!prev_bitmap || prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height)
+      if (!prev_bitmap || prev_width != dialog->shape_drawing_area_width || prev_height != 
dialog->shape_drawing_area_height)
         {
           g_free(prev_bitmap);
-          prev_bitmap = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + 
shape_drawing_area_height);
-          prev_width = shape_drawing_area_width;
-          prev_height = shape_drawing_area_height;
+          prev_bitmap = g_malloc ((dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 8 
+ dialog->shape_drawing_area_height);
+          prev_width = dialog->shape_drawing_area_width;
+          prev_height = dialog->shape_drawing_area_height;
         }
 
-      memcpy (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + 
shape_drawing_area_height);
+      memcpy (prev_bitmap, bitmap, (dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 
8 + dialog->shape_drawing_area_height);
     }
 }
 
+/***************** CALLBACKS ********************/
+
 static gboolean
-popup_menu (GtkWidget      *widget,
-            GdkEventButton *event)
+popup_menu (GtkWidget           *widget,
+            AnimationPlayDialog *dialog)
 {
-  GtkWidget *menu = gtk_ui_manager_get_widget (ui_manager, "/anim-play-popup");
+  GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
 
   gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
   gtk_menu_popup (GTK_MENU (menu),
                   NULL, NULL, NULL, NULL,
-                  event ? event->button : 0,
-                  event ? event->time   : gtk_get_current_event_time ());
+                  0, gtk_get_current_event_time ());
 
   return TRUE;
 }
 
 static gboolean
-button_press (GtkWidget      *widget,
-              GdkEventButton *event)
+button_press (GtkWidget           *widget,
+              GdkEventButton      *event,
+              AnimationPlayDialog *dialog)
 {
   if (gdk_event_triggers_context_menu ((GdkEvent *) event))
-    return popup_menu (widget, event);
+    {
+      GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
+
+      gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
+      gtk_menu_popup (GTK_MENU (menu),
+                      NULL, NULL, NULL, NULL,
+                      event->button,
+                      event->time);
+      return TRUE;
+    }
 
   return FALSE;
 }
 
 /*
- * Update the actual drawing area metrics, which may be different as requested,
- * because there is no full control of the WM.
- * data is always NULL. */
+ * Update the actual drawing area metrics, which may be different from
+ * requested, since there is no full control of the WM.
+ */
 static void
-da_size_callback (GtkWidget *widget,
-                  GtkAllocation *allocation, void *data)
+da_size_callback (GtkWidget           *drawing_area,
+                  GtkAllocation       *allocation,
+                  AnimationPlayDialog *dialog)
 {
-  if (allocation->width == drawing_area_width && allocation->height == drawing_area_height)
-    return;
-
-  drawing_area_width = allocation->width;
-  drawing_area_height = allocation->height;
-  scale = MIN ((gdouble) drawing_area_width / (gdouble) preview_width,
-               (gdouble) drawing_area_height / (gdouble) preview_height);
+  guchar   **drawing_data;
 
-  g_free (drawing_area_data);
-  drawing_area_data = g_malloc (drawing_area_width * drawing_area_height * 3);
-
-  update_alpha_preview ();
-
-  if (! detached)
+  if (drawing_area == dialog->shape_drawing_area)
     {
-      /* Update the zoom information. */
-      GtkEntry *zoomcombo_text_child;
-
-      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo)));
-      if (zoomcombo_text_child)
-        {
-          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), scale * 100.0);
+      if (allocation->width  == dialog->shape_drawing_area_width &&
+          allocation->height == dialog->shape_drawing_area_height)
+        return;
 
-          gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
-          g_free (new_entry_text);
-        }
+      dialog->shape_drawing_area_width  = allocation->width;
+      dialog->shape_drawing_area_height = allocation->height;
 
-      /* Update the rawframe. */
-      g_free (rawframe);
-      rawframe = g_malloc ((unsigned long) drawing_area_width * drawing_area_height * 4);
-
-      /* As we re-allocated the drawn data, let's render it again. */
-      force_render = TRUE;
-      if (frame_number - frame_number_min < total_frames)
-        render_frame (frame_number);
+      g_free (dialog->shape_drawing_area_data);
+      drawing_data = &dialog->shape_drawing_area_data;
     }
   else
     {
-      /* Set "alpha grid" background. */
-      total_alpha_preview (drawing_area_data, drawing_area_width, drawing_area_height);
-      repaint_da(drawing_area, NULL, NULL);
-    }
-}
+      if (allocation->width  == dialog->drawing_area_width &&
+          allocation->height == dialog->drawing_area_height)
+        return;
 
-/*
- * Update the actual shape drawing area metrics, which may be different as requested,
- * They *should* be the same as the drawing area, but the safe way is to make sure
- * and process it separately.
- * data is always NULL. */
-static void
-sda_size_callback (GtkWidget *widget,
-                   GtkAllocation *allocation, void *data)
-{
-  if (allocation->width == shape_drawing_area_width && allocation->height == shape_drawing_area_height)
-    return;
+      dialog->drawing_area_width  = allocation->width;
+      dialog->drawing_area_height = allocation->height;
 
-  shape_drawing_area_width = allocation->width;
-  shape_drawing_area_height = allocation->height;
-  shape_scale = MIN ((gdouble) shape_drawing_area_width / (gdouble) preview_width,
-                     (gdouble) shape_drawing_area_height / (gdouble) preview_height);
+      g_free (dialog->drawing_area_data);
+      drawing_data = &dialog->drawing_area_data;
+    }
 
-  g_free (shape_drawing_area_data);
-  g_free (shape_preview_mask);
+  dialog->settings.scale = MIN ((gdouble) allocation->width / (gdouble) dialog->preview_width,
+                                (gdouble) allocation->height / (gdouble) dialog->preview_height);
 
-  shape_drawing_area_data = g_malloc (shape_drawing_area_width * shape_drawing_area_height * 3);
-  shape_preview_mask = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + 1 + 
shape_drawing_area_height);
+  *drawing_data = g_malloc (allocation->width * allocation->height * 3);
 
-  if (detached)
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))) &&
+      drawing_area == dialog->drawing_area)
+    {
+      /* Set "alpha grid" background. */
+      total_alpha_preview (dialog->drawing_area_data,
+                           allocation->width,
+                           allocation->height);
+      repaint_da (dialog->drawing_area, NULL, dialog);
+    }
+  else 
     {
       /* Update the zoom information. */
       GtkEntry *zoomcombo_text_child;
 
-      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo)));
+      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo)));
       if (zoomcombo_text_child)
         {
-          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), shape_scale * 100.0);
+          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), dialog->settings.scale * 100.0);
 
           gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
           g_free (new_entry_text);
         }
 
-      /* Update the rawframe. */
-      g_free (rawframe);
-      rawframe = g_malloc ((unsigned long) shape_drawing_area_width * shape_drawing_area_height * 4);
-
-      force_render = TRUE;
-      if (frame_number - frame_number_min < total_frames)
-        render_frame (frame_number);
+      /* As we re-allocated the drawn data, let's render it again. */
+      if (dialog->settings.current_frame - dialog->settings.frame_min < dialog->settings.num_frames)
+        render_frame (dialog, TRUE);
     }
 }
 
 static gboolean
-shape_pressed (GtkWidget      *widget,
-               GdkEventButton *event)
+shape_pressed (GtkWidget           *widget,
+               GdkEventButton      *event,
+               AnimationPlayDialog *dialog)
 {
-  if (button_press (widget, event))
+  if (button_press (widget, event, dialog))
     return TRUE;
 
   /* ignore double and triple click */
@@ -542,26 +616,38 @@ shape_released (GtkWidget *widget)
 }
 
 static gboolean
-adjustment_pressed (GtkWidget      *widget,
-                    GdkEventButton *event,
-                    gpointer        user_data)
+adjustment_pressed (GtkWidget           *widget,
+                    GdkEventButton      *event,
+                    AnimationPlayDialog *dialog)
 {
-  if (event->type == GDK_2BUTTON_PRESS)
+  gboolean event_processed = FALSE;
+
+  if (event->type == GDK_BUTTON_PRESS &&
+      event->button == 2)
     {
       GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
       GtkAdjustment *adj = gtk_spin_button_get_adjustment (spin);
 
-      gtk_adjustment_set_value (adj, (gdouble) frame_number);
+      gtk_adjustment_set_value (adj,
+                                (gdouble) dialog->settings.current_frame);
+
+      /* We don't want the middle click to have another usage (in
+       * particular, there is likely no need to copy-paste in these spin
+       * buttons). */
+      event_processed = TRUE;
     }
 
-  return FALSE;
+  return event_processed;
 }
 
 static gboolean
-progress_button (GtkWidget      *widget,
-                 GdkEventButton *event,
-                 gpointer        user_data)
+progress_button (GtkWidget           *widget,
+                 GdkEventButton      *event,
+                 AnimationPlayDialog *dialog)
 {
+  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
+  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
+
   /* ignore double and triple click */
   if (event->type == GDK_BUTTON_PRESS)
     {
@@ -570,18 +656,18 @@ progress_button (GtkWidget      *widget,
 
       gtk_widget_get_allocation (widget, &allocation);
 
-      goto_frame = frame_number_min + (gint) (event->x / (allocation.width / total_frames));
+      goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
 
-      if (goto_frame < start_frame)
+      if (goto_frame < dialog->settings.start_frame)
         gtk_adjustment_set_value (startframe_adjust, (gdouble) goto_frame);
 
-      if (goto_frame > end_frame)
+      if (goto_frame > dialog->settings.end_frame)
         gtk_adjustment_set_value (endframe_adjust, (gdouble) goto_frame);
 
-      if (goto_frame >= frame_number_min && goto_frame < frame_number_min + total_frames)
+      if (goto_frame >= dialog->settings.frame_min && goto_frame < dialog->settings.frame_min + 
dialog->settings.num_frames)
         {
-          frame_number = goto_frame;
-          render_frame (frame_number);
+          dialog->settings.current_frame = goto_frame;
+          render_frame (dialog, FALSE);
         }
     }
 
@@ -589,45 +675,45 @@ progress_button (GtkWidget      *widget,
 }
 
 static gboolean
-progress_entered (GtkWidget        *widget,
-                  GdkEventCrossing *event,
-                  gpointer          user_data)
+progress_entered (GtkWidget           *widget,
+                  GdkEventCrossing    *event,
+                  AnimationPlayDialog *dialog)
 {
   GtkAllocation  allocation;
   guint          goto_frame;
 
   gtk_widget_get_allocation (widget, &allocation);
 
-  goto_frame = frame_number_min + (gint) (event->x / (allocation.width / total_frames));
+  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
 
-  show_goto_progress (goto_frame);
+  show_goto_progress (goto_frame, dialog);
 
   return FALSE;
 }
 
 static gboolean
-progress_motion (GtkWidget      *widget,
-                 GdkEventMotion *event,
-                 gpointer        user_data)
+progress_motion (GtkWidget           *widget,
+                 GdkEventMotion      *event,
+                 AnimationPlayDialog *dialog)
 {
   GtkAllocation  allocation;
   guint          goto_frame;
 
   gtk_widget_get_allocation (widget, &allocation);
 
-  goto_frame = frame_number_min + (gint) (event->x / (allocation.width / total_frames));
+  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
 
-  show_goto_progress (goto_frame);
+  show_goto_progress (goto_frame, dialog);
 
   return FALSE;
 }
 
 static gboolean
-progress_left (GtkWidget        *widget,
-               GdkEventCrossing *event,
-               gpointer          user_data)
+progress_left (GtkWidget           *widget,
+               GdkEventCrossing    *event,
+               AnimationPlayDialog *dialog)
 {
-  show_playing_progress ();
+  show_playing_progress (dialog);
 
   return FALSE;
 }
@@ -638,7 +724,9 @@ shape_motion (GtkWidget      *widget,
 {
   GdkModifierType  mask;
   gint             xp, yp;
+  GdkWindow       *root_win;
 
+  root_win = gdk_get_default_root_window ();
   gdk_window_get_pointer (root_win, &xp, &yp, &mask);
 
   /* if a button is still held by the time we process this event... */
@@ -660,107 +748,127 @@ shape_motion (GtkWidget      *widget,
 }
 
 static gboolean
-repaint_da (GtkWidget      *darea,
-            GdkEventExpose *event,
-            gpointer        data)
+repaint_da (GtkWidget           *darea,
+            GdkEventExpose      *event,
+            AnimationPlayDialog *dialog)
 {
   GtkStyle *style = gtk_widget_get_style (darea);
+  gint      da_width;
+  gint      da_height;
+  guchar   *da_data;
 
-  gdk_draw_rgb_image (gtk_widget_get_window (darea),
-                      style->white_gc,
-                      (gint) ((drawing_area_width - scale * preview_width) / 2),
-                      (gint) ((drawing_area_height - scale * preview_height) / 2),
-                      drawing_area_width, drawing_area_height,
-                      (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
-                      drawing_area_data, drawing_area_width * 3);
-
-  return TRUE;
-}
-
-static gboolean
-repaint_sda (GtkWidget      *darea,
-             GdkEventExpose *event,
-             gpointer        data)
-{
-  GtkStyle *style = gtk_widget_get_style (darea);
+  if (darea == dialog->drawing_area)
+    {
+      da_width  = dialog->drawing_area_width;
+      da_height = dialog->drawing_area_height;
+      da_data   = dialog->drawing_area_data;
+    }
+  else
+    {
+      da_width  = dialog->shape_drawing_area_width;
+      da_height = dialog->shape_drawing_area_height;
+      da_data   = dialog->shape_drawing_area_data;
+    }
 
   gdk_draw_rgb_image (gtk_widget_get_window (darea),
                       style->white_gc,
-                      (gint) ((shape_drawing_area_width - shape_scale * preview_width) / 2),
-                      (gint) ((shape_drawing_area_height - shape_scale * preview_height) / 2),
-                      shape_drawing_area_width, shape_drawing_area_height,
-                      (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
-                      shape_drawing_area_data, shape_drawing_area_width * 3);
+                      (gint) ((da_width - dialog->settings.scale * dialog->preview_width) / 2),
+                      (gint) ((da_height - dialog->settings.scale * dialog->preview_height) / 2),
+                      da_width, da_height,
+                      (dialog->settings.num_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
+                      da_data, da_width * 3);
 
   return TRUE;
 }
 
 static void
-close_callback (GtkAction *action,
-                gpointer   data)
+close_callback (GtkAction           *action,
+                AnimationPlayDialog *dialog)
 {
-  clean_exit ();
+  clean_exit (dialog);
 }
 
 static void
-help_callback (GtkAction *action,
-               gpointer   data)
+help_callback (GtkAction           *action,
+               AnimationPlayDialog *dialog)
 {
-  gimp_standard_help_func (PLUG_IN_PROC, data);
+  gimp_standard_help_func (PLUG_IN_PROC, dialog->window);
 }
 
 
 static void
-detach_callback (GtkToggleAction *action)
+detach_callback (GtkToggleAction     *action,
+                 AnimationPlayDialog *dialog)
 {
-  gboolean active = gtk_toggle_action_get_active (action);
-
-  if (active == detached)
-    {
-      g_warning ("detached state and toggle action got out of sync");
-      return;
-    }
-
-  detached = active;
+  gboolean detached = gtk_toggle_action_get_active (action);
 
   if (detached)
     {
       gint x, y;
 
-      /* Create a total-alpha buffer merely for the not-shaped
-         drawing area to now display. */
-
-      gtk_window_set_screen (GTK_WINDOW (shape_window),
-                             gtk_widget_get_screen (drawing_area));
+      gtk_window_set_screen (GTK_WINDOW (dialog->shape_window),
+                             gtk_widget_get_screen (dialog->drawing_area));
 
-      gtk_widget_show (shape_window);
+      gtk_widget_show (dialog->shape_window);
 
-      if (!gtk_widget_get_realized (drawing_area))
-        gtk_widget_realize (drawing_area);
-      if (!gtk_widget_get_realized (shape_drawing_area))
-        gtk_widget_realize (shape_drawing_area);
+      if (! gtk_widget_get_realized (dialog->drawing_area))
+        {
+          gtk_widget_realize (dialog->drawing_area);
+        }
+      if (! gtk_widget_get_realized (dialog->shape_drawing_area))
+        {
+          gtk_widget_realize (dialog->shape_drawing_area);
+        }
 
-      gdk_window_get_origin (gtk_widget_get_window (drawing_area), &x, &y);
+      gdk_window_get_origin (gtk_widget_get_window (dialog->drawing_area), &x, &y);
 
-      gtk_window_move (GTK_WINDOW (shape_window), x + 6, y + 6);
+      gtk_window_move (GTK_WINDOW (dialog->shape_window), x + 6, y + 6);
 
-      gdk_window_set_back_pixmap (gtk_widget_get_window (shape_drawing_area), NULL, TRUE);
+      gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_drawing_area), NULL, TRUE);
 
       /* Set "alpha grid" background. */
-      total_alpha_preview (drawing_area_data, drawing_area_width, drawing_area_height);
-      repaint_da (drawing_area, NULL, NULL);
+      total_alpha_preview (dialog->drawing_area_data,
+                           dialog->drawing_area_width,
+                           dialog->drawing_area_height);
+      repaint_da (dialog->drawing_area, NULL, dialog);
     }
   else
-    gtk_widget_hide (shape_window);
+    {
+      gtk_widget_hide (dialog->shape_window);
+    }
 
-  force_render = TRUE;
-  render_frame (frame_number);
+  render_frame (dialog, TRUE);
 }
 
+static void
+connect_accelerators (GtkUIManager   *ui_manager,
+                      GtkActionGroup *group)
+{
+  GList          *action_list;
+  GList          *iter;
+
+  action_list = gtk_action_group_list_actions (group);
+  iter = action_list;
+  while (iter)
+    {
+      /* Make sure all the action's accelerator are correctly connected,
+       * even when there are no associated UI item. */
+      GtkAction *action = GTK_ACTION (iter->data);
+
+      gtk_action_set_accel_group (action,
+                                  gtk_ui_manager_get_accel_group (ui_manager));
+      gtk_action_connect_accelerator (action);
+
+      iter = iter->next;
+    }
+  g_list_free (action_list);
+}
+
+
 static GtkUIManager *
-ui_manager_new (GtkWidget *window)
+ui_manager_new (AnimationPlayDialog *dialog)
 {
-  static GtkActionEntry actions[] =
+  static GtkActionEntry play_entries[] =
   {
     { "step-back", "media-skip-backward",
       N_("Step _back"), "d", N_("Step back to previous frame"),
@@ -771,115 +879,163 @@ ui_manager_new (GtkWidget *window)
       G_CALLBACK (step_callback) },
 
     { "rewind", "media-seek-backward",
-      NULL, NULL, N_("Rewind the animation"),
+      NULL, "s", N_("Rewind the animation"),
       G_CALLBACK (rewind_callback) },
+  };
+
+  static GtkToggleActionEntry play_toggle_entries[] =
+  {
+    { "play", "media-playback-start",
+      NULL, "space", N_("Start playback"),
+      G_CALLBACK (play_callback), FALSE },
+  };
 
+  static GtkActionEntry settings_entries[] =
+  {
+    /* Refresh is not really a settings, but it makes sense to be grouped
+     * as such, because we want to be able to refresh and set things in the
+     * same moments. */
     { "refresh", GIMP_ICON_VIEW_REFRESH,
-      NULL, "<control>R", N_("Reload the image"),
+      N_("Refresh"), "<control>R", N_("Reload the image"),
       G_CALLBACK (refresh_callback) },
 
-    { "help", "help-browser",
-      NULL, NULL, NULL,
-      G_CALLBACK (help_callback) },
-
-    { "close", "window-close",
-      NULL, "<control>W", NULL,
-      G_CALLBACK (close_callback)
-    },
-    {
-      "quit", "application-quit",
-      NULL, "<control>Q", NULL,
-      G_CALLBACK (close_callback)
-    },
     {
       "speed-up", NULL,
-      N_("Faster"), "<control>L", N_("Increase the speed of the animation"),
+      N_("Faster"), "bracketright", N_("Increase the speed of the animation"),
       G_CALLBACK (speed_up_callback)
     },
     {
       "speed-down", NULL,
-      N_("Slower"), "<control>J", N_("Decrease the speed of the animation"),
+      N_("Slower"), "bracketleft", N_("Decrease the speed of the animation"),
       G_CALLBACK (speed_down_callback)
     },
-    {
-      "speed-reset", NULL,
-      N_("Reset speed"), "<control>K", N_("Reset the speed of the animation"),
-      G_CALLBACK (speed_reset_callback)
-    }
   };
 
-  static GtkToggleActionEntry toggle_actions[] =
+  static GtkActionEntry view_entries[] =
   {
-    { "play", "media-playback-start",
-      NULL, "space", N_("Start playback"),
-      G_CALLBACK (play_callback), FALSE },
+    { "zoom-in", GTK_STOCK_ZOOM_IN,
+      NULL, "plus", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
+
+    { "zoom-in-accel", GTK_STOCK_ZOOM_IN,
+      NULL, "KP_Add", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
+
+    { "zoom-out", GTK_STOCK_ZOOM_OUT,
+      NULL, "minus", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
+
+    { "zoom-out-accel", GTK_STOCK_ZOOM_OUT,
+      NULL, "KP_Subtract", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
+
+    { "zoom-reset", GTK_STOCK_ZOOM_OUT,
+      NULL, "equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
 
+    { "zoom-reset-accel", GTK_STOCK_ZOOM_OUT,
+      NULL, "KP_Equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
+  };
+
+  static GtkToggleActionEntry view_toggle_entries[] =
+  {
     { "detach", GIMP_ICON_DETACH,
       N_("Detach"), NULL,
       N_("Detach the animation from the dialog window"),
       G_CALLBACK (detach_callback), FALSE }
   };
 
-  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
-  GtkActionGroup *group      = gtk_action_group_new ("Actions");
-  GError         *error      = NULL;
+  static GtkActionEntry various_entries[] =
+  {
+    { "help", "help-browser",
+      N_("About the animation plug-in"), "question", NULL,
+      G_CALLBACK (help_callback) },
 
-  gtk_action_group_set_translation_domain (group, NULL);
+    { "close", "window-close",
+      N_("Quit"), "<control>W", NULL,
+      G_CALLBACK (close_callback)
+    },
+    {
+      "quit", "application-quit",
+      N_("Quit"), "<control>Q", NULL,
+      G_CALLBACK (close_callback)
+    },
+  };
 
-  gtk_action_group_add_actions (group,
-                                actions,
-                                G_N_ELEMENTS (actions),
-                                window);
-  gtk_action_group_add_toggle_actions (group,
-                                       toggle_actions,
-                                       G_N_ELEMENTS (toggle_actions),
-                                       NULL);
+  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
+  GError         *error      = NULL;
 
-  gtk_window_add_accel_group (GTK_WINDOW (window),
+  /* All playback related actions. */
+  dialog->play_actions = gtk_action_group_new ("playback");
+  gtk_action_group_set_translation_domain (dialog->play_actions, NULL);
+  gtk_action_group_add_actions (dialog->play_actions,
+                                play_entries,
+                                G_N_ELEMENTS (play_entries),
+                                dialog);
+  gtk_action_group_add_toggle_actions (dialog->play_actions,
+                                       play_toggle_entries,
+                                       G_N_ELEMENTS (play_toggle_entries),
+                                       dialog);
+  connect_accelerators (ui_manager, dialog->play_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, dialog->play_actions, -1);
+
+  /* All settings related actions. */
+  dialog->settings_actions = gtk_action_group_new ("settings");
+  gtk_action_group_set_translation_domain (dialog->settings_actions, NULL);
+  gtk_action_group_add_actions (dialog->settings_actions,
+                                settings_entries,
+                                G_N_ELEMENTS (settings_entries),
+                                dialog);
+  connect_accelerators (ui_manager, dialog->settings_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, dialog->settings_actions, -1);
+
+  /* All view actions. */
+  dialog->view_actions = gtk_action_group_new ("view");
+  gtk_action_group_set_translation_domain (dialog->view_actions, NULL);
+  gtk_action_group_add_actions (dialog->view_actions,
+                                view_entries,
+                                G_N_ELEMENTS (view_entries),
+                                dialog);
+  gtk_action_group_add_toggle_actions (dialog->view_actions,
+                                       view_toggle_entries,
+                                       G_N_ELEMENTS (view_toggle_entries),
+                                       dialog);
+  connect_accelerators (ui_manager, dialog->view_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, dialog->view_actions, -1);
+
+  /* Remaining various actions. */
+  dialog->various_actions = gtk_action_group_new ("various");
+
+  gtk_action_group_set_translation_domain (dialog->various_actions, NULL);
+
+  gtk_action_group_add_actions (dialog->various_actions,
+                                various_entries,
+                                G_N_ELEMENTS (various_entries),
+                                dialog);
+  connect_accelerators (ui_manager, dialog->various_actions);
+  gtk_ui_manager_insert_action_group (ui_manager, dialog->various_actions, -1);
+
+  /* Finalize. */
+  gtk_window_add_accel_group (GTK_WINDOW (dialog->window),
                               gtk_ui_manager_get_accel_group (ui_manager));
   gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager));
 
-  gtk_ui_manager_insert_action_group (ui_manager, group, -1);
-  g_object_unref (group);
-
+  /* Finally make some limited popup menu. */
   gtk_ui_manager_add_ui_from_string (ui_manager,
                                      "<ui>"
-                                     "  <toolbar name=\"anim-play-toolbar\">"
-                                     "    <toolitem action=\"play\" />"
-                                     "    <toolitem action=\"step-back\" />"
-                                     "    <toolitem action=\"step\" />"
-                                     "    <toolitem action=\"rewind\" />"
+                                     "  <popup name=\"anim-play-popup\" accelerators=\"true\">"
+                                     "    <menuitem action=\"refresh\" />"
                                      "    <separator />"
-                                     "    <toolitem action=\"detach\" />"
-                                     "    <toolitem action=\"refresh\" />"
-                                     "    <separator name=\"space\" />"
-                                     "    <toolitem action=\"help\" />"
-                                     "  </toolbar>"
-                                     "  <accelerator action=\"close\" />"
-                                     "  <accelerator action=\"quit\" />"
-                                     "</ui>",
-                                     -1, &error);
-
-  if (error)
-    {
-      g_warning ("error parsing ui: %s", error->message);
-      g_clear_error (&error);
-    }
-
-  gtk_ui_manager_add_ui_from_string (ui_manager,
-                                     "<ui>"
-                                     "  <popup name=\"anim-play-popup\">"
-                                     "    <menuitem action=\"play\" />"
-                                     "    <menuitem action=\"step-back\" />"
-                                     "    <menuitem action=\"step\" />"
-                                     "    <menuitem action=\"rewind\" />"
+                                     "    <menuitem action=\"zoom-in\" />"
+                                     "    <menuitem action=\"zoom-out\" />"
+                                     "    <menuitem action=\"zoom-reset\" />"
                                      "    <separator />"
-                                     "    <menuitem action=\"speed-down\" />"
                                      "    <menuitem action=\"speed-up\" />"
-                                     "    <menuitem action=\"speed-reset\" />"
+                                     "    <menuitem action=\"speed-down\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"help\" />"
                                      "    <separator />"
-                                     "    <menuitem action=\"detach\" />"
-                                     "    <menuitem action=\"refresh\" />"
                                      "    <menuitem action=\"close\" />"
                                      "  </popup>"
                                      "</ui>",
@@ -895,412 +1051,539 @@ ui_manager_new (GtkWidget *window)
 }
 
 static void
-refresh_dialog (gchar *imagename)
+refresh_dialog (AnimationPlayDialog *dialog)
 {
-  gchar     *name;
   GdkScreen *screen;
   guint      screen_width, screen_height;
   gint       window_width, window_height;
 
-  /* Image Name */
-  name = g_strconcat (_("Animation Playback:"), " ", imagename, NULL);
-  gtk_window_set_title (GTK_WINDOW (window), name);
-  g_free (name);
-
   /* Update GUI size. */
-  screen = gtk_widget_get_screen (window);
+  screen = gtk_widget_get_screen (dialog->window);
   screen_height = gdk_screen_get_height (screen);
   screen_width = gdk_screen_get_width (screen);
-  gtk_window_get_size (GTK_WINDOW (window), &window_width, &window_height);
-
-  /* Update the presence of quality checkbox. */
-  init_quality_checkbox ();
-  quality_checkbox_toggled (GTK_TOGGLE_BUTTON (quality_checkbox), NULL);
+  gtk_window_get_size (GTK_WINDOW (dialog->window), &window_width, &window_height);
 
   /* if the *window* size is bigger than the screen size,
    * diminish the drawing area by as much, then compute the corresponding scale. */
   if (window_width + 50 > screen_width || window_height + 50 > screen_height)
   {
-      gint expected_drawing_area_width = MAX (1, preview_width - window_width + screen_width);
-      gint expected_drawing_area_height = MAX (1, preview_height - window_height + screen_height);
+      gint expected_drawing_area_width = MAX (1, dialog->preview_width - window_width + screen_width);
+      gint expected_drawing_area_height = MAX (1, dialog->preview_height - window_height + screen_height);
 
-      gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) preview_width,
-                                    (gdouble) expected_drawing_area_height / (gdouble) preview_height);
-      update_scale (expected_scale);
+      gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) dialog->preview_width,
+                                    (gdouble) expected_drawing_area_height / (gdouble) 
dialog->preview_height);
+      update_scale (dialog, expected_scale);
 
       /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
-       * So we take a wild guess by making the window slightly smaller to fit into any case. */
-      gtk_window_set_default_size (GTK_WINDOW (window),
+       * So we take a wild guess by making the window slightly smaller to fit into most case. */
+      gtk_window_set_default_size (GTK_WINDOW (dialog->window),
                                    MIN (expected_drawing_area_width + 20, screen_width - 60),
                                    MIN (expected_drawing_area_height + 90, screen_height - 60));
 
-      gtk_window_reshow_with_initial_size (GTK_WINDOW (window));
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->window));
   }
 }
 
+/******************************/
+/*** Some Update procedures ***/
+/******************************/
+
+/* Update the tool sensitivity for playing, depending on the number of frames. */
 static void
-build_dialog (gchar             *imagename)
-{
-  GtkWidget   *toolbar;
-  GtkWidget   *frame;
-  GtkWidget   *viewport;
-  GtkWidget   *main_vbox;
-  GtkWidget   *vbox;
-  GtkWidget   *hbox;
-  GtkWidget   *abox;
-  GtkWidget   *progress_hbox;
-  GtkWidget   *config_hbox;
-  GtkToolItem *item;
-  GtkAction   *action;
-  GdkCursor   *cursor;
-  gint         index;
-  gchar       *text;
+update_ui_sensitivity (AnimationPlayDialog *dialog)
+{
+  gboolean animated;
 
-  gimp_ui_init (PLUG_IN_BINARY, TRUE);
+  animated = dialog->settings.end_frame - dialog->settings.start_frame > 1;
+  /* Play actions only if we selected several frames between start/end. */
+  gtk_action_group_set_sensitive (dialog->play_actions, animated);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->play_bar), animated);
 
+  /* We can access the progress bar if there are several frames. */
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->progress_bar),
+                            dialog->settings.num_frames > 1);
 
-  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_role (GTK_WINDOW (window), "animation-playback");
+  /* Settings are always changeable. */
+  gtk_action_group_set_sensitive (dialog->settings_actions, TRUE);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), TRUE);
 
-  destroy_handler = g_signal_connect (window, "destroy",
-                                      G_CALLBACK (window_destroy),
-                                      NULL);
-  g_signal_connect (window, "popup-menu",
-                    G_CALLBACK (popup_menu),
-                    NULL);
+  /* View are always meaningfull with at least 1 frame. */
+  gtk_action_group_set_sensitive (dialog->view_actions,
+                                  dialog->settings.num_frames >= 1);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->view_bar),
+                            dialog->settings.num_frames >= 1);
+}
 
-  gimp_help_connect (window, gimp_standard_help_func, PLUG_IN_PROC, NULL);
+static void
+update_scale (AnimationPlayDialog *dialog,
+              gdouble              scale)
+{
+  guint expected_drawing_area_width;
+  guint expected_drawing_area_height;
 
-  ui_manager = ui_manager_new (window);
+  /* FIXME: scales under 0.5 are broken. See bug 690265. */
+  if (scale <= 0.5)
+    scale = 0.51;
 
-  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
-  gtk_container_add (GTK_CONTAINER (window), main_vbox);
-  gtk_widget_show (main_vbox);
+  expected_drawing_area_width  = dialog->preview_width * scale;
+  expected_drawing_area_height = dialog->preview_height * scale;
+
+  /* We don't update dialog->settings.scale directly because this might
+   * end up not being the real scale. Instead we request this size for
+   * the drawing areas, and the actual scale update will be done on the
+   * callback when size is actually allocated. */
+  gtk_widget_set_size_request (dialog->drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+  gtk_widget_set_size_request (dialog->shape_drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+  /* I force the shape window to a smaller size if we scale down. */
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
+    {
+      gint x, y;
 
-  toolbar = gtk_ui_manager_get_widget (ui_manager, "/anim-play-toolbar");
-  gtk_box_pack_start (GTK_BOX (main_vbox), toolbar, FALSE, FALSE, 0);
-  gtk_widget_show (toolbar);
+      gdk_window_get_origin (gtk_widget_get_window (dialog->shape_window), &x, &y);
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->shape_window));
+      gtk_window_move (GTK_WINDOW (dialog->shape_window), x, y);
+    }
+}
 
-  item =
-    GTK_TOOL_ITEM (gtk_ui_manager_get_widget (ui_manager,
-                                              "/anim-play-toolbar/space"));
-  gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
-  gtk_tool_item_set_expand (item, TRUE);
 
-  /* Vbox for the preview window and lower option bar */
+static void
+init_ui (AnimationPlayDialog *dialog,
+         gchar               *imagename)
+{
+  GtkWidget     *upper_bar;
+  GtkWidget     *widget;
+  GtkAdjustment *adjust;
+  GtkWidget     *main_vbox;
+  GtkWidget     *vbox;
+  GtkWidget     *hbox;
+  GtkWidget     *abox;
+  GdkCursor     *cursor;
+  gchar         *text;
+  gint           index;
 
-  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
-  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
-  gtk_widget_show (vbox);
+  gimp_ui_init (PLUG_IN_BINARY, TRUE);
 
-  /* Alignment for the scrolling window, which can be resized by the user. */
-  abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
-  gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0);
-  gtk_widget_show (abox);
+  dialog->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_role (GTK_WINDOW (dialog->window), "animation-playback");
 
-  frame = gtk_scrolled_window_new (NULL, NULL);
-  gtk_container_add (GTK_CONTAINER (abox), frame);
-  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (frame),
-                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-  gtk_widget_show (frame);
+  /* Some basic signals. */
+  g_signal_connect (dialog->window, "destroy",
+                    G_CALLBACK (window_destroy),
+                    dialog);
+  g_signal_connect (dialog->window, "popup-menu",
+                    G_CALLBACK (popup_menu),
+                    dialog);
 
-  viewport = gtk_viewport_new (NULL, NULL);
-  gtk_container_add (GTK_CONTAINER (frame), viewport);
-  gtk_widget_show (viewport);
+  /* Window Title */
+  text = g_strconcat (_("Animation Playback:"), " ", imagename, NULL);
+  gtk_window_set_title (GTK_WINDOW (dialog->window), text);
+  g_free (text);
 
-  /* I add the drawing area inside an alignment box to prevent it from being resized. */
-  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
-  gtk_container_add (GTK_CONTAINER (viewport), abox);
-  gtk_widget_show (abox);
+  gimp_help_connect (dialog->window, gimp_standard_help_func, PLUG_IN_PROC, NULL);
 
-  /* Build a drawing area, with a default size same as the image */
-  drawing_area = gtk_drawing_area_new ();
-  gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
-  gtk_container_add (GTK_CONTAINER (abox), drawing_area);
-  gtk_widget_show (drawing_area);
+  dialog->ui_manager = ui_manager_new (dialog);
 
-  g_signal_connect (drawing_area, "size-allocate",
-                    G_CALLBACK(da_size_callback),
-                    NULL);
-  g_signal_connect (drawing_area, "button-press-event",
-                    G_CALLBACK (button_press),
-                    NULL);
+  /* Main vertical box. */
+  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_container_add (GTK_CONTAINER (dialog->window), main_vbox);
+  gtk_widget_show (main_vbox);
 
-  /* Lower option bar. */
+  /* Upper Bar */
+  upper_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_box_pack_start (GTK_BOX (main_vbox), upper_bar, FALSE, FALSE, 0);
+  gtk_widget_show (upper_bar);
 
+  /**********************************/
+  /* The upper bar is itself paned. */
   hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
-  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (upper_bar), hbox, FALSE, FALSE, 0);
   gtk_widget_show (hbox);
 
-  /* Progress box. */
+  /*****************/
+  /* Settings box. */
+  /*****************/
+  dialog->settings_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add1 (GTK_PANED (hbox), dialog->settings_bar);
+  gtk_widget_show (dialog->settings_bar);
 
-  progress_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_add2 (GTK_PANED (hbox), progress_hbox);
-  gtk_widget_show (progress_hbox);
+  /* Settings: expander to display the proxy settings. */
+  widget = gtk_expander_new_with_mnemonic (_("_Proxy Quality"));
+  gtk_expander_set_expanded (GTK_EXPANDER (widget), FALSE);
 
-  /* End frame spin button. */
+  g_signal_connect (GTK_EXPANDER (widget),
+                    "notify::expanded",
+                    G_CALLBACK (proxy_checkbox_expanded),
+                    dialog);
 
-  endframe_adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
-  endframe_spin = gtk_spin_button_new (endframe_adjust, 1.0, 0);
-  gtk_entry_set_width_chars (GTK_ENTRY (endframe_spin), 2);
+  gimp_help_set_help_data (widget, _("Degrade image quality for lower memory footprint"), NULL);
 
-  gtk_box_pack_end (GTK_BOX (progress_hbox), endframe_spin, FALSE, FALSE, 0);
-  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (endframe_spin), GTK_UPDATE_IF_VALID);
-  gtk_widget_show (endframe_spin);
+  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
 
-  g_signal_connect (endframe_adjust,
-                    "value-changed",
-                    G_CALLBACK (endframe_changed),
-                    NULL);
+  /* Settings: proxy image. */
+  dialog->proxycombo = gtk_combo_box_text_new_with_entry ();
 
-  g_signal_connect (endframe_spin, "button-press-event",
-                    G_CALLBACK (adjustment_pressed),
-                    NULL);
+  for (index = 0; index < 3; index++)
+    {
+      text = g_strdup_printf  (_("%.1f %%"), get_proxy (dialog, index) * 100.0);
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->proxycombo), text);
+      g_free (text);
+    }
 
-  gimp_help_set_help_data (endframe_spin, _("End frame"), NULL);
+  /* By default, we are at normal resolution. */
+  gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->proxycombo), -1);
+  text = g_strdup_printf  (_("%.1f %%"), 100.0);
+  gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))), text);
+  g_free (text);
 
-  /* Progress bar. */
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
+                    "activate",
+                    G_CALLBACK (proxycombo_activated),
+                    dialog);
+  g_signal_connect (dialog->proxycombo, "changed",
+                    G_CALLBACK (proxycombo_changed),
+                    dialog);
 
-  progress = gtk_progress_bar_new ();
-  gtk_box_pack_end (GTK_BOX (progress_hbox), progress, TRUE, TRUE, 0);
-  gtk_widget_show (progress);
+  gimp_help_set_help_data (dialog->proxycombo, _("Proxy resolution quality"), NULL);
 
-  gtk_widget_add_events (progress,
-                         GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
-                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
-  progress_entered_handler = g_signal_connect (progress, "enter-notify-event",
-                                               G_CALLBACK (progress_entered),
-                                               NULL);
-  progress_left_handler = g_signal_connect (progress, "leave-notify-event",
-                                            G_CALLBACK (progress_left),
-                                            NULL);
-  progress_motion_handler = g_signal_connect (progress, "motion-notify-event",
-                                              G_CALLBACK (progress_motion),
-                                              NULL);
-  progress_button_handler = g_signal_connect (progress, "button-press-event",
-                                              G_CALLBACK (progress_button),
-                                              NULL);
+  gtk_widget_show (dialog->proxycombo);
+  gtk_container_add (GTK_CONTAINER (widget), dialog->proxycombo);
 
-  /* Start frame spin button. */
+  /* Settings: fps */
+  dialog->fpscombo = gtk_combo_box_text_new_with_entry ();
 
-  startframe_adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
-  startframe_spin = gtk_spin_button_new (startframe_adjust, 1.0, 0);
-  gtk_entry_set_width_chars (GTK_ENTRY (startframe_spin), 2);
+  gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->fpscombo), -1);
+  for (index = 0; index < 5; index++)
+    {
+      /* list is given in "fps" - frames per second.
+       * We allow accurate fps (double) for special cases. */
+      text = g_strdup_printf  (_("%g fps"), get_fps (index));
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->fpscombo), text);
+      g_free (text);
+      if (get_fps (index) == dialog->settings.frame_rate)
+        gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->fpscombo), index);
+    }
 
-  gtk_box_pack_end (GTK_BOX (progress_hbox), startframe_spin, FALSE, FALSE, 0);
-  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (startframe_spin), GTK_UPDATE_IF_VALID);
-  gtk_widget_show (startframe_spin);
+  if (gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->fpscombo)) == -1)
+    {
+      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
+      g_free (text);
+    }
 
-  g_signal_connect (startframe_adjust,
-                    "value-changed",
-                    G_CALLBACK (startframe_changed),
-                    NULL);
+  g_signal_connect (dialog->fpscombo, "changed",
+                    G_CALLBACK (fpscombo_changed),
+                    dialog);
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
+                    "activate",
+                    G_CALLBACK (fpscombo_activated),
+                    dialog);
 
-  g_signal_connect (startframe_spin, "button-press-event",
-                    G_CALLBACK (adjustment_pressed),
-                    NULL);
+  gimp_help_set_help_data (dialog->fpscombo, _("Frame Rate"), NULL);
 
-  gimp_help_set_help_data (startframe_spin, _("Start frame"), NULL);
+  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), dialog->fpscombo, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->fpscombo);
 
-  /* Configuration box. */
+  /* Settings: frame mode. */
+  widget = gtk_combo_box_text_new ();
+
+  text = g_strdup (_("Cumulative layers (combine)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_COMBINE, text);
+  g_free (text);
+
+  text = g_strdup (_("One frame per layer (replace)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_REPLACE, text);
+  g_free (text);
 
-  config_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-  gtk_paned_add1 (GTK_PANED (hbox), config_hbox);
-  gtk_widget_show (config_hbox);
+  text = g_strdup (_("Use layer tags (custom)"));
+  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (widget), DISPOSE_TAGS, text);
+  g_free (text);
 
-  /* Degraded quality animation preview. */
-  quality_checkbox = gtk_check_button_new_with_label (_("Preview Quality"));
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (quality_checkbox), FALSE);
-  gtk_box_pack_end (GTK_BOX (config_hbox), quality_checkbox, FALSE, FALSE, 0);
-  gtk_widget_show (quality_checkbox);
+  gtk_combo_box_set_active (GTK_COMBO_BOX (widget), dialog->settings.frame_disposal);
 
-  init_quality_checkbox ();
-  quality_checkbox_toggled (GTK_TOGGLE_BUTTON (quality_checkbox), NULL);
+  g_signal_connect (widget, "changed",
+                    G_CALLBACK (framecombo_changed),
+                    dialog);
 
-  g_signal_connect (GTK_TOGGLE_BUTTON (quality_checkbox),
-                    "toggled",
-                    G_CALLBACK (quality_checkbox_toggled),
-                    NULL);
+  gtk_box_pack_end (GTK_BOX (dialog->settings_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
 
-  gimp_help_set_help_data (quality_checkbox, _("Degraded image quality for low memory footprint"), NULL);
+  /*************/
+  /* View box. */
+  /*************/
+  dialog->view_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_pack2 (GTK_PANED (hbox), dialog->view_bar, FALSE, TRUE);
+  gtk_widget_show (dialog->view_bar);
 
-  /* Zoom */
-  zoomcombo = gtk_combo_box_text_new_with_entry ();
-  gtk_box_pack_end (GTK_BOX (config_hbox), zoomcombo, FALSE, FALSE, 0);
-  gtk_widget_show (zoomcombo);
+  /* View: zoom. */
+  dialog->zoomcombo = gtk_combo_box_text_new_with_entry ();
   for (index = 0; index < 5; index++)
     {
-      /* list is given in "fps" - frames per second */
-      text = g_strdup_printf  (_("%.1f %%"), get_scale (index) * 100.0);
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (zoomcombo), text);
+      text = g_strdup_printf  (_("%.1f %%"), get_scale (dialog, index) * 100.0);
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dialog->zoomcombo), text);
       g_free (text);
-    }
 
-  gtk_combo_box_set_active (GTK_COMBO_BOX (zoomcombo), 2); /* 1.0 by default. */
+      if (get_scale (dialog, index) == 1.0)
+        gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->zoomcombo), index);
+    }
 
-  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo))),
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo))),
                     "activate",
                     G_CALLBACK (zoomcombo_activated),
-                    NULL);
-  g_signal_connect (zoomcombo, "changed",
+                    dialog);
+  g_signal_connect (dialog->zoomcombo, "changed",
                     G_CALLBACK (zoomcombo_changed),
-                    NULL);
+                    dialog);
 
-  gimp_help_set_help_data (zoomcombo, _("Zoom"), NULL);
+  gimp_help_set_help_data (dialog->zoomcombo, _("Zoom"), NULL);
 
-  /* fps combo */
-  fpscombo = gtk_combo_box_text_new ();
-  gtk_box_pack_end (GTK_BOX (config_hbox), fpscombo, FALSE, FALSE, 0);
-  gtk_widget_show (fpscombo);
+  gtk_box_pack_end (GTK_BOX (dialog->view_bar), dialog->zoomcombo, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->zoomcombo);
 
-  for (index = 0; index < 9; index++)
-    {
-      /* list is given in "fps" - frames per second */
-      text = g_strdup_printf  (_("%d fps"), get_fps (index));
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (fpscombo), text);
-      g_free (text);
-      if (settings.default_frame_duration == 1000 / get_fps(index))
-        gtk_combo_box_set_active (GTK_COMBO_BOX (fpscombo), index);
-    }
+  /* View: detach. */
+  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (widget), GIMP_STOCK_DETACH);
 
-  g_signal_connect (fpscombo, "changed",
-                    G_CALLBACK (fpscombo_changed),
-                    NULL);
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (dialog->view_actions, "detach"));
 
-  gimp_help_set_help_data (fpscombo, _("Default framerate"), NULL);
+  gtk_box_pack_end (GTK_BOX (dialog->view_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
 
-  /* Speed Combo */
-  speedcombo = gtk_combo_box_text_new ();
-  gtk_box_pack_end (GTK_BOX (config_hbox), speedcombo, FALSE, FALSE, 0);
-  gtk_widget_show (speedcombo);
+  /***********/
+  /* Various */
+  /***********/
 
-  for (index = 0; index < 7; index++)
-    {
-      text = g_strdup_printf  ("%g\303\227", (100 / get_duration_factor (index)) / 100);
-      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (speedcombo), text);
-      g_free (text);
-    }
+  /* Various: separator for some spacing in the UI. */
+  widget = GTK_WIDGET (gtk_separator_tool_item_new ());
+  gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (widget), FALSE);
+  gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
+  gtk_box_pack_start (GTK_BOX (upper_bar), widget, TRUE, FALSE, 0);
+  gtk_widget_show (widget);
 
-  gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index);
+  /* Various: refresh. */
+  dialog->refresh = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Reload the image")));
 
-  g_signal_connect (speedcombo, "changed",
-                    G_CALLBACK (speedcombo_changed),
-                    NULL);
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (dialog->refresh),
+                                      gtk_action_group_get_action (dialog->settings_actions, "refresh"));
 
-  gimp_help_set_help_data (speedcombo, _("Playback speed"), NULL);
+  gtk_box_pack_start (GTK_BOX (upper_bar), dialog->refresh, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->refresh);
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-reset");
-  gtk_action_set_sensitive (action, FALSE);
+  /***********/
+  /* Drawing */
+  /***********/
 
-  /* Set up the frame disposal combo. */
-  frame_disposal_combo = gtk_combo_box_text_new ();
+  /* Vbox for the preview window and lower option bar */
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
 
-  /* 2 styles of default frame disposals: cumulative layers and one frame per layer. */
-  text = g_strdup (_("Cumulative layers (combine)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_COMBINE, text);
-  g_free (text);
+  /* Alignment for the scrolling window, which can be resized by the user. */
+  abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+  gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0);
+  gtk_widget_show (abox);
 
-  text = g_strdup (_("One frame per layer (replace)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_REPLACE, text);
-  g_free (text);
+  widget = gtk_scrolled_window_new (NULL, NULL);
+  gtk_container_add (GTK_CONTAINER (abox), widget);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_widget_show (widget);
 
-  text = g_strdup (_("Use layer tags (custom)"));
-  gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_TAGS, text);
-  g_free (text);
+  /* I add the drawing area inside an alignment box to prevent it from being resized. */
+  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (widget), abox);
+  gtk_widget_show (abox);
 
-  gtk_combo_box_set_active (GTK_COMBO_BOX (frame_disposal_combo), settings.default_frame_disposal);
+  /* Build a drawing area, with a default size same as the image */
+  dialog->drawing_area = gtk_drawing_area_new ();
+  gtk_widget_add_events (dialog->drawing_area, GDK_BUTTON_PRESS_MASK);
+  gtk_container_add (GTK_CONTAINER (abox), dialog->drawing_area);
+  gtk_widget_show (dialog->drawing_area);
 
-  g_signal_connect (frame_disposal_combo, "changed",
-                    G_CALLBACK (framecombo_changed),
-                    NULL);
+  g_signal_connect (dialog->drawing_area, "size-allocate",
+                    G_CALLBACK(da_size_callback),
+                    dialog);
+  g_signal_connect (dialog->drawing_area, "button-press-event",
+                    G_CALLBACK (button_press),
+                    dialog);
+
+  /*****************/
+  /* Play toolbar. */
+  /*****************/
+
+  hbox = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+  gtk_widget_show (hbox);
+
+  /* Play buttons. */
+  dialog->play_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add1 (GTK_PANED (hbox), dialog->play_bar);
+  gtk_widget_show (dialog->play_bar);
 
-  gtk_box_pack_end (GTK_BOX (config_hbox), frame_disposal_combo, FALSE, FALSE, 0);
-  gtk_widget_show (frame_disposal_combo);
+  /* Play: play. */
+  widget = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (dialog->play_actions, "play"));
 
-  gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
-  gtk_window_set_default_size (GTK_WINDOW (window), preview_width + 20, preview_height + 90);
-  gtk_widget_show (window);
+  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: step backward. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step back to previous frame")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (dialog->play_actions, "step-back"));
+
+  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: step forward. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Step to next frame")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (dialog->play_actions, "step"));
+
+  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Play: rewind. */
+  widget = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Rewind the animation")));
+  gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget),
+                                      gtk_action_group_get_action (dialog->play_actions, "rewind"));
+
+  gtk_box_pack_start (GTK_BOX (dialog->play_bar), widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+
+  /* Progress box. */
+
+  dialog->progress_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_paned_add2 (GTK_PANED (hbox), dialog->progress_bar);
+  gtk_widget_show (dialog->progress_bar);
+
+  /* End frame spin button. */
+
+  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
+  dialog->endframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), 2);
+
+  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->endframe_spin, FALSE, FALSE, 0);
+  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (dialog->endframe_spin), GTK_UPDATE_IF_VALID);
+  gtk_widget_show (dialog->endframe_spin);
+
+  g_signal_connect (adjust,
+                    "value-changed",
+                    G_CALLBACK (endframe_changed),
+                    dialog);
+
+  g_signal_connect (dialog->endframe_spin, "button-press-event",
+                    G_CALLBACK (adjustment_pressed),
+                    dialog);
+
+  gimp_help_set_help_data (dialog->endframe_spin, _("End frame"), NULL);
+
+  /* Progress bar. */
+
+  dialog->progress = gtk_progress_bar_new ();
+  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->progress, TRUE, TRUE, 0);
+  gtk_widget_show (dialog->progress);
+
+  gtk_widget_add_events (dialog->progress,
+                         GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
+  g_signal_connect (dialog->progress, "enter-notify-event",
+                    G_CALLBACK (progress_entered),
+                    dialog);
+  g_signal_connect (dialog->progress, "leave-notify-event",
+                    G_CALLBACK (progress_left),
+                    dialog);
+  g_signal_connect (dialog->progress, "motion-notify-event",
+                    G_CALLBACK (progress_motion),
+                    dialog);
+  g_signal_connect (dialog->progress, "button-press-event",
+                    G_CALLBACK (progress_button),
+                    dialog);
+
+  /* Start frame spin button. */
+
+  adjust = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, 5.0, 0.0));
+  dialog->startframe_spin = gtk_spin_button_new (adjust, 1.0, 0);
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), 2);
+
+  gtk_box_pack_end (GTK_BOX (dialog->progress_bar), dialog->startframe_spin, FALSE, FALSE, 0);
+  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (dialog->startframe_spin), GTK_UPDATE_IF_VALID);
+  gtk_widget_show (dialog->startframe_spin);
+
+  g_signal_connect (adjust,
+                    "value-changed",
+                    G_CALLBACK (startframe_changed),
+                    dialog);
+
+  g_signal_connect (GTK_ENTRY (dialog->startframe_spin), "button-press-event",
+                    G_CALLBACK (adjustment_pressed),
+                    dialog);
+
+  gimp_help_set_help_data (dialog->startframe_spin, _("Start frame"), NULL);
+
+  /* Finalization. */
+  gtk_window_set_resizable (GTK_WINDOW (dialog->window), TRUE);
+  gtk_window_set_default_size (GTK_WINDOW (dialog->window), dialog->preview_width + 20, 
dialog->preview_height + 90);
+  gtk_widget_show (dialog->window);
 
   /* shape_drawing_area for detached feature. */
-  shape_window = gtk_window_new (GTK_WINDOW_POPUP);
-  gtk_window_set_resizable (GTK_WINDOW (shape_window), FALSE);
+  dialog->shape_window = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_window_set_resizable (GTK_WINDOW (dialog->shape_window), FALSE);
 
-  shape_drawing_area = gtk_drawing_area_new ();
-  gtk_container_add (GTK_CONTAINER (shape_window), shape_drawing_area);
-  gtk_widget_show (shape_drawing_area);
-  gtk_widget_add_events (shape_drawing_area, GDK_BUTTON_PRESS_MASK);
-  gtk_widget_realize (shape_drawing_area);
+  dialog->shape_drawing_area = gtk_drawing_area_new ();
+  gtk_container_add (GTK_CONTAINER (dialog->shape_window), dialog->shape_drawing_area);
+  gtk_widget_show (dialog->shape_drawing_area);
+  gtk_widget_add_events (dialog->shape_drawing_area, GDK_BUTTON_PRESS_MASK);
+  gtk_widget_realize (dialog->shape_drawing_area);
 
-  gdk_window_set_back_pixmap (gtk_widget_get_window (shape_window), NULL, FALSE);
+  gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_window), NULL, FALSE);
 
-  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (shape_window),
+  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (dialog->shape_window),
                                        GDK_HAND2);
-  gdk_window_set_cursor (gtk_widget_get_window (shape_window), cursor);
+  gdk_window_set_cursor (gtk_widget_get_window (dialog->shape_window), cursor);
   gdk_cursor_unref (cursor);
 
-  g_signal_connect(shape_drawing_area, "size-allocate",
-                   G_CALLBACK(sda_size_callback),
-                   NULL);
-  g_signal_connect (shape_window, "button-press-event",
+  g_signal_connect (dialog->shape_drawing_area, "size-allocate",
+                    G_CALLBACK(da_size_callback),
+                    dialog);
+  g_signal_connect (dialog->shape_window, "button-press-event",
                     G_CALLBACK (shape_pressed),
-                    NULL);
-  g_signal_connect (shape_window, "button-release-event",
+                    dialog);
+  g_signal_connect (dialog->shape_window, "button-release-event",
                     G_CALLBACK (shape_released),
                     NULL);
-  g_signal_connect (shape_window, "motion-notify-event",
+  g_signal_connect (dialog->shape_window, "motion-notify-event",
                     G_CALLBACK (shape_motion),
                     NULL);
 
-  g_object_set_data (G_OBJECT (shape_window),
+  g_object_set_data (G_OBJECT (dialog->shape_window),
                      "cursor-offset", g_new0 (CursorOffset, 1));
 
-  g_signal_connect (drawing_area, "expose-event",
+  g_signal_connect (dialog->drawing_area, "expose-event",
                     G_CALLBACK (repaint_da),
-                    NULL);
+                    dialog);
 
-  g_signal_connect (shape_drawing_area, "expose-event",
-                    G_CALLBACK (repaint_sda),
-                    NULL);
+  g_signal_connect (dialog->shape_drawing_area, "expose-event",
+                    G_CALLBACK (repaint_da),
+                    dialog);
 
   /* We request a minimum size *after* having connecting the
    * size-allocate signal for correct initialization. */
-  gtk_widget_set_size_request (drawing_area, preview_width, preview_height);
-  gtk_widget_set_size_request (shape_drawing_area, preview_width, preview_height);
-
-  root_win = gdk_get_default_root_window ();
-}
-
-static void
-init_quality_checkbox (void)
-{
-  GdkScreen         *screen;
-  guint              screen_width, screen_height;
-  static const gint  lower_quality_frame_limit = 10;
-
-  screen = gtk_widget_get_screen (window);
-  screen_height = gdk_screen_get_height (screen);
-  screen_width = gdk_screen_get_width (screen);
-
-  gtk_widget_show (quality_checkbox);
-  /* Small resolution, no need for this button. */
-  if (screen_width / 2 >= width && screen_height / 2 >= height)
-    {
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (quality_checkbox), FALSE);
-      gtk_widget_hide (quality_checkbox);
-    }
-  /* We will set a lower quality as default if image is more than the screen size
-   * and there are more than a given limit of frames. */
-  else if (total_frames > lower_quality_frame_limit &&
-          (width > screen_width || height > screen_height))
-    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (quality_checkbox), TRUE);
+  gtk_widget_set_size_request (dialog->drawing_area, dialog->preview_width, dialog->preview_height);
+  gtk_widget_set_size_request (dialog->shape_drawing_area, dialog->preview_width, dialog->preview_height);
 }
 
 /**
  * Recursive call to generate frames in TAGS mode.
  **/
 static void
-rec_init_frames (gint32 layer, GList *previous_frames)
+rec_init_frames (AnimationPlayDialog *dialog,
+                 gint32               frames_image_id,
+                 gint32               layer,
+                 GList               *previous_frames,
+                 gint                 image_width,
+                 gint                 image_height)
 {
   Frame        *empty_frame = NULL;
   gchar        *layer_name;
@@ -1317,20 +1600,23 @@ rec_init_frames (gint32 layer, GList *previous_frames)
 
       children = gimp_item_get_children (layer, &num_children);
       for (j = 0; j < num_children; j++)
-        rec_init_frames (children[num_children - j - 1], previous_frames);
+        rec_init_frames (dialog, frames_image_id,
+                         children[num_children - j - 1],
+                         previous_frames,
+                         image_width, image_height);
 
       return;
     }
 
   layer_name = gimp_item_get_name (layer);
   nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
-  preview_quality = preview_width != width || preview_height != height;
+  preview_quality = dialog->preview_width != image_width || dialog->preview_height != image_height;
 
   if (g_regex_match (all_reg, nospace_name, 0, NULL))
     {
-      for (j = 0; j < total_frames; j++)
+      for (j = 0; j < dialog->settings.num_frames; j++)
         {
-          if (! frames[j])
+          if (! dialog->frames[j])
             {
               if (! empty_frame)
                 {
@@ -1345,10 +1631,10 @@ rec_init_frames (gint32 layer, GList *previous_frames)
               if (! g_list_find (empty_frame->updated_indexes, GINT_TO_POINTER (j)))
                 empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, GINT_TO_POINTER 
(j));
 
-              frames[j] = empty_frame;
+              dialog->frames[j] = empty_frame;
             }
-          else if (! g_list_find (frames[j]->updated_indexes, GINT_TO_POINTER (j)))
-            frames[j]->updated_indexes = g_list_append (frames[j]->updated_indexes, GINT_TO_POINTER (j));
+          else if (! g_list_find (dialog->frames[j]->updated_indexes, GINT_TO_POINTER (j)))
+            dialog->frames[j]->updated_indexes = g_list_append (dialog->frames[j]->updated_indexes, 
GINT_TO_POINTER (j));
         }
     }
   else
@@ -1371,7 +1657,7 @@ rec_init_frames (gint32 layer, GList *previous_frames)
 
                   for (k = first; k <= second; k++)
                     {
-                      if (! frames[k - frame_number_min])
+                      if (! dialog->frames[k - dialog->settings.frame_min])
                         {
                           if (! empty_frame)
                             {
@@ -1383,21 +1669,21 @@ rec_init_frames (gint32 layer, GList *previous_frames)
                               previous_frames = g_list_append (previous_frames, empty_frame);
                             }
 
-                          if (! g_list_find (frames[k - frame_number_min]->updated_indexes, GINT_TO_POINTER 
(k - frame_number_min)))
-                            empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (k - frame_number_min));
+                          if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
+                            empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (k - dialog->settings.frame_min));
 
-                          frames[k - frame_number_min] = empty_frame;
+                          dialog->frames[k - dialog->settings.frame_min] = empty_frame;
                         }
-                      else if (! g_list_find (frames[k - frame_number_min]->updated_indexes, GINT_TO_POINTER 
(k - frame_number_min)))
-                        frames[k - frame_number_min]->updated_indexes = g_list_append (frames[k - 
frame_number_min]->updated_indexes,
-                                                                                       GINT_TO_POINTER (k - 
frame_number_min));
+                      else if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
+                        dialog->frames[k - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[k - dialog->settings.frame_min]->updated_indexes,
+                                                                                       GINT_TO_POINTER (k - 
dialog->settings.frame_min));
                     }
                 }
               else
                 {
                   gint32 num = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
 
-                  if (! frames[num - frame_number_min])
+                  if (! dialog->frames[num - dialog->settings.frame_min])
                     {
                       if (! empty_frame)
                         {
@@ -1409,14 +1695,14 @@ rec_init_frames (gint32 layer, GList *previous_frames)
                           previous_frames = g_list_append (previous_frames, empty_frame);
                         }
 
-                      if (! g_list_find (frames[num - frame_number_min]->updated_indexes, GINT_TO_POINTER 
(num - frame_number_min)))
-                        empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (num - frame_number_min));
+                      if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
+                        empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min));
 
-                      frames[num - frame_number_min] = empty_frame;
+                      dialog->frames[num - dialog->settings.frame_min] = empty_frame;
                     }
-                  else if (! g_list_find (frames[num - frame_number_min]->updated_indexes, GINT_TO_POINTER 
(num - frame_number_min)))
-                    frames[num - frame_number_min]->updated_indexes = g_list_append (frames[num - 
frame_number_min]->updated_indexes,
-                                                                                     GINT_TO_POINTER (num - 
frame_number_min));
+                  else if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
+                    dialog->frames[num - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[num - dialog->settings.frame_min]->updated_indexes,
+                                                                                     GINT_TO_POINTER (num - 
dialog->settings.frame_min));
                 }
             }
           g_strfreev (tokens);
@@ -1426,10 +1712,10 @@ rec_init_frames (gint32 layer, GList *previous_frames)
       g_match_info_free (match_info);
     }
 
-  for (j = 0; j < total_frames; j++)
+  for (j = 0; j < dialog->settings.num_frames; j++)
     {
       /* Check which frame must be updated with the current layer. */
-      if (frames[j] && g_list_length (frames[j]->updated_indexes))
+      if (dialog->frames[j] && g_list_length (dialog->frames[j]->updated_indexes))
         {
           new_layer = gimp_layer_new_from_drawable (layer, frames_image_id);
           gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
@@ -1439,66 +1725,66 @@ rec_init_frames (gint32 layer, GList *previous_frames)
               gint layer_offx, layer_offy;
 
               gimp_layer_scale (new_layer,
-                                (gimp_drawable_width (layer) * (gint) preview_width) / (gint) width,
-                                (gimp_drawable_height (layer) * (gint) preview_height) / (gint) height,
+                                (gimp_drawable_width (layer) * (gint) dialog->preview_width) / (gint) 
image_width,
+                                (gimp_drawable_height (layer) * (gint) dialog->preview_height) / (gint) 
image_height,
                                 FALSE);
               gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
-              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) preview_width) / (gint) width,
-                                      (layer_offy * (gint) preview_height) / (gint) height);
+              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / (gint) 
image_width,
+                                      (layer_offy * (gint) dialog->preview_height) / (gint) image_height);
             }
           gimp_layer_resize_to_image_size (new_layer);
 
-          if (frames[j]->drawable_id == 0)
+          if (dialog->frames[j]->drawable_id == 0)
             {
-              frames[j]->drawable_id = new_layer;
-              frames[j]->indexes = frames[j]->updated_indexes;
-              frames[j]->updated_indexes = NULL;
+              dialog->frames[j]->drawable_id = new_layer;
+              dialog->frames[j]->indexes = dialog->frames[j]->updated_indexes;
+              dialog->frames[j]->updated_indexes = NULL;
             }
-          else if (g_list_length (frames[j]->indexes) == g_list_length (frames[j]->updated_indexes))
+          else if (g_list_length (dialog->frames[j]->indexes) == g_list_length 
(dialog->frames[j]->updated_indexes))
             {
               gimp_item_set_visible (new_layer, TRUE);
-              gimp_item_set_visible (frames[j]->drawable_id, TRUE);
+              gimp_item_set_visible (dialog->frames[j]->drawable_id, TRUE);
 
-              frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
-              g_list_free (frames[j]->updated_indexes);
-              frames[j]->updated_indexes = NULL;
+              dialog->frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, 
GIMP_CLIP_TO_IMAGE);
+              g_list_free (dialog->frames[j]->updated_indexes);
+              dialog->frames[j]->updated_indexes = NULL;
             }
           else
             {
               GList    *idx;
               gboolean  move_j = FALSE;
               Frame    *forked_frame = g_new (Frame, 1);
-              gint32    forked_drawable_id = gimp_layer_new_from_drawable (frames[j]->drawable_id, 
frames_image_id);
+              gint32    forked_drawable_id = gimp_layer_new_from_drawable (dialog->frames[j]->drawable_id, 
frames_image_id);
 
-              /* if part only of the frames are updated, we fork the existing frame. */
+              /* if part only of the dialog->frames are updated, we fork the existing frame. */
               gimp_image_insert_layer (frames_image_id, forked_drawable_id, 0, 1);
               gimp_item_set_visible (new_layer, TRUE);
               gimp_item_set_visible (forked_drawable_id, TRUE);
               forked_drawable_id = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
 
               forked_frame->drawable_id = forked_drawable_id;
-              forked_frame->indexes = frames[j]->updated_indexes;
+              forked_frame->indexes = dialog->frames[j]->updated_indexes;
               forked_frame->updated_indexes = NULL;
-              frames[j]->updated_indexes = NULL;
+              dialog->frames[j]->updated_indexes = NULL;
 
               for (idx = g_list_first (forked_frame->indexes); idx != NULL; idx = g_list_next (idx))
                 {
-                  frames[j]->indexes = g_list_remove (frames[j]->indexes, idx->data);
+                  dialog->frames[j]->indexes = g_list_remove (dialog->frames[j]->indexes, idx->data);
                   if (GPOINTER_TO_INT (idx->data) != j)
-                    frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
+                    dialog->frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
                   else
                     /* Frame j must also be moved to the forked frame, but only after the loop. */
                     move_j = TRUE;
                 }
               if (move_j)
-                frames[j] = forked_frame;
+                dialog->frames[j] = forked_frame;
 
               gimp_item_set_visible (forked_drawable_id, FALSE);
 
               previous_frames = g_list_append (previous_frames, forked_frame);
             }
 
-          gimp_item_set_visible (frames[j]->drawable_id, FALSE);
+          gimp_item_set_visible (dialog->frames[j]->drawable_id, FALSE);
         }
     }
 
@@ -1506,66 +1792,48 @@ rec_init_frames (gint32 layer, GList *previous_frames)
   g_free (nospace_name);
 }
 
-static void
-init_frames (void)
+/* Initialize the frames, and return TRUE if render must be forced. */
+static gboolean
+init_frames (AnimationPlayDialog *dialog)
 {
   /* Frames are associated to an unused image. */
+  static gint32    frames_image_id = 0;
+  /* We keep track of the frames in a separate structure to free drawable
+   * memory. We can't use easily dialog->frames because some of the
+   * drawables may be used in more than 1 frame. */
   static GList    *previous_frames = NULL;
+
+  gint            *layers;
+  gint             num_layers;
+  gint             image_width;
+  gint             image_height;
   gint32           new_frame, previous_frame, new_layer;
-  gboolean         animated;
-  GtkAction       *action;
   gint             duration = 0;
-  DisposeType      disposal = settings.default_frame_disposal;
+  DisposeType      disposal = dialog->settings.frame_disposal;
   gint             frame_spin_size;
 
-  if (playing)
-    gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
-                                                    "/anim-play-toolbar/play"));
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
+    {
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
+    }
 
-  if (frames_lock)
-    return;
-  frames_lock = TRUE;
-
-  /* Block the frame-related UI during frame initialization. */
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/play");
-  gtk_action_set_sensitive (action, FALSE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step-back");
-  gtk_action_set_sensitive (action, FALSE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step");
-  gtk_action_set_sensitive (action, FALSE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/rewind");
-  gtk_action_set_sensitive (action, FALSE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/refresh");
-  gtk_action_set_sensitive (action, FALSE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/detach");
-  gtk_action_set_sensitive (action, FALSE);
-
-  gtk_widget_set_sensitive (GTK_WIDGET (zoomcombo), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (fpscombo), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (speedcombo), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (frame_disposal_combo), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (quality_checkbox), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (startframe_spin), FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (endframe_spin), FALSE);
-
-  g_signal_handler_block (progress, progress_entered_handler);
-  g_signal_handler_block (progress, progress_left_handler);
-  g_signal_handler_block (progress, progress_motion_handler);
-  g_signal_handler_block (progress, progress_button_handler);
+  if (dialog->frames_lock)
+    {
+      return FALSE;
+    }
+  dialog->frames_lock = TRUE;
+
+  /* Block most UI during frame initialization. */
+  gtk_action_group_set_sensitive (dialog->play_actions, FALSE);
+  gtk_widget_set_sensitive (dialog->play_bar, FALSE);
+  gtk_widget_set_sensitive (dialog->progress_bar, FALSE);
+  gtk_action_group_set_sensitive (dialog->settings_actions, FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), FALSE);
+  gtk_action_group_set_sensitive (dialog->view_actions, FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->view_bar), FALSE);
 
   /* Cleanup before re-generation. */
-  if (frames)
+  if (dialog->frames)
     {
       GList *idx;
 
@@ -1584,33 +1852,43 @@ init_frames (void)
       g_list_free (previous_frames);
       previous_frames = NULL;
 
-      g_free (frames);
-      g_free (frame_durations);
-      frames = NULL;
-      frame_durations = NULL;
+      g_free (dialog->frames);
+      dialog->frames = NULL;
+    }
+  if (! gimp_image_is_valid (dialog->image_id))
+    {
+      /* This is not necessarily an error. We may have simply wanted
+       * to clean up our GEGL buffers. */
+      return FALSE;
     }
 
-  if (total_frames <= 0)
+  layers = gimp_image_get_layers (dialog->image_id, &num_layers);
+  init_frame_numbers (dialog, layers, num_layers);
+  if (dialog->settings.num_frames <= 0)
     {
-      gtk_widget_set_sensitive (GTK_WIDGET (frame_disposal_combo), TRUE);
-      frames_lock = FALSE;
-      return;
+      update_ui_sensitivity (dialog);
+      dialog->frames_lock = FALSE;
+      g_free (layers);
+      return FALSE;
     }
 
-  frames = g_try_malloc0_n (total_frames, sizeof (Frame*));
-  frame_durations = g_try_malloc0_n (total_frames, sizeof (guint32));
-  if (! frames || ! frame_durations)
+  dialog->frames = g_try_malloc0_n (dialog->settings.num_frames, sizeof (Frame*));
+  if (! dialog->frames)
     {
-      frames_lock = FALSE;
+      dialog->frames_lock = FALSE;
       gimp_message (_("Memory could not be allocated to the frame container."));
-      clean_exit ();
-      return;
+      g_free (layers);
+      clean_exit (dialog);
+      return FALSE;
     }
 
+  image_width  = gimp_image_width (dialog->image_id);
+  image_height = gimp_image_height (dialog->image_id);
+
   /* We only use RGB images for display because indexed images would somehow
      render terrible colors. Layers from other types will be automatically
      converted. */
-  frames_image_id = gimp_image_new (preview_width, preview_height, GIMP_RGB);
+  frames_image_id = gimp_image_new (dialog->preview_width, dialog->preview_height, GIMP_RGB);
 
   /* Save processing time and memory by not saving history and merged frames. */
   gimp_image_undo_disable (frames_image_id);
@@ -1619,67 +1897,73 @@ init_frames (void)
     {
       gint        i;
 
-      for (i = 0; i < total_layers; i++)
+      for (i = 0; i < num_layers; i++)
         {
-          show_loading_progress (i);
-          rec_init_frames (layers[total_layers - (i + 1)], previous_frames);
+          show_loading_progress (dialog, i, num_layers);
+          rec_init_frames (dialog,
+                           frames_image_id,
+                           layers[num_layers - (i + 1)],
+                           previous_frames,
+                           image_width, image_height);
         }
 
-      for (i = 0; i < total_frames; i++)
+      for (i = 0; i < dialog->settings.num_frames; i++)
         {
           /* If for some reason a frame is absent, use the previous one.
            * We are ensured that there is at least a "first" frame for this. */
-          if (! frames[i])
+          if (! dialog->frames[i])
             {
-              frames[i] = frames[i - 1];
-              frames[i]->indexes = g_list_append (frames[i]->indexes, GINT_TO_POINTER (i));
+              dialog->frames[i] = dialog->frames[i - 1];
+              dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
             }
 
-          frame_durations[i] = settings.default_frame_duration;
+          /* A zero duration only means we use the global duration, whatever it is at the time. */
+          dialog->frames[i]->duration = 0;
         }
     }
   else
     {
-      gint layer_offx, layer_offy;
+      gint   layer_offx;
+      gint   layer_offy;
       gchar *layer_name;
       gint   i;
 
-      for (i = 0; i < total_frames; i++)
+      for (i = 0; i < dialog->settings.num_frames; i++)
         {
-          show_loading_progress (i);
+          show_loading_progress (dialog, i, num_layers);
 
-          frames[i] = g_new (Frame, 1);
-          frames[i]->indexes = NULL;
-          frames[i]->indexes = g_list_append (frames[i]->indexes, GINT_TO_POINTER (i));
-          frames[i]->updated_indexes = NULL;
+          dialog->frames[i] = g_new (Frame, 1);
+          dialog->frames[i]->indexes = NULL;
+          dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
+          dialog->frames[i]->updated_indexes = NULL;
 
-          previous_frames = g_list_append (previous_frames, frames[i]);
+          previous_frames = g_list_append (previous_frames, dialog->frames[i]);
 
-          layer_name = gimp_item_get_name (layers[total_layers - (i + 1)]);
+          layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
           if (layer_name)
             {
               duration = parse_ms_tag (layer_name);
-              disposal = parse_disposal_tag (layer_name);
+              disposal = parse_disposal_tag (dialog, layer_name);
               g_free (layer_name);
             }
 
           if (i > 0 && disposal != DISPOSE_REPLACE)
             {
-              previous_frame = gimp_layer_copy (frames[i - 1]->drawable_id);
+              previous_frame = gimp_layer_copy (dialog->frames[i - 1]->drawable_id);
 
               gimp_image_insert_layer (frames_image_id, previous_frame, 0, 0);
               gimp_item_set_visible (previous_frame, TRUE);
             }
 
-          new_layer = gimp_layer_new_from_drawable (layers[total_layers - (i + 1)], frames_image_id);
+          new_layer = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)], frames_image_id);
 
           gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
           gimp_item_set_visible (new_layer, TRUE);
-          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[total_layers - (i + 1)]) * (gint) 
preview_width) / (gint) width,
-                            (gimp_drawable_height (layers[total_layers - (i + 1)]) * (gint) preview_height) 
/ (gint) height, FALSE);
-          gimp_drawable_offsets (layers[total_layers - (i + 1)], &layer_offx, &layer_offy);
-          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) preview_width) / (gint) width,
-                                  (layer_offy * (gint) preview_height) / (gint) height);
+          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_width) / image_width,
+                            (gimp_drawable_height (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_height) / image_height, FALSE);
+          gimp_drawable_offsets (layers[num_layers - (i + 1)], &layer_offx, &layer_offy);
+          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / image_width,
+                                  (layer_offy * (gint) dialog->preview_height) / image_height);
           gimp_layer_resize_to_image_size (new_layer);
 
           if (gimp_item_is_group (new_layer))
@@ -1696,168 +1980,145 @@ init_frames (void)
             }
 
           new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
-          frames[i]->drawable_id = new_frame;
+          dialog->frames[i]->drawable_id = new_frame;
           gimp_item_set_visible (new_frame, FALSE);
 
           if (duration <= 0)
-            duration = settings.default_frame_duration;
-          frame_durations[i] = (guint32) duration;
+            duration = 0;
+          dialog->frames[i]->duration = (guint) duration;
         }
     }
 
   /* Update the UI. */
-  frame_spin_size = (gint) (log10 (frame_number_max - (frame_number_max % 10))) + 1;
-  gtk_entry_set_width_chars (GTK_ENTRY (startframe_spin), frame_spin_size);
-  gtk_entry_set_width_chars (GTK_ENTRY (endframe_spin), frame_spin_size);
-
-  gtk_adjustment_configure (startframe_adjust, start_frame, frame_number_min, frame_number_max, 1.0, 5.0, 
0.0);
-  gtk_adjustment_configure (endframe_adjust, end_frame, start_frame, frame_number_max, 1.0, 5.0, 0.0);
-
-  animated = end_frame - start_frame >= 1;
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/play");
-  gtk_action_set_sensitive (action, animated);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step-back");
-  gtk_action_set_sensitive (action, animated);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step");
-  gtk_action_set_sensitive (action, animated);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/rewind");
-  gtk_action_set_sensitive (action, animated);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/refresh");
-  gtk_action_set_sensitive (action, TRUE);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/detach");
-  gtk_action_set_sensitive (action, TRUE);
-
-  gtk_widget_set_sensitive (GTK_WIDGET (zoomcombo), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (fpscombo), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (speedcombo), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (frame_disposal_combo), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (quality_checkbox), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (startframe_spin), TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (endframe_spin), TRUE);
-
-  g_signal_handler_unblock (progress, progress_entered_handler);
-  g_signal_handler_unblock (progress, progress_left_handler);
-  g_signal_handler_unblock (progress, progress_motion_handler);
-  g_signal_handler_unblock (progress, progress_button_handler);
-
-  /* Keep the same frame number, unless it is now invalid. */
-  if (frame_number > end_frame || frame_number < start_frame)
-    frame_number = start_frame;
-
-  force_render = TRUE;
-
-  frames_lock = FALSE;
+  frame_spin_size = (gint) (log10 (dialog->settings.frame_max - (dialog->settings.frame_max % 10))) + 1;
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), frame_spin_size);
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), frame_spin_size);
+
+  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->startframe_spin)),
+                            dialog->settings.start_frame,
+                            dialog->settings.frame_min,
+                            dialog->settings.frame_max,
+                            1.0, 5.0, 0.0);
+  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin)),
+                            dialog->settings.end_frame,
+                            dialog->settings.start_frame,
+                            dialog->settings.frame_max,
+                            1.0, 5.0, 0.0);
+
+  update_ui_sensitivity (dialog);
+
+  dialog->frames_lock = FALSE;
+  g_free (layers);
 
+  return TRUE;
 }
 
 static void
-initialize (void)
+initialize (AnimationPlayDialog *dialog)
 {
   /* Freeing existing data after a refresh. */
-  g_free (layers);
-
   /* Catch the case when the user has closed the image in the meantime. */
-  if (! gimp_image_is_valid (image_id))
+  if (! gimp_image_is_valid (dialog->image_id))
     {
       gimp_message (_("Invalid image. Did you close it?"));
-      clean_exit ();
+      clean_exit (dialog);
       return;
     }
 
-  width     = gimp_image_width (image_id);
-  height    = gimp_image_height (image_id);
-  layers    = gimp_image_get_layers (image_id, &total_layers);
-
-  set_total_frames ();
-
-  if (!window)
-    build_dialog (gimp_image_get_name (image_id));
-  refresh_dialog (gimp_image_get_name (image_id));
+  if (! dialog->window)
+    {
+      /* First run. */
+      gchar *name = gimp_image_get_name (dialog->image_id);
+      init_ui (dialog, name);
+      g_free (name);
+    }
+  refresh_dialog (dialog);
 
   /* I want to make sure the progress bar is realized before init_frames()
    * which may take quite a bit of time. */
-  if (!gtk_widget_get_realized (progress))
-    gtk_widget_realize (progress);
+  if (!gtk_widget_get_realized (dialog->progress))
+    gtk_widget_realize (dialog->progress);
 
-  init_frames ();
-  render_frame (frame_number);
+  render_frame (dialog, init_frames (dialog));
 }
 
 /* Rendering Functions */
 
 static void
-render_frame (guint whichframe)
-{
-  static gint    last_frame_index = -1;
+render_frame (AnimationPlayDialog *dialog,
+              gboolean             force_render)
+{
+  static gint    last_frame_index        = -1;
+  static gchar  *shape_preview_mask      = NULL;
+  static guint   shape_preview_mask_size = 0;
+  static guchar *rawframe                = NULL;
+  static guint   rawframe_size           = 0;
   GeglBuffer    *buffer;
   gint           i, j, k;
   guchar        *srcptr;
   guchar        *destptr;
   GtkWidget     *da;
-  guint          drawing_width, drawing_height;
-  gdouble        drawing_scale;
+  guint          drawing_width;
+  guint          drawing_height;
   guchar        *preview_data;
 
   /* Do not try to update the drawing areas while init_frames() is still running. */
-  if (frames_lock)
+  if (dialog->frames_lock)
     return;
 
   /* Unless we are in a case where we always want to redraw
    * (after a zoom, preview mode change, reinitialization, and such),
    * we don't redraw if the same frame was already drawn. */
-  if ((! force_render) && total_frames > 0 && last_frame_index > -1 &&
-      g_list_find (frames[last_frame_index]->indexes, GINT_TO_POINTER (whichframe - frame_number_min)))
+  if ((! force_render) && dialog->settings.num_frames > 0 && last_frame_index > -1 &&
+      g_list_find (dialog->frames[last_frame_index]->indexes,
+                   GINT_TO_POINTER (dialog->settings.current_frame - dialog->settings.frame_min)))
     {
-      show_playing_progress ();
+      show_playing_progress (dialog);
       return;
     }
 
-  frames_lock = TRUE;
+  dialog->frames_lock = TRUE;
 
-  g_assert (total_frames < 1 || (whichframe >= start_frame && whichframe <= end_frame));
+  g_assert (dialog->settings.num_frames < 1 || (dialog->settings.current_frame >= 
dialog->settings.start_frame &&
+                                                dialog->settings.current_frame <= 
dialog->settings.end_frame));
 
-  if (detached)
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
     {
-      da = shape_drawing_area;
-      preview_data = shape_drawing_area_data;
-      drawing_width = shape_drawing_area_width;
-      drawing_height = shape_drawing_area_height;
-      drawing_scale = shape_scale;
+      da = dialog->shape_drawing_area;
+      preview_data = dialog->shape_drawing_area_data;
+      drawing_width = dialog->shape_drawing_area_width;
+      drawing_height = dialog->shape_drawing_area_height;
 
-      if (total_frames < 1)
+      if (dialog->settings.num_frames < 1)
         total_alpha_preview (preview_data, drawing_width, drawing_height);
     }
   else
     {
-      da = drawing_area;
-      preview_data = drawing_area_data;
-      drawing_width = drawing_area_width;
-      drawing_height = drawing_area_height;
-      drawing_scale = scale;
+      da = dialog->drawing_area;
+      preview_data = dialog->drawing_area_data;
+      drawing_width = dialog->drawing_area_width;
+      drawing_height = dialog->drawing_area_height;
 
       /* Set "alpha grid" background. */
       total_alpha_preview (preview_data, drawing_width, drawing_height);
     }
 
   /* When there is no frame to show, we simply display the alpha background and return. */
-  if (total_frames > 0)
+  if (dialog->settings.num_frames > 0)
     {
-      buffer = gimp_drawable_get_buffer (frames[whichframe - frame_number_min]->drawable_id);
+      /* Update the rawframe. */
+      if (rawframe_size < drawing_width * drawing_height * 4)
+        {
+          rawframe_size = drawing_width * drawing_height * 4;
+          g_free (rawframe);
+          rawframe = g_malloc (rawframe_size);
+        }
+
+      buffer = gimp_drawable_get_buffer (dialog->frames[dialog->settings.current_frame - 
dialog->settings.frame_min]->drawable_id);
 
       /* Fetch and scale the whole raw new frame */
       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
-                       drawing_scale, babl_format ("R'G'B'A u8"),
+                       dialog->settings.scale, babl_format ("R'G'B'A u8"),
                        rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
 
       /* Number of pixels. */
@@ -1881,9 +2142,20 @@ render_frame (guint whichframe)
         }
 
       /* calculate the shape mask */
-      if (detached)
+      if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->view_actions, "detach"))))
         {
-          memset (shape_preview_mask, 0, (drawing_width * drawing_height) / 8 + drawing_height);
+          gint ideal_shape_size = (drawing_width * drawing_height) /
+                                   8 + 1 + drawing_height;
+
+          if (shape_preview_mask_size < ideal_shape_size)
+            {
+              shape_preview_mask_size = ideal_shape_size;
+              g_free (shape_preview_mask);
+              shape_preview_mask = g_malloc (ideal_shape_size);
+            }
+
+          memset (shape_preview_mask, 0,
+                  (drawing_width * drawing_height) / 8 + drawing_height);
           srcptr = rawframe + 3;
 
           for (j = 0; j < drawing_height; j++)
@@ -1898,62 +2170,64 @@ render_frame (guint whichframe)
                   srcptr += 4;
                 }
             }
-          reshape_from_bitmap (shape_preview_mask);
+          reshape_from_bitmap (dialog, shape_preview_mask);
         }
 
       /* clean up */
       g_object_unref (buffer);
 
       /* Update UI. */
-      show_playing_progress ();
+      show_playing_progress (dialog);
 
-      last_frame_index = whichframe - frame_number_min;
+      last_frame_index = dialog->settings.current_frame - dialog->settings.frame_min;
     }
 
   /* Display the preview buffer. */
   gdk_draw_rgb_image (gtk_widget_get_window (da),
                       (gtk_widget_get_style (da))->white_gc,
-                      (gint) ((drawing_width - drawing_scale * preview_width) / 2),
-                      (gint) ((drawing_height - drawing_scale * preview_height) / 2),
+                      (gint) ((drawing_width - dialog->settings.scale * dialog->preview_width) / 2),
+                      (gint) ((drawing_height - dialog->settings.scale * dialog->preview_height) / 2),
                       drawing_width, drawing_height,
-                      (total_frames == 1 ?
+                      (dialog->settings.num_frames == 1 ?
                        GDK_RGB_DITHER_MAX : DITHERTYPE),
                       preview_data, drawing_width * 3);
 
-  force_render = FALSE;
-  frames_lock = FALSE;
+  dialog->frames_lock = FALSE;
 }
 
 
 static void
-show_goto_progress (guint goto_frame)
+show_goto_progress (guint                goto_frame,
+                    AnimationPlayDialog *dialog)
 {
   gchar         *text;
 
   /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress),
-                                 ((gfloat) (frame_number - frame_number_min) /
-                                  (gfloat) (total_frames - 0.999)));
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
+                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
+                                  (gfloat) (dialog->settings.num_frames - 0.999)));
 
-  if (settings.default_frame_disposal != DISPOSE_TAGS || frame_number_min == 1)
-    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, total_frames);
+  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
+    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, dialog->settings.num_frames);
   else
-    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - frame_number_min + 1, goto_frame, 
total_frames);
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text);
+    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - dialog->settings.frame_min + 1, 
goto_frame, dialog->settings.num_frames);
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
   g_free (text);
 }
 
 static void
-show_loading_progress (gint layer_nb)
+show_loading_progress (AnimationPlayDialog *dialog,
+                       gint                 layer_nb,
+                       gint                 num_layers)
 {
   gchar *text;
-  gfloat load_rate = (gfloat) layer_nb / ((gfloat) total_layers - 0.999);
+  gfloat load_rate = (gfloat) layer_nb / ((gfloat) num_layers - 0.999);
 
   /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), load_rate);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress), load_rate);
 
   text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text);
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
   g_free (text);
 
   /* Forcing the UI to update even with intensive computation. */
@@ -1962,544 +2236,934 @@ show_loading_progress (gint layer_nb)
 }
 
 static void
-show_playing_progress (void)
+show_playing_progress (AnimationPlayDialog *dialog)
 {
   gchar *text;
 
   /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress),
-                                 ((gfloat) (frame_number - frame_number_min) /
-                                  (gfloat) (total_frames - 0.999)));
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
+                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
+                                  (gfloat) (dialog->settings.num_frames - 0.999)));
 
-  if (settings.default_frame_disposal != DISPOSE_TAGS || frame_number_min == 1)
-    text = g_strdup_printf (_("Frame %d of %d"), frame_number, total_frames);
+  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
+    {
+      text = g_strdup_printf (_("Frame %d of %d"),
+                              dialog->settings.current_frame,
+                              dialog->settings.num_frames);
+    }
   else
-    text = g_strdup_printf (_("Frame %d (%d) of %d"), frame_number - frame_number_min + 1, frame_number, 
total_frames);
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text);
+    {
+      text = g_strdup_printf (_("Frame %d (%d) of %d"),
+                              dialog->settings.current_frame - dialog->settings.frame_min + 1,
+                              dialog->settings.current_frame, dialog->settings.num_frames);
+    }
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
   g_free (text);
 }
 
+/* total_alpha_preview:
+ * Fill the @drawing_data with an alpha (grey chess) pattern.
+ * This uses a static array, copied over each line (with some shift to
+ * reproduce the pattern), using `memcpy()`.
+ * The reason why we keep the pattern in the statically allocated memory,
+ * instead of simply looping through @drawing_data and recreating the
+ * pattern is simply because `memcpy()` implementations are supposed to
+ * be more efficient than loops over an array. */
 static void
-update_alpha_preview (void)
+total_alpha_preview (guchar *drawing_data,
+                     guint   drawing_width,
+                     guint   drawing_height)
 {
-  gint i;
+  static guint   alpha_line_width = 0;
+  static guchar *alpha_line       = NULL;
+  gint           i;
+
+  g_assert (drawing_width > 0);
+
+  /* If width change, we update the "alpha" line. */
+  if (alpha_line_width < drawing_width + 8)
+    {
+      alpha_line_width = drawing_width + 8;
 
-  g_free (preview_alpha1_data);
-  g_free (preview_alpha2_data);
+      g_free (alpha_line);
 
-  preview_alpha1_data = g_malloc (drawing_area_width * 3);
-  preview_alpha2_data = g_malloc (drawing_area_width * 3);
+      /* A full line + 8 pixels (1 square). */
+      alpha_line = g_malloc (alpha_line_width * 3);
+
+      for (i = 0; i < alpha_line_width; i++)
+        {
+          /* 8 pixels dark grey, 8 pixels light grey, and so on. */
+          if (i & 8)
+            {
+              alpha_line[i * 3 + 0] =
+                alpha_line[i * 3 + 1] =
+                alpha_line[i * 3 + 2] = 102;
+            }
+          else
+            {
+              alpha_line[i * 3 + 0] =
+                alpha_line[i * 3 + 1] =
+                alpha_line[i * 3 + 2] = 154;
+            }
+        }
+    }
 
-  for (i = 0; i < drawing_area_width; i++)
+  for (i = 0; i < drawing_height; i++)
     {
       if (i & 8)
         {
-          preview_alpha1_data[i*3 + 0] =
-          preview_alpha1_data[i*3 + 1] =
-          preview_alpha1_data[i*3 + 2] = 102;
-          preview_alpha2_data[i*3 + 0] =
-          preview_alpha2_data[i*3 + 1] =
-          preview_alpha2_data[i*3 + 2] = 154;
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line,
+                  3 * drawing_width);
         }
       else
         {
-          preview_alpha1_data[i*3 + 0] =
-          preview_alpha1_data[i*3 + 1] =
-          preview_alpha1_data[i*3 + 2] = 154;
-          preview_alpha2_data[i*3 + 0] =
-          preview_alpha2_data[i*3 + 1] =
-          preview_alpha2_data[i*3 + 2] = 102;
+          /* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line + 24,
+                  3 * drawing_width);
         }
     }
 }
 
 static void
-total_alpha_preview (guchar *da_data, guint da_width, guint da_height)
+do_back_step (AnimationPlayDialog *dialog)
 {
-  gint i;
-
-  for (i = 0; i < da_height; i++)
+  if (dialog->settings.current_frame == dialog->settings.start_frame)
     {
-      if (i & 8)
-        memcpy (&da_data[i * 3 * da_width], preview_alpha1_data, 3 * da_width);
-      else
-        memcpy (&da_data[i * 3 * da_width], preview_alpha2_data, 3 * da_width);
+      dialog->settings.current_frame = dialog->settings.end_frame;
     }
-}
-
-/* Util. */
-
-static void
-remove_timer (void)
-{
-  if (timer)
+  else
     {
-      g_source_remove (timer);
-      timer = 0;
+      --dialog->settings.current_frame;
     }
+  render_frame (dialog, FALSE);
 }
 
 static void
-do_back_step (void)
+do_step (AnimationPlayDialog *dialog)
 {
-  if (frame_number == start_frame)
-    frame_number = end_frame;
-  else
-    frame_number = frame_number - 1;
-  render_frame (frame_number);
+  dialog->settings.current_frame = dialog->settings.start_frame +
+                                   ((dialog->settings.current_frame - dialog->settings.start_frame + 1) %
+                                    (dialog->settings.end_frame - dialog->settings.start_frame + 1));
+
+  render_frame (dialog, FALSE);
 }
 
 static void
-do_step (void)
+window_destroy (GtkWidget           *widget,
+                AnimationPlayDialog *dialog)
 {
-  frame_number = start_frame + ((frame_number - start_frame + 1) % (end_frame - start_frame + 1));
-  render_frame (frame_number);
+  clean_exit (dialog);
 }
 
-
-/*  Callbacks  */
-
 static void
-window_destroy (GtkWidget *widget)
+set_timer (guint new_timer)
 {
-  clean_exit ();
-}
+  static guint timer = 0;
 
+  if (timer)
+    {
+      g_source_remove (timer);
+    }
+  timer = new_timer;
+}
 
-static gint
-advance_frame_callback (gpointer data)
+static gboolean
+advance_frame_callback (AnimationPlayDialog *dialog)
 {
-  gdouble duration;
-
-  remove_timer();
+  guint duration;
+  guint timer;
 
-  duration = frame_durations[(frame_number - frame_number_min + 1) % total_frames];
+  duration = dialog->frames[(dialog->settings.current_frame - dialog->settings.frame_min + 1) %
+             dialog->settings.num_frames]->duration;
+  if (duration <= 0)
+    {
+      duration = (guint) (1000.0 / ((AnimationPlayDialog *) dialog)->settings.frame_rate);
+    }
 
-  timer = g_timeout_add (duration * get_duration_factor (settings.duration_index),
-                         advance_frame_callback, NULL);
+  timer = g_timeout_add (duration,
+                         (GSourceFunc) advance_frame_callback,
+                         (AnimationPlayDialog *) dialog);
+  set_timer (timer);
 
-  do_step ();
+  do_step (dialog);
 
-  return FALSE;
+  return G_SOURCE_REMOVE;
 }
 
-
 static void
-play_callback (GtkToggleAction *action)
+play_callback (GtkToggleAction     *action,
+               AnimationPlayDialog *dialog)
 {
-  if (playing)
-    remove_timer ();
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
+    {
+      guint timer;
+      guint duration = dialog->frames[dialog->settings.current_frame - dialog->settings.frame_min]->duration;
 
-  playing = gtk_toggle_action_get_active (action);
+      if (duration <= 0)
+        duration = (guint) (1000.0 / dialog->settings.frame_rate);
 
-  if (playing)
-    {
-      timer = g_timeout_add ((gdouble) frame_durations[frame_number - frame_number_min] *
-                             get_duration_factor (settings.duration_index),
-                             advance_frame_callback, NULL);
+      timer = g_timeout_add (duration, (GSourceFunc) advance_frame_callback, dialog);
+      set_timer (timer);
 
       gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause");
     }
   else
     {
+      /* Remove any previous timer to stock playback. */
+      set_timer (0);
+
       gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-start");
     }
 
   g_object_set (action,
-                "tooltip", playing ? _("Stop playback") : _("Start playback"),
+                "tooltip",
+                gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play"))) ?
+                  _("Pause playback") : _("Start playback"),
                 NULL);
 }
 
-static gdouble
-get_duration_factor (gint index)
+static void
+step_back_callback (GtkAction           *action,
+                    AnimationPlayDialog *dialog)
 {
-  switch (index)
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
     {
-    case 0:
-      return 0.125;
-    case 1:
-      return 0.25;
-    case 2:
-      return 0.5;
-    case 3:
-      return 1.0;
-    case 4:
-      return 2.0;
-    case 5:
-      return 4.0;
-    case 6:
-      return 8.0;
-    default:
-      return 1.0;
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
     }
+  do_back_step (dialog);
 }
 
-static gint
-get_fps (gint index)
+static void
+step_callback (GtkAction           *action,
+               AnimationPlayDialog *dialog)
 {
-  switch (index)
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
     {
-    case 0:
-      return 10;
-    case 1:
-      return 12;
-    case 2:
-      return 15;
-    case 3:
-      return 24;
-    case 4:
-      return 25;
-    case 5:
-      return 30;
-    case 6:
-      return 50;
-    case 7:
-      return 60;
-    case 8:
-      return 72;
-    default:
-      return 10;
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
     }
+  do_step (dialog);
 }
 
-static gdouble
-get_scale (gint index)
+static void
+rewind_callback (GtkAction           *action,
+                 AnimationPlayDialog *dialog)
 {
-  switch (index)
+  gboolean was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
+
+  if (was_playing)
     {
-    case 0:
-      return 0.51;
-    case 1:
-      return 1.0;
-    case 2:
-      return 1.25;
-    case 3:
-      return 1.5;
-    case 4:
-      return 2.0;
-    default:
-      {
-        /* likely -1 returned if there is no active item from the list.
-         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
-        gchar* active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (zoomcombo));
-        gdouble zoom = g_strtod (active_text, NULL);
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
+    }
 
-        /* Negative scales are inconsistent. And we want to avoid huge scaling. */
-        if (zoom > 400.0)
-          zoom = 400.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;
-      }
+  dialog->settings.current_frame = dialog->settings.start_frame;
+  render_frame (dialog, FALSE);
+
+  /* If we were playing, start playing again. */
+  if (was_playing)
+    {
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
     }
 }
 
 static void
-step_back_callback (GtkAction *action)
+refresh_callback (GtkAction           *action,
+                  AnimationPlayDialog *dialog)
 {
-  if (playing)
-    gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
-                                                    "/anim-play-toolbar/play"));
-  do_back_step();
+  initialize (dialog);
 }
 
 static void
-step_callback (GtkAction *action)
+zoom_in_callback (GtkAction           *action,
+                  AnimationPlayDialog *dialog)
 {
-  if (playing)
-    gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
-                                                    "/anim-play-toolbar/play"));
-  do_step();
-}
+  gdouble  scale = get_scale (dialog, -1);
 
-static void
-refresh_callback (GtkAction *action)
-{
-  initialize ();
+  g_signal_handlers_block_by_func (dialog->zoomcombo,
+                                   G_CALLBACK (zoomcombo_changed),
+                                   dialog);
+  update_scale (dialog, scale + 0.1);
+
+  g_signal_handlers_unblock_by_func (dialog->zoomcombo,
+                                     G_CALLBACK (zoomcombo_changed),
+                                     dialog);
 }
 
 static void
-rewind_callback (GtkAction *action)
+zoom_out_callback (GtkAction           *action,
+                   AnimationPlayDialog *dialog)
 {
-  if (playing)
-    gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
-                                                    "/anim-play-toolbar/play"));
-  frame_number = start_frame;
-  render_frame (frame_number);
+  gdouble  scale = get_scale (dialog, -1);
+
+  if (scale > 0.1)
+    {
+      g_signal_handlers_block_by_func (dialog->zoomcombo,
+                                       G_CALLBACK (zoomcombo_changed),
+                                       dialog);
+      update_scale (dialog, scale - 0.1);
+      g_signal_handlers_unblock_by_func (dialog->zoomcombo,
+                                         G_CALLBACK (zoomcombo_changed),
+                                         dialog);
+    }
 }
 
 static void
-speed_up_callback (GtkAction *action)
+zoom_reset_callback (GtkAction           *action,
+                     AnimationPlayDialog *dialog)
 {
-  if (settings.duration_index > 0)
-    --settings.duration_index;
-
-  gtk_action_set_sensitive (action, settings.duration_index > 0);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-reset");
-  gtk_action_set_sensitive (action, settings.duration_index != 3);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-down");
-  gtk_action_set_sensitive (action, TRUE);
+  gdouble  scale = get_scale (dialog, -1);
 
-  update_combobox ();
+  if (scale != 1.0)
+    {
+      g_signal_handlers_block_by_func (dialog->zoomcombo,
+                                       G_CALLBACK (zoomcombo_changed),
+                                       dialog);
+      update_scale (dialog, 1.0);
+      g_signal_handlers_unblock_by_func (dialog->zoomcombo,
+                                         G_CALLBACK (zoomcombo_changed),
+                                         dialog);
+    }
 }
 
 static void
-speed_down_callback (GtkAction *action)
+speed_up_callback (GtkAction           *action,
+                   AnimationPlayDialog *dialog)
 {
-  if (settings.duration_index < 6)
-    ++settings.duration_index;
+  if (dialog->settings.frame_rate <= MAX_FRAMERATE - 1)
+    {
+      gchar *text;
 
-  gtk_action_set_sensitive (action, settings.duration_index < 6);
+      ++dialog->settings.frame_rate;
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-reset");
-  gtk_action_set_sensitive (action, settings.duration_index != 3);
+      g_signal_handlers_block_by_func (dialog->fpscombo,
+                                       G_CALLBACK (fpscombo_changed),
+                                       dialog);
+      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
+                                       G_CALLBACK (fpscombo_activated),
+                                       dialog);
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-up");
-  gtk_action_set_sensitive (action, TRUE);
+      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
+      g_free (text);
 
-  update_combobox ();
+      g_signal_handlers_unblock_by_func (dialog->fpscombo,
+                                         G_CALLBACK (fpscombo_changed),
+                                         dialog);
+      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
+                                         G_CALLBACK (fpscombo_activated),
+                                         dialog);
+    }
 }
 
 static void
-speed_reset_callback (GtkAction *action)
+speed_down_callback (GtkAction           *action,
+                     AnimationPlayDialog *dialog)
 {
-  settings.duration_index = 3;
+  if (dialog->settings.frame_rate > 1)
+    {
+      gchar *text;
 
-  gtk_action_set_sensitive (action, FALSE);
+      --dialog->settings.frame_rate;
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-down");
-  gtk_action_set_sensitive (action, TRUE);
+      g_signal_handlers_block_by_func (dialog->fpscombo,
+                                       G_CALLBACK (fpscombo_changed),
+                                       dialog);
+      g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
+                                       G_CALLBACK (fpscombo_activated),
+                                       dialog);
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-up");
-  gtk_action_set_sensitive (action, TRUE);
+      text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
+      gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))), text);
+      g_free (text);
 
-  update_combobox ();
+      g_signal_handlers_unblock_by_func (dialog->fpscombo,
+                                         G_CALLBACK (fpscombo_changed),
+                                         dialog);
+      g_signal_handlers_unblock_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->fpscombo))),
+                                         G_CALLBACK (fpscombo_activated),
+                                         dialog);
+    }
 }
 
 static void
-framecombo_changed (GtkWidget *combo, gpointer data)
+framecombo_changed (GtkWidget           *combo,
+                    AnimationPlayDialog *dialog)
 {
-  settings.default_frame_disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+  gboolean was_playing;
+
+  dialog->settings.frame_disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+  was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
+
+  render_frame (dialog, init_frames (dialog));
 
-  set_total_frames ();
-  init_frames ();
-  render_frame (frame_number);
+  if (was_playing)
+    {
+      /* Initializing frames stopped the playing. I restart it. */
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
+    }
 }
 
+/*
+ * Callback emitted when the user hits the Enter key of the fps combo.
+ */
 static void
-speedcombo_changed (GtkWidget *combo, gpointer data)
+fpscombo_activated (GtkEntry            *combo_entry,
+                    AnimationPlayDialog *dialog)
 {
-  GtkAction * action;
+  const gchar *active_text;
+  gdouble      fps;
+  gchar       *text;
 
-  settings.duration_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+  active_text = gtk_entry_get_text (combo_entry);
+  /* Try a text conversion, locale-aware. */
+  fps         = g_strtod (active_text, NULL);
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-reset");
-  gtk_action_set_sensitive (action, settings.duration_index != 3);
+  if (fps >= MAX_FRAMERATE)
+    {
+      /* Let's avoid huge frame rates. */
+      fps = MAX_FRAMERATE;
+    }
+  else if (fps <= 0)
+    {
+      /* Null or negative framerates are impossible. */
+      fps = 0.1;
+    }
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-down");
-  gtk_action_set_sensitive (action, settings.duration_index < 6);
+  dialog->settings.frame_rate = fps;
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/anim-play-popup/speed-up");
-  gtk_action_set_sensitive (action, settings.duration_index > 0);
+  /* Now let's format the text cleanly: "%g fps". */
+  text = g_strdup_printf  (_("%g fps"), dialog->settings.frame_rate);
+  gtk_entry_set_text (combo_entry, text);
+  g_free (text);
 }
 
 static void
-update_scale (gdouble scale)
+fpscombo_changed (GtkWidget           *combo,
+                  AnimationPlayDialog *dialog)
 {
-  guint expected_drawing_area_width;
-  guint expected_drawing_area_height;
-
-  /* FIXME: scales under 0.5 are broken. See bug 690265. */
-  if (scale <= 0.5)
-    scale = 0.51;
-
-  expected_drawing_area_width = preview_width * scale;
-  expected_drawing_area_height = preview_height * scale;
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
 
-  gtk_widget_set_size_request (drawing_area, expected_drawing_area_width, expected_drawing_area_height);
-  gtk_widget_set_size_request (shape_drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
-  /* I force the shape window to a smaller size if we scale down. */
-  if (detached)
+  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
+  if (index != -1)
     {
-      gint x, y;
-
-      gdk_window_get_origin (gtk_widget_get_window (shape_window), &x, &y);
-      gtk_window_reshow_with_initial_size (GTK_WINDOW (shape_window));
-      gtk_window_move (GTK_WINDOW (shape_window), x, y);
+      dialog->settings.frame_rate = get_fps (index);
     }
 }
 
-/* Update the tool sensitivity for playing, depending on the number of frames. */
+/*
+ * Callback emitted when the user hits the Enter key of the zoom combo.
+ */
 static void
-update_ui (void)
+zoomcombo_activated (GtkEntry            *combo,
+                     AnimationPlayDialog *dialog)
 {
-  GtkAction * action;
-  gboolean animated;
-
-  animated = end_frame - start_frame >= 1;
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/play");
-  gtk_action_set_sensitive (action, animated);
-
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step-back");
-  gtk_action_set_sensitive (action, animated);
+  update_scale (dialog, get_scale (dialog, -1));
+}
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/step");
-  gtk_action_set_sensitive (action, animated);
+/*
+ * Callback emitted when the user selects a zoom in the dropdown,
+ * or edits the text entry.
+ * We don't want to process manual edits because it greedily emits
+ * signals after each character deleted or added.
+ */
+static void
+zoomcombo_changed (GtkWidget           *combo,
+                   AnimationPlayDialog *dialog)
+{
+  gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
 
-  action = gtk_ui_manager_get_action (ui_manager,
-                                      "/ui/anim-play-toolbar/rewind");
-  gtk_action_set_sensitive (action, animated);
+  /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
+  if (index != -1)
+    update_scale (dialog, get_scale (dialog, index));
 }
 
 static void
-startframe_changed (GtkAdjustment *adjustment,
-                    gpointer       user_data)
+startframe_changed (GtkAdjustment       *adjustment,
+                    AnimationPlayDialog *dialog)
 {
-  gdouble value = gtk_adjustment_get_value (adjustment);
+  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
+  gdouble        value = gtk_adjustment_get_value (adjustment);
 
-  if (value < frame_number_min || value > frame_number_max)
+  if (value < dialog->settings.frame_min || value > dialog->settings.frame_max)
     {
-      value = frame_number_min;
-      gtk_adjustment_set_value (adjustment, frame_number_min);
+      value = dialog->settings.frame_min;
+      gtk_adjustment_set_value (adjustment, dialog->settings.frame_min);
     }
 
-  start_frame = (guint) value;
+  dialog->settings.start_frame = (guint) value;
 
   if (gtk_adjustment_get_value (endframe_adjust) < value)
     {
-      gtk_adjustment_set_value (endframe_adjust, frame_number_max);
-      end_frame = (guint) frame_number_max;
+      gtk_adjustment_set_value (endframe_adjust, dialog->settings.frame_max);
+      dialog->settings.end_frame = (guint) dialog->settings.frame_max;
     }
 
-  if (frame_number < start_frame)
+  if (dialog->settings.current_frame < dialog->settings.start_frame)
     {
-      frame_number = start_frame;
-      render_frame (frame_number);
+      dialog->settings.current_frame = dialog->settings.start_frame;
+      render_frame (dialog, FALSE);
     }
 
-  update_ui ();
+  update_ui_sensitivity (dialog);
 }
 
 static void
-endframe_changed (GtkAdjustment *adjustment,
-                  gpointer       user_data)
+endframe_changed (GtkAdjustment       *adjustment,
+                  AnimationPlayDialog *dialog)
 {
-  gdouble value = gtk_adjustment_get_value (adjustment);
+  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
+  gdouble        value = gtk_adjustment_get_value (adjustment);
 
-  if (value < frame_number_min || value > frame_number_max)
+  if (value < dialog->settings.frame_min || value > dialog->settings.frame_max)
     {
-      value = frame_number_max;
-      gtk_adjustment_set_value (adjustment, frame_number_max);
+      value = dialog->settings.frame_max;
+      gtk_adjustment_set_value (adjustment, dialog->settings.frame_max);
     }
 
-  end_frame = (guint) value;
+  dialog->settings.end_frame = (guint) value;
 
   if (gtk_adjustment_get_value (startframe_adjust) > value)
     {
-      gtk_adjustment_set_value (startframe_adjust, frame_number_min);
-      start_frame = (guint) frame_number_min;
+      gtk_adjustment_set_value (startframe_adjust, dialog->settings.frame_min);
+      dialog->settings.start_frame = (guint) dialog->settings.frame_min;
     }
 
-  if (frame_number > end_frame)
+  if (dialog->settings.current_frame > dialog->settings.end_frame)
     {
-      frame_number = end_frame;
-      render_frame (frame_number);
+      dialog->settings.current_frame = dialog->settings.end_frame;
+      render_frame (dialog, FALSE);
     }
 
-  update_ui ();
+  update_ui_sensitivity (dialog);
 }
 
 /*
- * Callback emitted when the user toggle the quality checkbox.
+ * Callback emitted when the user toggle the proxy checkbox.
  */
 static void
-quality_checkbox_toggled (GtkToggleButton *button,
-                          gpointer         data)
+proxy_checkbox_expanded (GtkExpander         *expander,
+                         GParamSpec          *param_spec,
+                         AnimationPlayDialog *dialog)
 {
-  gint previous_preview_width = preview_width;
+  gint previous_preview_width  = dialog->preview_width;
+  gint previous_preview_height = dialog->preview_height;
+  gint image_width  = gimp_image_width (dialog->image_id);
+  gint image_height = gimp_image_height (dialog->image_id);
 
-  if (gtk_toggle_button_get_active (button))
+  if (gtk_expander_get_expanded (expander))
     {
-      GdkScreen         *screen;
-      guint              screen_width, screen_height;
-
-      screen = gtk_widget_get_screen (window);
-      screen_height = gdk_screen_get_height (screen);
-      screen_width = gdk_screen_get_width (screen);
-
-      /* Get some maximum value for both dimension. */
-      preview_width = MIN (screen_width / 2, width);
-      preview_height = MIN (screen_height / 2, height);
-      /* Get the correct ratio. */
-      preview_width = MIN (preview_width, preview_height * width / height);
-      preview_height = preview_width * height / width;
+      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
+                            dialog);
     }
-  else
+  else if (dialog->preview_width  != image_width ||
+           dialog->preview_height != image_height)
     {
-      preview_width = width;
-      preview_height = height;
-    }
+      dialog->preview_width  = image_width;
+      dialog->preview_height = image_height;
 
-  if (initialized_once && previous_preview_width != preview_width)
-    {
-      gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (zoomcombo));
+      if (previous_preview_width  != dialog->preview_width ||
+          previous_preview_height != dialog->preview_height)
+        {
+          gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->zoomcombo));
+
+          update_scale (dialog, get_scale (dialog, index));
 
-      init_frames ();
-      update_scale (get_scale (index));
+          init_frames (dialog);
+          render_frame (dialog, TRUE);
+        }
     }
 }
 
 /*
- * Callback emitted when the user hits the Enter key of the zoom combo.
+ * Callback emitted when the user hits the Enter key of the fps combo.
  */
 static void
-zoomcombo_activated (GtkEntry *combo, gpointer data)
+proxycombo_activated (GtkEntry            *combo_entry,
+                      AnimationPlayDialog *dialog)
 {
-  update_scale (get_scale (-1));
+  const gchar *active_text;
+  gchar       *text;
+  gint         image_width;
+  gint         image_height;
+  gdouble      ratio;
+
+  active_text = gtk_entry_get_text (combo_entry);
+  /* Try a text conversion, locale-aware. */
+  ratio       = g_strtod (active_text, NULL);
+
+  ratio = ratio / 100.0;
+  if (ratio >= 1.0)
+    {
+      ratio = 1.0;
+    }
+  else if (ratio <= 0)
+    {
+      /* Null or negative ratio are impossible. */
+      ratio = 0.1;
+    }
+
+  image_width  = gimp_image_width (dialog->image_id);
+  image_height = gimp_image_height (dialog->image_id);
+
+  /* Now let's format the text cleanly: "%.1f %%". */
+  text = g_strdup_printf  (_("%.1f %%"), ratio * 100.0);
+  gtk_entry_set_text (combo_entry, text);
+  g_free (text);
+
+  /* Finally set the preview size, unless they were already good. */
+  if (dialog->preview_width  != image_width * ratio ||
+      dialog->preview_height != image_height * ratio)
+    {
+      gboolean     was_playing;
+
+      was_playing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->play_actions, "play")));
+
+      dialog->preview_width  = image_width * ratio;
+      dialog->preview_height = image_height * ratio;
+      update_scale (dialog, get_scale (dialog, -1));
+
+      init_frames (dialog);
+      render_frame (dialog, TRUE);
+
+      if (was_playing)
+        {
+          /* Initializing frames stopped the playing. I restart it. */
+          gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
+        }
+    }
 }
 
-/*
- * Callback emitted when the user selects a zoom in the dropdown,
- * or edits the text entry.
- * We don't want to process manual edits because it greedily emits
- * signals after each character deleted or added.
- */
 static void
-zoomcombo_changed (GtkWidget *combo, gpointer data)
+proxycombo_changed (GtkWidget           *combo,
+                    AnimationPlayDialog *dialog)
 {
   gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
 
   /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
   if (index != -1)
-    update_scale (get_scale (index));
+    {
+      proxycombo_activated (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->proxycombo))),
+                            dialog);
+    }
 }
 
+/**
+ * Set num_frames, which is not necessarily the number of layers, in
+ * particular with the DISPOSE_TAGS disposal.
+ * Will set 0 if no layer has frame tags in tags mode, or if there is
+ * no layer in combine/replace.
+ */
 static void
-fpscombo_changed (GtkWidget *combo, gpointer data)
+init_frame_numbers (AnimationPlayDialog *dialog,
+                    gint                *layers,
+                    gint                 num_layers)
 {
-  settings.default_frame_duration = 1000 / get_fps (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)));
+  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
+  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
+  gboolean       start_from_first = FALSE;
+  gboolean       end_at_last      = FALSE;
+
+  /* As a special exception, when we start or end at first or last frames,
+   * we want to stay that way, even with different first and last frames. */
+  if (dialog->settings.start_frame == dialog->settings.frame_min)
+    start_from_first = TRUE;
+  if (dialog->settings.end_frame == dialog->settings.frame_max)
+    end_at_last = TRUE;
+
+  if (dialog->settings.frame_disposal != DISPOSE_TAGS)
+    {
+      dialog->settings.num_frames = num_layers;
+      dialog->settings.frame_min = 1;
+      dialog->settings.frame_max = dialog->settings.frame_min + dialog->settings.num_frames - 1;
+    }
+  else
+    {
+      gint i;
+      gint max = G_MININT;
+      gint min = G_MAXINT;
+
+      for (i = 0; i < num_layers; i++)
+        rec_set_total_frames (layers[i], &min, &max);
+
+      dialog->settings.num_frames = (max > min)? max + 1 - min : 0;
+      dialog->settings.frame_min = min;
+      dialog->settings.frame_max = max;
+    }
+
+  /* Keep the same frame number, unless it is now invalid. */
+  if (dialog->settings.current_frame > dialog->settings.end_frame ||
+      dialog->settings.current_frame < dialog->settings.start_frame)
+    {
+      dialog->settings.current_frame = dialog->settings.start_frame;
+    }
+
+  /* Update frame counting UI widgets. */
+  if (startframe_adjust)
+    {
+      dialog->settings.start_frame = gtk_adjustment_get_value (startframe_adjust);
+      if (start_from_first                        ||
+          dialog->settings.start_frame < dialog->settings.frame_min ||
+          dialog->settings.start_frame > dialog->settings.frame_max)
+        {
+          dialog->settings.start_frame = dialog->settings.frame_min;
+        }
+    }
+  else
+    {
+      dialog->settings.start_frame = dialog->settings.frame_min;
+    }
+
+  if (endframe_adjust)
+    {
+      dialog->settings.end_frame = gtk_adjustment_get_value (endframe_adjust);
+      if (end_at_last                           ||
+          dialog->settings.end_frame < dialog->settings.frame_min ||
+          dialog->settings.end_frame > dialog->settings.frame_max ||
+          dialog->settings.end_frame < dialog->settings.start_frame)
+        {
+          dialog->settings.end_frame = dialog->settings.frame_max;
+        }
+    }
+  else
+    {
+      dialog->settings.end_frame = dialog->settings.frame_max;
+    }
+}
+
+static gboolean
+is_disposal_tag (const gchar *str,
+                 DisposeType *disposal,
+                 gint        *taglength)
+{
+  if (strlen (str) != 9)
+    return FALSE;
+
+  if (strncmp (str, "(combine)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_COMBINE;
+      return TRUE;
+    }
+  else if (strncmp (str, "(replace)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_REPLACE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static DisposeType
+parse_disposal_tag (AnimationPlayDialog *dialog,
+                    const gchar         *str)
+{
+  gint i;
+  gint length = strlen (str);
+
+  for (i = 0; i < length; i++)
+    {
+      DisposeType rtn;
+      gint        dummy;
+
+      if (is_disposal_tag (&str[i], &rtn, &dummy))
+        return rtn;
+    }
+
+  return dialog->settings.frame_disposal;
+}
+
+/* Util. */
+
+/* get_fps:
+ * Frame rate proposed as default.
+ * These are the most common framerates.
+ */
+static gdouble
+get_fps (gint       index)
+{
+  gdouble fps;
+
+  switch (index)
+    {
+    case 0:
+      fps = 12.0;
+      break;
+    case 1:
+      fps = 24.0;
+      break;
+    case 2:
+      fps = 25.0;
+      break;
+    case 3:
+      fps = 30.0;
+      break;
+    case 4:
+      fps = 48.0;
+      break;
+    default:
+      fps = 24.0;
+      break;
+    }
+
+  return fps;
+}
+
+static gdouble
+get_proxy (AnimationPlayDialog *dialog,
+           gint                 index)
+{
+  switch (index)
+    {
+    case 0:
+      return 0.25;
+    case 1:
+      return 0.5;
+    case 2:
+      return 0.75;
+    default:
+      {
+        /* likely -1 returned if there is no active item from the list.
+         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
+        gchar   *active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dialog->proxycombo));
+        gdouble  proxy       = g_strtod (active_text, NULL);
+
+        /* Proxy over 100% are meaningless. */
+        if (proxy > 100.0)
+          {
+            proxy = 100.0;
+          }
+        else if (proxy <= 0.0)
+          {
+            proxy = 1.0;
+          }
+        g_free (active_text);
+        return proxy / 100.0;
+      }
+    }
+}
+
+static gdouble
+get_scale (AnimationPlayDialog *dialog,
+           gint                 index)
+{
+  switch (index)
+    {
+    case 0:
+      return 0.51;
+    case 1:
+      return 1.0;
+    case 2:
+      return 1.25;
+    case 3:
+      return 1.5;
+    case 4:
+      return 2.0;
+    default:
+      {
+        /* likely -1 returned if there is no active item from the list.
+         * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
+        gchar   *active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dialog->zoomcombo));
+        gdouble  zoom        = g_strtod (active_text, NULL);
+
+        /* Negative scales are inconsistent. And we want to avoid huge scaling. */
+        if (zoom > 300.0)
+          zoom = 300.0;
+        else if (zoom <= 50.0)
+          /* FIXME: scales under 0.5 are broken. See bug 690265. */
+          zoom = 50.1;
+        g_free (active_text);
+        return zoom / 100.0;
+      }
+    }
 }
 
 static void
-update_combobox (void)
+save_settings (AnimationPlayDialog *dialog)
+{
+  GimpParasite *old_parasite;
+  gboolean      undo_step_started = FALSE;
+
+  /* First saving the settings globally. */
+  gimp_set_data (PLUG_IN_PROC, &dialog->settings, sizeof (&dialog->settings));
+
+  /* Then as a parasite for the specific image.
+   * If there was already parasites and they were all the same as the
+   * current state, do not resave them.
+   * This prevents setting the image in a dirty state while it stayed
+   * the same. */
+  old_parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-disposal");
+  if (! old_parasite ||
+      *(DisposeType *) gimp_parasite_data (old_parasite) != dialog->settings.frame_disposal)
+    {
+      GimpParasite *parasite;
+
+      gimp_image_undo_group_start (dialog->image_id);
+      undo_step_started = TRUE;
+
+      parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-disposal",
+                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
+                                    sizeof (dialog->settings.frame_disposal),
+                                    &dialog->settings.frame_disposal);
+      gimp_image_attach_parasite (dialog->image_id, parasite);
+      gimp_parasite_free (parasite);
+    }
+  gimp_parasite_free (old_parasite);
+
+  old_parasite = gimp_image_get_parasite (dialog->image_id, PLUG_IN_PROC "/frame-rate");
+  if (! old_parasite ||
+      *(gdouble *) gimp_parasite_data (old_parasite) != dialog->settings.frame_rate)
+    {
+      GimpParasite *parasite;
+      if (! undo_step_started)
+        {
+          gimp_image_undo_group_start (dialog->image_id);
+          undo_step_started = TRUE;
+        }
+      parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-rate",
+                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
+                                    sizeof (dialog->settings.frame_rate),
+                                    &dialog->settings.frame_rate);
+      gimp_image_attach_parasite (dialog->image_id, parasite);
+      gimp_parasite_free (parasite);
+    }
+  gimp_parasite_free (old_parasite);
+
+  if (undo_step_started)
+    {
+      gimp_image_undo_group_end (dialog->image_id);
+    }
+}
+
+static void
+clean_exit (AnimationPlayDialog *dialog)
 {
-  gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index);
+  save_settings (dialog);
+
+  /* Frames are associated to an unused GimpImage.
+   * Initializing frames with a NULL image allows to free this image,
+   * otherwise we would leak GEGL buffers. */
+  dialog->image_id = 0;
+  init_frames (dialog);
+
+  if (dialog->shape_window)
+    gtk_widget_destroy (GTK_WIDGET (dialog->shape_window));
+
+  g_signal_handlers_disconnect_by_func (dialog->window,
+                                        G_CALLBACK (window_destroy),
+                                        dialog);
+  if (dialog->window)
+    gtk_widget_destroy (GTK_WIDGET (dialog->window));
+
+  if (dialog->frames)
+    g_free (dialog->frames);
+
+  g_free (dialog);
+
+  gegl_exit ();
+  gtk_main_quit ();
+  gimp_quit ();
 }
 
 /* tag util. */
@@ -2587,7 +3251,9 @@ parse_ms_tag (const gchar *str)
  * and update the min/max progressively.
  **/
 static void
-rec_set_total_frames (const gint32 layer, gint *min, gint *max)
+rec_set_total_frames (const gint32  layer,
+                      gint         *min,
+                      gint         *max)
 {
   gchar        *layer_name;
   gchar        *nospace_name;
@@ -2606,6 +3272,7 @@ rec_set_total_frames (const gint32 layer, gint *min, gint *max)
       return;
     }
 
+  /* TODO: use parasites instead! */
   layer_name = gimp_item_get_name (layer);
   nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
 
@@ -2644,102 +3311,3 @@ rec_set_total_frames (const gint32 layer, gint *min, gint *max)
   g_match_info_free (match_info);
 }
 
-/**
- * Set the `total_frames` for the DISPOSE_TAGS disposal.
- * Will set 0 if no layer has frame tags in tags mode, or if there is no layer in combine/replace.
- */
-static void
-set_total_frames (void)
-{
-  gboolean      start_from_first    = FALSE;
-  gboolean      end_at_last         = FALSE;
-
-  /* As a special exception, when we start or end at first or last frames,
-   * we want to stay that way, even with different first and last frames. */
-  if (start_frame == frame_number_min)
-    start_from_first = TRUE;
-  if (end_frame == frame_number_max)
-    end_at_last = TRUE;
-
-  if (settings.default_frame_disposal != DISPOSE_TAGS)
-    {
-      total_frames = total_layers;
-      frame_number_min = 1;
-      frame_number_max = frame_number_min + total_frames - 1;
-    }
-  else
-    {
-      gint i;
-      gint max = G_MININT;
-      gint min = G_MAXINT;
-
-      for (i = 0; i < total_layers; i++)
-        rec_set_total_frames (layers[i], &min, &max);
-
-      total_frames = (max > min)? max + 1 - min : 0;
-      frame_number_min = min;
-      frame_number_max = max;
-    }
-
-  /* Update frame counting UI widgets. */
-  if (startframe_adjust)
-    {
-      start_frame = gtk_adjustment_get_value (startframe_adjust);
-      if (start_from_first || start_frame < frame_number_min || start_frame > frame_number_max)
-        start_frame = frame_number_min;
-    }
-  else
-    start_frame = frame_number_min;
-
-  if (endframe_adjust)
-    {
-      end_frame = gtk_adjustment_get_value (endframe_adjust);
-      if (end_at_last || end_frame < frame_number_min || end_frame > frame_number_max || end_frame < 
start_frame)
-        end_frame = frame_number_max;
-    }
-  else
-    end_frame = frame_number_max;
-}
-
-static gboolean
-is_disposal_tag (const gchar *str,
-                 DisposeType *disposal,
-                 gint        *taglength)
-{
-  if (strlen (str) != 9)
-    return FALSE;
-
-  if (strncmp (str, "(combine)", 9) == 0)
-    {
-      *taglength = 9;
-      *disposal = DISPOSE_COMBINE;
-      return TRUE;
-    }
-  else if (strncmp (str, "(replace)", 9) == 0)
-    {
-      *taglength = 9;
-      *disposal = DISPOSE_REPLACE;
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static DisposeType
-parse_disposal_tag (const gchar *str)
-{
-  gint i;
-  gint length = strlen (str);
-
-  for (i = 0; i < length; i++)
-    {
-      DisposeType rtn;
-      gint        dummy;
-
-      if (is_disposal_tag (&str[i], &rtn, &dummy))
-        return rtn;
-    }
-
-  return settings.default_frame_disposal;
-}
-


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