[gimp/wip/gradient-edit: 36/42] app: implement tool undo for gradient editing in the blend tool



commit 64b281d658239fc235814d7c442e620cc2c41b58
Author: Ell <ell_se yahoo com>
Date:   Thu Aug 3 19:59:21 2017 -0400

    app: implement tool undo for gradient editing in the blend tool
    
    Move the tool undo functionality of the blend tool to the editor,
    and add support for undoing gradient edit operations.  Each undo
    step that affects the gradient holds, in addition to the line
    endpoint poisitions, a copy of the gradient at the beginning of the
    operation, as well as necessary information to allow the selection
    to "follow" undo.  When undoing the operation, the saved gradient
    is copied back to the active gradient.
    
    To avoid all kinds of complex scenarios, when the active gradient
    changes, or when the gradient is modified externally (e.g., by the
    (old) gradient editor), all undo steps that affect the gradient are
    deleted from the history, while those that affect only the endpoint
    positions are kept.

 app/tools/gimpblendtool-editor.c |  708 ++++++++++++++++++++++++++++++++------
 app/tools/gimpblendtool-editor.h |   26 +-
 app/tools/gimpblendtool.c        |  197 +++--------
 app/tools/gimpblendtool.h        |    9 +-
 4 files changed, 682 insertions(+), 258 deletions(-)
---
diff --git a/app/tools/gimpblendtool-editor.c b/app/tools/gimpblendtool-editor.c
index 6f3bd0e..727af93 100644
--- a/app/tools/gimpblendtool-editor.c
+++ b/app/tools/gimpblendtool-editor.c
@@ -61,101 +61,139 @@ typedef enum
 } Direction;
 
 
+typedef struct
+{
+  /* line endpoints at the beginning of the operation */
+  gdouble       start_x;
+  gdouble       start_y;
+  gdouble       end_x;
+  gdouble       end_y;
+
+  /* copy of the gradient at the beginning of the operation, owned by the blend
+   * info, or NULL, if the gradient isn't affected
+   */
+  GimpGradient *gradient;
+
+  /* handle added by the operation, or HANDLE_NONE */
+  gint          added_handle;
+  /* handle removed by the operation, or HANDLE_NONE */
+  gint          removed_handle;
+  /* selected handle at the end of the operation, or HANDLE_NONE */
+  gint          selected_handle;
+} BlendInfo;
+
+
 /*  local function prototypes  */
 
-static gboolean              gimp_blend_tool_editor_line_can_add_slider       (GimpToolLine         *line,
-                                                                               gdouble               value,
-                                                                               GimpBlendTool        
*blend_tool);
-static gint                  gimp_blend_tool_editor_line_add_slider           (GimpToolLine         *line,
-                                                                               gdouble               value,
-                                                                               GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_line_remove_slider        (GimpToolLine         *line,
-                                                                               gint                  slider,
-                                                                               GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_line_selection_changed    (GimpToolLine         *line,
-                                                                               GimpBlendTool        
*blend_tool);
-static gboolean              gimp_blend_tool_editor_line_handle_clicked       (GimpToolLine         *line,
-                                                                               gint                  handle,
-                                                                               GdkModifierType       state,
-                                                                               GimpButtonPressType   
press_type,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_gui_response              (GimpToolGui          *gui,
-                                                                               gint                  
response_id,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_color_entry_color_changed (GimpColorButton      *button,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_color_entry_type_changed  (GtkComboBox          *combo,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_endpoint_se_value_changed (GimpSizeEntry        *se,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_stop_se_value_changed     (GimpSizeEntry        *se,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_stop_delete_clicked       (GtkWidget            *button,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_midpoint_se_value_changed (GimpSizeEntry        *se,
-                                                                               GimpBlendTool        
*blend_tool);
-
-static void                  gimp_blend_tool_editor_midpoint_type_changed     (GtkComboBox          *combo,
-                                                                               GimpBlendTool        
*blend_tool);
+static gboolean              gimp_blend_tool_editor_line_can_add_slider        (GimpToolLine          *line,
+                                                                                gdouble                value,
+                                                                                GimpBlendTool         
*blend_tool);
+static gint                  gimp_blend_tool_editor_line_add_slider            (GimpToolLine          *line,
+                                                                                gdouble                value,
+                                                                                GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_line_remove_slider         (GimpToolLine          *line,
+                                                                                gint                   
slider,
+                                                                                GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_line_selection_changed     (GimpToolLine          *line,
+                                                                                GimpBlendTool         
*blend_tool);
+static gboolean              gimp_blend_tool_editor_line_handle_clicked        (GimpToolLine          *line,
+                                                                                gint                   
handle,
+                                                                                GdkModifierType        state,
+                                                                                GimpButtonPressType    
press_type,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_gui_response               (GimpToolGui           *gui,
+                                                                                gint                   
response_id,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_color_entry_color_clicked  (GimpColorButton       
*button,
+                                                                                GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_color_entry_color_changed  (GimpColorButton       
*button,
+                                                                                GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_color_entry_color_response (GimpColorButton       
*button,
+                                                                                GimpColorDialogState   state,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_color_entry_type_changed   (GtkComboBox           *combo,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_endpoint_se_value_changed  (GimpSizeEntry         *se,
+                                                                                GimpBlendTool         
*blend_tool);
+
+static void                  gimp_blend_tool_editor_stop_se_value_changed      (GimpSizeEntry        *se,
+                                                                                GimpBlendTool        
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_stop_delete_clicked        (GtkWidget             
*button,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                               
+static void                  gimp_blend_tool_editor_midpoint_se_value_changed  (GimpSizeEntry        *se,
+                                                                                GimpBlendTool        
*blend_tool);
+
+static void                  gimp_blend_tool_editor_midpoint_type_changed      (GtkComboBox           *combo,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                              
+static void                  gimp_blend_tool_editor_midpoint_color_changed     (GtkComboBox           *combo,
+                                                                                GimpBlendTool         
*blend_tool);
+                                                                              
+static void                  gimp_blend_tool_editor_midpoint_new_stop_clicked  (GtkWidget             
*button,
+                                                                                GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_midpoint_center_clicked    (GtkWidget             
*button,
+                                                                                GimpBlendTool         
*blend_tool);
                                                                               
-static void                  gimp_blend_tool_editor_midpoint_color_changed    (GtkComboBox          *combo,
-                                                                               GimpBlendTool        
*blend_tool);
+static gboolean              gimp_blend_tool_editor_flush_idle                 (GimpBlendTool         
*blend_tool);
                                                                               
-static void                  gimp_blend_tool_editor_midpoint_new_stop_clicked (GtkWidget            *button,
-                                                                               GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_midpoint_center_clicked   (GtkWidget            *button,
-                                                                               GimpBlendTool        
*blend_tool);
+static gboolean              gimp_blend_tool_editor_is_gradient_editable       (GimpBlendTool         
*blend_tool);
                                                                               
-static gboolean              gimp_blend_tool_editor_is_gradient_editable      (GimpBlendTool        
*blend_tool);
+static gboolean              gimp_blend_tool_editor_handle_is_endpoint         (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
handle);
+static gboolean              gimp_blend_tool_editor_handle_is_stop             (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
handle);
+static gboolean              gimp_blend_tool_editor_handle_is_midpoint         (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
handle);
+static GimpGradientSegment * gimp_blend_tool_editor_handle_get_segment         (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
handle);
                                                                               
-static gboolean              gimp_blend_tool_editor_handle_is_endpoint        (GimpBlendTool        
*blend_tool,
-                                                                               gint                  handle);
-static gboolean              gimp_blend_tool_editor_handle_is_stop            (GimpBlendTool        
*blend_tool,
-                                                                               gint                  handle);
-static gboolean              gimp_blend_tool_editor_handle_is_midpoint        (GimpBlendTool        
*blend_tool,
-                                                                               gint                  handle);
-static GimpGradientSegment * gimp_blend_tool_editor_handle_get_segment        (GimpBlendTool        
*blend_tool,
-                                                                               gint                  handle);
+static void                  gimp_blend_tool_editor_block_handlers             (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_unblock_handlers           (GimpBlendTool         
*blend_tool);
+static gboolean              gimp_blend_tool_editor_are_handlers_blocked       (GimpBlendTool         
*blend_tool);
                                                                               
-static void                  gimp_blend_tool_editor_block_handlers            (GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_unblock_handlers          (GimpBlendTool        
*blend_tool);
-static gboolean              gimp_blend_tool_editor_are_handlers_blocked      (GimpBlendTool        
*blend_tool);
+static void                  gimp_blend_tool_editor_freeze_gradient            (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_thaw_gradient              (GimpBlendTool         
*blend_tool);
                                                                               
-static void                  gimp_blend_tool_editor_freeze_gradient           (GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_thaw_gradient             (GimpBlendTool        
*blend_tool);
+static gint                  gimp_blend_tool_editor_add_stop                   (GimpBlendTool         
*blend_tool,
+                                                                                gdouble                
value);
+static void                  gimp_blend_tool_editor_delete_stop                (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
slider);
+static gint                  gimp_blend_tool_editor_midpoint_to_stop           (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
slider);
                                                                               
-static gint                  gimp_blend_tool_editor_add_stop                  (GimpBlendTool        
*blend_tool,
-                                                                               gdouble               value);
-static void                  gimp_blend_tool_editor_delete_stop               (GimpBlendTool        
*blend_tool,
-                                                                               gint                  slider);
-static gint                  gimp_blend_tool_editor_midpoint_to_stop          (GimpBlendTool        
*blend_tool,
-                                                                               gint                  slider);
+static void                  gimp_blend_tool_editor_update_sliders             (GimpBlendTool         
*blend_tool);
                                                                               
-static void                  gimp_blend_tool_editor_update_sliders            (GimpBlendTool        
*blend_tool);
+static void                  gimp_blend_tool_editor_purge_gradient_history     (GSList               
**stack);
+static void                  gimp_blend_tool_editor_purge_gradient             (GimpBlendTool         
*blend_tool);
                                                                               
-static GtkWidget           * gimp_blend_tool_editor_color_entry_new           (GimpBlendTool        
*blend_tool,
-                                                                               const gchar          *title,
-                                                                               Direction             
direction,
-                                                                               GtkWidget            
*chain_button,
-                                                                               GtkWidget           
**color_panel,
-                                                                               GtkWidget           
**type_combo);
-static void                  gimp_blend_tool_editor_init_endpoint_gui         (GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_init_stop_gui             (GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_init_midpoint_gui         (GimpBlendTool        
*blend_tool);
-static void                  gimp_blend_tool_editor_update_endpoint_gui       (GimpBlendTool        
*blend_tool,
-                                                                               gint                  
selection);
-static void                  gimp_blend_tool_editor_update_stop_gui           (GimpBlendTool        
*blend_tool,
-                                                                               gint                  
selection);
-static void                  gimp_blend_tool_editor_update_midpoint_gui       (GimpBlendTool        
*blend_tool,
-                                                                               gint                  
selection);
-static void                  gimp_blend_tool_editor_update_gui                (GimpBlendTool        
*blend_tool);
+static GtkWidget           * gimp_blend_tool_editor_color_entry_new            (GimpBlendTool         
*blend_tool,
+                                                                                const gchar           *title,
+                                                                                Direction              
direction,
+                                                                                GtkWidget             
*chain_button,
+                                                                                GtkWidget            
**color_panel,
+                                                                                GtkWidget            
**type_combo);
+static void                  gimp_blend_tool_editor_init_endpoint_gui          (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_init_stop_gui              (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_init_midpoint_gui          (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_update_endpoint_gui        (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
selection);
+static void                  gimp_blend_tool_editor_update_stop_gui            (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
selection);
+static void                  gimp_blend_tool_editor_update_midpoint_gui        (GimpBlendTool         
*blend_tool,
+                                                                                gint                   
selection);
+static void                  gimp_blend_tool_editor_update_gui                 (GimpBlendTool         
*blend_tool);
+                                                                              
+static BlendInfo           * gimp_blend_tool_editor_blend_info_new             (GimpBlendTool         
*blend_tool);
+static void                  gimp_blend_tool_editor_blend_info_free            (BlendInfo             *info);
+static void                  gimp_blend_tool_editor_blend_info_apply           (GimpBlendTool         
*blend_tool,
+                                                                                const BlendInfo       *info,
+                                                                                gboolean               
set_selection);
 
 
 /*  private functions  */
@@ -287,6 +325,13 @@ gimp_blend_tool_editor_gui_response (GimpToolGui   *gui,
 }
 
 static void
+gimp_blend_tool_editor_color_entry_color_clicked (GimpColorButton *button,
+                                                  GimpBlendTool   *blend_tool)
+{
+  gimp_blend_tool_editor_start_edit (blend_tool);
+}
+
+static void
 gimp_blend_tool_editor_color_entry_color_changed (GimpColorButton *button,
                                                   GimpBlendTool   *blend_tool)
 {
@@ -312,6 +357,7 @@ gimp_blend_tool_editor_color_entry_color_changed (GimpColorButton *button,
   chain_button = g_object_get_data (G_OBJECT (button),
                                     "gimp-blend-tool-editor-chain-button");
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   /* swap the endpoint handles, if necessary */
@@ -362,6 +408,15 @@ gimp_blend_tool_editor_color_entry_color_changed (GimpColorButton *button,
     }
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
+}
+
+static void
+gimp_blend_tool_editor_color_entry_color_response (GimpColorButton      *button,
+                                                   GimpColorDialogState  state,
+                                                   GimpBlendTool        *blend_tool)
+{
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -391,6 +446,7 @@ gimp_blend_tool_editor_color_entry_type_changed (GtkComboBox   *combo,
   chain_button = g_object_get_data (G_OBJECT (combo),
                                     "gimp-blend-tool-editor-chain-button");
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   /* swap the endpoint handles, if necessary */
@@ -437,6 +493,7 @@ gimp_blend_tool_editor_color_entry_type_changed (GtkComboBox   *combo,
     }
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -456,6 +513,7 @@ gimp_blend_tool_editor_endpoint_se_value_changed (GimpSizeEntry *se,
   x = gimp_size_entry_get_refval (se, 0);
   y = gimp_size_entry_get_refval (se, 1);
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_block_handlers (blend_tool);
 
   switch (selection)
@@ -479,6 +537,7 @@ gimp_blend_tool_editor_endpoint_se_value_changed (GimpSizeEntry *se,
     }
 
   gimp_blend_tool_editor_unblock_handlers (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -500,6 +559,7 @@ gimp_blend_tool_editor_stop_se_value_changed (GimpSizeEntry *se,
 
   value = gimp_size_entry_get_refval (se, 0) / 100.0;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, selection);
@@ -512,6 +572,7 @@ gimp_blend_tool_editor_stop_se_value_changed (GimpSizeEntry *se,
                                         value, seg->next->right);
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -545,6 +606,7 @@ gimp_blend_tool_editor_midpoint_se_value_changed (GimpSizeEntry *se,
 
   value = gimp_size_entry_get_refval (se, 0) / 100.0;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, selection);
@@ -552,6 +614,7 @@ gimp_blend_tool_editor_midpoint_se_value_changed (GimpSizeEntry *se,
   seg->middle = value;
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -571,6 +634,7 @@ gimp_blend_tool_editor_midpoint_type_changed (GtkComboBox   *combo,
   if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &type))
     return;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, selection);
@@ -578,6 +642,7 @@ gimp_blend_tool_editor_midpoint_type_changed (GtkComboBox   *combo,
   seg->type = type;
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -597,6 +662,7 @@ gimp_blend_tool_editor_midpoint_color_changed (GtkComboBox   *combo,
   if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &color))
     return;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, selection);
@@ -604,6 +670,7 @@ gimp_blend_tool_editor_midpoint_color_changed (GtkComboBox   *combo,
   seg->color = color;
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static void
@@ -631,6 +698,7 @@ gimp_blend_tool_editor_midpoint_center_clicked (GtkWidget     *button,
   selection =
     gimp_tool_line_get_selection (GIMP_TOOL_LINE (blend_tool->widget));
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, selection);
@@ -638,6 +706,19 @@ gimp_blend_tool_editor_midpoint_center_clicked (GtkWidget     *button,
   gimp_gradient_segment_range_recenter_handles (blend_tool->gradient, seg, seg);
 
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
+}
+
+static gboolean
+gimp_blend_tool_editor_flush_idle (GimpBlendTool *blend_tool)
+{
+  GimpDisplay *display = GIMP_TOOL (blend_tool)->display;
+
+  gimp_image_flush (gimp_display_get_image (display));
+
+  blend_tool->flush_idle_id = 0;
+
+  return G_SOURCE_REMOVE;
 }
 
 static gboolean
@@ -735,6 +816,7 @@ gimp_blend_tool_editor_freeze_gradient (GimpBlendTool *blend_tool)
 {
   GimpBlendOptions *options = GIMP_BLEND_TOOL_GET_OPTIONS (blend_tool);
   GimpGradient     *custom;
+  BlendInfo        *info;
 
   gimp_blend_tool_editor_block_handlers (blend_tool);
 
@@ -760,6 +842,17 @@ gimp_blend_tool_editor_freeze_gradient (GimpBlendTool *blend_tool)
       g_assert (blend_tool->gradient == custom);
       g_assert (gimp_blend_tool_editor_is_gradient_editable (blend_tool));
     }
+
+  if (blend_tool->edit_count > 0)
+    {
+      info = blend_tool->undo_stack->data;
+
+      if (! info->gradient)
+        {
+          info->gradient =
+            GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (blend_tool->gradient)));
+        }
+    }
 }
 
 static void
@@ -780,7 +873,9 @@ gimp_blend_tool_editor_add_stop (GimpBlendTool *blend_tool,
   GimpBlendOptions    *options = GIMP_BLEND_TOOL_GET_OPTIONS (blend_tool);
   GimpGradientSegment *seg;
   gint                 stop;
+  BlendInfo           *info;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   gimp_gradient_split_at (blend_tool->gradient,
@@ -791,7 +886,11 @@ gimp_blend_tool_editor_add_stop (GimpBlendTool *blend_tool,
                                                 blend_tool->gradient->segments,
                                                 seg) - 1;
 
+  info               = blend_tool->undo_stack->data;
+  info->added_handle = stop;
+
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 
   return stop;
 }
@@ -801,7 +900,9 @@ gimp_blend_tool_editor_delete_stop (GimpBlendTool *blend_tool,
                                     gint           slider)
 {
   GimpGradientSegment *seg;
+  BlendInfo           *info;
 
+  gimp_blend_tool_editor_start_edit (blend_tool);
   gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
   seg = gimp_blend_tool_editor_handle_get_segment (blend_tool, slider);
@@ -809,7 +910,15 @@ gimp_blend_tool_editor_delete_stop (GimpBlendTool *blend_tool,
   gimp_gradient_segment_range_merge (blend_tool->gradient,
                                      seg, seg->next, NULL, NULL);
 
+  info = blend_tool->undo_stack->data;
+
+  if (info->added_handle == slider)
+    info->added_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+  else
+    info->removed_handle = slider;
+
   gimp_blend_tool_editor_thaw_gradient (blend_tool);
+  gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
 }
 
 static gint
@@ -824,8 +933,16 @@ gimp_blend_tool_editor_midpoint_to_stop (GimpBlendTool *blend_tool,
   if (sliders[slider].value > sliders[slider].min + EPSILON &&
       sliders[slider].value < sliders[slider].max - EPSILON)
     {
-      slider = gimp_blend_tool_editor_add_stop (blend_tool,
-                                                sliders[slider].value);
+      gint       stop;
+      BlendInfo *info;
+
+      stop = gimp_blend_tool_editor_add_stop (blend_tool,
+                                              sliders[slider].value);
+
+      info                 = blend_tool->undo_stack->data;
+      info->removed_handle = slider;
+
+      slider = stop;
     }
 
   return slider;
@@ -939,6 +1056,44 @@ gimp_blend_tool_editor_update_sliders (GimpBlendTool *blend_tool)
   g_free (sliders);
 }
 
+static void
+gimp_blend_tool_editor_purge_gradient_history (GSList **stack)
+{
+  GSList *link;
+
+  /* eliminate all history steps that modify the gradient */
+  while ((link = *stack))
+    {
+      BlendInfo *info = link->data;
+
+      if (info->gradient)
+        {
+          gimp_blend_tool_editor_blend_info_free (info);
+
+          *stack = g_slist_delete_link (*stack, link);
+        }
+      else
+        {
+          stack = &link->next;
+        }
+    }
+}
+
+static void
+gimp_blend_tool_editor_purge_gradient (GimpBlendTool *blend_tool)
+{
+  if (blend_tool->widget)
+    {
+      gimp_blend_tool_editor_update_sliders (blend_tool);
+
+      gimp_tool_line_set_selection (GIMP_TOOL_LINE (blend_tool->widget),
+                                    GIMP_TOOL_LINE_HANDLE_NONE);
+    }
+
+  gimp_blend_tool_editor_purge_gradient_history (&blend_tool->undo_stack);
+  gimp_blend_tool_editor_purge_gradient_history (&blend_tool->redo_stack);
+}
+
 static GtkWidget *
 gimp_blend_tool_editor_color_entry_new (GimpBlendTool  *blend_tool,
                                         const gchar    *title,
@@ -971,9 +1126,15 @@ gimp_blend_tool_editor_color_entry_new (GimpBlendTool  *blend_tool,
                      "gimp-blend-tool-editor-chain-button",
                      chain_button);
 
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (gimp_blend_tool_editor_color_entry_color_clicked),
+                    blend_tool);
   g_signal_connect (button, "color-changed",
                     G_CALLBACK (gimp_blend_tool_editor_color_entry_color_changed),
                     blend_tool);
+  g_signal_connect (button, "response",
+                    G_CALLBACK (gimp_blend_tool_editor_color_entry_color_response),
+                    blend_tool);
 
   /* the color type combo */
   *type_combo = combo = gimp_enum_combo_box_new (GIMP_TYPE_GRADIENT_COLOR);
@@ -1644,6 +1805,187 @@ gimp_blend_tool_editor_update_gui (GimpBlendTool *blend_tool)
     gimp_tool_gui_hide (blend_tool->gui);
 }
 
+static BlendInfo *
+gimp_blend_tool_editor_blend_info_new (GimpBlendTool *blend_tool)
+{
+  BlendInfo *info = g_slice_new (BlendInfo);
+
+  info->start_x         = blend_tool->start_x;
+  info->start_y         = blend_tool->start_y;
+  info->end_x           = blend_tool->end_x;
+  info->end_y           = blend_tool->end_y;
+
+  info->gradient        = NULL;
+
+  info->added_handle    = GIMP_TOOL_LINE_HANDLE_NONE;
+  info->removed_handle  = GIMP_TOOL_LINE_HANDLE_NONE;
+  info->selected_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+
+  return info;
+}
+
+static void
+gimp_blend_tool_editor_blend_info_free (BlendInfo *info)
+{
+  if (info->gradient)
+    g_object_unref (info->gradient);
+
+  g_slice_free (BlendInfo, info);
+}
+
+static void
+gimp_blend_tool_editor_blend_info_apply (GimpBlendTool   *blend_tool,
+                                         const BlendInfo *info,
+                                         gboolean         set_selection)
+{
+  gint selection;
+
+  g_assert (blend_tool->widget   != NULL);
+  g_assert (blend_tool->gradient != NULL);
+
+  /* pick the handle to select */
+  if (info->gradient)
+    {
+      if (info->removed_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+        {
+          /* we're undoing a stop-deletion or midpoint-to-stop operation;
+           * select the removed handle
+           */
+          selection = info->removed_handle;
+        }
+      else if (info->added_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+        {
+          /* we're undoing a stop addition operation */
+          g_assert (gimp_blend_tool_editor_handle_is_stop (blend_tool,
+                                                           info->added_handle));
+
+          selection =
+            gimp_tool_line_get_selection (GIMP_TOOL_LINE (blend_tool->widget));
+
+          /* if the selected handle is a stop... */
+          if (gimp_blend_tool_editor_handle_is_stop (blend_tool, selection))
+            {
+              /* if the added handle is selected, clear the selection */
+              if (selection == info->added_handle)
+                selection = GIMP_TOOL_LINE_HANDLE_NONE;
+              /* otherwise, keep the currently selected stop, possibly
+               * adjusting its handle index
+               */
+              else if (selection > info->added_handle)
+                selection--;
+            }
+          /* otherwise, if the selected handle is a midpoint... */
+          else if (gimp_blend_tool_editor_handle_is_midpoint (blend_tool, selection))
+            {
+              const GimpControllerSlider *sliders;
+              gint                        seg_i;
+
+              sliders =
+                gimp_tool_line_get_sliders (GIMP_TOOL_LINE (blend_tool->widget),
+                                            NULL);
+
+              seg_i = GPOINTER_TO_INT (sliders[selection].data);
+
+              /* if the midpoint belongs to one of the two segments incident to
+               * the added stop, clear the selection
+               */
+              if (seg_i == info->added_handle ||
+                  seg_i == info->added_handle + 1)
+                {
+                  selection = GIMP_TOOL_LINE_HANDLE_NONE;
+                }
+              /* otherwise, keep the currently selected stop, adjusting its
+               * handle index
+               */
+              else
+                {
+                  /* midpoint handles follow stop handles; since we removed a
+                   * stop, we must decrement the handle index
+                   */
+                  selection--;
+
+                  if (seg_i > info->added_handle)
+                    selection--;
+                }
+            }
+          /* otherwise, don't change the selection */
+          else
+            {
+              set_selection = FALSE;
+            }
+        }
+      else if (info->selected_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+        {
+          /* we're undoing a property change operation; select the handle
+           * corresponding to the affected object
+           */
+          selection = info->selected_handle;
+        }
+      else
+        {
+          /* we're undoing an operation in which the same handle was added and
+           * then removed; don't change the selection
+           */
+          set_selection = FALSE;
+        }
+    }
+  else if ((info->start_x != blend_tool->start_x  ||
+            info->start_y != blend_tool->start_y) &&
+           (info->end_x   == blend_tool->end_x    &&
+            info->end_y   == blend_tool->end_y))
+    {
+      /* we're undoing a start-endpoint move operation; select the start
+       * endpoint
+       */
+      selection = GIMP_TOOL_LINE_HANDLE_START;
+    }
+  else if ((info->end_x   != blend_tool->end_x    ||
+            info->end_y   != blend_tool->end_y)   &&
+           (info->start_x == blend_tool->start_x  &&
+            info->start_y == blend_tool->start_y))
+
+    {
+      /* we're undoing am end-endpoint move operation; select the end
+       * endpoint
+       */
+      selection = GIMP_TOOL_LINE_HANDLE_END;
+    }
+  else
+    {
+      /* we're undoing a line move operation; don't change the selection */
+      set_selection = FALSE;
+    }
+
+  gimp_blend_tool_editor_block_handlers (blend_tool);
+
+  g_object_set (blend_tool->widget,
+                "x1", info->start_x,
+                "y1", info->start_y,
+                "x2", info->end_x,
+                "y2", info->end_y,
+                NULL);
+
+  if (info->gradient)
+    {
+      gimp_blend_tool_editor_freeze_gradient (blend_tool);
+
+      gimp_data_copy (GIMP_DATA (blend_tool->gradient),
+                      GIMP_DATA (info->gradient));
+
+      gimp_blend_tool_editor_thaw_gradient (blend_tool);
+    }
+
+  if (set_selection)
+    {
+      gimp_tool_line_set_selection (GIMP_TOOL_LINE (blend_tool->widget),
+                                    selection);
+    }
+
+  gimp_blend_tool_editor_update_gui (blend_tool);
+
+  gimp_blend_tool_editor_unblock_handlers (blend_tool);
+}
+
 
 /*  public functions  */
 
@@ -1715,6 +2057,28 @@ void
 gimp_blend_tool_editor_halt (GimpBlendTool *blend_tool)
 {
   g_clear_object (&blend_tool->gui);
+
+  blend_tool->edit_count = 0;
+
+  if (blend_tool->undo_stack)
+    {
+      g_slist_free_full (blend_tool->undo_stack,
+                         (GDestroyNotify) gimp_blend_tool_editor_blend_info_free);
+      blend_tool->undo_stack = NULL;
+    }
+
+  if (blend_tool->redo_stack)
+    {
+      g_slist_free_full (blend_tool->redo_stack,
+                         (GDestroyNotify) gimp_blend_tool_editor_blend_info_free);
+      blend_tool->redo_stack = NULL;
+    }
+
+  if (blend_tool->flush_idle_id)
+    {
+      g_source_remove (blend_tool->flush_idle_id);
+      blend_tool->flush_idle_id = 0;
+    }
 }
 
 void
@@ -1763,6 +2127,7 @@ gimp_blend_tool_editor_line_changed (GimpBlendTool *blend_tool)
         {
           if (! changed)
             {
+              gimp_blend_tool_editor_start_edit (blend_tool);
               gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
               /* refetch the segment, since the gradient might have changed */
@@ -1795,6 +2160,7 @@ gimp_blend_tool_editor_line_changed (GimpBlendTool *blend_tool)
         {
           if (! changed)
             {
+              gimp_blend_tool_editor_start_edit (blend_tool);
               gimp_blend_tool_editor_freeze_gradient (blend_tool);
 
               /* refetch the segment, since the gradient might have changed */
@@ -1813,7 +2179,10 @@ gimp_blend_tool_editor_line_changed (GimpBlendTool *blend_tool)
     }
 
   if (changed)
-    gimp_blend_tool_editor_thaw_gradient (blend_tool);
+    {
+      gimp_blend_tool_editor_thaw_gradient (blend_tool);
+      gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
+    }
 
   gimp_blend_tool_editor_update_gui (blend_tool);
 }
@@ -1824,13 +2193,7 @@ gimp_blend_tool_editor_gradient_dirty (GimpBlendTool *blend_tool)
   if (gimp_blend_tool_editor_are_handlers_blocked (blend_tool))
     return;
 
-  if (blend_tool->widget)
-    {
-      gimp_blend_tool_editor_update_sliders (blend_tool);
-
-      gimp_tool_line_set_selection (GIMP_TOOL_LINE (blend_tool->widget),
-                                    GIMP_TOOL_LINE_HANDLE_NONE);
-    }
+  gimp_blend_tool_editor_purge_gradient (blend_tool);
 }
 
 void
@@ -1856,11 +2219,160 @@ gimp_blend_tool_editor_gradient_changed (GimpBlendTool *blend_tool)
   if (gimp_blend_tool_editor_are_handlers_blocked (blend_tool))
     return;
 
-  if (blend_tool->widget)
+  gimp_blend_tool_editor_purge_gradient (blend_tool);
+}
+
+const gchar *
+gimp_blend_tool_editor_can_undo (GimpBlendTool *blend_tool)
+{
+  if (! blend_tool->undo_stack || blend_tool->edit_count > 0)
+    return NULL;
+
+  return _("Blend Step");
+}
+
+const gchar *
+gimp_blend_tool_editor_can_redo (GimpBlendTool *blend_tool)
+{
+  if (! blend_tool->redo_stack || blend_tool->edit_count > 0)
+    return NULL;
+
+  return _("Blend Step");
+}
+
+gboolean
+gimp_blend_tool_editor_undo (GimpBlendTool *blend_tool)
+{
+  GimpTool  *tool = GIMP_TOOL (blend_tool);
+  BlendInfo *info;
+  BlendInfo *new_info;
+
+  g_assert (blend_tool->undo_stack != NULL);
+  g_assert (blend_tool->edit_count == 0);
+
+  info = blend_tool->undo_stack->data;
+
+  new_info = gimp_blend_tool_editor_blend_info_new (blend_tool);
+
+  if (info->gradient)
     {
-      gimp_blend_tool_editor_update_sliders (blend_tool);
+      new_info->gradient =
+        GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (blend_tool->gradient)));
 
-      gimp_tool_line_set_selection (GIMP_TOOL_LINE (blend_tool->widget),
-                                    GIMP_TOOL_LINE_HANDLE_NONE);
+      /* swap the added and removed handles, so that blend_info_apply() does
+       * the right thing on redo
+       */
+      new_info->added_handle    = info->removed_handle;
+      new_info->removed_handle  = info->added_handle;
+      new_info->selected_handle = info->selected_handle;
+    }
+
+  blend_tool->undo_stack = g_slist_remove (blend_tool->undo_stack, info);
+  blend_tool->redo_stack = g_slist_prepend (blend_tool->redo_stack, new_info);
+
+  gimp_blend_tool_editor_blend_info_apply (blend_tool, info, TRUE);
+  gimp_blend_tool_editor_blend_info_free (info);
+
+  /* the initial state of the blend tool is not useful; we might as well halt */
+  if (! blend_tool->undo_stack)
+    gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+  return TRUE;
+}
+
+gboolean
+gimp_blend_tool_editor_redo (GimpBlendTool *blend_tool)
+{
+  BlendInfo *info;
+  BlendInfo *new_info;
+
+  g_assert (blend_tool->redo_stack != NULL);
+  g_assert (blend_tool->edit_count == 0);
+
+  info = blend_tool->redo_stack->data;
+
+  new_info = gimp_blend_tool_editor_blend_info_new (blend_tool);
+
+  if (info->gradient)
+    {
+      new_info->gradient =
+        GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (blend_tool->gradient)));
+
+      /* swap the added and removed handles, so that blend_info_apply() does
+       * the right thing on undo
+       */
+      new_info->added_handle    = info->removed_handle;
+      new_info->removed_handle  = info->added_handle;
+      new_info->selected_handle = info->selected_handle;
+    }
+
+  blend_tool->redo_stack = g_slist_remove (blend_tool->redo_stack, info);
+  blend_tool->undo_stack = g_slist_prepend (blend_tool->undo_stack, new_info);
+
+  gimp_blend_tool_editor_blend_info_apply (blend_tool, info, TRUE);
+  gimp_blend_tool_editor_blend_info_free (info);
+
+  return TRUE;
+}
+
+void
+gimp_blend_tool_editor_start_edit (GimpBlendTool *blend_tool)
+{
+  if (blend_tool->edit_count++ == 0)
+    {
+      BlendInfo *info;
+
+      info = gimp_blend_tool_editor_blend_info_new (blend_tool);
+
+      blend_tool->undo_stack = g_slist_prepend (blend_tool->undo_stack, info);
+    }
+}
+
+void
+gimp_blend_tool_editor_end_edit (GimpBlendTool *blend_tool,
+                                 gboolean       cancel)
+{
+  /* can happen when halting using esc */
+  if (blend_tool->edit_count == 0)
+    return;
+
+  if (--blend_tool->edit_count == 0)
+    {
+      BlendInfo *info = blend_tool->undo_stack->data;
+
+      info->selected_handle =
+        gimp_tool_line_get_selection (GIMP_TOOL_LINE (blend_tool->widget));
+
+      if (cancel                                ||
+          (info->start_x == blend_tool->start_x &&
+           info->start_y == blend_tool->start_y &&
+           info->end_x   == blend_tool->end_x   &&
+           info->end_y   == blend_tool->end_y   &&
+           ! info->gradient))
+        {
+          /* if the edit is canceled, or if nothing changed, undo the last
+           * step
+           */
+          gimp_blend_tool_editor_blend_info_apply (blend_tool, info, FALSE);
+
+          blend_tool->undo_stack = g_slist_remove (blend_tool->undo_stack,
+                                                   info);
+          gimp_blend_tool_editor_blend_info_free (info);
+        }
+      else
+        {
+          /* otherwise, blow the redo stack */
+          g_slist_free_full (blend_tool->redo_stack,
+                             (GDestroyNotify) gimp_blend_tool_editor_blend_info_free);
+          blend_tool->redo_stack = NULL;
+        }
+
+      /*  update the undo actions / menu items  */
+      if (! blend_tool->flush_idle_id)
+        {
+          blend_tool->flush_idle_id =
+            g_idle_add ((GSourceFunc) gimp_blend_tool_editor_flush_idle,
+                        blend_tool);
+        }
     }
 }
diff --git a/app/tools/gimpblendtool-editor.h b/app/tools/gimpblendtool-editor.h
index fc7bcf2..c72fa47 100644
--- a/app/tools/gimpblendtool-editor.h
+++ b/app/tools/gimpblendtool-editor.h
@@ -19,18 +19,28 @@
 #define  __GIMP_BLEND_TOOL_EDITOR_H__
 
 
-void   gimp_blend_tool_editor_options_notify   (GimpBlendTool    *blend_tool,
-                                                GimpToolOptions  *options,
-                                                const GParamSpec *pspec);
+void          gimp_blend_tool_editor_options_notify   (GimpBlendTool    *blend_tool,
+                                                       GimpToolOptions  *options,
+                                                       const GParamSpec *pspec);
 
-void   gimp_blend_tool_editor_start            (GimpBlendTool    *blend_tool);
-void   gimp_blend_tool_editor_halt             (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_start            (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_halt             (GimpBlendTool    *blend_tool);
 
-void   gimp_blend_tool_editor_line_changed     (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_line_changed     (GimpBlendTool    *blend_tool);
 
-void   gimp_blend_tool_editor_gradient_dirty   (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_gradient_dirty   (GimpBlendTool    *blend_tool);
 
-void   gimp_blend_tool_editor_gradient_changed (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_gradient_changed (GimpBlendTool    *blend_tool);
+
+const gchar * gimp_blend_tool_editor_can_undo         (GimpBlendTool    *blend_tool);
+const gchar * gimp_blend_tool_editor_can_redo         (GimpBlendTool    *blend_tool);
+
+gboolean      gimp_blend_tool_editor_undo             (GimpBlendTool    *blend_tool);
+gboolean      gimp_blend_tool_editor_redo             (GimpBlendTool    *blend_tool);
+
+void          gimp_blend_tool_editor_start_edit       (GimpBlendTool    *blend_tool);
+void          gimp_blend_tool_editor_end_edit         (GimpBlendTool    *blend_tool,
+                                                       gboolean          cancel);
 
 
 #endif  /*  __GIMP_BLEND_TOOL_EDITOR_H__  */
diff --git a/app/tools/gimpblendtool.c b/app/tools/gimpblendtool.c
index 3d83241..9feea2d 100644
--- a/app/tools/gimpblendtool.c
+++ b/app/tools/gimpblendtool.c
@@ -53,17 +53,6 @@
 #include "gimp-intl.h"
 
 
-typedef struct _BlendInfo BlendInfo;
-
-struct _BlendInfo
-{
-  gdouble start_x;
-  gdouble start_y;
-  gdouble end_x;
-  gdouble end_y;
-};
-
-
 /*  local function prototypes  */
 
 static void   gimp_blend_tool_dispose             (GObject               *object);
@@ -91,6 +80,9 @@ static void   gimp_blend_tool_motion              (GimpTool              *tool,
                                                    guint32                time,
                                                    GdkModifierType        state,
                                                    GimpDisplay           *display);
+static gboolean gimp_blend_tool_key_press         (GimpTool              *tool,
+                                                   GdkEventKey           *kevent,
+                                                   GimpDisplay           *display);
 static void   gimp_blend_tool_modifier_key        (GimpTool              *tool,
                                                    GdkModifierType        key,
                                                    gboolean               press,
@@ -140,12 +132,6 @@ static void   gimp_blend_tool_create_filter       (GimpBlendTool         *blend_
 static void   gimp_blend_tool_filter_flush        (GimpDrawableFilter    *filter,
                                                    GimpTool              *tool);
 
-static BlendInfo * blend_info_new  (gdouble    start_x,
-                                    gdouble    start_y,
-                                    gdouble    end_x,
-                                    gdouble    end_y);
-static void        blend_info_free (BlendInfo *info);
-
 
 G_DEFINE_TYPE (GimpBlendTool, gimp_blend_tool, GIMP_TYPE_DRAW_TOOL)
 
@@ -186,6 +172,7 @@ gimp_blend_tool_class_init (GimpBlendToolClass *klass)
   tool_class->button_press   = gimp_blend_tool_button_press;
   tool_class->button_release = gimp_blend_tool_button_release;
   tool_class->motion         = gimp_blend_tool_motion;
+  tool_class->key_press      = gimp_blend_tool_key_press;
   tool_class->modifier_key   = gimp_blend_tool_modifier_key;
   tool_class->cursor_update  = gimp_blend_tool_cursor_update;
   tool_class->can_undo       = gimp_blend_tool_can_undo;
@@ -310,11 +297,8 @@ gimp_blend_tool_button_press (GimpTool            *tool,
                               GimpButtonPressType  press_type,
                               GimpDisplay         *display)
 {
-  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
-  gdouble        start_x;
-  gdouble        start_y;
-  gdouble        end_x;
-  gdouble        end_y;
+  GimpBlendTool    *blend_tool = GIMP_BLEND_TOOL (tool);
+  GimpBlendOptions *options    = GIMP_BLEND_TOOL_GET_OPTIONS (tool);
 
   if (tool->display && display != tool->display)
     gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
@@ -326,21 +310,18 @@ gimp_blend_tool_button_press (GimpTool            *tool,
       gimp_tool_widget_hover (blend_tool->widget, coords, state, TRUE);
     }
 
-  /*  save the current line for undo, widget_button_press() might change it
+  /* call start_edit() before widget_button_press(), because we need to record
+   * the undo state before widget_button_press() potentially changes it.  note
+   * that if widget_button_press() return FALSE, nothing changes and no undo
+   * step is created.
    */
-  start_x = blend_tool->start_x;
-  start_y = blend_tool->start_y;
-  end_x   = blend_tool->end_x;
-  end_y   = blend_tool->end_y;
+  if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+    gimp_blend_tool_editor_start_edit (blend_tool);
 
   if (gimp_tool_widget_button_press (blend_tool->widget, coords, time, state,
                                      press_type))
     {
       blend_tool->grab_widget = blend_tool->widget;
-
-      blend_tool->undo_stack =
-        g_list_prepend (blend_tool->undo_stack,
-                        blend_info_new (start_x, start_y, end_x, end_y));
     }
 
   if (press_type == GIMP_BUTTON_PRESS_NORMAL)
@@ -375,32 +356,13 @@ gimp_blend_tool_button_release (GimpTool              *tool,
           else
             gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
         }
-      else
-        {
-          if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
-            {
-              /*  simply destroy the undo step we pushed in button_press(),
-               *  the tool widget restored the old position by itself
-               */
-              blend_info_free (blend_tool->undo_stack->data);
-              blend_tool->undo_stack = g_list_remove (blend_tool->undo_stack,
-                                                      blend_tool->undo_stack->data);
-            }
-          else
-            {
-              /*  blow the redo stack, we had an actual undoable movement
-               */
-              if (blend_tool->redo_stack)
-                {
-                  g_list_free_full (blend_tool->redo_stack,
-                                    (GDestroyNotify) blend_info_free);
-                  blend_tool->redo_stack = NULL;
-                }
-            }
-
-          /*  update the undo actions / menu items  */
-          gimp_image_flush (gimp_display_get_image (display));
-        }
+    }
+
+  if (! options->instant)
+    {
+      gimp_blend_tool_editor_end_edit (blend_tool,
+                                       release_type ==
+                                       GIMP_BUTTON_RELEASE_CANCEL);
     }
 }
 
@@ -419,6 +381,31 @@ gimp_blend_tool_motion (GimpTool         *tool,
     }
 }
 
+static gboolean
+gimp_blend_tool_key_press (GimpTool    *tool,
+                           GdkEventKey *kevent,
+                           GimpDisplay *display)
+{
+  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
+  GimpDrawTool  *draw_tool  = GIMP_DRAW_TOOL (tool);
+  gboolean       result;
+
+  /* call start_edit() before widget_key_press(), because we need to record the
+   * undo state before widget_key_press() potentially changes it.  note that if
+   * widget_key_press() return FALSE, nothing changes and no undo step is
+   * created.
+   */
+  if (display == draw_tool->display)
+    gimp_blend_tool_editor_start_edit (blend_tool);
+
+  result = GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+
+  if (display == draw_tool->display)
+    gimp_blend_tool_editor_end_edit (blend_tool, FALSE);
+
+  return result;
+}
+
 static void
 gimp_blend_tool_modifier_key (GimpTool        *tool,
                               GdkModifierType  key,
@@ -467,80 +454,28 @@ static const gchar *
 gimp_blend_tool_can_undo (GimpTool    *tool,
                           GimpDisplay *display)
 {
-  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
-
-  if (! blend_tool->undo_stack)
-    return NULL;
-
-  return _("Blend Step");
+  return gimp_blend_tool_editor_can_undo (GIMP_BLEND_TOOL (tool));
 }
 
 static const gchar *
 gimp_blend_tool_can_redo (GimpTool    *tool,
                           GimpDisplay *display)
 {
-  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
-
-  if (! blend_tool->redo_stack)
-    return NULL;
-
-  return _("Blend Step");
+  return gimp_blend_tool_editor_can_redo (GIMP_BLEND_TOOL (tool));
 }
 
 static gboolean
 gimp_blend_tool_undo (GimpTool    *tool,
                       GimpDisplay *display)
 {
-  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
-  BlendInfo     *info;
-
-  info = blend_info_new (blend_tool->start_x,
-                         blend_tool->start_y,
-                         blend_tool->end_x,
-                         blend_tool->end_y);
-  blend_tool->redo_stack = g_list_prepend (blend_tool->redo_stack, info);
-
-  info = blend_tool->undo_stack->data;
-
-  g_object_set (blend_tool->widget,
-                "x1", info->start_x,
-                "y1", info->start_y,
-                "x2", info->end_x,
-                "y2", info->end_y,
-                NULL);
-
-  blend_tool->undo_stack = g_list_remove (blend_tool->undo_stack, info);
-  blend_info_free (info);
-
-  return TRUE;
+  return gimp_blend_tool_editor_undo (GIMP_BLEND_TOOL (tool));
 }
 
 static gboolean
 gimp_blend_tool_redo (GimpTool    *tool,
                       GimpDisplay *display)
 {
-  GimpBlendTool *blend_tool = GIMP_BLEND_TOOL (tool);
-  BlendInfo     *info;
-
-  info = blend_info_new (blend_tool->start_x,
-                         blend_tool->start_y,
-                         blend_tool->end_x,
-                         blend_tool->end_y);
-  blend_tool->undo_stack = g_list_prepend (blend_tool->undo_stack, info);
-
-  info = blend_tool->redo_stack->data;
-
-  g_object_set (blend_tool->widget,
-                "x1", info->start_x,
-                "y1", info->start_y,
-                "x2", info->end_x,
-                "y2", info->end_y,
-                NULL);
-
-  blend_tool->redo_stack = g_list_remove (blend_tool->redo_stack, info);
-  blend_info_free (info);
-
-  return TRUE;
+  return gimp_blend_tool_editor_redo (GIMP_BLEND_TOOL (tool));
 }
 
 static void
@@ -693,20 +628,6 @@ gimp_blend_tool_halt (GimpBlendTool *blend_tool)
       gimp_image_flush (gimp_display_get_image (tool->display));
     }
 
-  if (blend_tool->undo_stack)
-    {
-      g_list_free_full (blend_tool->undo_stack,
-                        (GDestroyNotify) blend_info_free);
-      blend_tool->undo_stack = NULL;
-    }
-
-  if (blend_tool->redo_stack)
-    {
-      g_list_free_full (blend_tool->redo_stack,
-                        (GDestroyNotify) blend_info_free);
-      blend_tool->redo_stack = NULL;
-    }
-
   if (tool->display)
     gimp_tool_pop_status (tool, tool->display);
 
@@ -1021,25 +942,3 @@ gimp_blend_tool_filter_flush (GimpDrawableFilter *filter,
 
   gimp_projection_flush (gimp_image_get_projection (image));
 }
-
-static BlendInfo *
-blend_info_new (gdouble start_x,
-                gdouble start_y,
-                gdouble end_x,
-                gdouble end_y)
-{
-  BlendInfo *info = g_slice_new0 (BlendInfo);
-
-  info->start_x = start_x;
-  info->start_y = start_y;
-  info->end_x   = end_x;
-  info->end_y   = end_y;
-
-  return info;
-}
-
-static void
-blend_info_free (BlendInfo *info)
-{
-  g_slice_free (BlendInfo, info);
-}
diff --git a/app/tools/gimpblendtool.h b/app/tools/gimpblendtool.h
index f8affba..0231c45 100644
--- a/app/tools/gimpblendtool.h
+++ b/app/tools/gimpblendtool.h
@@ -46,9 +46,6 @@ struct _GimpBlendTool
   gdouble             end_x;      /*  ending x coord    */
   gdouble             end_y;      /*  ending y coord    */
 
-  GList              *undo_stack;
-  GList              *redo_stack;
-
   GimpToolWidget     *widget;
   GimpToolWidget     *grab_widget;
 
@@ -66,6 +63,12 @@ struct _GimpBlendTool
 
   gint                block_handlers_count;
 
+  gint                edit_count;
+  GSList             *undo_stack;
+  GSList             *redo_stack;
+
+  guint               flush_idle_id;
+
   GimpToolGui        *gui;
   GtkWidget          *endpoint_editor;
   GtkWidget          *endpoint_se;


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