/* The GIMP -- an image manipulation program * * This file Copyright (C) 1999 Simon Budig * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Complete new path-tool by Simon Budig * * a path manipulation core independent of the underlying formula: * implement bezier-curves, intelligent scissors-curves, splines... * * A Path is a collection of curves, which are constructed from * segments between two anchors. */ #include /* #include "appenv.h" */ #include "draw_core.h" #include "cursorutil.h" #include "path_tool.h" #include "path_toolP.h" #include "path_curves.h" #include "config.h" #include "libgimp/gimpintl.h" /* * Every new curve-type has to have a parameter between 0 and 1, and * should go from a starting to a target point. */ /* Some defines... */ #define PATH_TOOL_WIDTH 8 #define PATH_TOOL_HALFWIDTH 4 /* local function prototypes */ /* Small functions to determine coordinates, iterate over path/curve/segment */ void path_segment_get_coordinates (PathSegment *, gdouble, gint *, gint *); void path_traverse_path (Path *, PathTraverseFunc, CurveTraverseFunc, SegmentTraverseFunc, gpointer); void path_traverse_curve (Path *, PathCurve *, CurveTraverseFunc, SegmentTraverseFunc, gpointer); void path_traverse_segment (Path *, PathCurve *, PathSegment *, SegmentTraverseFunc, gpointer); gdouble path_locate_point (Path *, PathCurve **, PathSegment **, gint, gint, gint, gint, gint); /* Tools to manipulate paths, curves, segments */ PathCurve * path_add_curve (Path *, gint, gint); PathSegment * path_append_segment (Path *, PathCurve *, SegmentType, gint, gint); PathSegment * path_prepend_segment (Path *, PathCurve *, SegmentType, gint, gint); PathSegment * path_split_segment (PathSegment *, gdouble); void path_join_curves (PathSegment *, PathSegment *); void path_flip_curve (PathCurve *); void path_free_path (Path *); void path_free_curve (PathCurve *); void path_free_segment (PathSegment *); void path_delete_segment (PathSegment *); void path_print (Path *); void path_offset_active (Path *, gdouble, gdouble); void path_set_flags (PathTool *, Path *, PathCurve *, PathSegment *, guint32, guint32); /* High level image-manipulation functions */ void path_stroke (PathTool *, Path *); void path_to_selection (PathTool *, Path *); /* Functions necessary for the tool */ void path_tool_button_press (Tool *, GdkEventButton *, gpointer); void path_tool_button_release (Tool *, GdkEventButton *, gpointer); void path_tool_motion (Tool *, GdkEventMotion *, gpointer); void path_tool_cursor_update (Tool *, GdkEventMotion *, gpointer); void path_tool_control (Tool *, ToolAction, gpointer); void path_tool_draw (Tool *); void path_tool_draw_curve (Tool *, PathCurve *); void path_tool_draw_segment (Tool *, PathSegment *); gdouble path_tool_on_curve (Tool *, gint, gint, gint, Path**, PathCurve**, PathSegment**); gboolean path_tool_on_anchors (Tool *, gint, gint, gint, Path**, PathCurve**, PathSegment**); gint path_tool_on_handles (Tool *, gint, gint, gint, Path **, PathCurve **, PathSegment **); gint path_tool_button_press_canvas (Tool *, GdkEventButton *, GDisplay *); gint path_tool_button_press_anchor (Tool *, GdkEventButton *, GDisplay *); gint path_tool_button_press_handle (Tool *, GdkEventButton *, GDisplay *); gint path_tool_button_press_curve (Tool *, GdkEventButton *, GDisplay *); void path_tool_motion_anchor (Tool *, GdkEventMotion *, GDisplay *); void path_tool_motion_handle (Tool *, GdkEventMotion *, GDisplay *); void path_tool_motion_curve (Tool *, GdkEventMotion *, GDisplay *); /* the path tool options */ static ToolOptions *path_options = NULL; /* * * * Here we go! * * */ /* * These functions are for applying a function over a complete * path/curve/segment. They can pass information to each other * with a arbitrary data structure * * The idea behind the three different functions is: * if pathfunc != NULL * call pathfunc for every curve * else * if curvefunc != NULL * call curvefunc for every segment * else * call segmentfunc for every point * */ void path_traverse_path (Path *path, PathTraverseFunc pathfunc, CurveTraverseFunc curvefunc, SegmentTraverseFunc segmentfunc, gpointer data) { PathCurve *cur_curve; if (path && path->curves) { cur_curve = path->curves; if (pathfunc) do { (* pathfunc) (path, cur_curve, data); cur_curve = cur_curve->next; } while (cur_curve && cur_curve != path->curves); else do { path_traverse_curve (path, cur_curve, curvefunc, segmentfunc, data); cur_curve = cur_curve->next; } while (cur_curve && cur_curve != path->curves); } } void path_traverse_curve (Path *path, PathCurve *curve, CurveTraverseFunc curvefunc, SegmentTraverseFunc segmentfunc, gpointer data) { PathSegment *cur_segment; if (curve && curve->segments) { cur_segment = curve->segments; if (curvefunc) do { (* curvefunc) (path, curve, cur_segment, data); cur_segment = cur_segment->next; } while (cur_segment && cur_segment != curve->segments); else do { path_traverse_segment (path, curve, cur_segment, segmentfunc, data); cur_segment = cur_segment->next; } while (cur_segment && cur_segment != curve->segments); } } void path_traverse_segment (Path *path, PathCurve *curve, PathSegment *segment, SegmentTraverseFunc function, gpointer data) { #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_traverse_segment\n"); #endif PATH_TOOL_DEBUG /* XXX: here we need path_curve_get_point(s) */ /* Something like: * for i = 1 to subsamples { * (x,y) = get_coordinates(i / subsamples) * (* function) (....) * } */ } /************************************************************** * Helper functions for manipulating the data-structures: */ PathCurve * path_add_curve (Path * cur_path, gint x, gint y) { PathCurve * tmp = cur_path->curves; PathCurve * new_curve = NULL; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_add_curve\n"); #endif PATH_TOOL_DEBUG if (cur_path) { new_curve = g_new (PathCurve, 1); new_curve->parent = cur_path; new_curve->next = tmp; new_curve->prev = NULL; new_curve->cur_segment = NULL; new_curve->segments = NULL; if (tmp) tmp->prev = new_curve; cur_path->curves = cur_path->cur_curve = new_curve; new_curve->segments = path_prepend_segment (cur_path, new_curve, SEGMENT_BEZIER, x, y); } #ifdef PATH_TOOL_DEBUG else fprintf (stderr, "Fatal Error: path_add_curve called without valid path\n"); #endif PATH_TOOL_DEBUG return new_curve; } PathSegment * path_append_segment (Path * cur_path, PathCurve * cur_curve, SegmentType type, gint x, gint y) { PathSegment * tmp = cur_curve->segments; PathSegment * new_segment = NULL; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_append_segment\n"); #endif PATH_TOOL_DEBUG if (cur_curve) { tmp = cur_curve->segments; while (tmp && tmp->next && tmp->next != cur_curve->segments) { tmp = tmp->next; } if (tmp == NULL || tmp->next == NULL) { new_segment = g_new (PathSegment, 1); new_segment->type = type; new_segment->x = x; new_segment->y = y; new_segment->flags = 0; new_segment->parent = cur_curve; new_segment->next = NULL; new_segment->prev = tmp; new_segment->data = NULL; if (tmp) tmp->next = new_segment; cur_curve->cur_segment = new_segment; path_curve_init_segment (new_segment); } #ifdef PATH_TOOL_DEBUG else fprintf(stderr, "Fatal Error: path_append_segment called with a closed curve\n"); #endif PATH_TOOL_DEBUG } #ifdef PATH_TOOL_DEBUG else fprintf(stderr, "Fatal Error: path_append_segment called without valid curve\n"); #endif PATH_TOOL_DEBUG return new_segment; } PathSegment * path_prepend_segment (Path * cur_path, PathCurve * cur_curve, SegmentType type, gint x, gint y) { PathSegment * tmp = cur_curve->segments; PathSegment * new_segment = NULL; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_prepend_segment\n"); #endif PATH_TOOL_DEBUG if (cur_curve) { tmp = cur_curve->segments; if (tmp == NULL || tmp->prev == NULL) { new_segment = g_new (PathSegment, 1); new_segment->type = type; new_segment->x = x; new_segment->y = y; new_segment->flags = 0; new_segment->parent = cur_curve; new_segment->next = tmp; new_segment->prev = NULL; new_segment->data = NULL; if (tmp) tmp->prev = new_segment; cur_curve->segments = new_segment; cur_curve->cur_segment = new_segment; path_curve_init_segment (new_segment); } #ifdef PATH_TOOL_DEBUG else fprintf(stderr, "Fatal Error: path_prepend_segment called with a closed curve\n"); #endif PATH_TOOL_DEBUG } #ifdef PATH_TOOL_DEBUG else fprintf(stderr, "Fatal Error: path_prepend_segment called without valid curve\n"); #endif PATH_TOOL_DEBUG return new_segment; } PathSegment * path_split_segment (PathSegment *segment, gdouble position) { PathSegment * new_segment = NULL; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_split_segment\n"); #endif PATH_TOOL_DEBUG if (segment && segment->next) { new_segment = g_new (PathSegment, 1); new_segment->type = segment->type; /* XXX: Giving PathTool as NULL Pointer! */ path_curve_get_point (NULL, segment, position, &(new_segment->x), &(new_segment->y)); new_segment->flags = 0; new_segment->parent = segment->parent; new_segment->next = segment->next; new_segment->prev = segment; new_segment->data = NULL; path_curve_init_segment (new_segment); new_segment->next->prev = new_segment; segment->next = new_segment; return new_segment; } #ifdef PATH_TOOL_DEBUG else fprintf(stderr, "path_split_segment without valid segment\n"); #endif PATH_TOOL_DEBUG return NULL; } /* * Join two arbitrary endpoints and free the parent from the second * segment, if it differs from the first parents. */ void path_join_curves (PathSegment *segment1, PathSegment *segment2) { PathCurve *curve1, *curve2; PathSegment *tmp; if ((segment1->next && segment1->prev) || (segment2->next && segment2->prev)) { #ifdef PATH_TOOL_DEBUG fprintf(stderr, "Fatal Error: path_join_curves called with a closed segment\n"); #endif PATH_TOOL_DEBUG return; } if (segment1->parent == segment2->parent) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Joining beginning and end of the same curve...\n"); #endif PATH_TOOL_DEBUG if (segment2->next == NULL) { segment2->next = segment1; segment1->prev = segment2; } else { segment2->prev = segment1; segment1->next = segment2; } /* XXX: Probably some segment-updates needed */ return; } if (segment1->next == NULL && segment2->next == NULL) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Flipping second curve (next, next)...\n"); #endif PATH_TOOL_DEBUG path_flip_curve (segment2->parent); /* segment2 = segment2->parent->segments; */ } if (segment1->prev == NULL && segment2->prev == NULL) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Flipping second curve (prev, prev)...\n"); #endif PATH_TOOL_DEBUG path_flip_curve (segment2->parent); /* segment2 = segment2->parent->segments; * while (segment2->next) * segment2 = segment2->next; */ } if (segment1->next == NULL && segment2->prev == NULL) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Appending second to first curve...\n"); #endif PATH_TOOL_DEBUG curve1 = segment1->parent; curve2 = segment2->parent; segment1->next = segment2; segment2->prev = segment1; curve2->segments = NULL; if (curve2->prev) curve2->prev->next = curve2->next; if (curve2->next) curve2->next->prev = curve2->prev; if (curve2->parent->curves == curve2) curve2->parent->curves = curve2->next; path_free_curve (curve2); tmp = segment2; while (tmp) { tmp->parent = curve1; tmp = tmp->next; } /* XXX: Probably some segment-updates needed */ return; } if (segment1->prev == NULL && segment2->next == NULL) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Prepending second to first curve...\n"); #endif PATH_TOOL_DEBUG curve1 = segment1->parent; curve2 = segment2->parent; segment1->prev = segment2; segment2->next = segment1; curve2->segments = NULL; if (curve2->prev) curve2->prev->next = curve2->next; if (curve2->next) curve2->next->prev = curve2->prev; if (curve2->parent->curves == curve2) curve2->parent->curves = curve2->next; path_free_curve (curve2); tmp = segment2; while (tmp) { tmp->parent = curve1; curve1->segments = tmp; tmp = tmp->prev; } return; /* XXX: Probably some segment-updates needed */ } #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Cant join these curves yet...\nThis should not happen."); return; #endif PATH_TOOL_DEBUG } /* * This function reverses the order of the anchors. This is * necessary for some joining operations. */ void path_flip_curve (PathCurve *curve) { gpointer *end_data; SegmentType end_type; PathSegment *tmp, *tmp2; if (!curve && !curve->segments) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_flip_curve: No curve o no segments to flip!\n"); #endif PATH_TOOL_DEBUG return; } tmp = curve->segments; while (tmp->next) tmp = tmp->next; end_data = tmp->data; end_type = tmp->type; tmp->parent->segments = tmp; while (tmp) { tmp2 = tmp->next; tmp->next = tmp->prev; tmp->prev = tmp2; if (tmp->next) { tmp->type = tmp->next->type; tmp->data = tmp->next->data; } else { tmp->type = end_type; tmp->data = end_data; } path_curve_flip_segment (tmp); tmp = tmp->next; } } void path_free_path (Path * path) { PathCurve *tmp1, *tmp2; if (path) { tmp2 = path->curves; while ((tmp1 = tmp2) != NULL) { tmp2 = tmp1->next; path_free_curve (tmp1); } g_string_free(path->name, TRUE); g_free(path); } } void path_free_curve (PathCurve *curve) { PathSegment *tmp1, *tmp2; if (curve) { tmp2 = curve->segments; /* break closed curves */ if (tmp2 && tmp2->prev) tmp2->prev->next = NULL; while ((tmp1 = tmp2) != NULL) { tmp2 = tmp1->next; path_free_segment (tmp1); } g_free(curve); } } void path_free_segment (PathSegment *segment) { if (segment) { /* Clear the active flag to keep path_tool->single_active_segment * consistent */ path_set_flags (segment->parent->parent->path_tool, segment->parent->parent, segment->parent, segment, 0, SEGMENT_ACTIVE); path_curve_cleanup_segment(segment); g_free (segment); } } void path_delete_curve (PathCurve *curve) { if (curve) { if (curve->next) curve->next->prev = curve->prev; if (curve->prev) curve->prev->next = curve->next; if (curve == curve->parent->curves) { curve->parent->curves = curve->next; } path_free_curve (curve); } } void path_delete_segment (PathSegment *segment) { if (segment) { if (segment->next) segment->next->prev = segment->prev; if (segment->prev) segment->prev->next = segment->next; /* If the remaining curve is closed and has a * single point only, open it. */ if (segment->next == segment->prev && segment->next) segment->next->next = segment->next->prev = NULL; if (segment == segment->parent->segments) segment->parent->segments = segment->next; if (segment->parent->segments == NULL) path_delete_curve (segment->parent); path_free_segment (segment); /* * here we have to update the surrounding segments */ /* XXX: Please add path_curve_update_segment here */ } } /* * A function to determine, which object is hit by the cursor */ gint path_tool_cursor_position (Tool *tool, gint x, gint y, gint halfwidth, Path **pathP, PathCurve **curveP, PathSegment **segmentP, gdouble *positionP, gint *handle_idP) { gdouble pos; gint handle_id; if (path_tool_on_anchors (tool, x, y, halfwidth, pathP, curveP, segmentP)) return ON_ANCHOR; handle_id = path_tool_on_handles (tool, x, y, halfwidth, pathP, curveP, segmentP); if (handle_id) { if (handle_idP) (*handle_idP) = handle_id; return ON_HANDLE; } pos = path_tool_on_curve (tool, x, y, halfwidth, pathP, curveP, segmentP); if (pos >= 0 && pos <= 1) { if (positionP) (*positionP) = pos; return ON_CURVE; } return ON_CANVAS; } /************************************************************** * The click-callbacks for the tool */ void path_tool_button_press (Tool *tool, GdkEventButton *bevent, gpointer gdisp_ptr) { GDisplay * gdisp; PathTool * path_tool; gint grab_pointer=0; gint x, y, halfwidth, dummy; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_button_press\n"); #endif PATH_TOOL_DEBUG gdisp = (GDisplay *) gdisp_ptr; path_tool = (PathTool *) tool->private; tool->gdisp_ptr = gdisp_ptr; /* Transform window-coordinates to canvas-coordinates */ gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y, TRUE, 0); #ifdef PATH_TOOL_DEBUG fprintf(stderr, "Clickcoordinates %d, %d\n",x,y); #endif PATH_TOOL_DEBUG path_tool->click_x = x; path_tool->click_y = y; path_tool->click_modifier = bevent->state; /* get halfwidth in image coord */ gdisplay_untransform_coords (gdisp, bevent->x + PATH_TOOL_HALFWIDTH, 0, &halfwidth, &dummy, TRUE, 0); halfwidth -= x; path_tool->click_halfwidth = halfwidth; if (!path_tool->cur_path->curves) draw_core_start (path_tool->core, gdisp->canvas->window, tool); /* determine point, where clicked, * switch accordingly. */ path_tool->click_type = path_tool_cursor_position (tool, x, y, halfwidth, &(path_tool->click_path), &(path_tool->click_curve), &(path_tool->click_segment), &(path_tool->click_position), &(path_tool->click_handle_id)); switch (path_tool->click_type) { case ON_CANVAS: grab_pointer = path_tool_button_press_canvas(tool, bevent, gdisp); break; case ON_ANCHOR: grab_pointer = path_tool_button_press_anchor(tool, bevent, gdisp); break; case ON_HANDLE: grab_pointer = path_tool_button_press_handle(tool, bevent, gdisp); break; case ON_CURVE: grab_pointer = path_tool_button_press_curve(tool, bevent, gdisp); break; default: g_message("Huh? Whats happening here? (button_press_*)"); } if (grab_pointer) gdk_pointer_grab (gdisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, bevent->time); tool->state = ACTIVE; } gint path_tool_button_press_anchor (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { static guint32 last_click_time=0; gboolean doubleclick=FALSE; PathTool *path_tool = tool->private; Path * cur_path = path_tool->cur_path; PathSegment *p_sas; gint grab_pointer; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_tool_button_press_anchor:\n"); #endif PATH_TOOL_DEBUG grab_pointer = 1; if (!cur_path) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Fatal error: No current Path\n"); #endif PATH_TOOL_DEBUG return 0; } /* * We have to determine, if this was a doubleclick for ourself, because * disp_callback.c ignores the GDK_[23]BUTTON_EVENT's and adding them to * the switch statement confuses some tools. */ if (bevent->time - last_click_time < 250) { doubleclick=TRUE; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Doppelclick!\n"); #endif PATH_TOOL_DEBUG } else doubleclick=FALSE; last_click_time = bevent->time; draw_core_pause (path_tool->core, tool); /* The user pressed on an anchor: * normally this activates this anchor * + SHIFT toggles the activity of an anchor. * if this anchor is at the end of an open curve and the other * end is active, close the curve. * * Doubleclick (de)activates the whole curve (not Path!). */ p_sas = path_tool->single_active_segment; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "p_sas: %p\n", p_sas); #endif PATH_TOOL_DEBUG if (path_tool->click_modifier & GDK_SHIFT_MASK) { if (path_tool->active_count == 1 && p_sas && p_sas != path_tool->click_segment && (p_sas->next == NULL || p_sas->prev == NULL) && (path_tool->click_segment->next == NULL || path_tool->click_segment->prev == NULL)) { /* * if this is the end of an open curve and the single active segment was another * open end, connect those ends. */ path_join_curves (path_tool->click_segment, p_sas); path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, NULL, 0, SEGMENT_ACTIVE); } if (doubleclick) /* * Doubleclick set the whole curve to the same state, depending on the * state of the clicked anchor. */ if (path_tool->click_segment->flags & SEGMENT_ACTIVE) path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, NULL, SEGMENT_ACTIVE, 0); else path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, NULL, 0, SEGMENT_ACTIVE); else /* * Toggle the state of the clicked anchor. */ if (path_tool->click_segment->flags & SEGMENT_ACTIVE) path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, path_tool->click_segment, 0, SEGMENT_ACTIVE); else path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, path_tool->click_segment, SEGMENT_ACTIVE, 0); } /* * Delete anchors, when CONTROL is pressed */ else if (path_tool->click_modifier & GDK_CONTROL_MASK) { if (path_tool->click_segment->flags & SEGMENT_ACTIVE) { if (path_tool->click_segment->prev) path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, path_tool->click_segment->prev, SEGMENT_ACTIVE, 0); else if (path_tool->click_segment->next) path_set_flags (path_tool, path_tool->click_path, path_tool->click_curve, path_tool->click_segment->next, SEGMENT_ACTIVE, 0); } path_delete_segment (path_tool->click_segment); path_tool->click_segment = NULL; /* Maybe CTRL-ALT Click should remove the whole curve? Or the active points? */ } else if (!(path_tool->click_segment->flags & SEGMENT_ACTIVE)) { path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); path_set_flags (path_tool, cur_path, path_tool->click_curve, path_tool->click_segment, SEGMENT_ACTIVE, 0); } draw_core_resume(path_tool->core, tool); return grab_pointer; } gint path_tool_button_press_handle (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { static guint32 last_click_time=0; gboolean doubleclick=FALSE; PathTool *path_tool = tool->private; Path * cur_path = path_tool->cur_path; gint grab_pointer; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_tool_button_press_handle:\n"); #endif PATH_TOOL_DEBUG grab_pointer = 1; if (!cur_path) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Fatal error: No current Path\n"); #endif PATH_TOOL_DEBUG return 0; } /* * We have to determine, if this was a doubleclick for ourself, because * disp_callback.c ignores the GDK_[23]BUTTON_EVENT's and adding them to * the switch statement confuses some tools. */ if (bevent->time - last_click_time < 250) { doubleclick=TRUE; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Doppelclick!\n"); #endif PATH_TOOL_DEBUG } else doubleclick=FALSE; last_click_time = bevent->time; return grab_pointer; } gint path_tool_button_press_canvas (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { PathTool *path_tool = tool->private; Path * cur_path = path_tool->cur_path; PathCurve * cur_curve; PathSegment * cur_segment; gint grab_pointer; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_tool_button_press_canvas:\n"); #endif PATH_TOOL_DEBUG grab_pointer = 1; if (!cur_path) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Fatal error: No current Path\n"); #endif PATH_TOOL_DEBUG return 0; } draw_core_pause (path_tool->core, tool); if (path_tool->active_count == 1 && path_tool->single_active_segment != NULL && (path_tool->single_active_segment->prev == NULL || path_tool->single_active_segment->next == NULL)) { cur_segment = path_tool->single_active_segment; cur_curve = cur_segment->parent; path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); if (cur_segment->next == NULL) cur_curve->cur_segment = path_append_segment(cur_path, cur_curve, SEGMENT_BEZIER, path_tool->click_x, path_tool->click_y); else cur_curve->cur_segment = path_prepend_segment(cur_path, cur_curve, SEGMENT_BEZIER, path_tool->click_x, path_tool->click_y); if (cur_curve->cur_segment) { path_set_flags (path_tool, cur_path, cur_curve, cur_curve->cur_segment, SEGMENT_ACTIVE, 0); } } else { if (path_tool->active_count == 0) { path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); cur_path->cur_curve = path_add_curve(cur_path, path_tool->click_x, path_tool->click_y); path_set_flags (path_tool, cur_path, cur_path->cur_curve, cur_path->cur_curve->segments, SEGMENT_ACTIVE, 0); } else { path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); } } draw_core_resume(path_tool->core, tool); return 0; } gint path_tool_button_press_curve (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { PathTool *path_tool = tool->private; Path * cur_path = path_tool->cur_path; PathSegment * cur_segment; gint grab_pointer; #ifdef PATH_TOOL_DEBUG fprintf(stderr, "path_tool_button_press_curve:\n"); #endif PATH_TOOL_DEBUG grab_pointer = 1; if (!cur_path) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Fatal error: No current Path\n"); #endif PATH_TOOL_DEBUG return 0; } draw_core_pause (path_tool->core, tool); if (path_tool->click_modifier & GDK_SHIFT_MASK) { cur_segment = path_curve_insert_anchor (path_tool, path_tool->click_segment, path_tool->click_position); path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); path_set_flags (path_tool, cur_path, path_tool->click_curve, cur_segment, SEGMENT_ACTIVE, 0); path_tool->click_type = ON_ANCHOR; path_tool->click_segment = cur_segment; } else { path_set_flags (path_tool, cur_path, NULL, NULL, 0, SEGMENT_ACTIVE); path_set_flags (path_tool, cur_path, path_tool->click_curve, path_tool->click_segment, SEGMENT_ACTIVE, 0); path_set_flags (path_tool, cur_path, path_tool->click_curve, path_tool->click_segment->next, SEGMENT_ACTIVE, 0); } draw_core_resume(path_tool->core, tool); return 0; } void path_tool_button_release (Tool *tool, GdkEventButton *bevent, gpointer gdisp_ptr) { GDisplay * gdisp; PathTool * path_tool; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_button_release\n"); #endif PATH_TOOL_DEBUG gdisp = (GDisplay *) gdisp_ptr; path_tool = (PathTool *) tool->private; path_tool->state &= ~PATH_TOOL_DRAG; gdk_pointer_ungrab (bevent->time); gdk_flush (); } /************************************************************** * The motion-callbacks for the tool */ void path_tool_motion (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { GDisplay * gdisp; PathTool * path_tool; if (gtk_events_pending()) return; gdisp = (GDisplay *) gdisp_ptr; path_tool = (PathTool *) tool->private; switch (path_tool->click_type) { case ON_ANCHOR: path_tool_motion_anchor (tool, mevent, gdisp); break; case ON_HANDLE: path_tool_motion_handle (tool, mevent, gdisp); break; case ON_CURVE: path_tool_motion_curve (tool, mevent, gdisp); break; default: return; } } void path_tool_motion_anchor (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { PathTool * path_tool; gdouble dx, dy, d; gint x,y; static gint dxsum = 0; static gint dysum = 0; path_tool = (PathTool *) tool->private; /* * Dont do anything, if the user clicked with pressed CONTROL-Key, * because he deleted an anchor. */ if (path_tool->click_modifier & GDK_CONTROL_MASK) return; if (!(path_tool->state & PATH_TOOL_DRAG)) { path_tool->state |= PATH_TOOL_DRAG; dxsum = 0; dysum = 0; } gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, TRUE, 0); dx = x - path_tool->click_x - dxsum; dy = y - path_tool->click_y - dysum; /* restrict to horizontal/vertical lines, if modifiers are pressed * I'm not sure, if this is intuitive for the user. Esp. When moving * an endpoint of an curve I'd expect, that the *line* is * horiz/vertical - not the delta to the point, where the point was * originally... */ if (mevent->state & GDK_MOD1_MASK) { if (mevent->state & GDK_CONTROL_MASK) { d = (fabs(dx) + fabs(dy)) / 2; d = (fabs(x - path_tool->click_x) + fabs(y - path_tool->click_y)) / 2; dx = ((x < path_tool->click_x) ? -d : d ) - dxsum; dy = ((y < path_tool->click_y) ? -d : d ) - dysum; } else dx = - dxsum; } else if (mevent->state & GDK_CONTROL_MASK) dy = - dysum; path_tool->draw |= PATH_TOOL_REDRAW_ACTIVE; draw_core_pause(path_tool->core, tool); path_offset_active (path_tool->cur_path, dx, dy); dxsum += dx; dysum += dy; draw_core_resume (path_tool->core, tool); path_tool->draw &= ~PATH_TOOL_REDRAW_ACTIVE; } void path_tool_motion_handle (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { PathTool * path_tool; gdouble dx, dy; gint x,y; static gint dxsum = 0; static gint dysum = 0; path_tool = (PathTool *) tool->private; /* * Dont do anything, if the user clicked with pressed CONTROL-Key, * because he moved the handle to the anchor an anchor. * XXX: Not yet! :-) */ if (path_tool->click_modifier & GDK_CONTROL_MASK) return; if (!(path_tool->state & PATH_TOOL_DRAG)) { path_tool->state |= PATH_TOOL_DRAG; dxsum = 0; dysum = 0; } gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, TRUE, 0); dx = x - path_tool->click_x - dxsum; dy = y - path_tool->click_y - dysum; path_tool->draw |= PATH_TOOL_REDRAW_ACTIVE; draw_core_pause(path_tool->core, tool); path_curve_drag_handle (path_tool, path_tool->click_segment, dx, dy, path_tool->click_handle_id); dxsum += dx; dysum += dy; draw_core_resume (path_tool->core, tool); path_tool->draw &= ~PATH_TOOL_REDRAW_ACTIVE; } void path_tool_motion_curve (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { PathTool * path_tool; gdouble dx, dy; gint x,y; static gint dxsum = 0; static gint dysum = 0; path_tool = (PathTool *) tool->private; if (!(path_tool->state & PATH_TOOL_DRAG)) { path_tool->state |= PATH_TOOL_DRAG; dxsum = 0; dysum = 0; } gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, TRUE, 0); dx = x - path_tool->click_x - dxsum; dy = y - path_tool->click_y - dysum; path_tool->draw |= PATH_TOOL_REDRAW_ACTIVE; draw_core_pause(path_tool->core, tool); path_curve_drag_segment (path_tool, path_tool->click_segment, path_tool->click_position, dx, dy); dxsum += dx; dysum += dy; draw_core_resume (path_tool->core, tool); path_tool->draw &= ~PATH_TOOL_REDRAW_ACTIVE; } void path_tool_cursor_update (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { PathTool *path_tool; GDisplay *gdisp; gint x, y, halfwidth, dummy, cursor_location; #ifdef PATH_TOOL_DEBUG /* fprintf (stderr, "path_tool_cursor_update\n"); */ #endif PATH_TOOL_DEBUG gdisp = (GDisplay *) gdisp_ptr; path_tool = (PathTool *) tool->private; gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, TRUE, 0); /* get halfwidth in image coord */ gdisplay_untransform_coords (gdisp, mevent->x + PATH_TOOL_HALFWIDTH, 0, &halfwidth, &dummy, TRUE, 0); halfwidth -= x; cursor_location = path_tool_cursor_position (tool, x, y, halfwidth, NULL, NULL, NULL, NULL, NULL); switch (cursor_location) { case ON_CANVAS: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE1AP_CURSOR); break; case ON_ANCHOR: gdisplay_install_tool_cursor (gdisp, GDK_FLEUR); break; case ON_HANDLE: gdisplay_install_tool_cursor (gdisp, GDK_CROSSHAIR); break; case ON_CURVE: gdisplay_install_tool_cursor (gdisp, GDK_CROSSHAIR); break; default: gdisplay_install_tool_cursor (gdisp, GDK_QUESTION_ARROW); break; } } /************************************************************** * Tool-control functions */ void path_tool_control (Tool *tool, ToolAction action, gpointer gdisp_ptr) { GDisplay * gdisp; PathTool * path_tool; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_control\n"); #endif PATH_TOOL_DEBUG gdisp = (GDisplay *) tool->gdisp_ptr; path_tool = (PathTool *) tool->private; switch (action) { case PAUSE: draw_core_pause (path_tool->core, tool); break; case RESUME: draw_core_resume (path_tool->core, tool); break; case HALT: #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_control: HALT\n"); #endif PATH_TOOL_DEBUG draw_core_stop (path_tool->core, tool); tool->state = INACTIVE; break; default: break; } #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_control: end\n"); #endif PATH_TOOL_DEBUG } Tool * tools_new_path_tool (void) { Tool * tool; PathTool * private; /* The tool options */ if (! path_options) { path_options = tool_options_new (_("Path Tool")); tools_register (PATH_TOOL, (ToolOptions *) path_options); } tool = tools_new_tool (PATH_TOOL); private = g_new0 (PathTool, 1); private->click_type = ON_CANVAS; private->click_x = 0; private->click_y = 0; private->click_halfwidth = 0; private->click_modifier = 0; private->click_path = NULL; private->click_curve = NULL; private->click_segment = NULL; private->click_position = -1; private->active_count = 0; private->single_active_segment = NULL; private->state = 0; private->draw = PATH_TOOL_REDRAW_ALL; private->core = draw_core_new (path_tool_draw); private->cur_path = g_new0(Path, 1); private->scanlines = NULL; tool->private = (void *) private; tool->button_press_func = path_tool_button_press; tool->button_release_func = path_tool_button_release; tool->motion_func = path_tool_motion; tool->cursor_update_func = path_tool_cursor_update; tool->control_func = path_tool_control; private->cur_path->curves = NULL; private->cur_path->cur_curve = NULL; private->cur_path->name = g_string_new("Path 0"); private->cur_path->state = 0; private->cur_path->path_tool = private; return tool; } void tools_free_path_tool (Tool *tool) { GDisplay * gdisp; PathTool * path_tool; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "tools_free_path_tool start\n"); #endif PATH_TOOL_DEBUG path_tool = (PathTool *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; if (tool->state == ACTIVE) { draw_core_stop (path_tool->core, tool); } path_free_path (path_tool->cur_path); draw_core_free (path_tool->core); g_free (path_tool); #ifdef PATH_TOOL_DEBUG fprintf (stderr, "tools_free_path_tool end\n"); #endif PATH_TOOL_DEBUG } /************************************************************** * Set of function to determine, if the click was on a segment */ typedef struct { Tool *tool; Path *path; PathCurve *curve; PathSegment *segment; gint testx; gint testy; gint halfwidth; gint distance; gdouble position; gboolean found; } Path_on_curve_type; /* This is a CurveTraverseFunc */ void path_tool_on_curve_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer ptr) { gint distance; gdouble position; Path_on_curve_type *data = (Path_on_curve_type *) ptr; if (segment && segment->next && data && data->distance > 0) { position = path_curve_on_segment (data->tool, segment, data->testx, data->testy, data->halfwidth, &distance); if (position >= 0 && distance < data->distance ) { data->path = path; data->curve = curve; data->segment = segment; data->distance = distance; data->position = position; data->found = TRUE; } } } gdouble path_tool_on_curve (Tool *tool, gint x, gint y, gint halfwidth, Path **ret_pathP, PathCurve **ret_curveP, PathSegment **ret_segmentP) { Path_on_curve_type *data = g_new (Path_on_curve_type, 1); gdouble position; data->tool = tool; data->path = NULL; data->segment = NULL; data->segment = NULL; data->testx = x; data->testy = y; data->halfwidth = halfwidth; data->distance = halfwidth * halfwidth + 1; data->position = -1; data->found = FALSE; path_traverse_path (((PathTool *) data->tool->private)->cur_path, NULL, path_tool_on_curve_helper, NULL, data); if (ret_pathP) *ret_pathP = data->path; if (ret_curveP) *ret_curveP = data->curve; if (ret_segmentP) *ret_segmentP = data->segment; position = data->position; g_free(data); return position; } /************************************************************** * Set of function to determine, if the click was on an anchor */ typedef struct { Path *path; PathCurve *curve; PathSegment *segment; gint testx; gint testy; gint distance; gboolean found; } Path_on_anchors_type; /* This is a CurveTraverseFunc */ void path_tool_on_anchors_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer ptr) { gint distance; Path_on_anchors_type *data = (Path_on_anchors_type *) ptr; if (segment && data && data->distance > 0) { distance = ((data->testx - segment->x) * (data->testx - segment->x) + (data->testy - segment->y) * (data->testy - segment->y)); if ( distance < data->distance ) { data->path = path; data->curve = curve; data->segment = segment; data->distance = distance; data->found = TRUE; } } } gboolean path_tool_on_anchors (Tool *tool, gint x, gint y, gint halfwidth, Path **ret_pathP, PathCurve **ret_curveP, PathSegment **ret_segmentP) { Path_on_anchors_type *data = g_new (Path_on_anchors_type, 1); gboolean ret_found; data->path = NULL; data->curve = NULL; data->segment = NULL; data->testx = x; data->testy = y; data->distance = halfwidth * halfwidth + 1; data->found = FALSE; path_traverse_path (((PathTool *) tool->private)->cur_path, NULL, path_tool_on_anchors_helper, NULL, data); if (ret_pathP) *ret_pathP = data->path; if (ret_curveP) *ret_curveP = data->curve; if (ret_segmentP) *ret_segmentP = data->segment; ret_found = data->found; g_free(data); return ret_found; } /************************************************************** * Set of function to determine, if the click was on an handle */ typedef struct { Path *path; PathCurve *curve; PathSegment *segment; gint testx; gint testy; gint halfwidth; gint handle_id; gboolean found; } Path_on_handles_type; /* This is a CurveTraverseFunc */ void path_tool_on_handles_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer ptr) { Path_on_handles_type *data = (Path_on_handles_type *) ptr; gint handle; if (segment && data && !data->found) { handle = path_curve_on_handle (NULL, segment, data->testx, data->testy, data->halfwidth); if (handle) { data->path = path; data->curve = curve; data->segment = segment; data->handle_id = handle; data->found = TRUE; } } } gint path_tool_on_handles (Tool *tool, gint x, gint y, gint halfwidth, Path **ret_pathP, PathCurve **ret_curveP, PathSegment **ret_segmentP) { Path_on_handles_type *data = g_new (Path_on_handles_type, 1); gint handle_ret; data->path = NULL; data->curve = NULL; data->segment = NULL; data->testx = x; data->testy = y; data->halfwidth = halfwidth; data->handle_id = 0; data->found = FALSE; path_traverse_path (((PathTool *) tool->private)->cur_path, NULL, path_tool_on_handles_helper, NULL, data); if (ret_pathP) *ret_pathP = data->path; if (ret_curveP) *ret_curveP = data->curve; if (ret_segmentP) *ret_segmentP = data->segment; handle_ret = data->handle_id; g_free(data); return handle_ret; } /************************************************************** * Set of function to offset all active anchors */ typedef struct { gdouble dx; gdouble dy; } Path_offset_active_type; /* This is a CurveTraverseFunc */ void path_offset_active_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer ptr) { Path_offset_active_type *data = (Path_offset_active_type *) ptr; if (segment && data && (segment->flags & SEGMENT_ACTIVE)) { segment->x += data->dx; segment->y += data->dy; } /* XXX: Do a segment_update here! */ } void path_offset_active (Path *path, gdouble dx, gdouble dy) { Path_offset_active_type *data = g_new (Path_offset_active_type, 1); data->dx = dx; data->dy = dy; if (path) path_traverse_path (path, NULL, path_offset_active_helper, NULL, data); g_free(data); } /************************************************************** * Set of function to set the state of all anchors */ typedef struct { guint32 bits_set; guint32 bits_clear; PathTool *path_tool; } Path_set_flags_type; /* This is a CurveTraverseFunc */ void path_set_flags_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer ptr) { Path_set_flags_type *tmp = (Path_set_flags_type *) ptr; guint32 oldflags; guint tmp_uint; if (segment) { oldflags = segment->flags; segment->flags &= ~(tmp->bits_clear); segment->flags |= tmp->bits_set; /* * Some black magic: We try to remember, which is the single active segment. * We count, how many segments are active (in path_tool->active_count) and * XOR path_tool->single_active_segment every time we select or deselect * an anchor. So if exactly one anchor is active, path_tool->single_active_segment * points to it. */ /* If SEGMENT_ACTIVE state has changed change the PathTool data accordingly.*/ if (((segment->flags ^ oldflags) & SEGMENT_ACTIVE) && tmp && tmp->path_tool) { if (segment->flags & SEGMENT_ACTIVE) tmp->path_tool->active_count++; else tmp->path_tool->active_count--; /* Does this work on all (16|32|64)-bit Machines? */ tmp_uint = GPOINTER_TO_UINT(tmp->path_tool->single_active_segment); tmp_uint ^= GPOINTER_TO_UINT(segment); tmp->path_tool->single_active_segment = GUINT_TO_POINTER(tmp_uint); } } } void path_set_flags (PathTool *path_tool, Path *path, PathCurve *curve, PathSegment *segment, guint32 bits_set, guint32 bits_clear) { Path_set_flags_type *tmp = g_new (Path_set_flags_type, 1); tmp->bits_set=bits_set; tmp->bits_clear=bits_clear; tmp->path_tool = path_tool; if (segment) path_set_flags_helper (path, curve, segment, tmp); else if (curve) path_traverse_curve (path, curve, path_set_flags_helper, NULL, tmp); else if (path) path_traverse_path (path, NULL, path_set_flags_helper, NULL, tmp); g_free (tmp); } /************************************************************** * Set of functions to draw the segments to the window */ /* This is a CurveTraverseFunc */ void path_tool_draw_helper (Path *path, PathCurve *curve, PathSegment *segment, gpointer tool_ptr) { Tool * tool = (Tool *) tool_ptr; GDisplay * gdisp; PathTool * path_tool; DrawCore * core; gint x1, y1; gboolean draw = TRUE; if (!tool) { #ifdef PATH_TOOL_DEBUG fprintf (stderr, "Fatal Error: path_tool_draw_segment called without valid tool *\n"); #endif PATH_TOOL_DEBUG return; } gdisp = tool->gdisp_ptr; path_tool = (PathTool *) tool->private; core = path_tool->core; if (path_tool->draw & PATH_TOOL_REDRAW_ACTIVE) draw = (segment->flags & SEGMENT_ACTIVE || (segment->next && segment->next->flags & SEGMENT_ACTIVE)); if (segment && draw) { gdisplay_transform_coords (gdisp, (gint) segment->x, (gint) segment->y, &x1, &y1, FALSE); if (segment->flags & SEGMENT_ACTIVE) gdk_draw_arc (core->win, core->gc, 0, x1 - PATH_TOOL_HALFWIDTH, y1 - PATH_TOOL_HALFWIDTH, PATH_TOOL_WIDTH, PATH_TOOL_WIDTH, 0, 23040); else gdk_draw_arc (core->win, core->gc, 1, x1 - PATH_TOOL_HALFWIDTH, y1 - PATH_TOOL_HALFWIDTH, PATH_TOOL_WIDTH, PATH_TOOL_WIDTH, 0, 23040); path_curve_draw_handles (tool, segment); if (segment->next) { path_curve_draw_segment (tool, segment); } } #ifdef PATH_TOOL_DEBUG else if (!segment) fprintf(stderr, "path_tool_draw_segment: no segment to draw\n"); #endif PATH_TOOL_DEBUG } void path_tool_draw (Tool *tool) { GDisplay * gdisp; Path * cur_path; PathTool * path_tool; #ifdef PATH_TOOL_DEBUG fprintf (stderr, "path_tool_draw\n"); #endif PATH_TOOL_DEBUG gdisp = tool->gdisp_ptr; path_tool = tool->private; cur_path = path_tool->cur_path; path_traverse_path (cur_path, NULL, path_tool_draw_helper, NULL, tool); #ifdef PATH_TOOL_DEBUG /* fprintf (stderr, "path_tool_draw end.\n"); */ #endif PATH_TOOL_DEBUG }