[gimp/wip/animation: 189/197] plug-ins: allow interrupting animation export.



commit 26d2a4dd7e8359abfd6df150d884c8d35e44f4b1
Author: Jehan <jehan girinstud io>
Date:   Wed Aug 2 22:37:19 2017 +0200

    plug-ins: allow interrupting animation export.
    
    Adding a return value to the "loading" signal so that an export can be
    interrupted in the middle (rather than waiting for the end of a long
    export if you discover you made a mistake). When an export is in
    progress, the export button becomes an export-cancel button.
    Also add a parameterized progression message.
    Finally make the full window non sensitive, but for the export-cancel
    button.

 plug-ins/animation-play/core/animation.c           |   97 ++++++++-------
 plug-ins/animation-play/core/animation.h           |   11 +-
 .../widgets/animation-dialog-export.c              |   20 +++-
 plug-ins/animation-play/widgets/animation-dialog.c |  126 ++++++++++++++------
 4 files changed, 162 insertions(+), 92 deletions(-)
---
diff --git a/plug-ins/animation-play/core/animation.c b/plug-ins/animation-play/core/animation.c
index b387912..959efc0 100644
--- a/plug-ins/animation-play/core/animation.c
+++ b/plug-ins/animation-play/core/animation.c
@@ -46,10 +46,10 @@ enum
   LOADING,
   LOADED,
   SIZE_CHANGED,
-  FRAMES_CHANGED,
-  INVALIDATE_CACHE,
   DURATION_CHANGED,
   FRAMERATE_CHANGED,
+  FRAMES_CHANGED,
+  INVALIDATE_CACHE,
   LAST_SIGNAL
 };
 
@@ -106,22 +106,27 @@ animation_class_init (AnimationClass *klass)
    * Animation::loading:
    * @animation: the animation loading.
    * @ratio: fraction loaded [0-1].
+   * @label: the text to show as progression message.
    *
-   * The ::loading signal must be emitted by a subclass of Animation.
-   * It can be used by a GUI to display a progress bar during loading.
+   * The ::loading signal must be emitted when a long process is taking
+   * place and you want the GUI to display a progress bar.
    * GUI widgets depending on a consistent state of @animation should
    * become unresponsive.
+   *
+   * Returns: TRUE is the loading should be stopped, FALSE otherwise.
+   * This allows long loading to be stopped.
    */
   animation_signals[LOADING] =
     g_signal_new ("loading",
                   G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_FIRST,
+                  G_SIGNAL_ACTION,
                   G_STRUCT_OFFSET (AnimationClass, loading),
                   NULL, NULL,
-                  g_cclosure_marshal_VOID__DOUBLE,
-                  G_TYPE_NONE,
-                  1,
-                  G_TYPE_DOUBLE);
+                  NULL,
+                  G_TYPE_BOOLEAN,
+                  2,
+                  G_TYPE_DOUBLE,
+                  G_TYPE_STRING);
   /**
    * Animation::loaded:
    * @animation: the animation loading.
@@ -163,6 +168,42 @@ animation_class_init (AnimationClass *klass)
                   G_TYPE_INT,
                   G_TYPE_INT);
   /**
+   * Animation::duration:
+   * @animation: the animation.
+   * @duration: the new duration of @animation in number of frames.
+   *
+   * The ::duration signal must be emitted when the duration of
+   * @animation changes.
+   */
+  animation_signals[DURATION_CHANGED] =
+    g_signal_new ("duration-changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (AnimationClass, duration_changed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__INT,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_INT);
+  /**
+   * Animation::framerate-changed:
+   * @animation: the animation.
+   * @framerate: the new framerate of @animation in frames per second.
+   *
+   * The ::framerate-changed signal is emitted when the framerate of
+   * @animation changes.
+   */
+  animation_signals[FRAMERATE_CHANGED] =
+    g_signal_new ("framerate-changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (AnimationClass, framerate_changed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__DOUBLE,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_DOUBLE);
+  /**
    * Animation::frames-changed:
    * @animation: the animation.
    * @position: the first frame position whose contents changed.
@@ -188,7 +229,7 @@ animation_class_init (AnimationClass *klass)
    * @position: the first frame position whose contents changed.
    * @length: the number of changed frames from @position.
    *
-   * The ::invalidate-cache signal must be emitted when one or more
+   * The ::invalidate-cache signal is emitted when one or more
    * successive frames have to be updated. This is similar to
    * ::frames-changed except that it forces the cache update even if the
    * contents apparently did not change. It will also invalidate cache
@@ -205,42 +246,6 @@ animation_class_init (AnimationClass *klass)
                   2,
                   G_TYPE_INT,
                   G_TYPE_INT);
-  /**
-   * Animation::duration:
-   * @animation: the animation.
-   * @duration: the new duration of @animation in number of frames.
-   *
-   * The ::duration signal must be emitted when the duration of
-   * @animation changes.
-   */
-  animation_signals[DURATION_CHANGED] =
-    g_signal_new ("duration-changed",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_FIRST,
-                  G_STRUCT_OFFSET (AnimationClass, duration_changed),
-                  NULL, NULL,
-                  g_cclosure_marshal_VOID__INT,
-                  G_TYPE_NONE,
-                  1,
-                  G_TYPE_INT);
-  /**
-   * Animation::framerate-changed:
-   * @animation: the animation.
-   * @framerate: the new framerate of @animation in frames per second.
-   *
-   * The ::framerate-changed signal is emitted when the framerate of
-   * @animation changes.
-   */
-  animation_signals[FRAMERATE_CHANGED] =
-    g_signal_new ("framerate-changed",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_FIRST,
-                  G_STRUCT_OFFSET (AnimationClass, framerate_changed),
-                  NULL, NULL,
-                  g_cclosure_marshal_VOID__DOUBLE,
-                  G_TYPE_NONE,
-                  1,
-                  G_TYPE_DOUBLE);
 
   object_class->finalize     = animation_finalize;
   object_class->set_property = animation_set_property;
diff --git a/plug-ins/animation-play/core/animation.h b/plug-ins/animation-play/core/animation.h
index da10205..3fb7274 100644
--- a/plug-ins/animation-play/core/animation.h
+++ b/plug-ins/animation-play/core/animation.h
@@ -42,22 +42,23 @@ struct _AnimationClass
 
   /* Signals */
   void         (*loading)            (Animation    *animation,
-                                      gdouble       ratio);
+                                      gdouble       ratio,
+                                      const gchar  *label);
   void         (*loaded)             (Animation    *animation);
 
   void         (*size_changed)       (Animation    *animation,
                                       gint          width,
                                       gint          height);
+  void         (*duration_changed)   (Animation    *animation,
+                                      gint          duration);
+  void         (*framerate_changed)  (Animation    *animation,
+                                      gdouble       fps);
   void         (*frames_changed)     (Animation    *animation,
                                       gint          position,
                                       gint          length);
   void         (*invalidate_cache)   (Animation    *animation,
                                       gint          position,
                                       gint          length);
-  void         (*duration_changed)   (Animation    *animation,
-                                      gint          duration);
-  void         (*framerate_changed)  (Animation    *animation,
-                                      gdouble       fps);
 
   /* These virtual methods must be implemented by any subclass. */
   gint         (*get_duration)       (Animation    *animation);
diff --git a/plug-ins/animation-play/widgets/animation-dialog-export.c 
b/plug-ins/animation-play/widgets/animation-dialog-export.c
index b8d2fbd..7bdcd82 100644
--- a/plug-ins/animation-play/widgets/animation-dialog-export.c
+++ b/plug-ins/animation-play/widgets/animation-dialog-export.c
@@ -137,6 +137,8 @@ animation_dialog_export_video (AnimationPlayback *playback,
   GeglNode  *graph;
   GeglNode  *export;
   GeglNode  *input;
+  gchar     *label = g_strdup_printf(_("Exporting \"%s\""),
+                                     filename);
   gint       duration;
   gint       i;
 
@@ -158,14 +160,19 @@ animation_dialog_export_video (AnimationPlayback *playback,
   for (i = 0; i < duration; i++)
     {
       GeglBuffer *buffer;
+      gboolean    stops_loading;
 
       g_signal_emit_by_name (animation, "loading",
-                             (gdouble) i / ((gdouble) duration - 0.999));
+                             (gdouble) i / ((gdouble) duration - 0.999),
+                             label, &stops_loading);
+      if (stops_loading)
+        break;
       buffer = animation_playback_get_buffer (playback, i);
       gegl_node_set (input, "buffer", buffer, NULL);
       gegl_node_process (export);
       g_object_unref (buffer);
     }
+  g_free (label);
   g_object_unref (graph);
   g_signal_emit_by_name (animation, "loaded");
 }
@@ -202,9 +209,18 @@ animation_dialog_export_images (AnimationPlayback *playback,
     {
       GeglBuffer *buffer;
       gchar      *path;
+      gboolean    stops_loading;
+
+      /* Make sure GUI interactions are processed, so that one can
+       * cancel the export anytime. */
+      while (g_main_context_pending (NULL))
+        g_main_context_iteration (NULL, FALSE);
 
       g_signal_emit_by_name (animation, "loading",
-                             (gdouble) i / ((gdouble) duration - 0.999));
+                             (gdouble) i / ((gdouble) duration - 0.999),
+                             _("Exporting frames"), &stops_loading);
+      if (stops_loading)
+        break;
       path = g_strdup_printf ("%s-%.*d.%s",
                               filename,
                               (gint) floor (log10(duration)) + 1,
diff --git a/plug-ins/animation-play/widgets/animation-dialog.c 
b/plug-ins/animation-play/widgets/animation-dialog.c
index 51bd557..dd2d5c4 100755
--- a/plug-ins/animation-play/widgets/animation-dialog.c
+++ b/plug-ins/animation-play/widgets/animation-dialog.c
@@ -82,6 +82,7 @@ struct _AnimationDialogPrivate
   GtkWidget         *zoomcombo;
   GtkWidget         *refresh;
   GtkWidget         *export;
+  gboolean           cancel_export;
 
   GtkWidget         *scrolled_drawing_area;
   GtkWidget         *drawing_area;
@@ -158,6 +159,8 @@ static gboolean    on_dialog_expose                (GtkWidget      *widget,
 static void        update_ui_sensitivity     (AnimationDialog  *dialog);
 
 /* UI callbacks */
+static void        on_cancel_export          (GtkToolButton    *button,
+                                              AnimationDialog  *dialog);
 static void        export_callback           (GtkAction        *action,
                                               AnimationDialog  *dialog);
 static void        close_callback            (GtkAction        *action,
@@ -226,8 +229,13 @@ static gboolean    popup_menu                (GtkWidget        *widget,
                                               AnimationDialog  *dialog);
 
 /* Animation Signals */
-static void        show_loading_progress     (Animation         *animation,
+static gboolean    show_loading_progress     (Animation         *animation,
+                                              gdouble            load_rate,
+                                              const gchar       *label,
+                                              AnimationDialog   *dialog);
+static gboolean    check_cancel_loading      (Animation         *animation,
                                               gdouble            load_rate,
+                                              const gchar       *label,
                                               AnimationDialog   *dialog);
 static void        playback_range_changed    (AnimationPlayback *playback,
                                               gint               playback_start,
@@ -290,11 +298,6 @@ static gboolean    on_progress_event         (GtkWidget        *widget,
 static void        show_playing_progress     (AnimationDialog  *dialog);
 
 /* Utils */
-static void        inactive_on_loading       (Animation       *animation,
-                                              gdouble          load_rate,
-                                              GtkWidget       *widget);
-static void        active_on_loaded          (Animation       *animation,
-                                              GtkWidget       *widget);
 static void        hide_on_animatic          (AnimationDialog  *dialog,
                                               GParamSpec       *param_spec,
                                               GtkWidget        *widget);
@@ -716,7 +719,7 @@ animation_dialog_constructed (GObject *object)
   gtk_widget_show (priv->zoomcombo);
 
   /* Export. */
-  priv->export = GTK_WIDGET (gtk_tool_button_new (NULL, N_("Export the animation")));
+  priv->export = GTK_WIDGET (gtk_tool_button_new (NULL, NULL));
 
   gtk_activatable_set_related_action (GTK_ACTIVATABLE (priv->export),
                                       gtk_action_group_get_action (priv->various_actions, "export"));
@@ -1003,18 +1006,6 @@ animation_dialog_set_property (GObject      *object,
     case PROP_ANIMATION:
       g_clear_object (&priv->animation);
       priv->animation = g_value_get_object (value);
-      g_signal_connect (priv->animation, "loading",
-                        (GCallback) inactive_on_loading,
-                        priv->settings);
-      g_signal_connect (priv->animation, "loaded",
-                        (GCallback) active_on_loaded,
-                        priv->settings);
-      g_signal_connect (priv->animation, "loading",
-                        (GCallback) inactive_on_loading,
-                        priv->upper_bar);
-      g_signal_connect (priv->animation, "loaded",
-                        (GCallback) active_on_loaded,
-                        priv->upper_bar);
       break;
 
     default:
@@ -1158,7 +1149,7 @@ ui_manager_new (AnimationDialog *dialog)
   static GtkActionEntry various_entries[] =
   {
     { "export", GIMP_ICON_DOCUMENT_SAVE,
-      NULL, "<control>e", N_("Export the video"),
+      NULL, "<control>e", N_("Export the animation"),
       G_CALLBACK (export_callback) },
 
     { "help", "help-browser",
@@ -1513,6 +1504,9 @@ animation_dialog_set_animation (AnimationDialog *dialog,
   g_signal_connect (priv->animation, "loading",
                     (GCallback) show_loading_progress,
                     dialog);
+  g_signal_connect (priv->animation, "loading",
+                    (GCallback) check_cancel_loading,
+                    dialog);
   g_signal_connect_swapped (priv->animation, "loaded",
                             (GCallback) update_progress,
                             dialog);
@@ -1581,12 +1575,63 @@ update_ui_sensitivity (AnimationDialog *dialog)
 /**** UI CALLBACKS ****/
 
 static void
+on_cancel_export (GtkToolButton   *button,
+                  AnimationDialog *dialog)
+{
+  GET_PRIVATE (dialog)->cancel_export = TRUE;
+}
+
+static void
 export_callback (GtkAction       *action,
                  AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  animation_dialog_export (GTK_WINDOW (dialog), priv->playback);
+  if (! priv->cancel_export)
+    {
+      priv->cancel_export = FALSE;
+      /* Change the button icon and tooltip. */
+      gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (priv->export),
+                                     "process-stop");
+      gtk_widget_set_tooltip_text (priv->export, _("Stop the Export"));
+
+      /* Make all but this button sensitive. */
+      gtk_widget_set_sensitive (GTK_WIDGET (priv->play_bar), FALSE);
+      gtk_widget_set_sensitive (GTK_WIDGET (priv->right_pane), FALSE);
+      if (priv->xsheet)
+        gtk_widget_set_sensitive (GTK_WIDGET (priv->xsheet), FALSE);
+      gtk_container_foreach (GTK_CONTAINER (priv->upper_bar),
+                             (GtkCallback) gtk_widget_set_sensitive,
+                             GINT_TO_POINTER (FALSE));
+      gtk_widget_set_sensitive (GTK_WIDGET (priv->export), TRUE);
+
+      /* Run the export. */
+      g_signal_connect (priv->export, "clicked",
+                        G_CALLBACK (on_cancel_export),
+                        dialog);
+      animation_dialog_export (GTK_WINDOW (dialog), priv->playback);
+      g_signal_handlers_disconnect_by_func (priv->export,
+                                            G_CALLBACK (on_cancel_export),
+                                            dialog);
+
+      /* Reset icon and tooltip. */
+      gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (priv->export),
+                                     GIMP_ICON_DOCUMENT_SAVE);
+      gtk_widget_set_tooltip_text (priv->export, _("Export the animation"));
+
+      /* Reset to normal sensitivity of the GUI. */
+      gtk_widget_set_sensitive (GTK_WIDGET (priv->play_bar), TRUE);
+      gtk_widget_set_sensitive (GTK_WIDGET (priv->right_pane), TRUE);
+      if (priv->xsheet)
+        gtk_widget_set_sensitive (GTK_WIDGET (priv->xsheet), TRUE);
+      gtk_container_foreach (GTK_CONTAINER (priv->upper_bar),
+                             (GtkCallback) gtk_widget_set_sensitive,
+                             GINT_TO_POINTER (TRUE));
+    }
+  else
+    {
+      priv->cancel_export = FALSE;
+    }
 }
 
 static void
@@ -2171,26 +2216,44 @@ popup_menu (GtkWidget       *widget,
   return TRUE;
 }
 
-static void
+static gboolean
 show_loading_progress (Animation       *animation,
                        gdouble          load_rate,
+                       const gchar     *label,
                        AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  gchar *text;
+  const gchar            *progress_label;
+  gchar                  *text;
+
+  if (label)
+    progress_label = label;
+  else
+    progress_label = _("Loading animation");
 
   block_ui (dialog);
 
   /* update the dialog's progress bar */
   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progress), load_rate);
 
-  text = g_strdup_printf (_("Loading animation %d %%"), (gint) (load_rate * 100));
+  text = g_strdup_printf ("%s %d %%", progress_label, (gint) (load_rate * 100));
   gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progress), text);
   g_free (text);
 
   /* Forcing the UI to update even with intensive computation. */
   while (gtk_events_pending ())
     gtk_main_iteration ();
+
+  return FALSE;
+}
+
+static gboolean
+check_cancel_loading (Animation       *animation,
+                      gdouble          load_rate,
+                      const gchar     *label,
+                      AnimationDialog *dialog)
+{
+  return GET_PRIVATE (dialog)->cancel_export;
 }
 
 static void
@@ -2968,21 +3031,6 @@ show_playing_progress (AnimationDialog *dialog)
 }
 
 static void
-inactive_on_loading (Animation *animation,
-                     gdouble    load_rate,
-                     GtkWidget *widget)
-{
-  gtk_widget_set_sensitive (widget, FALSE);
-}
-
-static void
-active_on_loaded (Animation *animation,
-                  GtkWidget *widget)
-{
-  gtk_widget_set_sensitive (widget, TRUE);
-}
-
-static void
 hide_on_animatic (AnimationDialog *dialog,
                   GParamSpec      *param_spec,
                   GtkWidget       *widget)


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