X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fobject-edit.cpp;h=63ccf827e740cf4b55c1a632686031a9f104bdcc;hb=b7d95f994b4858eeab9ce480b04933b5e94d40eb;hp=487caa09c503923bce9a949f5abdb701814e46cf;hpb=e267a6ecd07f6559ee4ab3058626c84ed7e4b083;p=inkscape.git diff --git a/src/object-edit.cpp b/src/object-edit.cpp index 487caa09c..63ccf827e 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -30,6 +30,7 @@ #include "desktop-affine.h" #include #include "desktop.h" +#include "desktop-handles.h" #include "sp-namedview.h" #include "sp-pattern.h" @@ -50,7 +51,7 @@ static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop); //static -SPKnotHolder *sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop, guint number_of_handles); +SPKnotHolder *sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_arc_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_star_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop); @@ -65,7 +66,7 @@ sp_item_knot_holder(SPItem *item, SPDesktop *desktop) if (SP_IS_RECT(item)) { return sp_rect_knot_holder(item, desktop); } else if (SP_IS_3DBOX(item)) { - return sp_3dbox_knot_holder(item, desktop, SP3DBoxContext::number_of_handles); + return sp_3dbox_knot_holder(item, desktop); } else if (SP_IS_ARC(item)) { return sp_arc_knot_holder(item, desktop); } else if (SP_IS_STAR(item)) { @@ -224,7 +225,7 @@ static NR::Point snap_knot_position(SPItem *item, NR::Point const &p) NR::Matrix const i2d (sp_item_i2d_affine (item)); NR::Point s = p * i2d; SnapManager const &m = desktop->namedview->snap_manager; - s = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, s, item).getPoint(); + s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, s, item).getPoint(); return s * i2d.inverse(); } @@ -529,132 +530,255 @@ static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop) /* 3D Box */ -static void sp_3dbox_knot_set(SPItem *item, guint knot_id, Box3D::Axis direction, - NR::Point const &new_pos, NR::Point const &origin, guint state) +static inline Box3D::Axis movement_axis_of_3dbox_corner (guint corner, guint state) +{ + // this function has the purpose to simplify a change in the resizing behaviour of boxes + switch (corner) { + case 0: + case 1: + case 2: + case 3: + return ((state & GDK_SHIFT_MASK) ? Box3D::Z : Box3D::XY); + case 4: + case 5: + case 6: + case 7: + return ((state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z); + } + return Box3D::NONE; +} + +/* + * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to + * their intersection, we remember the last snapped line and keep snapping to this specific line as long + * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold. + */ + +// Should we make the threshold settable in the preferences? +static double remember_snap_threshold = 30; +static guint remember_snap_index = 0; +static guint remember_snap_index_center = 0; + +static NR::Point snap_knot_position_3dbox (SP3DBox *box, guint corner, Box3D::Axis direction, NR::Point const &origin, NR::Point const &p, guint state) { - g_assert(item != NULL); - SP3DBox *box = SP_3DBOX(item); + SPDesktop * desktop = inkscape_active_desktop(); + Box3D::Perspective3D *persp = sp_desktop_document (desktop)->get_persp_of_box (box); - // FIXME: Why must the coordinates be flipped vertically??? - SPDocument *doc = SP_OBJECT_DOCUMENT(box); - gdouble height = sp_document_height(doc); + if (is_single_axis_direction (direction)) return p; - NR::Matrix const i2d (sp_item_i2d_affine (item)); - if (direction == Box3D::Z) { - sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, !(state & GDK_SHIFT_MASK)); + Box3D::Axis axis1 = Box3D::extract_first_axis_direction (direction); + Box3D::Axis axis2 = Box3D::extract_second_axis_direction (direction); + + NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (box))); + NR::Point origin_dt = origin * i2d; + NR::Point p_dt = p * i2d; + + Box3D::PerspectiveLine pl1 (origin_dt, axis1, persp); + Box3D::PerspectiveLine pl2 (origin_dt, axis2, persp); + Box3D::Line diag1 (origin_dt, box->corners[corner ^ Box3D::XY]); + + int num_snap_lines = 3; + NR::Point snap_pts[num_snap_lines]; + + snap_pts[0] = pl1.closest_to (p_dt); + snap_pts[1] = pl2.closest_to (p_dt); + snap_pts[2] = diag1.closest_to (p_dt); + + gdouble const zoom = desktop->current_zoom(); + + double snap_dists[num_snap_lines]; + + for (int i = 0; i < num_snap_lines; ++i) { + snap_dists[i] = NR::L2 (snap_pts[i] - p_dt) * zoom; + } + + bool within_tolerance = true; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] > remember_snap_threshold) { + within_tolerance = false; + break; + } + } + + int snap_index = -1; + double snap_dist = NR_HUGE; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] < snap_dist) { + snap_index = i; + snap_dist = snap_dists[i]; + } + } + + if (within_tolerance) { + return snap_pts[remember_snap_index] * i2d.inverse(); } else { - sp_3dbox_move_corner_in_XY_plane (box, knot_id, new_pos * i2d, (state & GDK_SHIFT_MASK) ? direction : Box3D::XY); + remember_snap_index = snap_index; + return snap_pts[snap_index] * i2d.inverse(); } - sp_3dbox_update_curves (box); } -static NR::Point sp_3dbox_knot_get(SPItem *item, guint knot_id) +static NR::Point snap_center_position_3dbox (SP3DBox *box, NR::Point const &origin, NR::Point const &p) { - g_assert(item != NULL); - SP3DBox *box = SP_3DBOX(item); + SPDesktop * desktop = inkscape_active_desktop(); + Box3D::Perspective3D *persp = sp_desktop_document (desktop)->get_persp_of_box (box); - NR::Matrix const i2d (sp_item_i2d_affine (item)); - return sp_3dbox_get_corner(box, knot_id) * i2d; -} + Box3D::Axis axis1 = Box3D::X; + Box3D::Axis axis2 = Box3D::Y; -static void sp_3dbox_knot1_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) -{ - sp_3dbox_knot_set (item, 1, Box3D::Y, new_pos, origin, state); -} + NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (box))); + NR::Point origin_dt = origin * i2d; + NR::Point p_dt = p * i2d; -/* -static void sp_3dbox_knot1_set_constrained(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) -{ - sp_3dbox_knot_set (item, 1, Box3D::Y, new_pos, origin, state ^ GDK_SHIFT_MASK); -} -*/ + Box3D::PerspectiveLine pl1 (origin_dt, axis1, persp); + Box3D::PerspectiveLine pl2 (origin_dt, axis2, persp); + NR::Point midpt1 = sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner5, Box3D::Z, persp); + NR::Point midpt2 = sp_3dbox_get_midpoint_in_axis_direction (box->old_corner3, box->old_corner7, Box3D::Z, persp); + Box3D::Line diag1 (origin_dt, midpt1); + Box3D::Line diag2 (origin_dt, midpt2); -static void sp_3dbox_knot2_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) -{ - sp_3dbox_knot_set (item, 2, Box3D::X, new_pos, origin, state); -} + int num_snap_lines = 4; + NR::Point snap_pts[num_snap_lines]; -/* -static void sp_3dbox_knot2_set_constrained(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) -{ - sp_3dbox_knot_set (item, 2, Box3D::X, new_pos, origin, state ^ GDK_SHIFT_MASK); -} + // should we snap to the closest point or to the projection along perspective lines? + snap_pts[0] = pl1.closest_to (p_dt); + snap_pts[1] = pl2.closest_to (p_dt); + snap_pts[2] = diag1.closest_to (p_dt); + snap_pts[3] = diag2.closest_to (p_dt); -static void sp_3dbox_knot3_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) -{ - if (!(state & GDK_SHIFT_MASK)) { - sp_3dbox_knot_set (item, 3, Box3D::Y, new_pos, origin, state); + gdouble const zoom = desktop->current_zoom(); + + double snap_dists[num_snap_lines]; + + for (int i = 0; i < num_snap_lines; ++i) { + snap_dists[i] = NR::L2 (snap_pts[i] - p_dt) * zoom; + } + + bool within_tolerance = true; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] > remember_snap_threshold) { + within_tolerance = false; + break; + } + } + + int snap_index = -1; + double snap_dist = NR_HUGE; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] < snap_dist) { + snap_index = i; + snap_dist = snap_dists[i]; + } + } + + if (within_tolerance) { + return snap_pts[remember_snap_index_center] * i2d.inverse(); } else { - sp_3dbox_knot_set (item, 3, Box3D::Z, new_pos, origin, state ^ GDK_SHIFT_MASK); + remember_snap_index_center = snap_index; + return snap_pts[snap_index] * i2d.inverse(); } } -*/ -static void sp_3dbox_knot5_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point sp_3dbox_knot_get(SPItem *item, guint knot_id) { - sp_3dbox_knot_set (item, 5, Box3D::Z, new_pos, origin, state); + g_assert(item != NULL); + SP3DBox *box = SP_3DBOX(item); + + NR::Matrix const i2d (sp_item_i2d_affine (item)); + return sp_3dbox_get_corner(box, knot_id) * i2d; } -/* -static void sp_3dbox_knot7_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot_set(SPItem *item, guint knot_id, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set (item, 7, Box3D::Z, new_pos, origin, state); + g_assert(item != NULL); + SP3DBox *box = SP_3DBOX(item); + + NR::Matrix const i2d (sp_item_i2d_affine (item)); + Box3D::Axis direction = movement_axis_of_3dbox_corner (knot_id, state); + if ((state & GDK_CONTROL_MASK) && !is_single_axis_direction (direction)) { + // snap if Ctrl is pressed and movement isn't already constrained to a single axis + NR::Point const s = snap_knot_position_3dbox (box, knot_id, direction, origin, new_pos, state); + sp_3dbox_move_corner_in_Z_direction (box, knot_id, s * i2d, false); + } else { + if (direction == Box3D::Z) { + sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, true); + } else { + sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, false); + } + } + sp_3dbox_update_curves (box); + sp_3dbox_set_ratios (box); + sp_3dbox_update_perspective_lines (); + sp_3dbox_set_z_orders_later_on (box); } -*/ -// defined a uniform behaviour for all knots -static void sp_3dbox_knot_set_uniformly(SPItem *item, guint knot_id, Box3D::Axis direction, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot_center_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - g_assert(item != NULL); SP3DBox *box = SP_3DBOX(item); NR::Matrix const i2d (sp_item_i2d_affine (item)); - if (direction == Box3D::Z) { - sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, !(state & GDK_SHIFT_MASK)); + NR::Point new_pt (new_pos); + + if ((state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { + // snap if Ctrl is pressed and movement isn't already constrained to a single axis + new_pt = snap_center_position_3dbox (box, origin, new_pos); + } + + if (state & GDK_SHIFT_MASK) { + sp_3dbox_recompute_Z_corners_from_new_center (box, new_pt * i2d); } else { - sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, (state & GDK_SHIFT_MASK)); + sp_3dbox_recompute_XY_corners_from_new_center (box, new_pt * i2d); } + sp_3dbox_update_curves (box); + sp_3dbox_set_z_orders_later_on (box); +} + +static NR::Point sp_3dbox_knot_center_get(SPItem *item) +{ + NR::Maybe center = sp_3dbox_get_center(SP_3DBOX(item)); + if (!center) return NR::Point (0, 0); + NR::Matrix const i2d (sp_item_i2d_affine (item)); + return (*center) * i2d; } -static void sp_3dbox_knot0_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot0_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 0, Box3D::XY, new_pos, origin, state); + sp_3dbox_knot_set(item, 0, new_pos, origin, state); } -static void sp_3dbox_knot1_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot1_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 1, Box3D::XY, new_pos, origin, state); + sp_3dbox_knot_set(item, 1, new_pos, origin, state); } -static void sp_3dbox_knot2_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot2_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 2, Box3D::XY, new_pos, origin, state); + sp_3dbox_knot_set(item, 2, new_pos, origin, state); } -static void sp_3dbox_knot3_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot3_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 3, Box3D::XY, new_pos, origin, state); + sp_3dbox_knot_set(item, 3, new_pos, origin, state); } -static void sp_3dbox_knot4_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot4_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 4, Box3D::Z, new_pos, origin, state); + sp_3dbox_knot_set(item, 4, new_pos, origin, state); } -static void sp_3dbox_knot5_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot5_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 5, Box3D::Z, new_pos, origin, state); + sp_3dbox_knot_set(item, 5, new_pos, origin, state); } -static void sp_3dbox_knot6_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot6_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 6, Box3D::Z, new_pos, origin, state); + sp_3dbox_knot_set(item, 6, new_pos, origin, state); } -static void sp_3dbox_knot7_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void sp_3dbox_knot7_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - sp_3dbox_knot_set_uniformly(item, 7, Box3D::Z, new_pos, origin, state); + sp_3dbox_knot_set(item, 7, new_pos, origin, state); } static NR::Point sp_3dbox_knot0_get(SPItem *item) @@ -700,48 +824,33 @@ static NR::Point sp_3dbox_knot7_get(SPItem *item) //static SPKnotHolder * -sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop, guint number_of_handles) +sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop) { g_assert(item != NULL); SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); - switch (number_of_handles) { - case 3: - sp_knot_holder_add(knot_holder, sp_3dbox_knot1_set, sp_3dbox_knot1_get, NULL,_("Resize box in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot2_set, sp_3dbox_knot2_get, NULL,_("Resize box in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot5_set, sp_3dbox_knot5_get, NULL,_("Resize box in Z direction")); - sp_pat_knot_holder(item, knot_holder); - break; - case 4: - /*** - sp_knot_holder_add(knot_holder, sp_3dbox_knot1_set_constrained, sp_3dbox_knot1_get, NULL,_("Resize box in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot2_set_constrained, sp_3dbox_knot2_get, NULL,_("Resize box in X/Y direction")); - sp_knot_holder_add_full(knot_holder, sp_3dbox_knot3_set, sp_3dbox_knot3_get, NULL, - SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, _("Resize box in Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot7_set, sp_3dbox_knot7_get, NULL,_("Resize box in Z direction")); - ***/ - sp_knot_holder_add(knot_holder, sp_3dbox_knot0_set_uniformly, sp_3dbox_knot0_get, NULL, - _("Resize box in X/Y direction; with Shift along the Z axis")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot1_set_uniformly, sp_3dbox_knot1_get, NULL, - _("Resize box in X/Y direction; with Shift along the Z axis")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot2_set_uniformly, sp_3dbox_knot2_get, NULL, - _("Resize box in X/Y direction; with Shift along the Z axis")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot3_set_uniformly, sp_3dbox_knot3_get, NULL, - _("Resize box in X/Y direction; with Shift along the Z axis")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot4_set_uniformly, sp_3dbox_knot4_get, NULL, - _("Resize box along the Z axis; with Shift in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot5_set_uniformly, sp_3dbox_knot5_get, NULL, - _("Resize box along the Z axis; with Shift in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot6_set_uniformly, sp_3dbox_knot6_get, NULL, - _("Resize box along the Z axis; with Shift in X/Y direction")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot7_set_uniformly, sp_3dbox_knot7_get, NULL, - _("Resize box along the Z axis; with Shift in X/Y direction")); - sp_pat_knot_holder(item, knot_holder); - break; - default: - g_print ("Wrong number of handles (%d): Not implemented yet.\n", number_of_handles); - break; - } + sp_knot_holder_add(knot_holder, sp_3dbox_knot0_set, sp_3dbox_knot0_get, NULL, + _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot1_set, sp_3dbox_knot1_get, NULL, + _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot2_set, sp_3dbox_knot2_get, NULL, + _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot3_set, sp_3dbox_knot3_get, NULL, + _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot4_set, sp_3dbox_knot4_get, NULL, + _("Resize box along the Z axis; with Shift in X/Y direction; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot5_set, sp_3dbox_knot5_get, NULL, + _("Resize box along the Z axis; with Shift in X/Y direction; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot6_set, sp_3dbox_knot6_get, NULL, + _("Resize box along the Z axis; with Shift in X/Y direction; with Ctrl to constrain to the directions of edges or diagonals")); + sp_knot_holder_add(knot_holder, sp_3dbox_knot7_set, sp_3dbox_knot7_get, NULL, + _("Resize box along the Z axis; with Shift in X/Y direction; with Ctrl to constrain to the directions of edges or diagonals")); + + // center dragging + sp_knot_holder_add_full(knot_holder, sp_3dbox_knot_center_set, sp_3dbox_knot_center_get, NULL, + SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,_("Move the box in perspective.")); + + sp_pat_knot_holder(item, knot_holder); return knot_holder; } @@ -1250,7 +1359,7 @@ sp_offset_knot_holder(SPItem *item, SPDesktop *desktop) static SPKnotHolder * sp_misc_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, instead make a pattern-drag similar to gradient-drag { - if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) + if ((SP_OBJECT(item)->style->fill.isPaintserver()) && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) { SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); @@ -1265,7 +1374,7 @@ sp_misc_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, inste static void sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder) { - if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) + if ((SP_OBJECT(item)->style->fill.isPaintserver()) && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) { sp_knot_holder_add_full(knot_holder, sp_pattern_xy_set, sp_pattern_xy_get, NULL, SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,