From 4842e2a14ff14f58f3eba6985fa7c77f961de24a Mon Sep 17 00:00:00 2001 From: Michael Natterer Date: Sat, 28 Mar 2015 21:31:03 +0100 Subject: [PATCH] Bug 670031 - Would like to undo intelligent scissors selections in progress Add undo to the Isissors tool, along with some refactoring: - Always modify the actual curve, instead of a set of obscure states kept around in the tool instance - On cancel, simply go back to the curve on the undo stack - Draw handles on top of curve segments - Draw the currently edited segments and handles in the highlight color --- app/tools/gimpiscissorstool.c | 717 +++++++++++++++++++++++----------- app/tools/gimpiscissorstool.h | 15 +- 2 files changed, 485 insertions(+), 247 deletions(-) diff --git a/app/tools/gimpiscissorstool.c b/app/tools/gimpiscissorstool.c index 6d6761c871..0a613c765d 100644 --- a/app/tools/gimpiscissorstool.c +++ b/app/tools/gimpiscissorstool.c @@ -68,6 +68,7 @@ #include "widgets/gimphelp-ids.h" #include "widgets/gimpwidgets-utils.h" +#include "display/gimpcanvasitem.h" #include "display/gimpdisplay.h" #include "gimpiscissorsoptions.h" @@ -114,47 +115,59 @@ struct _ICurve /* local function prototypes */ -static void gimp_iscissors_tool_finalize (GObject *object); +static void gimp_iscissors_tool_finalize (GObject *object); -static void gimp_iscissors_tool_control (GimpTool *tool, - GimpToolAction action, - GimpDisplay *display); -static void gimp_iscissors_tool_button_press (GimpTool *tool, - const GimpCoords *coords, - guint32 time, - GdkModifierType state, - GimpButtonPressType press_type, - GimpDisplay *display); -static void gimp_iscissors_tool_button_release (GimpTool *tool, - const GimpCoords *coords, - guint32 time, - GdkModifierType state, - GimpButtonReleaseType release_type, - GimpDisplay *display); -static void gimp_iscissors_tool_motion (GimpTool *tool, - const GimpCoords *coords, - guint32 time, - GdkModifierType state, - GimpDisplay *display); -static void gimp_iscissors_tool_oper_update (GimpTool *tool, - const GimpCoords *coords, - GdkModifierType state, - gboolean proximity, - GimpDisplay *display); -static void gimp_iscissors_tool_cursor_update (GimpTool *tool, - const GimpCoords *coords, - GdkModifierType state, - GimpDisplay *display); -static gboolean gimp_iscissors_tool_key_press (GimpTool *tool, - GdkEventKey *kevent, - GimpDisplay *display); +static void gimp_iscissors_tool_control (GimpTool *tool, + GimpToolAction action, + GimpDisplay *display); +static void gimp_iscissors_tool_button_press (GimpTool *tool, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type, + GimpDisplay *display); +static void gimp_iscissors_tool_button_release (GimpTool *tool, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type, + GimpDisplay *display); +static void gimp_iscissors_tool_motion (GimpTool *tool, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpDisplay *display); +static void gimp_iscissors_tool_oper_update (GimpTool *tool, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity, + GimpDisplay *display); +static void gimp_iscissors_tool_cursor_update (GimpTool *tool, + const GimpCoords *coords, + GdkModifierType state, + GimpDisplay *display); +static gboolean gimp_iscissors_tool_key_press (GimpTool *tool, + GdkEventKey *kevent, + GimpDisplay *display); +static const gchar * gimp_iscissors_tool_get_undo_desc (GimpTool *tool, + GimpDisplay *display); +static const gchar * gimp_iscissors_tool_get_redo_desc (GimpTool *tool, + GimpDisplay *display); +static gboolean gimp_iscissors_tool_undo (GimpTool *tool, + GimpDisplay *display); +static gboolean gimp_iscissors_tool_redo (GimpTool *tool, + GimpDisplay *display); -static void gimp_iscissors_tool_draw (GimpDrawTool *draw_tool); +static void gimp_iscissors_tool_draw (GimpDrawTool *draw_tool); -static void gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors, - GimpDisplay *display); -static void gimp_iscissors_tool_commit (GimpIscissorsTool *iscissors, - GimpDisplay *display); +static void gimp_iscissors_tool_push_undo (GimpIscissorsTool *iscissors); +static void gimp_iscissors_tool_pop_undo (GimpIscissorsTool *iscissors); +static void gimp_iscissors_tool_free_redo (GimpIscissorsTool *iscissors); + + static void gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors, + GimpDisplay *display); +static void gimp_iscissors_tool_commit (GimpIscissorsTool *iscissors, + GimpDisplay *display); static void iscissors_convert (GimpIscissorsTool *iscissors, GimpDisplay *display); @@ -174,7 +187,7 @@ static void find_max_gradient (GimpIscissorsTool *iscissors, gint *y); static void calculate_segment (GimpIscissorsTool *iscissors, ISegment *segment); -static void iscissors_draw_segment (GimpDrawTool *draw_tool, +static GimpCanvasItem * iscissors_draw_segment (GimpDrawTool *draw_tool, ISegment *segment); static gint mouse_over_vertex (GimpIscissorsTool *iscissors, @@ -203,9 +216,11 @@ static ISegment * isegment_new (gint x1, gint y1, gint x2, gint y2); +static ISegment * isegment_copy (ISegment *segment); static void isegment_free (ISegment *segment); static ICurve * icurve_new (void); +static ICurve * icurve_copy (ICurve *curve); static void icurve_clear (ICurve *curve); static void icurve_free (ICurve *curve); @@ -280,9 +295,13 @@ gimp_iscissors_tool_class_init (GimpIscissorsToolClass *klass) tool_class->button_press = gimp_iscissors_tool_button_press; tool_class->button_release = gimp_iscissors_tool_button_release; tool_class->motion = gimp_iscissors_tool_motion; + tool_class->key_press = gimp_iscissors_tool_key_press; tool_class->oper_update = gimp_iscissors_tool_oper_update; tool_class->cursor_update = gimp_iscissors_tool_cursor_update; - tool_class->key_press = gimp_iscissors_tool_key_press; + tool_class->get_undo_desc = gimp_iscissors_tool_get_undo_desc; + tool_class->get_redo_desc = gimp_iscissors_tool_get_redo_desc; + tool_class->undo = gimp_iscissors_tool_undo; + tool_class->redo = gimp_iscissors_tool_redo; draw_tool_class->draw = gimp_iscissors_tool_draw; @@ -376,8 +395,10 @@ gimp_iscissors_tool_button_press (GimpTool *tool, GimpButtonPressType press_type, GimpDisplay *display) { - GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); - GimpImage *image = gimp_display_get_image (display); + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool); + GimpImage *image = gimp_display_get_image (display); + ISegment *segment; iscissors->x = RINT (coords->x); iscissors->y = RINT (coords->y); @@ -389,6 +410,8 @@ gimp_iscissors_tool_button_press (GimpTool *tool, gimp_tool_control_activate (tool->control); tool->display = display; + gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool)); + switch (iscissors->state) { case NO_ACTION: @@ -396,14 +419,19 @@ gimp_iscissors_tool_button_press (GimpTool *tool, if (! (state & GDK_SHIFT_MASK)) find_max_gradient (iscissors, image, - &iscissors->x, - &iscissors->y); + &iscissors->x, &iscissors->y); iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1); iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1); - iscissors->ix = iscissors->x; - iscissors->iy = iscissors->y; + gimp_iscissors_tool_push_undo (iscissors); + + segment = isegment_new (iscissors->x, + iscissors->y, + iscissors->x, + iscissors->y); + + g_queue_push_tail (iscissors->curve->segments, segment); /* Initialize the draw tool only on starting the tool */ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display); @@ -413,13 +441,26 @@ gimp_iscissors_tool_button_press (GimpTool *tool, /* Check if the mouse click occurred on a vertex or the curve itself */ if (clicked_on_vertex (iscissors, coords->x, coords->y)) { - gimp_draw_tool_pause (GIMP_DRAW_TOOL (iscissors)); - - iscissors->nx = iscissors->x; - iscissors->ny = iscissors->y; iscissors->state = SEED_ADJUSTMENT; - gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool)); + /* recalculate both segments */ + if (iscissors->segment1) + { + iscissors->segment1->x1 = iscissors->x; + iscissors->segment1->y1 = iscissors->y; + + if (options->interactive) + calculate_segment (iscissors, iscissors->segment1); + } + + if (iscissors->segment2) + { + iscissors->segment2->x2 = iscissors->x; + iscissors->segment2->y2 = iscissors->y; + + if (options->interactive) + calculate_segment (iscissors, iscissors->segment2); + } } /* If the iscissors is connected, check if the click was inside */ else if (iscissors->curve->connected && iscissors->mask && @@ -434,14 +475,38 @@ gimp_iscissors_tool_button_press (GimpTool *tool, { /* if we're not connected, we're adding a new point */ - gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool)); + ISegment *last = g_queue_peek_tail (iscissors->curve->segments); iscissors->state = SEED_PLACEMENT; - gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool)); + gimp_iscissors_tool_push_undo (iscissors); + + if (last->x1 == last->x2 && + last->y1 == last->y2) + { + last->x2 = iscissors->x; + last->y2 = iscissors->y; + + if (options->interactive) + calculate_segment (iscissors, last); + } + else + { + segment = isegment_new (last->x2, + last->y2, + iscissors->x, + iscissors->y); + + g_queue_push_tail (iscissors->curve->segments, segment); + + if (options->interactive) + calculate_segment (iscissors, segment); + } } break; } + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool)); } @@ -517,7 +582,8 @@ gimp_iscissors_tool_button_release (GimpTool *tool, GimpButtonReleaseType release_type, GimpDisplay *display) { - GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool); gimp_tool_control_halt (tool->control); @@ -539,44 +605,55 @@ gimp_iscissors_tool_button_release (GimpTool *tool, if (! iscissors->curve->first_point) { /* Determine if we're connecting to the first point */ - if (! g_queue_is_empty (iscissors->curve->segments)) - { - ISegment *segment = g_queue_peek_head (iscissors->curve->segments); - if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display, - iscissors->x, iscissors->y, - GIMP_HANDLE_CIRCLE, - segment->x1, segment->y1, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_HANDLE_ANCHOR_CENTER)) - { - iscissors->x = segment->x1; - iscissors->y = segment->y1; - iscissors->curve->connected = TRUE; - } + ISegment *segment = g_queue_peek_head (iscissors->curve->segments); + + if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display, + iscissors->x, iscissors->y, + GIMP_HANDLE_CIRCLE, + segment->x1, segment->y1, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER)) + { + iscissors->x = segment->x1; + iscissors->y = segment->y1; + + segment = g_queue_peek_tail (iscissors->curve->segments); + + segment->x2 = iscissors->x; + segment->y2 = iscissors->y; + + iscissors->curve->connected = TRUE; + + if (! options->interactive) + calculate_segment (iscissors, segment); + + gimp_iscissors_tool_free_redo (iscissors); } - - /* Create the new curve segment */ - if (iscissors->ix != iscissors->x || - iscissors->iy != iscissors->y) + else { - ISegment *segment = isegment_new (iscissors->ix, - iscissors->iy, - iscissors->x, - iscissors->y); + segment = g_queue_peek_tail (iscissors->curve->segments); - iscissors->ix = iscissors->x; - iscissors->iy = iscissors->y; + if (segment->x1 != segment->x2 || + segment->y1 != segment->y2) + { + if (! options->interactive) + calculate_segment (iscissors, segment); - g_queue_push_tail (iscissors->curve->segments, segment); - - calculate_segment (iscissors, segment); + gimp_iscissors_tool_free_redo (iscissors); + } + else + { + gimp_iscissors_tool_pop_undo (iscissors); + } } } else /* this was our first point */ { iscissors->curve->first_point = FALSE; + + gimp_iscissors_tool_free_redo (iscissors); } break; @@ -584,19 +661,33 @@ gimp_iscissors_tool_button_release (GimpTool *tool, /* recalculate both segments */ if (iscissors->segment1) { - iscissors->segment1->x1 = iscissors->nx; - iscissors->segment1->y1 = iscissors->ny; - - calculate_segment (iscissors, iscissors->segment1); + if (! options->interactive) + calculate_segment (iscissors, iscissors->segment1); } if (iscissors->segment2) { - iscissors->segment2->x2 = iscissors->nx; - iscissors->segment2->y2 = iscissors->ny; - - calculate_segment (iscissors, iscissors->segment2); + if (! options->interactive) + calculate_segment (iscissors, iscissors->segment2); } + + gimp_iscissors_tool_free_redo (iscissors); + break; + + default: + break; + } + } + else + { + switch (iscissors->state) + { + case SEED_PLACEMENT: + gimp_iscissors_tool_pop_undo (iscissors); + break; + + case SEED_ADJUSTMENT: + gimp_iscissors_tool_pop_undo (iscissors); break; default: @@ -620,8 +711,10 @@ gimp_iscissors_tool_motion (GimpTool *tool, GdkModifierType state, GimpDisplay *display) { - GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); - GimpImage *image = gimp_display_get_image (display); + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool); + GimpImage *image = gimp_display_get_image (display); + ISegment *segment; if (iscissors->state == NO_ACTION) return; @@ -631,35 +724,52 @@ gimp_iscissors_tool_motion (GimpTool *tool, iscissors->x = RINT (coords->x); iscissors->y = RINT (coords->y); + /* Hold the shift key down to disable the auto-edge snap feature */ + if (! (state & GDK_SHIFT_MASK)) + find_max_gradient (iscissors, image, + &iscissors->x, &iscissors->y); + + iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1); + iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1); + switch (iscissors->state) { case SEED_PLACEMENT: - /* Hold the shift key down to disable the auto-edge snap feature */ - if (! (state & GDK_SHIFT_MASK)) - find_max_gradient (iscissors, image, - &iscissors->x, &iscissors->y); + segment = g_queue_peek_tail (iscissors->curve->segments); - iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1); - iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1); + segment->x2 = iscissors->x; + segment->y2 = iscissors->y; if (iscissors->curve->first_point) { - iscissors->ix = iscissors->x; - iscissors->iy = iscissors->y; + segment->x1 = segment->x2; + segment->y1 = segment->y2; + } + else + { + if (options->interactive) + calculate_segment (iscissors, segment); } break; case SEED_ADJUSTMENT: - /* Move the current seed to the location of the cursor */ - if (! (state & GDK_SHIFT_MASK)) - find_max_gradient (iscissors, image, - &iscissors->x, &iscissors->y); + if (iscissors->segment1) + { + iscissors->segment1->x1 = iscissors->x; + iscissors->segment1->y1 = iscissors->y; - iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1); - iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1); + if (options->interactive) + calculate_segment (iscissors, iscissors->segment1); + } - iscissors->nx = iscissors->x; - iscissors->ny = iscissors->y; + if (iscissors->segment2) + { + iscissors->segment2->x2 = iscissors->x; + iscissors->segment2->y2 = iscissors->y; + + if (options->interactive) + calculate_segment (iscissors, iscissors->segment2); + } break; default: @@ -674,144 +784,97 @@ gimp_iscissors_tool_draw (GimpDrawTool *draw_tool) { GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (draw_tool); GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (draw_tool); + GimpCanvasItem *item; + GList *list; - if (iscissors->state == SEED_PLACEMENT) - { - /* Draw the crosshairs target if we're placing a seed */ - gimp_draw_tool_add_handle (draw_tool, - GIMP_HANDLE_CROSS, - iscissors->x, iscissors->y, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_HANDLE_ANCHOR_CENTER); - - /* Draw a line boundary */ - if (! iscissors->curve->first_point) - { - if (! options->interactive) - { - gimp_draw_tool_add_line (draw_tool, - iscissors->ix, iscissors->iy, - iscissors->x, iscissors->y); - } - else - { - /* See if the mouse has moved. If so, create a new segment... */ - if (! iscissors->livewire || - (iscissors->ix != iscissors->livewire->x1 || - iscissors->iy != iscissors->livewire->y1 || - iscissors->x != iscissors->livewire->x2 || - iscissors->y != iscissors->livewire->y2)) - { - if (iscissors->livewire) - isegment_free (iscissors->livewire); - - iscissors->livewire = isegment_new (iscissors->ix, - iscissors->iy, - iscissors->x, - iscissors->y); - - calculate_segment (iscissors, iscissors->livewire); - } - - /* plot the segment */ - iscissors_draw_segment (draw_tool, iscissors->livewire); - } - } - } - + /* First, render all segments and lines */ if (! iscissors->curve->first_point) { - GList *list; - - /* Draw a point at the init point coordinates */ - if (! iscissors->curve->connected) - { - gimp_draw_tool_add_handle (draw_tool, - GIMP_HANDLE_FILLED_CIRCLE, - iscissors->ix, - iscissors->iy, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_HANDLE_ANCHOR_CENTER); - } - - /* Go through the list of isegments, and render each one... */ for (list = g_queue_peek_head_link (iscissors->curve->segments); list; list = g_list_next (list)) { ISegment *segment = list->data; - if (iscissors->state == SEED_ADJUSTMENT) - { - /* don't draw segment1 at all */ - if (segment == iscissors->segment1) - continue; - } - - gimp_draw_tool_add_handle (draw_tool, - GIMP_HANDLE_FILLED_CIRCLE, - segment->x1, - segment->y1, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_HANDLE_ANCHOR_CENTER); - - if (iscissors->state == SEED_ADJUSTMENT) - { - /* draw only the start handle of segment2 */ - if (segment == iscissors->segment2) - continue; - } - /* plot the segment */ - iscissors_draw_segment (draw_tool, segment); + item = iscissors_draw_segment (draw_tool, segment); + + /* if this segment is currently being added or adjusted */ + if ((iscissors->state == SEED_PLACEMENT && + ! list->next) + || + (iscissors->state == SEED_ADJUSTMENT && + (segment == iscissors->segment1 || + segment == iscissors->segment2))) + { + if (! options->interactive) + item = gimp_draw_tool_add_line (draw_tool, + segment->x1, segment->y1, + segment->x2, segment->y2); + + gimp_canvas_item_set_highlight (item, TRUE); + } } } - if (iscissors->state == SEED_ADJUSTMENT) + /* Then, render the handles on top of the segments */ + for (list = g_queue_peek_head_link (iscissors->curve->segments); + list; + list = g_list_next (list)) { - /* plot both segments, and the control point between them */ - if (iscissors->segment1) + ISegment *segment = list->data; + + if (! iscissors->curve->first_point) { - gimp_draw_tool_add_line (draw_tool, - iscissors->segment1->x2, - iscissors->segment1->y2, - iscissors->nx, - iscissors->ny); + gboolean adjustment = (iscissors->state == SEED_ADJUSTMENT && + segment == iscissors->segment1); + + item = gimp_draw_tool_add_handle (draw_tool, + adjustment ? + GIMP_HANDLE_CROSS : + GIMP_HANDLE_FILLED_CIRCLE, + segment->x1, + segment->y1, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER); + + if (adjustment) + gimp_canvas_item_set_highlight (item, TRUE); } - if (iscissors->segment2) + /* Draw the last point if the curve is not connected */ + if (! list->next && ! iscissors->curve->connected) { - gimp_draw_tool_add_line (draw_tool, - iscissors->segment2->x1, - iscissors->segment2->y1, - iscissors->nx, - iscissors->ny); - } + gboolean placement = (iscissors->state == SEED_PLACEMENT); - gimp_draw_tool_add_handle (draw_tool, - GIMP_HANDLE_FILLED_CIRCLE, - iscissors->nx, - iscissors->ny, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_TOOL_HANDLE_SIZE_CIRCLE, - GIMP_HANDLE_ANCHOR_CENTER); + item = gimp_draw_tool_add_handle (draw_tool, + placement ? + GIMP_HANDLE_CROSS : + GIMP_HANDLE_FILLED_CIRCLE, + segment->x2, + segment->y2, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_TOOL_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER); + + if (placement) + gimp_canvas_item_set_highlight (item, TRUE); + } } } - -static void +static GimpCanvasItem * iscissors_draw_segment (GimpDrawTool *draw_tool, ISegment *segment) { - GimpVector2 *points; - gpointer *point; - gint i, len; + GimpCanvasItem *item; + GimpVector2 *points; + gpointer *point; + gint i, len; if (! segment->points) - return; + return NULL; len = segment->points->len; @@ -825,9 +888,11 @@ iscissors_draw_segment (GimpDrawTool *draw_tool, points[i].y = (coords >> 16); } - gimp_draw_tool_add_lines (draw_tool, points, len, FALSE); + item = gimp_draw_tool_add_lines (draw_tool, points, len, FALSE); g_free (points); + + return item; } static void @@ -866,14 +931,14 @@ gimp_iscissors_tool_oper_update (GimpTool *tool, GIMP_TOOL_HANDLE_SIZE_CIRCLE, GIMP_HANDLE_ANCHOR_CENTER)) { - gimp_tool_replace_status (tool, display, _("Click to close the" - " curve")); + gimp_tool_replace_status (tool, display, + _("Click to close the curve")); iscissors->op = ISCISSORS_OP_CONNECT; } else { - gimp_tool_replace_status (tool, display, _("Click to add a point" - " on this segment")); + gimp_tool_replace_status (tool, display, + _("Click to add a point on this segment")); iscissors->op = ISCISSORS_OP_ADD_POINT; } } @@ -1021,37 +1086,159 @@ gimp_iscissors_tool_key_press (GimpTool *tool, } } +static const gchar * +gimp_iscissors_tool_get_undo_desc (GimpTool *tool, + GimpDisplay *display) +{ + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + + if (display != tool->display || ! iscissors->undo_stack) + return NULL; + + return _("Modify Scissors Curve"); +} + +static const gchar * +gimp_iscissors_tool_get_redo_desc (GimpTool *tool, + GimpDisplay *display) +{ + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + + if (display != tool->display || ! iscissors->redo_stack) + return NULL; + + return _("Modify Scissors Curve"); +} + +static gboolean +gimp_iscissors_tool_undo (GimpTool *tool, + GimpDisplay *display) +{ + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + + if (! gimp_iscissors_tool_get_undo_desc (tool, display)) + return FALSE; + + gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool)); + + iscissors->redo_stack = g_list_prepend (iscissors->redo_stack, + iscissors->curve); + + iscissors->curve = iscissors->undo_stack->data; + + iscissors->undo_stack = g_list_remove (iscissors->undo_stack, + iscissors->curve); + + if (! iscissors->undo_stack) + { + iscissors->state = NO_ACTION; + + gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool)); + } + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool)); + + return TRUE; +} + +static gboolean +gimp_iscissors_tool_redo (GimpTool *tool, + GimpDisplay *display) +{ + GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool); + + if (! gimp_iscissors_tool_get_redo_desc (tool, display)) + return FALSE; + + gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool)); + + if (! iscissors->undo_stack) + { + iscissors->state = WAITING; + + gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display); + } + + iscissors->undo_stack = g_list_prepend (iscissors->undo_stack, + iscissors->curve); + + iscissors->curve = iscissors->redo_stack->data; + + iscissors->redo_stack = g_list_remove (iscissors->redo_stack, + iscissors->curve); + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool)); + + return TRUE; +} + +static void +gimp_iscissors_tool_push_undo (GimpIscissorsTool *iscissors) +{ + iscissors->undo_stack = g_list_prepend (iscissors->undo_stack, + icurve_copy (iscissors->curve)); +} + +static void +gimp_iscissors_tool_pop_undo (GimpIscissorsTool *iscissors) +{ + icurve_free (iscissors->curve); + iscissors->curve = iscissors->undo_stack->data; + + iscissors->undo_stack = g_list_remove (iscissors->undo_stack, + iscissors->curve); +} + +static void +gimp_iscissors_tool_free_redo (GimpIscissorsTool *iscissors) +{ + g_list_free_full (iscissors->redo_stack, + (GDestroyNotify) icurve_free); + iscissors->redo_stack = NULL; + + /* update the undo actions / menu items */ + gimp_image_flush (gimp_display_get_image (GIMP_TOOL (iscissors)->display)); +} + static void gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors, GimpDisplay *display) { - /* Free and reset the curve */ icurve_clear (iscissors->curve); - /* free mask */ - if (iscissors->mask) + iscissors->segment1 = NULL; + iscissors->segment2 = NULL; + iscissors->state = NO_ACTION; + + if (iscissors->undo_stack) { - g_object_unref (iscissors->mask); - iscissors->mask = NULL; + g_list_free_full (iscissors->undo_stack, (GDestroyNotify) icurve_free); + iscissors->undo_stack = NULL; + } + + if (iscissors->redo_stack) + { + g_list_free_full (iscissors->redo_stack, (GDestroyNotify) icurve_free); + iscissors->redo_stack = NULL; } - /* free the gradient map */ if (iscissors->gradient_map) { g_object_unref (iscissors->gradient_map); iscissors->gradient_map = NULL; } - iscissors->segment1 = NULL; - iscissors->segment2 = NULL; - iscissors->state = NO_ACTION; - - /* Reset the dp buffers */ if (iscissors->dp_buf) { gimp_temp_buf_unref (iscissors->dp_buf); iscissors->dp_buf = NULL; } + + if (iscissors->mask) + { + g_object_unref (iscissors->mask); + iscissors->mask = NULL; + } } static void @@ -1142,7 +1329,11 @@ clicked_on_vertex (GimpIscissorsTool *iscissors, gint segments_found = mouse_over_vertex (iscissors, x, y); if (segments_found > 1) - return TRUE; + { + gimp_iscissors_tool_push_undo (iscissors); + + return TRUE; + } /* if only one segment was found, the segments are unconnected, and * the user only wants to move either the first or last point @@ -1173,6 +1364,9 @@ mouse_over_segment (GimpIscissorsTool *iscissors, gpointer *pt; gint len; + if (! segment->points) + continue; + pt = segment->points->pdata; len = segment->points->len; @@ -1217,6 +1411,8 @@ clicked_on_segment (GimpIscissorsTool *iscissors, ISegment *segment = list->data; ISegment *new_segment; + gimp_iscissors_tool_push_undo (iscissors); + /* Create the new segment */ new_segment = isegment_new (iscissors->x, iscissors->y, @@ -1653,8 +1849,9 @@ find_max_gradient (GimpIscissorsTool *iscissors, gint x1, y1, x2, y2; gfloat max_gradient; - /* Initialise the gradient map tile manager for this image if we - * don't already have one. */ + /* Initialise the gradient map buffer for this image if we don't + * already have one. + */ if (! iscissors->gradient_map) iscissors->gradient_map = gradient_map_new (image); @@ -1726,6 +1923,31 @@ isegment_new (gint x1, return segment; } +static ISegment * +isegment_copy (ISegment *segment) +{ + ISegment *copy = isegment_new (segment->x1, + segment->y1, + segment->x2, + segment->y2); + + if (segment->points) + { + gint i; + + copy->points = g_ptr_array_sized_new (segment->points->len); + + for (i = 0; i < segment->points->len; i++) + { + gpointer value = g_ptr_array_index (segment->points, i); + + g_ptr_array_add (copy->points, value); + } + } + + return copy; +} + static void isegment_free (ISegment *segment) { @@ -1746,6 +1968,25 @@ icurve_new (void) return curve; } +static ICurve * +icurve_copy (ICurve *curve) +{ + ICurve *copy = icurve_new (); + GList *link; + + for (link = g_queue_peek_head_link (curve->segments); + link; + link = g_list_next (link)) + { + g_queue_push_tail (copy->segments, isegment_copy (link->data)); + } + + copy->first_point = curve->first_point; + copy->connected = curve->connected; + + return copy; +} + static void icurve_clear (ICurve *curve) { diff --git a/app/tools/gimpiscissorstool.h b/app/tools/gimpiscissorstool.h index f1aa7f72ae..a3bffa6cde 100644 --- a/app/tools/gimpiscissorstool.h +++ b/app/tools/gimpiscissorstool.h @@ -65,24 +65,21 @@ struct _GimpIscissorsTool IscissorsOps op; - gint x, y; /* upper left hand coordinate */ - gint ix, iy; /* initial coordinates */ - gint nx, ny; /* new coordinates */ - - GimpTempBuf *dp_buf; /* dynamic programming buffer */ - - ISegment *livewire; /* livewire boundary segment */ + gint x, y; /* mouse coordinates */ ISegment *segment1; /* 1st segment connected to current point */ ISegment *segment2; /* 2nd segment connected to current point */ ICurve *curve; /* the curve */ + GList *undo_stack; /* stack of ICurves for undo */ + GList *redo_stack; /* stack of ICurves for redo */ + IscissorsState state; /* state of iscissors */ - /* XXX might be useful */ - GimpChannel *mask; /* selection mask */ GeglBuffer *gradient_map; /* lazily filled gradient map */ + GimpTempBuf *dp_buf; /* dynamic programming buffer */ + GimpChannel *mask; /* selection mask */ }; struct _GimpIscissorsToolClass