[gimp/wip/animation: 11/145] plug-ins: reorder all functions in animation-play...



commit 465afd5d8f454619315febdd2b3ef34c1924f36f
Author: Jehan <jehan girinstud io>
Date:   Mon Sep 1 00:57:37 2014 +0200

    plug-ins: reorder all functions in animation-play...
    
    and make sure they all have a declaration.

 plug-ins/common/animation-play.c | 2815 +++++++++++++++++++-------------------
 1 files changed, 1425 insertions(+), 1390 deletions(-)
---
diff --git a/plug-ins/common/animation-play.c b/plug-ins/common/animation-play.c
index a4c5fc4..664b8ab 100644
--- a/plug-ins/common/animation-play.c
+++ b/plug-ins/common/animation-play.c
@@ -160,25 +160,62 @@ static void        init_frame_numbers        (AnimationPlayDialog  *dialog,
                                               gint                 *layers,
                                               gint                  num_layers);
 static gboolean    init_frames               (AnimationPlayDialog  *dialog);
+static void        rec_init_frames           (AnimationPlayDialog  *dialog,
+                                              gint32                frames_image_id,
+                                              gint32                layer,
+                                              GList                *previous_frames,
+                                              gint                  image_width,
+                                              gint                  image_height);
 static void        init_ui                   (AnimationPlayDialog  *dialog,
                                               gchar                *imagename);
+static GtkUIManager * ui_manager_new         (AnimationPlayDialog  *dialog);
 
 static void        refresh_dialog            (AnimationPlayDialog  *dialog);
 static void        update_ui_sensitivity     (AnimationPlayDialog  *dialog);
-static void        update_scale              (AnimationPlayDialog  *dialog,
-                                              gdouble               scale);
 
 /* All callbacks. */
+static void        close_callback            (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        help_callback             (GtkAction            *action,
+                                              AnimationPlayDialog  *dialog);
+static void        window_destroy            (GtkWidget            *widget,
+                                              AnimationPlayDialog  *dialog);
 static gboolean    popup_menu                (GtkWidget            *widget,
                                               AnimationPlayDialog  *dialog);
-static gboolean    button_press              (GtkWidget            *widget,
+
+static gboolean    adjustment_pressed        (GtkWidget            *widget,
+                                              GdkEventButton       *event,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    da_button_press           (GtkWidget            *widget,
                                               GdkEventButton       *event,
                                               AnimationPlayDialog  *dialog);
 static void        da_size_callback          (GtkWidget            *widget,
                                               GtkAllocation        *allocation,
                                               AnimationPlayDialog  *dialog);
+static gboolean    shape_pressed             (GtkWidget            *widget,
+                                              GdkEventButton       *event,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    shape_released            (GtkWidget            *widget);
+static gboolean    shape_motion              (GtkWidget            *widget,
+                                              GdkEventMotion       *event);
+static gboolean    repaint_da                (GtkWidget            *darea,
+                                              GdkEventExpose       *event,
+                                              AnimationPlayDialog  *dialog);
 
-static void        window_destroy            (GtkWidget            *widget,
+static gboolean    progress_button           (GtkWidget            *widget,
+                                              GdkEventButton       *event,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    progress_entered          (GtkWidget            *widget,
+                                              GdkEventCrossing     *event,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    progress_motion           (GtkWidget            *widget,
+                                              GdkEventMotion       *event,
+                                              AnimationPlayDialog  *dialog);
+static gboolean    progress_left             (GtkWidget            *widget,
+                                              GdkEventCrossing     *event,
+                                              AnimationPlayDialog  *dialog);
+
+static void        detach_callback           (GtkToggleAction      *action,
                                               AnimationPlayDialog  *dialog);
 static void        play_callback             (GtkToggleAction      *action,
                                               AnimationPlayDialog  *dialog);
@@ -222,45 +259,51 @@ static void        proxycombo_activated      (GtkEntry             *combo_entry,
 static void        proxycombo_changed        (GtkWidget            *combo,
                                               AnimationPlayDialog  *dialog);
 
-static gboolean    repaint_da                (GtkWidget            *darea,
-                                              GdkEventExpose       *event,
-                                              AnimationPlayDialog  *dialog);
-
+/* Rendering/Playing Functions */
 static void        render_frame              (AnimationPlayDialog  *dialog,
                                               gboolean              force);
+static void        total_alpha_preview       (guchar               *drawing_data,
+                                              guint                 drawing_width,
+                                              guint                 drawing_height);
+static void        reshape_from_bitmap       (AnimationPlayDialog   *dialog,
+                                              const gchar           *bitmap);
 static void        show_playing_progress     (AnimationPlayDialog  *dialog);
 static void        show_loading_progress     (AnimationPlayDialog  *dialog,
                                               gint                  layer_nb,
                                               gint                  num_layers);
+static void        do_back_step              (AnimationPlayDialog  *dialog);
+static void        do_step                   (AnimationPlayDialog  *dialog);
+static void        set_timer                 (guint                 new_timer);
+static gboolean    advance_frame_callback    (AnimationPlayDialog  *dialog);
 static void        show_goto_progress        (guint                 frame_nb,
                                               AnimationPlayDialog  *dialog);
-static void        total_alpha_preview       (guchar               *drawing_data,
-                                              guint                 drawing_width,
-                                              guint                 drawing_height);
 
 /* Utils */
+static void        connect_accelerators      (GtkUIManager         *ui_manager,
+                                              GtkActionGroup       *group);
 static gdouble     get_fps                   (gint                  index);
 static gdouble     get_proxy                 (AnimationPlayDialog  *dialog,
                                               gint                  index);
+static void        update_scale              (AnimationPlayDialog  *dialog,
+                                              gdouble               scale);
 static gdouble     get_scale                 (AnimationPlayDialog  *dialog,
                                               gint                  index);
-static void        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 DisposeType parse_disposal_tag        (AnimationPlayDialog  *dialog,
-                                              const gchar          *str);
 static gboolean    is_disposal_tag           (const gchar          *str,
                                               DisposeType          *disposal,
                                               gint                 *taglength);
+static DisposeType parse_disposal_tag        (AnimationPlayDialog  *dialog,
+                                              const gchar          *str);
 static gboolean    is_ms_tag                 (const gchar          *str,
                                               gint                 *duration,
                                               gint                 *taglength);
+static gint        parse_ms_tag              (const gchar          *str);
+static void        rec_set_total_frames      (const gint32          layer,
+                                              gint                 *min,
+                                              gint                 *max);
 
 const GimpPlugInInfo PLUG_IN_INFO =
 {
@@ -391,30 +434,30 @@ run (const gchar      *name,
        }
 
      /* 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->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);
@@ -433,718 +476,555 @@ run (const gchar      *name,
 }
 
 static void
-reshape_from_bitmap (AnimationPlayDialog *dialog,
-                     const gchar         *bitmap)
+initialize (AnimationPlayDialog *dialog)
 {
-  static gchar *prev_bitmap = NULL;
-  static guint  prev_width = -1;
-  static guint  prev_height = -1;
-
-  if ((! prev_bitmap)                                  ||
-      prev_width != dialog->shape_drawing_area_width   ||
-      prev_height != dialog->shape_drawing_area_height ||
-      (memcmp (prev_bitmap, bitmap,
-               (dialog->shape_drawing_area_width *
-                dialog->shape_drawing_area_height) / 8 +
-               dialog->shape_drawing_area_height)))
+  /* Freeing existing data after a refresh. */
+  /* Catch the case when the user has closed the image in the meantime. */
+  if (! gimp_image_is_valid (dialog->image_id))
     {
-      GdkBitmap *shape_mask;
-
-      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (dialog->shape_window),
-                                                bitmap,
-                                                dialog->shape_drawing_area_width, 
dialog->shape_drawing_area_height);
-      gtk_widget_shape_combine_mask (dialog->shape_window, shape_mask, 0, 0);
-      g_object_unref (shape_mask);
-
-      if (!prev_bitmap || prev_width != dialog->shape_drawing_area_width || prev_height != 
dialog->shape_drawing_area_height)
-        {
-          g_free(prev_bitmap);
-          prev_bitmap = g_malloc ((dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 8 
+ dialog->shape_drawing_area_height);
-          prev_width = dialog->shape_drawing_area_width;
-          prev_height = dialog->shape_drawing_area_height;
-        }
-
-      memcpy (prev_bitmap, bitmap, (dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 
8 + dialog->shape_drawing_area_height);
+      gimp_message (_("Invalid image. Did you close it?"));
+      clean_exit (dialog);
+      return;
     }
-}
 
-/***************** CALLBACKS ********************/
-
-static gboolean
-popup_menu (GtkWidget           *widget,
-            AnimationPlayDialog *dialog)
-{
-  GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
-
-  gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
-  gtk_menu_popup (GTK_MENU (menu),
-                  NULL, NULL, NULL, NULL,
-                  0, gtk_get_current_event_time ());
-
-  return TRUE;
-}
-
-static gboolean
-button_press (GtkWidget           *widget,
-              GdkEventButton      *event,
-              AnimationPlayDialog *dialog)
-{
-  if (gdk_event_triggers_context_menu ((GdkEvent *) event))
+  if (! dialog->window)
     {
-      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;
+      /* First run. */
+      gchar *name = gimp_image_get_name (dialog->image_id);
+      init_ui (dialog, name);
+      g_free (name);
     }
+  refresh_dialog (dialog);
 
-  return FALSE;
+  /* I want to make sure the progress bar is realized before init_frames()
+   * which may take quite a bit of time. */
+  if (!gtk_widget_get_realized (dialog->progress))
+    gtk_widget_realize (dialog->progress);
+
+  render_frame (dialog, init_frames (dialog));
 }
 
-/*
- * Update the actual drawing area metrics, which may be different from
- * requested, since there is no full control of the WM.
+/**
+ * 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
-da_size_callback (GtkWidget           *drawing_area,
-                  GtkAllocation       *allocation,
-                  AnimationPlayDialog *dialog)
+init_frame_numbers (AnimationPlayDialog *dialog,
+                    gint                *layers,
+                    gint                 num_layers)
 {
-  guchar   **drawing_data;
-
-  if (drawing_area == dialog->shape_drawing_area)
-    {
-      if (allocation->width  == dialog->shape_drawing_area_width &&
-          allocation->height == dialog->shape_drawing_area_height)
-        return;
+  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;
 
-      dialog->shape_drawing_area_width  = allocation->width;
-      dialog->shape_drawing_area_height = allocation->height;
+  /* 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;
 
-      g_free (dialog->shape_drawing_area_data);
-      drawing_data = &dialog->shape_drawing_area_data;
+  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
     {
-      if (allocation->width  == dialog->drawing_area_width &&
-          allocation->height == dialog->drawing_area_height)
-        return;
+      gint i;
+      gint max = G_MININT;
+      gint min = G_MAXINT;
 
-      dialog->drawing_area_width  = allocation->width;
-      dialog->drawing_area_height = allocation->height;
+      for (i = 0; i < num_layers; i++)
+        rec_set_total_frames (layers[i], &min, &max);
 
-      g_free (dialog->drawing_area_data);
-      drawing_data = &dialog->drawing_area_data;
+      dialog->settings.num_frames = (max > min)? max + 1 - min : 0;
+      dialog->settings.frame_min = min;
+      dialog->settings.frame_max = max;
     }
 
-  dialog->settings.scale = MIN ((gdouble) allocation->width / (gdouble) dialog->preview_width,
-                                (gdouble) allocation->height / (gdouble) dialog->preview_height);
-
-  *drawing_data = g_malloc (allocation->width * allocation->height * 3);
+  /* 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;
+    }
 
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))) &&
-      drawing_area == dialog->drawing_area)
+  /* Update frame counting UI widgets. */
+  if (startframe_adjust)
     {
-      /* Set "alpha grid" background. */
-      total_alpha_preview (dialog->drawing_area_data,
-                           allocation->width,
-                           allocation->height);
-      repaint_da (dialog->drawing_area, NULL, dialog);
+      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 
+  else
     {
-      /* Update the zoom information. */
-      GtkEntry *zoomcombo_text_child;
+      dialog->settings.start_frame = dialog->settings.frame_min;
+    }
 
-      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo)));
-      if (zoomcombo_text_child)
+  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)
         {
-          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);
+          dialog->settings.end_frame = dialog->settings.frame_max;
         }
-
-      /* As we re-allocated the drawn data, let's render it again. */
-      if (dialog->settings.current_frame - dialog->settings.frame_min < dialog->settings.num_frames)
-        render_frame (dialog, TRUE);
     }
-}
-
-static gboolean
-shape_pressed (GtkWidget           *widget,
-               GdkEventButton      *event,
-               AnimationPlayDialog *dialog)
-{
-  if (button_press (widget, event, dialog))
-    return TRUE;
-
-  /* ignore double and triple click */
-  if (event->type == GDK_BUTTON_PRESS)
+  else
     {
-      CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset");
-
-      if (!p)
-        return FALSE;
-
-      p->x = (gint) event->x;
-      p->y = (gint) event->y;
-
-      gtk_grab_add (widget);
-      gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
-                        GDK_BUTTON_RELEASE_MASK |
-                        GDK_BUTTON_MOTION_MASK  |
-                        GDK_POINTER_MOTION_HINT_MASK,
-                        NULL, NULL, 0);
-      gdk_window_raise (gtk_widget_get_window (widget));
+      dialog->settings.end_frame = dialog->settings.frame_max;
     }
-
-  return FALSE;
 }
 
+/* Initialize the frames, and return TRUE if render must be forced. */
 static gboolean
-shape_released (GtkWidget *widget)
+init_frames (AnimationPlayDialog *dialog)
 {
-  gtk_grab_remove (widget);
-  gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0);
-  gdk_flush ();
-
-  return FALSE;
-}
+  /* 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;
 
-static gboolean
-adjustment_pressed (GtkWidget           *widget,
-                    GdkEventButton      *event,
-                    AnimationPlayDialog *dialog)
-{
-  gboolean event_processed = FALSE;
+  gint            *layers;
+  gint             num_layers;
+  gint             image_width;
+  gint             image_height;
+  gint32           new_frame, previous_frame, new_layer;
+  gint             duration = 0;
+  DisposeType      disposal = dialog->settings.frame_disposal;
+  gint             frame_spin_size;
 
-  if (event->type == GDK_BUTTON_PRESS &&
-      event->button == 2)
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
     {
-      GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
-      GtkAdjustment *adj = gtk_spin_button_get_adjustment (spin);
-
-      gtk_adjustment_set_value (adj,
-                                (gdouble) dialog->settings.current_frame);
-
-      /* We don't want the middle click to have another usage (in
-       * particular, there is likely no need to copy-paste in these spin
-       * buttons). */
-      event_processed = TRUE;
+      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
     }
 
-  return event_processed;
-}
-
-static gboolean
-progress_button (GtkWidget           *widget,
-                 GdkEventButton      *event,
-                 AnimationPlayDialog *dialog)
-{
-  GtkAdjustment *startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON 
(dialog->startframe_spin));
-  GtkAdjustment *endframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin));
-
-  /* ignore double and triple click */
-  if (event->type == GDK_BUTTON_PRESS)
+  if (dialog->frames_lock)
     {
-      GtkAllocation  allocation;
-      guint          goto_frame;
-
-      gtk_widget_get_allocation (widget, &allocation);
-
-      goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
-
-      if (goto_frame < dialog->settings.start_frame)
-        gtk_adjustment_set_value (startframe_adjust, (gdouble) goto_frame);
-
-      if (goto_frame > dialog->settings.end_frame)
-        gtk_adjustment_set_value (endframe_adjust, (gdouble) goto_frame);
-
-      if (goto_frame >= dialog->settings.frame_min && goto_frame < dialog->settings.frame_min + 
dialog->settings.num_frames)
-        {
-          dialog->settings.current_frame = goto_frame;
-          render_frame (dialog, FALSE);
-        }
+      return FALSE;
     }
+  dialog->frames_lock = TRUE;
 
-  return FALSE;
-}
-
-static gboolean
-progress_entered (GtkWidget           *widget,
-                  GdkEventCrossing    *event,
-                  AnimationPlayDialog *dialog)
-{
-  GtkAllocation  allocation;
-  guint          goto_frame;
-
-  gtk_widget_get_allocation (widget, &allocation);
-
-  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
-
-  show_goto_progress (goto_frame, dialog);
-
-  return FALSE;
-}
-
-static gboolean
-progress_motion (GtkWidget           *widget,
-                 GdkEventMotion      *event,
-                 AnimationPlayDialog *dialog)
-{
-  GtkAllocation  allocation;
-  guint          goto_frame;
-
-  gtk_widget_get_allocation (widget, &allocation);
-
-  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
-
-  show_goto_progress (goto_frame, dialog);
-
-  return FALSE;
-}
-
-static gboolean
-progress_left (GtkWidget           *widget,
-               GdkEventCrossing    *event,
-               AnimationPlayDialog *dialog)
-{
-  show_playing_progress (dialog);
-
-  return FALSE;
-}
+  /* 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);
 
-static gboolean
-shape_motion (GtkWidget      *widget,
-              GdkEventMotion *event)
-{
-  GdkModifierType  mask;
-  gint             xp, yp;
-  GdkWindow       *root_win;
+  /* Cleanup before re-generation. */
+  if (dialog->frames)
+    {
+      GList *idx;
 
-  root_win = gdk_get_default_root_window ();
-  gdk_window_get_pointer (root_win, &xp, &yp, &mask);
+      gimp_image_delete (frames_image_id);
+      frames_image_id = 0;
 
-  /* if a button is still held by the time we process this event... */
-  if (mask & GDK_BUTTON1_MASK)
-    {
-      CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset");
+      /* Freeing previous frames only once. */
+      for (idx = g_list_first (previous_frames); idx != NULL; idx = g_list_next (idx))
+        {
+          Frame* frame = (Frame*) idx->data;
 
-      if (!p)
-        return FALSE;
+          g_list_free (frame->indexes);
+          g_list_free (frame->updated_indexes);
+          g_free (frame);
+        }
+      g_list_free (previous_frames);
+      previous_frames = NULL;
 
-      gtk_window_move (GTK_WINDOW (widget), xp  - p->x, yp  - p->y);
+      g_free (dialog->frames);
+      dialog->frames = NULL;
     }
-  else /* the user has released all buttons */
+  if (! gimp_image_is_valid (dialog->image_id))
     {
-      shape_released (widget);
+      /* This is not necessarily an error. We may have simply wanted
+       * to clean up our GEGL buffers. */
+      return FALSE;
     }
 
-  return FALSE;
-}
-
-static gboolean
-repaint_da (GtkWidget           *darea,
-            GdkEventExpose      *event,
-            AnimationPlayDialog *dialog)
-{
-  GtkStyle *style = gtk_widget_get_style (darea);
-  gint      da_width;
-  gint      da_height;
-  guchar   *da_data;
-
-  if (darea == dialog->drawing_area)
+  layers = gimp_image_get_layers (dialog->image_id, &num_layers);
+  init_frame_numbers (dialog, layers, num_layers);
+  if (dialog->settings.num_frames <= 0)
     {
-      da_width  = dialog->drawing_area_width;
-      da_height = dialog->drawing_area_height;
-      da_data   = dialog->drawing_area_data;
+      update_ui_sensitivity (dialog);
+      dialog->frames_lock = FALSE;
+      g_free (layers);
+      return FALSE;
     }
-  else
+
+  dialog->frames = g_try_malloc0_n (dialog->settings.num_frames, sizeof (Frame*));
+  if (! dialog->frames)
     {
-      da_width  = dialog->shape_drawing_area_width;
-      da_height = dialog->shape_drawing_area_height;
-      da_data   = dialog->shape_drawing_area_data;
+      dialog->frames_lock = FALSE;
+      gimp_message (_("Memory could not be allocated to the frame container."));
+      g_free (layers);
+      clean_exit (dialog);
+      return FALSE;
     }
 
-  gdk_draw_rgb_image (gtk_widget_get_window (darea),
-                      style->white_gc,
-                      (gint) ((da_width - dialog->settings.scale * dialog->preview_width) / 2),
-                      (gint) ((da_height - dialog->settings.scale * dialog->preview_height) / 2),
-                      da_width, da_height,
-                      (dialog->settings.num_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
-                      da_data, da_width * 3);
-
-  return TRUE;
-}
-
-static void
-close_callback (GtkAction           *action,
-                AnimationPlayDialog *dialog)
-{
-  clean_exit (dialog);
-}
-
-static void
-help_callback (GtkAction           *action,
-               AnimationPlayDialog *dialog)
-{
-  gimp_standard_help_func (PLUG_IN_PROC, dialog->window);
-}
+  image_width  = gimp_image_width (dialog->image_id);
+  image_height = gimp_image_height (dialog->image_id);
 
+  /* We only use RGB images for display because indexed images would somehow
+     render terrible colors. Layers from other types will be automatically
+     converted. */
+  frames_image_id = gimp_image_new (dialog->preview_width, dialog->preview_height, GIMP_RGB);
 
-static void
-detach_callback (GtkToggleAction     *action,
-                 AnimationPlayDialog *dialog)
-{
-  gboolean detached = gtk_toggle_action_get_active (action);
+  /* Save processing time and memory by not saving history and merged frames. */
+  gimp_image_undo_disable (frames_image_id);
 
-  if (detached)
+  if (disposal == DISPOSE_TAGS)
     {
-      gint x, y;
-
-      gtk_window_set_screen (GTK_WINDOW (dialog->shape_window),
-                             gtk_widget_get_screen (dialog->drawing_area));
-
-      gtk_widget_show (dialog->shape_window);
+      gint        i;
 
-      if (! gtk_widget_get_realized (dialog->drawing_area))
-        {
-          gtk_widget_realize (dialog->drawing_area);
-        }
-      if (! gtk_widget_get_realized (dialog->shape_drawing_area))
+      for (i = 0; i < num_layers; i++)
         {
-          gtk_widget_realize (dialog->shape_drawing_area);
+          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);
         }
 
-      gdk_window_get_origin (gtk_widget_get_window (dialog->drawing_area), &x, &y);
-
-      gtk_window_move (GTK_WINDOW (dialog->shape_window), x + 6, y + 6);
-
-      gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_drawing_area), NULL, TRUE);
+      for (i = 0; i < dialog->settings.num_frames; i++)
+        {
+          /* If for some reason a frame is absent, use the previous one.
+           * We are ensured that there is at least a "first" frame for this. */
+          if (! dialog->frames[i])
+            {
+              dialog->frames[i] = dialog->frames[i - 1];
+              dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
+            }
 
-      /* Set "alpha grid" background. */
-      total_alpha_preview (dialog->drawing_area_data,
-                           dialog->drawing_area_width,
-                           dialog->drawing_area_height);
-      repaint_da (dialog->drawing_area, NULL, dialog);
+          /* A zero duration only means we use the global duration, whatever it is at the time. */
+          dialog->frames[i]->duration = 0;
+        }
     }
   else
     {
-      gtk_widget_hide (dialog->shape_window);
-    }
-
-  render_frame (dialog, TRUE);
-}
-
-static void
-connect_accelerators (GtkUIManager   *ui_manager,
-                      GtkActionGroup *group)
-{
-  GList          *action_list;
-  GList          *iter;
+      gint   layer_offx;
+      gint   layer_offy;
+      gchar *layer_name;
+      gint   i;
 
-  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);
+      for (i = 0; i < dialog->settings.num_frames; i++)
+        {
+          show_loading_progress (dialog, i, num_layers);
 
-      gtk_action_set_accel_group (action,
-                                  gtk_ui_manager_get_accel_group (ui_manager));
-      gtk_action_connect_accelerator (action);
+          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;
 
-      iter = iter->next;
-    }
-  g_list_free (action_list);
-}
+          previous_frames = g_list_append (previous_frames, dialog->frames[i]);
 
+          layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
+          if (layer_name)
+            {
+              duration = parse_ms_tag (layer_name);
+              disposal = parse_disposal_tag (dialog, layer_name);
+              g_free (layer_name);
+            }
 
-static GtkUIManager *
-ui_manager_new (AnimationPlayDialog *dialog)
-{
-  static GtkActionEntry play_entries[] =
-  {
-    { "step-back", "media-skip-backward",
-      N_("Step _back"), "d", N_("Step back to previous frame"),
-      G_CALLBACK (step_back_callback) },
+          if (i > 0 && disposal != DISPOSE_REPLACE)
+            {
+              previous_frame = gimp_layer_copy (dialog->frames[i - 1]->drawable_id);
 
-    { "step", "media-skip-forward",
-      N_("_Step"), "f", N_("Step to next frame"),
-      G_CALLBACK (step_callback) },
+              gimp_image_insert_layer (frames_image_id, previous_frame, 0, 0);
+              gimp_item_set_visible (previous_frame, TRUE);
+            }
 
-    { "rewind", "media-seek-backward",
-      NULL, "s", N_("Rewind the animation"),
-      G_CALLBACK (rewind_callback) },
-  };
+          new_layer = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)], frames_image_id);
 
-  static GtkToggleActionEntry play_toggle_entries[] =
-  {
-    { "play", "media-playback-start",
-      NULL, "space", N_("Start playback"),
-      G_CALLBACK (play_callback), FALSE },
-  };
+          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
+          gimp_item_set_visible (new_layer, TRUE);
+          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_width) / image_width,
+                            (gimp_drawable_height (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_height) / image_height, FALSE);
+          gimp_drawable_offsets (layers[num_layers - (i + 1)], &layer_offx, &layer_offy);
+          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / image_width,
+                                  (layer_offy * (gint) dialog->preview_height) / image_height);
+          gimp_layer_resize_to_image_size (new_layer);
 
-  static GtkActionEntry settings_entries[] =
-  {
-    /* Refresh is not really a settings, but it makes sense to be grouped
-     * as such, because we want to be able to refresh and set things in the
-     * same moments. */
-    { "refresh", GIMP_ICON_VIEW_REFRESH,
-      N_("Refresh"), "<control>R", N_("Reload the image"),
-      G_CALLBACK (refresh_callback) },
+          if (gimp_item_is_group (new_layer))
+            {
+              gint    num_children;
+              gint32 *children;
+              gint    j;
 
-    {
-      "speed-up", NULL,
-      N_("Faster"), "bracketright", N_("Increase the speed of the animation"),
-      G_CALLBACK (speed_up_callback)
-    },
-    {
-      "speed-down", NULL,
-      N_("Slower"), "bracketleft", N_("Decrease the speed of the animation"),
-      G_CALLBACK (speed_down_callback)
-    },
-  };
+              /* I want to make all layers in the group visible, so that when I'll make
+               * the group visible too at render time, it will display everything in it. */
+              children = gimp_item_get_children (new_layer, &num_children);
+              for (j = 0; j < num_children; j++)
+                gimp_item_set_visible (children[j], TRUE);
+            }
 
-  static GtkActionEntry view_entries[] =
-  {
-    { "zoom-in", GTK_STOCK_ZOOM_IN,
-      NULL, "plus", N_("Zoom in"),
-      G_CALLBACK (zoom_in_callback) },
+          new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
+          dialog->frames[i]->drawable_id = new_frame;
+          gimp_item_set_visible (new_frame, FALSE);
 
-    { "zoom-in-accel", GTK_STOCK_ZOOM_IN,
-      NULL, "KP_Add", N_("Zoom in"),
-      G_CALLBACK (zoom_in_callback) },
+          if (duration <= 0)
+            duration = 0;
+          dialog->frames[i]->duration = (guint) duration;
+        }
+    }
 
-    { "zoom-out", GTK_STOCK_ZOOM_OUT,
-      NULL, "minus", N_("Zoom out"),
-      G_CALLBACK (zoom_out_callback) },
+  /* Update the UI. */
+  frame_spin_size = (gint) (log10 (dialog->settings.frame_max - (dialog->settings.frame_max % 10))) + 1;
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), frame_spin_size);
+  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), frame_spin_size);
 
-    { "zoom-out-accel", GTK_STOCK_ZOOM_OUT,
-      NULL, "KP_Subtract", N_("Zoom out"),
-      G_CALLBACK (zoom_out_callback) },
+  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);
 
-    { "zoom-reset", GTK_STOCK_ZOOM_OUT,
-      NULL, "equal", N_("Zoom out"),
-      G_CALLBACK (zoom_reset_callback) },
+  update_ui_sensitivity (dialog);
 
-    { "zoom-reset-accel", GTK_STOCK_ZOOM_OUT,
-      NULL, "KP_Equal", N_("Zoom out"),
-      G_CALLBACK (zoom_reset_callback) },
-  };
+  dialog->frames_lock = FALSE;
+  g_free (layers);
 
-  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 }
-  };
+  return TRUE;
+}
 
-  static GtkActionEntry various_entries[] =
-  {
-    { "help", "help-browser",
-      N_("About the animation plug-in"), "question", NULL,
-      G_CALLBACK (help_callback) },
+/**
+ * Recursive call to generate frames in TAGS mode.
+ **/
+static void
+rec_init_frames (AnimationPlayDialog *dialog,
+                 gint32               frames_image_id,
+                 gint32               layer,
+                 GList               *previous_frames,
+                 gint                 image_width,
+                 gint                 image_height)
+{
+  Frame        *empty_frame = NULL;
+  gchar        *layer_name;
+  gchar        *nospace_name;
+  GMatchInfo   *match_info;
+  gboolean      preview_quality;
+  gint32        new_layer;
+  gint          j, k;
 
-    { "close", "window-close",
-      N_("Quit"), "<control>W", NULL,
-      G_CALLBACK (close_callback)
-    },
+  if (gimp_item_is_group (layer))
     {
-      "quit", "application-quit",
-      N_("Quit"), "<control>Q", NULL,
-      G_CALLBACK (close_callback)
-    },
-  };
+      gint    num_children;
+      gint32 *children;
 
-  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
-  GError         *error      = NULL;
+      children = gimp_item_get_children (layer, &num_children);
+      for (j = 0; j < num_children; j++)
+        rec_init_frames (dialog, frames_image_id,
+                         children[num_children - j - 1],
+                         previous_frames,
+                         image_width, image_height);
 
-  /* 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);
+      return;
+    }
 
-  /* 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);
+  layer_name = gimp_item_get_name (layer);
+  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
+  preview_quality = dialog->preview_width != image_width || dialog->preview_height != image_height;
 
-  /* 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);
+  if (g_regex_match (all_reg, nospace_name, 0, NULL))
+    {
+      for (j = 0; j < dialog->settings.num_frames; j++)
+        {
+          if (! dialog->frames[j])
+            {
+              if (! empty_frame)
+                {
+                  empty_frame = g_new (Frame, 1);
+                  empty_frame->indexes = NULL;
+                  empty_frame->updated_indexes = NULL;
+                  empty_frame->drawable_id = 0;
 
-  /* Remaining various actions. */
-  dialog->various_actions = gtk_action_group_new ("various");
+                  previous_frames = g_list_append (previous_frames, empty_frame);
+                }
 
-  gtk_action_group_set_translation_domain (dialog->various_actions, NULL);
+              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));
 
-  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);
+              dialog->frames[j] = empty_frame;
+            }
+          else if (! g_list_find (dialog->frames[j]->updated_indexes, GINT_TO_POINTER (j)))
+            dialog->frames[j]->updated_indexes = g_list_append (dialog->frames[j]->updated_indexes, 
GINT_TO_POINTER (j));
+        }
+    }
+  else
+    {
+      g_regex_match (layers_reg, nospace_name, 0, &match_info);
 
-  /* 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));
+      while (g_match_info_matches (match_info))
+        {
+          gchar *tag = g_match_info_fetch (match_info, 1);
+          gchar** tokens = g_strsplit(tag, ",", 0);
 
-  /* Finally make some limited popup menu. */
-  gtk_ui_manager_add_ui_from_string (ui_manager,
-                                     "<ui>"
-                                     "  <popup name=\"anim-play-popup\" accelerators=\"true\">"
-                                     "    <menuitem action=\"refresh\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"zoom-in\" />"
-                                     "    <menuitem action=\"zoom-out\" />"
-                                     "    <menuitem action=\"zoom-reset\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"speed-up\" />"
-                                     "    <menuitem action=\"speed-down\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"help\" />"
-                                     "    <separator />"
-                                     "    <menuitem action=\"close\" />"
-                                     "  </popup>"
-                                     "</ui>",
-                                     -1, &error);
+          for (j = 0; tokens[j] != NULL; j++)
+            {
+              gchar* hyphen = g_strrstr(tokens[j], "-");
 
-  if (error)
-    {
-      g_warning ("error parsing ui: %s", error->message);
-      g_clear_error (&error);
-    }
+              if (hyphen != NULL)
+                {
+                  gint32 first = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
+                  gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
 
-  return ui_manager;
-}
+                  for (k = first; k <= second; k++)
+                    {
+                      if (! dialog->frames[k - dialog->settings.frame_min])
+                        {
+                          if (! empty_frame)
+                            {
+                              empty_frame = g_new (Frame, 1);
+                              empty_frame->indexes = NULL;
+                              empty_frame->updated_indexes = NULL;
+                              empty_frame->drawable_id = 0;
 
-static void
-refresh_dialog (AnimationPlayDialog *dialog)
-{
-  GdkScreen *screen;
-  guint      screen_width, screen_height;
-  gint       window_width, window_height;
+                              previous_frames = g_list_append (previous_frames, empty_frame);
+                            }
 
-  /* Update GUI size. */
-  screen = gtk_widget_get_screen (dialog->window);
-  screen_height = gdk_screen_get_height (screen);
-  screen_width = gdk_screen_get_width (screen);
-  gtk_window_get_size (GTK_WINDOW (dialog->window), &window_width, &window_height);
+                          if (! 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));
 
-  /* if the *window* size is bigger than the screen size,
-   * diminish the drawing area by as much, then compute the corresponding scale. */
-  if (window_width + 50 > screen_width || window_height + 50 > screen_height)
-  {
-      gint expected_drawing_area_width = MAX (1, dialog->preview_width - window_width + screen_width);
-      gint expected_drawing_area_height = MAX (1, dialog->preview_height - window_height + screen_height);
+                          dialog->frames[k - dialog->settings.frame_min] = empty_frame;
+                        }
+                      else if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
+                        dialog->frames[k - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[k - dialog->settings.frame_min]->updated_indexes,
+                                                                                       GINT_TO_POINTER (k - 
dialog->settings.frame_min));
+                    }
+                }
+              else
+                {
+                  gint32 num = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
 
-      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);
+                  if (! dialog->frames[num - dialog->settings.frame_min])
+                    {
+                      if (! empty_frame)
+                        {
+                          empty_frame = g_new (Frame, 1);
+                          empty_frame->indexes = NULL;
+                          empty_frame->updated_indexes = NULL;
+                          empty_frame->drawable_id = 0;
 
-      /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
-       * So we take a wild guess by making the window slightly smaller to fit into most case. */
-      gtk_window_set_default_size (GTK_WINDOW (dialog->window),
-                                   MIN (expected_drawing_area_width + 20, screen_width - 60),
-                                   MIN (expected_drawing_area_height + 90, screen_height - 60));
+                          previous_frames = g_list_append (previous_frames, empty_frame);
+                        }
 
-      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->window));
-  }
-}
+                      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));
 
-/******************************/
-/*** Some Update procedures ***/
-/******************************/
+                      dialog->frames[num - dialog->settings.frame_min] = empty_frame;
+                    }
+                  else if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
+                    dialog->frames[num - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[num - dialog->settings.frame_min]->updated_indexes,
+                                                                                     GINT_TO_POINTER (num - 
dialog->settings.frame_min));
+                }
+            }
+          g_strfreev (tokens);
+          g_free (tag);
+          g_match_info_next (match_info, NULL);
+        }
+      g_match_info_free (match_info);
+    }
 
-/* Update the tool sensitivity for playing, depending on the number of frames. */
-static void
-update_ui_sensitivity (AnimationPlayDialog *dialog)
-{
-  gboolean animated;
+  for (j = 0; j < dialog->settings.num_frames; j++)
+    {
+      /* Check which frame must be updated with the current layer. */
+      if (dialog->frames[j] && g_list_length (dialog->frames[j]->updated_indexes))
+        {
+          new_layer = gimp_layer_new_from_drawable (layer, frames_image_id);
+          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
 
-  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);
+          if (preview_quality)
+            {
+              gint layer_offx, layer_offy;
 
-  /* 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);
+              gimp_layer_scale (new_layer,
+                                (gimp_drawable_width (layer) * (gint) dialog->preview_width) / (gint) 
image_width,
+                                (gimp_drawable_height (layer) * (gint) dialog->preview_height) / (gint) 
image_height,
+                                FALSE);
+              gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
+              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / (gint) 
image_width,
+                                      (layer_offy * (gint) dialog->preview_height) / (gint) image_height);
+            }
+          gimp_layer_resize_to_image_size (new_layer);
 
-  /* Settings are always changeable. */
-  gtk_action_group_set_sensitive (dialog->settings_actions, TRUE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), TRUE);
+          if (dialog->frames[j]->drawable_id == 0)
+            {
+              dialog->frames[j]->drawable_id = new_layer;
+              dialog->frames[j]->indexes = dialog->frames[j]->updated_indexes;
+              dialog->frames[j]->updated_indexes = NULL;
+            }
+          else if (g_list_length (dialog->frames[j]->indexes) == g_list_length 
(dialog->frames[j]->updated_indexes))
+            {
+              gimp_item_set_visible (new_layer, TRUE);
+              gimp_item_set_visible (dialog->frames[j]->drawable_id, TRUE);
 
-  /* 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);
-}
+              dialog->frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, 
GIMP_CLIP_TO_IMAGE);
+              g_list_free (dialog->frames[j]->updated_indexes);
+              dialog->frames[j]->updated_indexes = NULL;
+            }
+          else
+            {
+              GList    *idx;
+              gboolean  move_j = FALSE;
+              Frame    *forked_frame = g_new (Frame, 1);
+              gint32    forked_drawable_id = gimp_layer_new_from_drawable (dialog->frames[j]->drawable_id, 
frames_image_id);
 
-static void
-update_scale (AnimationPlayDialog *dialog,
-              gdouble              scale)
-{
-  guint expected_drawing_area_width;
-  guint expected_drawing_area_height;
+              /* 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);
 
-  /* FIXME: scales under 0.5 are broken. See bug 690265. */
-  if (scale <= 0.5)
-    scale = 0.51;
+              forked_frame->drawable_id = forked_drawable_id;
+              forked_frame->indexes = dialog->frames[j]->updated_indexes;
+              forked_frame->updated_indexes = NULL;
+              dialog->frames[j]->updated_indexes = NULL;
 
-  expected_drawing_area_width  = dialog->preview_width * scale;
-  expected_drawing_area_height = dialog->preview_height * scale;
+              for (idx = g_list_first (forked_frame->indexes); idx != NULL; idx = g_list_next (idx))
+                {
+                  dialog->frames[j]->indexes = g_list_remove (dialog->frames[j]->indexes, idx->data);
+                  if (GPOINTER_TO_INT (idx->data) != j)
+                    dialog->frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
+                  else
+                    /* Frame j must also be moved to the forked frame, but only after the loop. */
+                    move_j = TRUE;
+                }
+              if (move_j)
+                dialog->frames[j] = forked_frame;
 
-  /* 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;
+              gimp_item_set_visible (forked_drawable_id, FALSE);
 
-      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);
+              previous_frames = g_list_append (previous_frames, forked_frame);
+            }
+
+          gimp_item_set_visible (dialog->frames[j]->drawable_id, FALSE);
+        }
     }
-}
 
+  g_free (layer_name);
+  g_free (nospace_name);
+}
 
 static void
 init_ui (AnimationPlayDialog *dialog,
@@ -1404,7 +1284,7 @@ init_ui (AnimationPlayDialog *dialog,
                     G_CALLBACK(da_size_callback),
                     dialog);
   g_signal_connect (dialog->drawing_area, "button-press-event",
-                    G_CALLBACK (button_press),
+                    G_CALLBACK (da_button_press),
                     dialog);
 
   /*****************/
@@ -1574,822 +1454,627 @@ init_ui (AnimationPlayDialog *dialog,
   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 (AnimationPlayDialog *dialog,
-                 gint32               frames_image_id,
-                 gint32               layer,
-                 GList               *previous_frames,
-                 gint                 image_width,
-                 gint                 image_height)
+static GtkUIManager *
+ui_manager_new (AnimationPlayDialog *dialog)
 {
-  Frame        *empty_frame = NULL;
-  gchar        *layer_name;
-  gchar        *nospace_name;
-  GMatchInfo   *match_info;
-  gboolean      preview_quality;
-  gint32        new_layer;
-  gint          j, k;
+  static GtkActionEntry play_entries[] =
+  {
+    { "step-back", "media-skip-backward",
+      N_("Step _back"), "d", N_("Step back to previous frame"),
+      G_CALLBACK (step_back_callback) },
 
-  if (gimp_item_is_group (layer))
-    {
-      gint    num_children;
-      gint32 *children;
+    { "step", "media-skip-forward",
+      N_("_Step"), "f", N_("Step to next frame"),
+      G_CALLBACK (step_callback) },
 
-      children = gimp_item_get_children (layer, &num_children);
-      for (j = 0; j < num_children; j++)
-        rec_init_frames (dialog, frames_image_id,
-                         children[num_children - j - 1],
-                         previous_frames,
-                         image_width, image_height);
+    { "rewind", "media-seek-backward",
+      NULL, "s", N_("Rewind the animation"),
+      G_CALLBACK (rewind_callback) },
+  };
 
-      return;
-    }
+  static GtkToggleActionEntry play_toggle_entries[] =
+  {
+    { "play", "media-playback-start",
+      NULL, "space", N_("Start playback"),
+      G_CALLBACK (play_callback), FALSE },
+  };
 
-  layer_name = gimp_item_get_name (layer);
-  nospace_name = g_regex_replace_literal (nospace_reg, layer_name, -1, 0, "", 0, NULL);
-  preview_quality = dialog->preview_width != image_width || dialog->preview_height != image_height;
+  static GtkActionEntry settings_entries[] =
+  {
+    /* Refresh is not really a settings, but it makes sense to be grouped
+     * as such, because we want to be able to refresh and set things in the
+     * same moments. */
+    { "refresh", GIMP_ICON_VIEW_REFRESH,
+      N_("Refresh"), "<control>R", N_("Reload the image"),
+      G_CALLBACK (refresh_callback) },
 
-  if (g_regex_match (all_reg, nospace_name, 0, NULL))
     {
-      for (j = 0; j < dialog->settings.num_frames; j++)
-        {
-          if (! dialog->frames[j])
-            {
-              if (! empty_frame)
-                {
-                  empty_frame = g_new (Frame, 1);
-                  empty_frame->indexes = NULL;
-                  empty_frame->updated_indexes = NULL;
-                  empty_frame->drawable_id = 0;
-
-                  previous_frames = g_list_append (previous_frames, empty_frame);
-                }
-
-              if (! g_list_find (empty_frame->updated_indexes, GINT_TO_POINTER (j)))
-                empty_frame->updated_indexes = g_list_append (empty_frame->updated_indexes, GINT_TO_POINTER 
(j));
-
-              dialog->frames[j] = empty_frame;
-            }
-          else if (! g_list_find (dialog->frames[j]->updated_indexes, GINT_TO_POINTER (j)))
-            dialog->frames[j]->updated_indexes = g_list_append (dialog->frames[j]->updated_indexes, 
GINT_TO_POINTER (j));
-        }
-    }
-  else
+      "speed-up", NULL,
+      N_("Faster"), "bracketright", N_("Increase the speed of the animation"),
+      G_CALLBACK (speed_up_callback)
+    },
     {
-      g_regex_match (layers_reg, nospace_name, 0, &match_info);
-
-      while (g_match_info_matches (match_info))
-        {
-          gchar *tag = g_match_info_fetch (match_info, 1);
-          gchar** tokens = g_strsplit(tag, ",", 0);
-
-          for (j = 0; tokens[j] != NULL; j++)
-            {
-              gchar* hyphen = g_strrstr(tokens[j], "-");
-
-              if (hyphen != NULL)
-                {
-                  gint32 first = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
-                  gint32 second = (gint32) g_ascii_strtoll (&hyphen[1], NULL, 10);
+      "speed-down", NULL,
+      N_("Slower"), "bracketleft", N_("Decrease the speed of the animation"),
+      G_CALLBACK (speed_down_callback)
+    },
+  };
 
-                  for (k = first; k <= second; k++)
-                    {
-                      if (! dialog->frames[k - dialog->settings.frame_min])
-                        {
-                          if (! empty_frame)
-                            {
-                              empty_frame = g_new (Frame, 1);
-                              empty_frame->indexes = NULL;
-                              empty_frame->updated_indexes = NULL;
-                              empty_frame->drawable_id = 0;
+  static GtkActionEntry view_entries[] =
+  {
+    { "zoom-in", GTK_STOCK_ZOOM_IN,
+      NULL, "plus", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
 
-                              previous_frames = g_list_append (previous_frames, empty_frame);
-                            }
+    { "zoom-in-accel", GTK_STOCK_ZOOM_IN,
+      NULL, "KP_Add", N_("Zoom in"),
+      G_CALLBACK (zoom_in_callback) },
 
-                          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));
+    { "zoom-out", GTK_STOCK_ZOOM_OUT,
+      NULL, "minus", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
 
-                          dialog->frames[k - dialog->settings.frame_min] = empty_frame;
-                        }
-                      else if (! g_list_find (dialog->frames[k - 
dialog->settings.frame_min]->updated_indexes, GINT_TO_POINTER (k - dialog->settings.frame_min)))
-                        dialog->frames[k - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[k - dialog->settings.frame_min]->updated_indexes,
-                                                                                       GINT_TO_POINTER (k - 
dialog->settings.frame_min));
-                    }
-                }
-              else
-                {
-                  gint32 num = (gint32) g_ascii_strtoll (tokens[j], NULL, 10);
+    { "zoom-out-accel", GTK_STOCK_ZOOM_OUT,
+      NULL, "KP_Subtract", N_("Zoom out"),
+      G_CALLBACK (zoom_out_callback) },
 
-                  if (! dialog->frames[num - dialog->settings.frame_min])
-                    {
-                      if (! empty_frame)
-                        {
-                          empty_frame = g_new (Frame, 1);
-                          empty_frame->indexes = NULL;
-                          empty_frame->updated_indexes = NULL;
-                          empty_frame->drawable_id = 0;
+    { "zoom-reset", GTK_STOCK_ZOOM_OUT,
+      NULL, "equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
 
-                          previous_frames = g_list_append (previous_frames, empty_frame);
-                        }
+    { "zoom-reset-accel", GTK_STOCK_ZOOM_OUT,
+      NULL, "KP_Equal", N_("Zoom out"),
+      G_CALLBACK (zoom_reset_callback) },
+  };
 
-                      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));
+  static GtkToggleActionEntry view_toggle_entries[] =
+  {
+    { "detach", GIMP_STOCK_DETACH,
+      N_("Detach"), NULL,
+      N_("Detach the animation from the dialog window"),
+      G_CALLBACK (detach_callback), FALSE }
+  };
 
-                      dialog->frames[num - dialog->settings.frame_min] = empty_frame;
-                    }
-                  else if (! g_list_find (dialog->frames[num - dialog->settings.frame_min]->updated_indexes, 
GINT_TO_POINTER (num - dialog->settings.frame_min)))
-                    dialog->frames[num - dialog->settings.frame_min]->updated_indexes = g_list_append 
(dialog->frames[num - dialog->settings.frame_min]->updated_indexes,
-                                                                                     GINT_TO_POINTER (num - 
dialog->settings.frame_min));
-                }
-            }
-          g_strfreev (tokens);
-          g_free (tag);
-          g_match_info_next (match_info, NULL);
-        }
-      g_match_info_free (match_info);
-    }
+  static GtkActionEntry various_entries[] =
+  {
+    { "help", "help-browser",
+      N_("About the animation plug-in"), "question", NULL,
+      G_CALLBACK (help_callback) },
 
-  for (j = 0; j < dialog->settings.num_frames; j++)
+    { "close", "window-close",
+      N_("Quit"), "<control>W", NULL,
+      G_CALLBACK (close_callback)
+    },
     {
-      /* Check which frame must be updated with the current layer. */
-      if (dialog->frames[j] && g_list_length (dialog->frames[j]->updated_indexes))
-        {
-          new_layer = gimp_layer_new_from_drawable (layer, frames_image_id);
-          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
+      "quit", "application-quit",
+      N_("Quit"), "<control>Q", NULL,
+      G_CALLBACK (close_callback)
+    },
+  };
 
-          if (preview_quality)
-            {
-              gint layer_offx, layer_offy;
+  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
+  GError         *error      = NULL;
 
-              gimp_layer_scale (new_layer,
-                                (gimp_drawable_width (layer) * (gint) dialog->preview_width) / (gint) 
image_width,
-                                (gimp_drawable_height (layer) * (gint) dialog->preview_height) / (gint) 
image_height,
-                                FALSE);
-              gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
-              gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / (gint) 
image_width,
-                                      (layer_offy * (gint) dialog->preview_height) / (gint) image_height);
-            }
-          gimp_layer_resize_to_image_size (new_layer);
+  /* 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);
 
-          if (dialog->frames[j]->drawable_id == 0)
-            {
-              dialog->frames[j]->drawable_id = new_layer;
-              dialog->frames[j]->indexes = dialog->frames[j]->updated_indexes;
-              dialog->frames[j]->updated_indexes = NULL;
-            }
-          else if (g_list_length (dialog->frames[j]->indexes) == g_list_length 
(dialog->frames[j]->updated_indexes))
-            {
-              gimp_item_set_visible (new_layer, TRUE);
-              gimp_item_set_visible (dialog->frames[j]->drawable_id, TRUE);
+  /* 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);
 
-              dialog->frames[j]->drawable_id = gimp_image_merge_visible_layers (frames_image_id, 
GIMP_CLIP_TO_IMAGE);
-              g_list_free (dialog->frames[j]->updated_indexes);
-              dialog->frames[j]->updated_indexes = NULL;
-            }
-          else
-            {
-              GList    *idx;
-              gboolean  move_j = FALSE;
-              Frame    *forked_frame = g_new (Frame, 1);
-              gint32    forked_drawable_id = gimp_layer_new_from_drawable (dialog->frames[j]->drawable_id, 
frames_image_id);
+  /* 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);
 
-              /* 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);
+  /* Remaining various actions. */
+  dialog->various_actions = gtk_action_group_new ("various");
 
-              forked_frame->drawable_id = forked_drawable_id;
-              forked_frame->indexes = dialog->frames[j]->updated_indexes;
-              forked_frame->updated_indexes = NULL;
-              dialog->frames[j]->updated_indexes = NULL;
+  gtk_action_group_set_translation_domain (dialog->various_actions, NULL);
 
-              for (idx = g_list_first (forked_frame->indexes); idx != NULL; idx = g_list_next (idx))
-                {
-                  dialog->frames[j]->indexes = g_list_remove (dialog->frames[j]->indexes, idx->data);
-                  if (GPOINTER_TO_INT (idx->data) != j)
-                    dialog->frames[GPOINTER_TO_INT (idx->data)] = forked_frame;
-                  else
-                    /* Frame j must also be moved to the forked frame, but only after the loop. */
-                    move_j = TRUE;
-                }
-              if (move_j)
-                dialog->frames[j] = forked_frame;
+  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);
 
-              gimp_item_set_visible (forked_drawable_id, FALSE);
+  /* 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));
 
-              previous_frames = g_list_append (previous_frames, forked_frame);
-            }
+  /* Finally make some limited popup menu. */
+  gtk_ui_manager_add_ui_from_string (ui_manager,
+                                     "<ui>"
+                                     "  <popup name=\"anim-play-popup\" accelerators=\"true\">"
+                                     "    <menuitem action=\"refresh\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"zoom-in\" />"
+                                     "    <menuitem action=\"zoom-out\" />"
+                                     "    <menuitem action=\"zoom-reset\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"speed-up\" />"
+                                     "    <menuitem action=\"speed-down\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"help\" />"
+                                     "    <separator />"
+                                     "    <menuitem action=\"close\" />"
+                                     "  </popup>"
+                                     "</ui>",
+                                     -1, &error);
 
-          gimp_item_set_visible (dialog->frames[j]->drawable_id, FALSE);
-        }
+  if (error)
+    {
+      g_warning ("error parsing ui: %s", error->message);
+      g_clear_error (&error);
     }
 
-  g_free (layer_name);
-  g_free (nospace_name);
+  return ui_manager;
 }
 
-/* Initialize the frames, and return TRUE if render must be forced. */
-static gboolean
-init_frames (AnimationPlayDialog *dialog)
+static void
+refresh_dialog (AnimationPlayDialog *dialog)
 {
-  /* Frames are associated to an unused image. */
-  static gint32    frames_image_id = 0;
-  /* We keep track of the frames in a separate structure to free drawable
-   * memory. We can't use easily dialog->frames because some of the
-   * drawables may be used in more than 1 frame. */
-  static GList    *previous_frames = NULL;
-
-  gint            *layers;
-  gint             num_layers;
-  gint             image_width;
-  gint             image_height;
-  gint32           new_frame, previous_frame, new_layer;
-  gint             duration = 0;
-  DisposeType      disposal = dialog->settings.frame_disposal;
-  gint             frame_spin_size;
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->play_actions, 
"play"))))
-    {
-      gtk_action_activate (gtk_action_group_get_action (dialog->play_actions, "play"));
-    }
-
-  if (dialog->frames_lock)
-    {
-      return FALSE;
-    }
-  dialog->frames_lock = TRUE;
-
-  /* Block most UI during frame initialization. */
-  gtk_action_group_set_sensitive (dialog->play_actions, FALSE);
-  gtk_widget_set_sensitive (dialog->play_bar, FALSE);
-  gtk_widget_set_sensitive (dialog->progress_bar, FALSE);
-  gtk_action_group_set_sensitive (dialog->settings_actions, FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), FALSE);
-  gtk_action_group_set_sensitive (dialog->view_actions, FALSE);
-  gtk_widget_set_sensitive (GTK_WIDGET (dialog->view_bar), FALSE);
-
-  /* Cleanup before re-generation. */
-  if (dialog->frames)
-    {
-      GList *idx;
-
-      gimp_image_delete (frames_image_id);
-      frames_image_id = 0;
-
-      /* Freeing previous frames only once. */
-      for (idx = g_list_first (previous_frames); idx != NULL; idx = g_list_next (idx))
-        {
-          Frame* frame = (Frame*) idx->data;
-
-          g_list_free (frame->indexes);
-          g_list_free (frame->updated_indexes);
-          g_free (frame);
-        }
-      g_list_free (previous_frames);
-      previous_frames = NULL;
-
-      g_free (dialog->frames);
-      dialog->frames = NULL;
-    }
-  if (! gimp_image_is_valid (dialog->image_id))
-    {
-      /* This is not necessarily an error. We may have simply wanted
-       * to clean up our GEGL buffers. */
-      return FALSE;
-    }
+  GdkScreen *screen;
+  guint      screen_width, screen_height;
+  gint       window_width, window_height;
 
-  layers = gimp_image_get_layers (dialog->image_id, &num_layers);
-  init_frame_numbers (dialog, layers, num_layers);
-  if (dialog->settings.num_frames <= 0)
-    {
-      update_ui_sensitivity (dialog);
-      dialog->frames_lock = FALSE;
-      g_free (layers);
-      return FALSE;
-    }
+  /* Update GUI size. */
+  screen = gtk_widget_get_screen (dialog->window);
+  screen_height = gdk_screen_get_height (screen);
+  screen_width = gdk_screen_get_width (screen);
+  gtk_window_get_size (GTK_WINDOW (dialog->window), &window_width, &window_height);
 
-  dialog->frames = g_try_malloc0_n (dialog->settings.num_frames, sizeof (Frame*));
-  if (! dialog->frames)
-    {
-      dialog->frames_lock = FALSE;
-      gimp_message (_("Memory could not be allocated to the frame container."));
-      g_free (layers);
-      clean_exit (dialog);
-      return FALSE;
-    }
+  /* if the *window* size is bigger than the screen size,
+   * diminish the drawing area by as much, then compute the corresponding scale. */
+  if (window_width + 50 > screen_width || window_height + 50 > screen_height)
+  {
+      gint expected_drawing_area_width = MAX (1, dialog->preview_width - window_width + screen_width);
+      gint expected_drawing_area_height = MAX (1, dialog->preview_height - window_height + screen_height);
 
-  image_width  = gimp_image_width (dialog->image_id);
-  image_height = gimp_image_height (dialog->image_id);
+      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);
 
-  /* We only use RGB images for display because indexed images would somehow
-     render terrible colors. Layers from other types will be automatically
-     converted. */
-  frames_image_id = gimp_image_new (dialog->preview_width, dialog->preview_height, GIMP_RGB);
+      /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
+       * So we take a wild guess by making the window slightly smaller to fit into most case. */
+      gtk_window_set_default_size (GTK_WINDOW (dialog->window),
+                                   MIN (expected_drawing_area_width + 20, screen_width - 60),
+                                   MIN (expected_drawing_area_height + 90, screen_height - 60));
 
-  /* Save processing time and memory by not saving history and merged frames. */
-  gimp_image_undo_disable (frames_image_id);
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->window));
+  }
+}
 
-  if (disposal == DISPOSE_TAGS)
-    {
-      gint        i;
+/* Update the tool sensitivity for playing, depending on the number of frames. */
+static void
+update_ui_sensitivity (AnimationPlayDialog *dialog)
+{
+  gboolean animated;
 
-      for (i = 0; i < num_layers; i++)
-        {
-          show_loading_progress (dialog, i, num_layers);
-          rec_init_frames (dialog,
-                           frames_image_id,
-                           layers[num_layers - (i + 1)],
-                           previous_frames,
-                           image_width, image_height);
-        }
+  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);
 
-      for (i = 0; i < dialog->settings.num_frames; i++)
-        {
-          /* If for some reason a frame is absent, use the previous one.
-           * We are ensured that there is at least a "first" frame for this. */
-          if (! dialog->frames[i])
-            {
-              dialog->frames[i] = dialog->frames[i - 1];
-              dialog->frames[i]->indexes = g_list_append (dialog->frames[i]->indexes, GINT_TO_POINTER (i));
-            }
+  /* 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);
 
-          /* 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;
-      gint   layer_offy;
-      gchar *layer_name;
-      gint   i;
+  /* Settings are always changeable. */
+  gtk_action_group_set_sensitive (dialog->settings_actions, TRUE);
+  gtk_widget_set_sensitive (GTK_WIDGET (dialog->settings_bar), TRUE);
 
-      for (i = 0; i < dialog->settings.num_frames; i++)
-        {
-          show_loading_progress (dialog, i, num_layers);
+  /* 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);
+}
 
-          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;
+/***************** CALLBACKS ********************/
 
-          previous_frames = g_list_append (previous_frames, dialog->frames[i]);
+static void
+close_callback (GtkAction           *action,
+                AnimationPlayDialog *dialog)
+{
+  clean_exit (dialog);
+}
 
-          layer_name = gimp_item_get_name (layers[num_layers - (i + 1)]);
-          if (layer_name)
-            {
-              duration = parse_ms_tag (layer_name);
-              disposal = parse_disposal_tag (dialog, layer_name);
-              g_free (layer_name);
-            }
+static void
+help_callback (GtkAction           *action,
+               AnimationPlayDialog *dialog)
+{
+  gimp_standard_help_func (PLUG_IN_PROC, dialog->window);
+}
 
-          if (i > 0 && disposal != DISPOSE_REPLACE)
-            {
-              previous_frame = gimp_layer_copy (dialog->frames[i - 1]->drawable_id);
+static void
+window_destroy (GtkWidget           *widget,
+                AnimationPlayDialog *dialog)
+{
+  clean_exit (dialog);
+}
 
-              gimp_image_insert_layer (frames_image_id, previous_frame, 0, 0);
-              gimp_item_set_visible (previous_frame, TRUE);
-            }
+static gboolean
+popup_menu (GtkWidget           *widget,
+            AnimationPlayDialog *dialog)
+{
+  GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
 
-          new_layer = gimp_layer_new_from_drawable (layers[num_layers - (i + 1)], frames_image_id);
+  gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
+  gtk_menu_popup (GTK_MENU (menu),
+                  NULL, NULL, NULL, NULL,
+                  0, gtk_get_current_event_time ());
 
-          gimp_image_insert_layer (frames_image_id, new_layer, 0, 0);
-          gimp_item_set_visible (new_layer, TRUE);
-          gimp_layer_scale (new_layer, (gimp_drawable_width (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_width) / image_width,
-                            (gimp_drawable_height (layers[num_layers - (i + 1)]) * (gint) 
dialog->preview_height) / image_height, FALSE);
-          gimp_drawable_offsets (layers[num_layers - (i + 1)], &layer_offx, &layer_offy);
-          gimp_layer_set_offsets (new_layer, (layer_offx * (gint) dialog->preview_width) / image_width,
-                                  (layer_offy * (gint) dialog->preview_height) / image_height);
-          gimp_layer_resize_to_image_size (new_layer);
+  return TRUE;
+}
 
-          if (gimp_item_is_group (new_layer))
-            {
-              gint    num_children;
-              gint32 *children;
-              gint    j;
+static gboolean
+adjustment_pressed (GtkWidget           *widget,
+                    GdkEventButton      *event,
+                    AnimationPlayDialog *dialog)
+{
+  gboolean event_processed = FALSE;
 
-              /* I want to make all layers in the group visible, so that when I'll make
-               * the group visible too at render time, it will display everything in it. */
-              children = gimp_item_get_children (new_layer, &num_children);
-              for (j = 0; j < num_children; j++)
-                gimp_item_set_visible (children[j], TRUE);
-            }
+  if (event->type == GDK_BUTTON_PRESS &&
+      event->button == 2)
+    {
+      GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
+      GtkAdjustment *adj = gtk_spin_button_get_adjustment (spin);
 
-          new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
-          dialog->frames[i]->drawable_id = new_frame;
-          gimp_item_set_visible (new_frame, FALSE);
+      gtk_adjustment_set_value (adj,
+                                (gdouble) dialog->settings.current_frame);
 
-          if (duration <= 0)
-            duration = 0;
-          dialog->frames[i]->duration = (guint) duration;
-        }
+      /* 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;
     }
 
-  /* Update the UI. */
-  frame_spin_size = (gint) (log10 (dialog->settings.frame_max - (dialog->settings.frame_max % 10))) + 1;
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->startframe_spin), frame_spin_size);
-  gtk_entry_set_width_chars (GTK_ENTRY (dialog->endframe_spin), frame_spin_size);
-
-  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->startframe_spin)),
-                            dialog->settings.start_frame,
-                            dialog->settings.frame_min,
-                            dialog->settings.frame_max,
-                            1.0, 5.0, 0.0);
-  gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (dialog->endframe_spin)),
-                            dialog->settings.end_frame,
-                            dialog->settings.start_frame,
-                            dialog->settings.frame_max,
-                            1.0, 5.0, 0.0);
-
-  update_ui_sensitivity (dialog);
-
-  dialog->frames_lock = FALSE;
-  g_free (layers);
-
-  return TRUE;
+  return event_processed;
 }
 
-static void
-initialize (AnimationPlayDialog *dialog)
+static gboolean
+da_button_press (GtkWidget           *widget,
+              GdkEventButton      *event,
+              AnimationPlayDialog *dialog)
 {
-  /* Freeing existing data after a refresh. */
-  /* Catch the case when the user has closed the image in the meantime. */
-  if (! gimp_image_is_valid (dialog->image_id))
+  if (gdk_event_triggers_context_menu ((GdkEvent *) event))
     {
-      gimp_message (_("Invalid image. Did you close it?"));
-      clean_exit (dialog);
-      return;
-    }
+      GtkWidget *menu = gtk_ui_manager_get_widget (dialog->ui_manager, "/anim-play-popup");
 
-  if (! dialog->window)
-    {
-      /* First run. */
-      gchar *name = gimp_image_get_name (dialog->image_id);
-      init_ui (dialog, name);
-      g_free (name);
+      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;
     }
-  refresh_dialog (dialog);
 
-  /* I want to make sure the progress bar is realized before init_frames()
-   * which may take quite a bit of time. */
-  if (!gtk_widget_get_realized (dialog->progress))
-    gtk_widget_realize (dialog->progress);
-
-  render_frame (dialog, init_frames (dialog));
+  return FALSE;
 }
 
-/* Rendering Functions */
-
+/*
+ * Update the actual drawing area metrics, which may be different from
+ * requested, since there is no full control of the WM.
+ */
 static void
-render_frame (AnimationPlayDialog *dialog,
-              gboolean             force_render)
+da_size_callback (GtkWidget           *drawing_area,
+                  GtkAllocation       *allocation,
+                  AnimationPlayDialog *dialog)
 {
-  static gint    last_frame_index        = -1;
-  static gchar  *shape_preview_mask      = NULL;
-  static guint   shape_preview_mask_size = 0;
-  static guchar *rawframe                = NULL;
-  static guint   rawframe_size           = 0;
-  GeglBuffer    *buffer;
-  gint           i, j, k;
-  guchar        *srcptr;
-  guchar        *destptr;
-  GtkWidget     *da;
-  guint          drawing_width;
-  guint          drawing_height;
-  guchar        *preview_data;
-
-  /* Do not try to update the drawing areas while init_frames() is still running. */
-  if (dialog->frames_lock)
-    return;
+  guchar   **drawing_data;
 
-  /* Unless we are in a case where we always want to redraw
-   * (after a zoom, preview mode change, reinitialization, and such),
-   * we don't redraw if the same frame was already drawn. */
-  if ((! force_render) && dialog->settings.num_frames > 0 && last_frame_index > -1 &&
-      g_list_find (dialog->frames[last_frame_index]->indexes,
-                   GINT_TO_POINTER (dialog->settings.current_frame - dialog->settings.frame_min)))
+  if (drawing_area == dialog->shape_drawing_area)
     {
-      show_playing_progress (dialog);
-      return;
-    }
-
-  dialog->frames_lock = TRUE;
-
-  g_assert (dialog->settings.num_frames < 1 || (dialog->settings.current_frame >= 
dialog->settings.start_frame &&
-                                                dialog->settings.current_frame <= 
dialog->settings.end_frame));
+      if (allocation->width  == dialog->shape_drawing_area_width &&
+          allocation->height == dialog->shape_drawing_area_height)
+        return;
 
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
-    {
-      da = dialog->shape_drawing_area;
-      preview_data = dialog->shape_drawing_area_data;
-      drawing_width = dialog->shape_drawing_area_width;
-      drawing_height = dialog->shape_drawing_area_height;
+      dialog->shape_drawing_area_width  = allocation->width;
+      dialog->shape_drawing_area_height = allocation->height;
 
-      if (dialog->settings.num_frames < 1)
-        total_alpha_preview (preview_data, drawing_width, drawing_height);
+      g_free (dialog->shape_drawing_area_data);
+      drawing_data = &dialog->shape_drawing_area_data;
     }
   else
     {
-      da = dialog->drawing_area;
-      preview_data = dialog->drawing_area_data;
-      drawing_width = dialog->drawing_area_width;
-      drawing_height = dialog->drawing_area_height;
+      if (allocation->width  == dialog->drawing_area_width &&
+          allocation->height == dialog->drawing_area_height)
+        return;
 
-      /* Set "alpha grid" background. */
-      total_alpha_preview (preview_data, drawing_width, drawing_height);
+      dialog->drawing_area_width  = allocation->width;
+      dialog->drawing_area_height = allocation->height;
+
+      g_free (dialog->drawing_area_data);
+      drawing_data = &dialog->drawing_area_data;
     }
 
-  /* When there is no frame to show, we simply display the alpha background and return. */
-  if (dialog->settings.num_frames > 0)
-    {
-      /* Update the rawframe. */
-      if (rawframe_size < drawing_width * drawing_height * 4)
-        {
-          rawframe_size = drawing_width * drawing_height * 4;
-          g_free (rawframe);
-          rawframe = g_malloc (rawframe_size);
-        }
+  dialog->settings.scale = MIN ((gdouble) allocation->width / (gdouble) dialog->preview_width,
+                                (gdouble) allocation->height / (gdouble) dialog->preview_height);
 
-      buffer = gimp_drawable_get_buffer (dialog->frames[dialog->settings.current_frame - 
dialog->settings.frame_min]->drawable_id);
+  *drawing_data = g_malloc (allocation->width * allocation->height * 3);
 
-      /* Fetch and scale the whole raw new frame */
-      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
-                       dialog->settings.scale, babl_format ("R'G'B'A u8"),
-                       rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+  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;
 
-      /* Number of pixels. */
-      i = drawing_width * drawing_height;
-      destptr = preview_data;
-      srcptr  = rawframe;
-      while (i--)
+      zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dialog->zoomcombo)));
+      if (zoomcombo_text_child)
         {
-          if (! (srcptr[3] & 128))
-            {
-              srcptr  += 4;
-              destptr += 3;
-              continue;
-            }
-
-          *(destptr++) = *(srcptr++);
-          *(destptr++) = *(srcptr++);
-          *(destptr++) = *(srcptr++);
+          char* new_entry_text = g_strdup_printf  (_("%.1f %%"), dialog->settings.scale * 100.0);
 
-          srcptr++;
+          gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
+          g_free (new_entry_text);
         }
 
-      /* calculate the shape mask */
-      if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->view_actions, "detach"))))
-        {
-          gint ideal_shape_size = (drawing_width * drawing_height) /
-                                   8 + 1 + drawing_height;
-
-          if (shape_preview_mask_size < ideal_shape_size)
-            {
-              shape_preview_mask_size = ideal_shape_size;
-              g_free (shape_preview_mask);
-              shape_preview_mask = g_malloc (ideal_shape_size);
-            }
-
-          memset (shape_preview_mask, 0,
-                  (drawing_width * drawing_height) / 8 + drawing_height);
-          srcptr = rawframe + 3;
-
-          for (j = 0; j < drawing_height; j++)
-            {
-              k = j * ((7 + drawing_width) / 8);
+      /* 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);
+    }
+}
 
-              for (i = 0; i < drawing_width; i++)
-                {
-                  if ((*srcptr) & 128)
-                    shape_preview_mask[k + i/8] |= (1 << (i&7));
+static gboolean
+shape_pressed (GtkWidget           *widget,
+               GdkEventButton      *event,
+               AnimationPlayDialog *dialog)
+{
+  if (da_button_press (widget, event, dialog))
+    return TRUE;
 
-                  srcptr += 4;
-                }
-            }
-          reshape_from_bitmap (dialog, shape_preview_mask);
-        }
+  /* ignore double and triple click */
+  if (event->type == GDK_BUTTON_PRESS)
+    {
+      CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset");
 
-      /* clean up */
-      g_object_unref (buffer);
+      if (!p)
+        return FALSE;
 
-      /* Update UI. */
-      show_playing_progress (dialog);
+      p->x = (gint) event->x;
+      p->y = (gint) event->y;
 
-      last_frame_index = dialog->settings.current_frame - dialog->settings.frame_min;
+      gtk_grab_add (widget);
+      gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
+                        GDK_BUTTON_RELEASE_MASK |
+                        GDK_BUTTON_MOTION_MASK  |
+                        GDK_POINTER_MOTION_HINT_MASK,
+                        NULL, NULL, 0);
+      gdk_window_raise (gtk_widget_get_window (widget));
     }
 
-  /* Display the preview buffer. */
-  gdk_draw_rgb_image (gtk_widget_get_window (da),
-                      (gtk_widget_get_style (da))->white_gc,
-                      (gint) ((drawing_width - dialog->settings.scale * dialog->preview_width) / 2),
-                      (gint) ((drawing_height - dialog->settings.scale * dialog->preview_height) / 2),
-                      drawing_width, drawing_height,
-                      (dialog->settings.num_frames == 1 ?
-                       GDK_RGB_DITHER_MAX : DITHERTYPE),
-                      preview_data, drawing_width * 3);
-
-  dialog->frames_lock = FALSE;
+  return FALSE;
 }
 
-
-static void
-show_goto_progress (guint                goto_frame,
-                    AnimationPlayDialog *dialog)
+static gboolean
+shape_released (GtkWidget *widget)
 {
-  gchar         *text;
-
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
-                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
-                                  (gfloat) (dialog->settings.num_frames - 0.999)));
+  gtk_grab_remove (widget);
+  gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0);
+  gdk_flush ();
 
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
-    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, dialog->settings.num_frames);
-  else
-    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - dialog->settings.frame_min + 1, 
goto_frame, dialog->settings.num_frames);
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
+  return FALSE;
 }
 
-static void
-show_loading_progress (AnimationPlayDialog *dialog,
-                       gint                 layer_nb,
-                       gint                 num_layers)
+static gboolean
+shape_motion (GtkWidget      *widget,
+              GdkEventMotion *event)
 {
-  gchar *text;
-  gfloat load_rate = (gfloat) layer_nb / ((gfloat) num_layers - 0.999);
+  GdkModifierType  mask;
+  gint             xp, yp;
+  GdkWindow       *root_win;
 
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress), load_rate);
+  root_win = gdk_get_default_root_window ();
+  gdk_window_get_pointer (root_win, &xp, &yp, &mask);
 
-  text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
+  /* if a button is still held by the time we process this event... */
+  if (mask & GDK_BUTTON1_MASK)
+    {
+      CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset");
 
-  /* Forcing the UI to update even with intensive computation. */
-  while (gtk_events_pending ())
-    gtk_main_iteration ();
+      if (!p)
+        return FALSE;
+
+      gtk_window_move (GTK_WINDOW (widget), xp  - p->x, yp  - p->y);
+    }
+  else /* the user has released all buttons */
+    {
+      shape_released (widget);
+    }
+
+  return FALSE;
 }
 
-static void
-show_playing_progress (AnimationPlayDialog *dialog)
+static gboolean
+repaint_da (GtkWidget           *darea,
+            GdkEventExpose      *event,
+            AnimationPlayDialog *dialog)
 {
-  gchar *text;
-
-  /* update the dialog's progress bar */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
-                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
-                                  (gfloat) (dialog->settings.num_frames - 0.999)));
+  GtkStyle *style = gtk_widget_get_style (darea);
+  gint      da_width;
+  gint      da_height;
+  guchar   *da_data;
 
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
+  if (darea == dialog->drawing_area)
     {
-      text = g_strdup_printf (_("Frame %d of %d"),
-                              dialog->settings.current_frame,
-                              dialog->settings.num_frames);
+      da_width  = dialog->drawing_area_width;
+      da_height = dialog->drawing_area_height;
+      da_data   = dialog->drawing_area_data;
     }
   else
     {
-      text = g_strdup_printf (_("Frame %d (%d) of %d"),
-                              dialog->settings.current_frame - dialog->settings.frame_min + 1,
-                              dialog->settings.current_frame, dialog->settings.num_frames);
+      da_width  = dialog->shape_drawing_area_width;
+      da_height = dialog->shape_drawing_area_height;
+      da_data   = dialog->shape_drawing_area_data;
     }
-  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
-  g_free (text);
+
+  gdk_draw_rgb_image (gtk_widget_get_window (darea),
+                      style->white_gc,
+                      (gint) ((da_width - dialog->settings.scale * dialog->preview_width) / 2),
+                      (gint) ((da_height - dialog->settings.scale * dialog->preview_height) / 2),
+                      da_width, da_height,
+                      (dialog->settings.num_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
+                      da_data, da_width * 3);
+
+  return TRUE;
 }
 
-/* total_alpha_preview:
- * Fill the @drawing_data with an alpha (grey chess) pattern.
- * This uses a static array, copied over each line (with some shift to
- * reproduce the pattern), using `memcpy()`.
- * The reason why we keep the pattern in the statically allocated memory,
- * instead of simply looping through @drawing_data and recreating the
- * pattern is simply because `memcpy()` implementations are supposed to
- * be more efficient than loops over an array. */
-static void
-total_alpha_preview (guchar *drawing_data,
-                     guint   drawing_width,
-                     guint   drawing_height)
+static gboolean
+progress_button (GtkWidget           *widget,
+                 GdkEventButton      *event,
+                 AnimationPlayDialog *dialog)
 {
-  static guint   alpha_line_width = 0;
-  static guchar *alpha_line       = NULL;
-  gint           i;
-
-  g_assert (drawing_width > 0);
+  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));
 
-  /* If width change, we update the "alpha" line. */
-  if (alpha_line_width < drawing_width + 8)
+  /* ignore double and triple click */
+  if (event->type == GDK_BUTTON_PRESS)
     {
-      alpha_line_width = drawing_width + 8;
+      GtkAllocation  allocation;
+      guint          goto_frame;
 
-      g_free (alpha_line);
+      gtk_widget_get_allocation (widget, &allocation);
 
-      /* A full line + 8 pixels (1 square). */
-      alpha_line = g_malloc (alpha_line_width * 3);
+      goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
 
-      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;
-            }
-        }
-    }
+      if (goto_frame < dialog->settings.start_frame)
+        gtk_adjustment_set_value (startframe_adjust, (gdouble) goto_frame);
 
-  for (i = 0; i < drawing_height; i++)
-    {
-      if (i & 8)
-        {
-          memcpy (&drawing_data[i * 3 * drawing_width],
-                  alpha_line,
-                  3 * drawing_width);
-        }
-      else
+      if (goto_frame > dialog->settings.end_frame)
+        gtk_adjustment_set_value (endframe_adjust, (gdouble) goto_frame);
+
+      if (goto_frame >= dialog->settings.frame_min && goto_frame < dialog->settings.frame_min + 
dialog->settings.num_frames)
         {
-          /* 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);
+          dialog->settings.current_frame = goto_frame;
+          render_frame (dialog, FALSE);
         }
     }
+
+  return FALSE;
 }
 
-static void
-do_back_step (AnimationPlayDialog *dialog)
+static gboolean
+progress_entered (GtkWidget           *widget,
+                  GdkEventCrossing    *event,
+                  AnimationPlayDialog *dialog)
 {
-  if (dialog->settings.current_frame == dialog->settings.start_frame)
-    {
-      dialog->settings.current_frame = dialog->settings.end_frame;
-    }
-  else
-    {
-      --dialog->settings.current_frame;
-    }
-  render_frame (dialog, FALSE);
+  GtkAllocation  allocation;
+  guint          goto_frame;
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
+
+  show_goto_progress (goto_frame, dialog);
+
+  return FALSE;
 }
 
-static void
-do_step (AnimationPlayDialog *dialog)
+static gboolean
+progress_motion (GtkWidget           *widget,
+                 GdkEventMotion      *event,
+                 AnimationPlayDialog *dialog)
 {
-  dialog->settings.current_frame = dialog->settings.start_frame +
-                                   ((dialog->settings.current_frame - dialog->settings.start_frame + 1) %
-                                    (dialog->settings.end_frame - dialog->settings.start_frame + 1));
+  GtkAllocation  allocation;
+  guint          goto_frame;
 
-  render_frame (dialog, FALSE);
+  gtk_widget_get_allocation (widget, &allocation);
+
+  goto_frame = dialog->settings.frame_min + (gint) (event->x / (allocation.width / 
dialog->settings.num_frames));
+
+  show_goto_progress (goto_frame, dialog);
+
+  return FALSE;
 }
 
-static void
-window_destroy (GtkWidget           *widget,
-                AnimationPlayDialog *dialog)
+static gboolean
+progress_left (GtkWidget           *widget,
+               GdkEventCrossing    *event,
+               AnimationPlayDialog *dialog)
 {
-  clean_exit (dialog);
+  show_playing_progress (dialog);
+
+  return FALSE;
 }
 
 static void
-set_timer (guint new_timer)
+detach_callback (GtkToggleAction     *action,
+                 AnimationPlayDialog *dialog)
 {
-  static guint timer = 0;
+  gboolean detached = gtk_toggle_action_get_active (action);
 
-  if (timer)
+  if (detached)
     {
-      g_source_remove (timer);
-    }
-  timer = new_timer;
-}
+      gint x, y;
 
-static gboolean
-advance_frame_callback (AnimationPlayDialog *dialog)
-{
-  guint duration;
-  guint timer;
+      gtk_window_set_screen (GTK_WINDOW (dialog->shape_window),
+                             gtk_widget_get_screen (dialog->drawing_area));
 
-  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);
-    }
+      gtk_widget_show (dialog->shape_window);
 
-  timer = g_timeout_add (duration,
-                         (GSourceFunc) advance_frame_callback,
-                         (AnimationPlayDialog *) dialog);
-  set_timer (timer);
+      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);
+        }
 
-  do_step (dialog);
+      gdk_window_get_origin (gtk_widget_get_window (dialog->drawing_area), &x, &y);
 
-  return G_SOURCE_REMOVE;
+      gtk_window_move (GTK_WINDOW (dialog->shape_window), x + 6, y + 6);
+
+      gdk_window_set_back_pixmap (gtk_widget_get_window (dialog->shape_drawing_area), NULL, TRUE);
+
+      /* Set "alpha grid" background. */
+      total_alpha_preview (dialog->drawing_area_data,
+                           dialog->drawing_area_width,
+                           dialog->drawing_area_height);
+      repaint_da (dialog->drawing_area, NULL, dialog);
+    }
+  else
+    {
+      gtk_widget_hide (dialog->shape_window);
+    }
+
+  render_frame (dialog, TRUE);
 }
 
 static void
@@ -2848,133 +2533,409 @@ proxycombo_changed (GtkWidget           *combo,
     }
 }
 
-/**
- * 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.
- */
+/* Rendering Functions */
+
 static void
-init_frame_numbers (AnimationPlayDialog *dialog,
-                    gint                *layers,
-                    gint                 num_layers)
+render_frame (AnimationPlayDialog *dialog,
+              gboolean             force_render)
 {
-  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;
+  static gint    last_frame_index        = -1;
+  static gchar  *shape_preview_mask      = NULL;
+  static guint   shape_preview_mask_size = 0;
+  static guchar *rawframe                = NULL;
+  static guint   rawframe_size           = 0;
+  GeglBuffer    *buffer;
+  gint           i, j, k;
+  guchar        *srcptr;
+  guchar        *destptr;
+  GtkWidget     *da;
+  guint          drawing_width;
+  guint          drawing_height;
+  guchar        *preview_data;
 
-  /* 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;
+  /* Do not try to update the drawing areas while init_frames() is still running. */
+  if (dialog->frames_lock)
+    return;
 
-  if (dialog->settings.frame_disposal != DISPOSE_TAGS)
+  /* Unless we are in a case where we always want to redraw
+   * (after a zoom, preview mode change, reinitialization, and such),
+   * we don't redraw if the same frame was already drawn. */
+  if ((! force_render) && dialog->settings.num_frames > 0 && last_frame_index > -1 &&
+      g_list_find (dialog->frames[last_frame_index]->indexes,
+                   GINT_TO_POINTER (dialog->settings.current_frame - dialog->settings.frame_min)))
     {
-      dialog->settings.num_frames = num_layers;
-      dialog->settings.frame_min = 1;
-      dialog->settings.frame_max = dialog->settings.frame_min + dialog->settings.num_frames - 1;
+      show_playing_progress (dialog);
+      return;
     }
-  else
+
+  dialog->frames_lock = TRUE;
+
+  g_assert (dialog->settings.num_frames < 1 || (dialog->settings.current_frame >= 
dialog->settings.start_frame &&
+                                                dialog->settings.current_frame <= 
dialog->settings.end_frame));
+
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
     {
-      gint i;
-      gint max = G_MININT;
-      gint min = G_MAXINT;
+      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;
 
-      for (i = 0; i < num_layers; i++)
-        rec_set_total_frames (layers[i], &min, &max);
+      if (dialog->settings.num_frames < 1)
+        total_alpha_preview (preview_data, drawing_width, drawing_height);
+    }
+  else
+    {
+      da = dialog->drawing_area;
+      preview_data = dialog->drawing_area_data;
+      drawing_width = dialog->drawing_area_width;
+      drawing_height = dialog->drawing_area_height;
 
-      dialog->settings.num_frames = (max > min)? max + 1 - min : 0;
-      dialog->settings.frame_min = min;
-      dialog->settings.frame_max = max;
+      /* Set "alpha grid" background. */
+      total_alpha_preview (preview_data, drawing_width, drawing_height);
     }
 
-  /* 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)
+  /* When there is no frame to show, we simply display the alpha background and return. */
+  if (dialog->settings.num_frames > 0)
     {
-      dialog->settings.current_frame = dialog->settings.start_frame;
+      /* Update the rawframe. */
+      if (rawframe_size < drawing_width * drawing_height * 4)
+        {
+          rawframe_size = drawing_width * drawing_height * 4;
+          g_free (rawframe);
+          rawframe = g_malloc (rawframe_size);
+        }
+
+      buffer = gimp_drawable_get_buffer (dialog->frames[dialog->settings.current_frame - 
dialog->settings.frame_min]->drawable_id);
+
+      /* Fetch and scale the whole raw new frame */
+      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
+                       dialog->settings.scale, babl_format ("R'G'B'A u8"),
+                       rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+      /* Number of pixels. */
+      i = drawing_width * drawing_height;
+      destptr = preview_data;
+      srcptr  = rawframe;
+      while (i--)
+        {
+          if (! (srcptr[3] & 128))
+            {
+              srcptr  += 4;
+              destptr += 3;
+              continue;
+            }
+
+          *(destptr++) = *(srcptr++);
+          *(destptr++) = *(srcptr++);
+          *(destptr++) = *(srcptr++);
+
+          srcptr++;
+        }
+
+      /* calculate the shape mask */
+      if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action 
(dialog->view_actions, "detach"))))
+        {
+          gint ideal_shape_size = (drawing_width * drawing_height) /
+                                   8 + 1 + drawing_height;
+
+          if (shape_preview_mask_size < ideal_shape_size)
+            {
+              shape_preview_mask_size = ideal_shape_size;
+              g_free (shape_preview_mask);
+              shape_preview_mask = g_malloc (ideal_shape_size);
+            }
+
+          memset (shape_preview_mask, 0,
+                  (drawing_width * drawing_height) / 8 + drawing_height);
+          srcptr = rawframe + 3;
+
+          for (j = 0; j < drawing_height; j++)
+            {
+              k = j * ((7 + drawing_width) / 8);
+
+              for (i = 0; i < drawing_width; i++)
+                {
+                  if ((*srcptr) & 128)
+                    shape_preview_mask[k + i/8] |= (1 << (i&7));
+
+                  srcptr += 4;
+                }
+            }
+          reshape_from_bitmap (dialog, shape_preview_mask);
+        }
+
+      /* clean up */
+      g_object_unref (buffer);
+
+      /* Update UI. */
+      show_playing_progress (dialog);
+
+      last_frame_index = dialog->settings.current_frame - dialog->settings.frame_min;
     }
 
-  /* Update frame counting UI widgets. */
-  if (startframe_adjust)
+  /* Display the preview buffer. */
+  gdk_draw_rgb_image (gtk_widget_get_window (da),
+                      (gtk_widget_get_style (da))->white_gc,
+                      (gint) ((drawing_width - dialog->settings.scale * dialog->preview_width) / 2),
+                      (gint) ((drawing_height - dialog->settings.scale * dialog->preview_height) / 2),
+                      drawing_width, drawing_height,
+                      (dialog->settings.num_frames == 1 ?
+                       GDK_RGB_DITHER_MAX : DITHERTYPE),
+                      preview_data, drawing_width * 3);
+
+  dialog->frames_lock = FALSE;
+}
+
+/* total_alpha_preview:
+ * Fill the @drawing_data with an alpha (grey chess) pattern.
+ * This uses a static array, copied over each line (with some shift to
+ * reproduce the pattern), using `memcpy()`.
+ * The reason why we keep the pattern in the statically allocated memory,
+ * instead of simply looping through @drawing_data and recreating the
+ * pattern is simply because `memcpy()` implementations are supposed to
+ * be more efficient than loops over an array. */
+static void
+total_alpha_preview (guchar *drawing_data,
+                     guint   drawing_width,
+                     guint   drawing_height)
+{
+  static guint   alpha_line_width = 0;
+  static guchar *alpha_line       = NULL;
+  gint           i;
+
+  g_assert (drawing_width > 0);
+
+  /* If width change, we update the "alpha" line. */
+  if (alpha_line_width < drawing_width + 8)
     {
-      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)
+      alpha_line_width = drawing_width + 8;
+
+      g_free (alpha_line);
+
+      /* A full line + 8 pixels (1 square). */
+      alpha_line = g_malloc (alpha_line_width * 3);
+
+      for (i = 0; i < alpha_line_width; i++)
         {
-          dialog->settings.start_frame = dialog->settings.frame_min;
+          /* 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;
+            }
         }
     }
-  else
+
+  for (i = 0; i < drawing_height; i++)
     {
-      dialog->settings.start_frame = dialog->settings.frame_min;
+      if (i & 8)
+        {
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line,
+                  3 * drawing_width);
+        }
+      else
+        {
+          /* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
+          memcpy (&drawing_data[i * 3 * drawing_width],
+                  alpha_line + 24,
+                  3 * drawing_width);
+        }
     }
+}
 
-  if (endframe_adjust)
+static void
+reshape_from_bitmap (AnimationPlayDialog *dialog,
+                     const gchar         *bitmap)
+{
+  static gchar *prev_bitmap = NULL;
+  static guint  prev_width = -1;
+  static guint  prev_height = -1;
+
+  if ((! prev_bitmap)                                  ||
+      prev_width != dialog->shape_drawing_area_width   ||
+      prev_height != dialog->shape_drawing_area_height ||
+      (memcmp (prev_bitmap, bitmap,
+               (dialog->shape_drawing_area_width *
+                dialog->shape_drawing_area_height) / 8 +
+               dialog->shape_drawing_area_height)))
     {
-      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)
+      GdkBitmap *shape_mask;
+
+      shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (dialog->shape_window),
+                                                bitmap,
+                                                dialog->shape_drawing_area_width, 
dialog->shape_drawing_area_height);
+      gtk_widget_shape_combine_mask (dialog->shape_window, shape_mask, 0, 0);
+      g_object_unref (shape_mask);
+
+      if (!prev_bitmap || prev_width != dialog->shape_drawing_area_width || prev_height != 
dialog->shape_drawing_area_height)
         {
-          dialog->settings.end_frame = dialog->settings.frame_max;
+          g_free(prev_bitmap);
+          prev_bitmap = g_malloc ((dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 8 
+ dialog->shape_drawing_area_height);
+          prev_width = dialog->shape_drawing_area_width;
+          prev_height = dialog->shape_drawing_area_height;
         }
+
+      memcpy (prev_bitmap, bitmap, (dialog->shape_drawing_area_width * dialog->shape_drawing_area_height) / 
8 + dialog->shape_drawing_area_height);
+    }
+}
+
+static void
+show_playing_progress (AnimationPlayDialog *dialog)
+{
+  gchar *text;
+
+  /* update the dialog's progress bar */
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
+                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
+                                  (gfloat) (dialog->settings.num_frames - 0.999)));
+
+  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
+    {
+      text = g_strdup_printf (_("Frame %d of %d"),
+                              dialog->settings.current_frame,
+                              dialog->settings.num_frames);
     }
   else
     {
-      dialog->settings.end_frame = dialog->settings.frame_max;
+      text = g_strdup_printf (_("Frame %d (%d) of %d"),
+                              dialog->settings.current_frame - dialog->settings.frame_min + 1,
+                              dialog->settings.current_frame, dialog->settings.num_frames);
     }
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
+  g_free (text);
 }
 
-static gboolean
-is_disposal_tag (const gchar *str,
-                 DisposeType *disposal,
-                 gint        *taglength)
+static void
+show_loading_progress (AnimationPlayDialog *dialog,
+                       gint                 layer_nb,
+                       gint                 num_layers)
 {
-  if (strlen (str) != 9)
-    return FALSE;
+  gchar *text;
+  gfloat load_rate = (gfloat) layer_nb / ((gfloat) num_layers - 0.999);
 
-  if (strncmp (str, "(combine)", 9) == 0)
+  /* update the dialog's progress bar */
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress), load_rate);
+
+  text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
+  g_free (text);
+
+  /* Forcing the UI to update even with intensive computation. */
+  while (gtk_events_pending ())
+    gtk_main_iteration ();
+}
+
+static void
+do_back_step (AnimationPlayDialog *dialog)
+{
+  if (dialog->settings.current_frame == dialog->settings.start_frame)
     {
-      *taglength = 9;
-      *disposal = DISPOSE_COMBINE;
-      return TRUE;
+      dialog->settings.current_frame = dialog->settings.end_frame;
     }
-  else if (strncmp (str, "(replace)", 9) == 0)
+  else
     {
-      *taglength = 9;
-      *disposal = DISPOSE_REPLACE;
-      return TRUE;
+      --dialog->settings.current_frame;
     }
+  render_frame (dialog, FALSE);
+}
 
-  return FALSE;
+static void
+do_step (AnimationPlayDialog *dialog)
+{
+  dialog->settings.current_frame = dialog->settings.start_frame +
+                                   ((dialog->settings.current_frame - dialog->settings.start_frame + 1) %
+                                    (dialog->settings.end_frame - dialog->settings.start_frame + 1));
+
+  render_frame (dialog, FALSE);
 }
 
-static DisposeType
-parse_disposal_tag (AnimationPlayDialog *dialog,
-                    const gchar         *str)
+static void
+set_timer (guint new_timer)
 {
-  gint i;
-  gint length = strlen (str);
+  static guint timer = 0;
 
-  for (i = 0; i < length; i++)
+  if (timer)
     {
-      DisposeType rtn;
-      gint        dummy;
+      g_source_remove (timer);
+    }
+  timer = new_timer;
+}
 
-      if (is_disposal_tag (&str[i], &rtn, &dummy))
-        return rtn;
+static gboolean
+advance_frame_callback (AnimationPlayDialog *dialog)
+{
+  guint duration;
+  guint timer;
+
+  duration = dialog->frames[(dialog->settings.current_frame - dialog->settings.frame_min + 1) %
+             dialog->settings.num_frames]->duration;
+  if (duration <= 0)
+    {
+      duration = (guint) (1000.0 / ((AnimationPlayDialog *) dialog)->settings.frame_rate);
     }
 
-  return dialog->settings.frame_disposal;
+  timer = g_timeout_add (duration,
+                         (GSourceFunc) advance_frame_callback,
+                         (AnimationPlayDialog *) dialog);
+  set_timer (timer);
+
+  do_step (dialog);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+show_goto_progress (guint                goto_frame,
+                    AnimationPlayDialog *dialog)
+{
+  gchar         *text;
+
+  /* update the dialog's progress bar */
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress),
+                                 ((gfloat) (dialog->settings.current_frame - dialog->settings.frame_min) /
+                                  (gfloat) (dialog->settings.num_frames - 0.999)));
+
+  if (dialog->settings.frame_disposal != DISPOSE_TAGS || dialog->settings.frame_min == 1)
+    text = g_strdup_printf (_("Go to frame %d of %d"), goto_frame, dialog->settings.num_frames);
+  else
+    text = g_strdup_printf (_("Go to frame %d (%d) of %d"), goto_frame - dialog->settings.frame_min + 1, 
goto_frame, dialog->settings.num_frames);
+  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dialog->progress), text);
+  g_free (text);
 }
 
-/* Util. */
+/************ UTILS ****************/
+
+static void
+connect_accelerators (GtkUIManager   *ui_manager,
+                      GtkActionGroup *group)
+{
+  GList          *action_list;
+  GList          *iter;
+
+  action_list = gtk_action_group_list_actions (group);
+  iter = action_list;
+  while (iter)
+    {
+      /* Make sure all the action's accelerator are correctly connected,
+       * even when there are no associated UI item. */
+      GtkAction *action = GTK_ACTION (iter->data);
+
+      gtk_action_set_accel_group (action,
+                                  gtk_ui_manager_get_accel_group (ui_manager));
+      gtk_action_connect_accelerator (action);
+
+      iter = iter->next;
+    }
+  g_list_free (action_list);
+}
 
 /* get_fps:
  * Frame rate proposed as default.
@@ -3044,6 +3005,37 @@ get_proxy (AnimationPlayDialog *dialog,
     }
 }
 
+static void
+update_scale (AnimationPlayDialog *dialog,
+              gdouble              scale)
+{
+  guint expected_drawing_area_width;
+  guint expected_drawing_area_height;
+
+  /* FIXME: scales under 0.5 are broken. See bug 690265. */
+  if (scale <= 0.5)
+    scale = 0.51;
+
+  expected_drawing_area_width  = dialog->preview_width * scale;
+  expected_drawing_area_height = dialog->preview_height * scale;
+
+  /* We don't update dialog->settings.scale directly because this might
+   * end up not being the real scale. Instead we request this size for
+   * the drawing areas, and the actual scale update will be done on the
+   * callback when size is actually allocated. */
+  gtk_widget_set_size_request (dialog->drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+  gtk_widget_set_size_request (dialog->shape_drawing_area, expected_drawing_area_width, 
expected_drawing_area_height);
+  /* I force the shape window to a smaller size if we scale down. */
+  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (dialog->view_actions, 
"detach"))))
+    {
+      gint x, y;
+
+      gdk_window_get_origin (gtk_widget_get_window (dialog->shape_window), &x, &y);
+      gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog->shape_window));
+      gtk_window_move (GTK_WINDOW (dialog->shape_window), x, y);
+    }
+}
+
 static gdouble
 get_scale (AnimationPlayDialog *dialog,
            gint                 index)
@@ -3166,7 +3158,50 @@ clean_exit (AnimationPlayDialog *dialog)
   gimp_quit ();
 }
 
-/* tag util. */
+/******* TAG UTILS **************/
+
+static gboolean
+is_disposal_tag (const gchar *str,
+                 DisposeType *disposal,
+                 gint        *taglength)
+{
+  if (strlen (str) != 9)
+    return FALSE;
+
+  if (strncmp (str, "(combine)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_COMBINE;
+      return TRUE;
+    }
+  else if (strncmp (str, "(replace)", 9) == 0)
+    {
+      *taglength = 9;
+      *disposal = DISPOSE_REPLACE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static DisposeType
+parse_disposal_tag (AnimationPlayDialog *dialog,
+                    const gchar         *str)
+{
+  gint i;
+  gint length = strlen (str);
+
+  for (i = 0; i < length; i++)
+    {
+      DisposeType rtn;
+      gint        dummy;
+
+      if (is_disposal_tag (&str[i], &rtn, &dummy))
+        return rtn;
+    }
+
+  return dialog->settings.frame_disposal;
+}
 
 static gboolean
 is_ms_tag (const gchar *str,


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