diff options
| author | Tavmjong Bah <tavmjong@free.fr> | 2012-05-23 12:00:49 +0000 |
|---|---|---|
| committer | tavmjong-free <tavmjong@free.fr> | 2012-05-23 12:00:49 +0000 |
| commit | 0ea9b448ee16fe941d46395c2877f4d2e815b9ed (patch) | |
| tree | a49cb57d691df46475d1e9fe197aeade8b0fa10c /src | |
| parent | Correct improper flipping of sRGB transform from RGB to BGR with cairo change... (diff) | |
| download | inkscape-0ea9b448ee16fe941d46395c2877f4d2e815b9ed.tar.gz inkscape-0ea9b448ee16fe941d46395c2877f4d2e815b9ed.zip | |
Add Mesh tool (experimental, requires Cario >= 1.11.4, disabled by default).
(bzr r11406)
Diffstat (limited to 'src')
36 files changed, 6156 insertions, 157 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4632f0907..ddfed3e75 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,9 @@ set(sp_SRC sp-line.cpp sp-lpe-item.cpp sp-mask.cpp + sp-mesh-array.cpp + sp-mesh-patch.cpp + sp-mesh-row.cpp sp-metadata.cpp sp-metrics.cpp sp-missing-glyph.cpp @@ -127,6 +130,13 @@ set(sp_SRC sp-lpe-item.h sp-marker-loc.h sp-mask.h + sp-mesh-array.h + sp-mesh-gradient-fns.h + sp-mesh-gradient.h + sp-mesh-patch-fns.h + sp-mesh-patch.h + sp-mesh-row-fns.h + sp-mesh-row.h sp-metadata.h sp-metric.h sp-metrics.h @@ -239,6 +249,7 @@ set(inkscape_SRC marker.cpp measure-context.cpp media.cpp + mesh-context.cpp message-context.cpp message-stack.cpp mod360.cpp @@ -402,6 +413,7 @@ set(inkscape_SRC media.h memeq.h menus-skeleton.h + mesh-context.h message-context.h message-stack.h message.h diff --git a/src/Makefile_insert b/src/Makefile_insert index e9c149cb2..1ee721abe 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -104,6 +104,7 @@ ink_common_sources += \ media.cpp media.h \ memeq.h \ menus-skeleton.h \ + mesh-context.cpp mesh-context.h \ message-context.cpp message-context.h \ message.h \ message-stack.cpp message-stack.h \ @@ -193,6 +194,13 @@ ink_common_sources += \ sp-marker-loc.h \ sp-mask.cpp sp-mask.h \ sp-metadata.cpp sp-metadata.h \ + sp-mesh-array.cpp sp-mesh-array.h \ + sp-mesh-gradient-fns.h \ + sp-mesh-gradient.h \ + sp-mesh-patch-fns.h \ + sp-mesh-patch.cpp sp-mesh-patch.h \ + sp-mesh-row-fns.h \ + sp-mesh-row.cpp sp-mesh-row.h \ sp-metric.h \ sp-metrics.cpp sp-metrics.h \ sp-missing-glyph.cpp sp-missing-glyph.h \ diff --git a/src/attributes.cpp b/src/attributes.cpp index 87cc0488c..7d6f8614d 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -289,6 +289,8 @@ static SPStyleProp const props[] = { /* SPRadialGradient */ {SP_ATTR_FX, "fx"}, {SP_ATTR_FY, "fy"}, + /* SPMeshPatch */ + {SP_ATTR_TENSOR, "tensor"}, /* SPPattern */ {SP_ATTR_PATTERNUNITS, "patternUnits"}, {SP_ATTR_PATTERNCONTENTUNITS, "patternContentUnits"}, @@ -453,6 +455,7 @@ static SPStyleProp const props[] = { /* Gradient */ {SP_PROP_STOP_COLOR, "stop-color"}, {SP_PROP_STOP_OPACITY, "stop-opacity"}, + {SP_PROP_STOP_PATH, "path"}, /* Interactivity */ {SP_PROP_POINTER_EVENTS, "pointer-events"}, /* Paint */ diff --git a/src/attributes.h b/src/attributes.h index c78882cbf..d8eb087b2 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -290,6 +290,8 @@ enum SPAttributeEnum { /* SPRadialGradient */ SP_ATTR_FX, SP_ATTR_FY, + /* SPMeshPatch */ + SP_ATTR_TENSOR, /* SPPattern */ SP_ATTR_PATTERNUNITS, SP_ATTR_PATTERNCONTENTUNITS, @@ -454,6 +456,7 @@ enum SPAttributeEnum { /* Gradient */ SP_PROP_STOP_COLOR, SP_PROP_STOP_OPACITY, + SP_PROP_STOP_PATH, /* Interactivity */ SP_PROP_POINTER_EVENTS, /* Paint */ diff --git a/src/display/sp-ctrlcurve.cpp b/src/display/sp-ctrlcurve.cpp index ec078af48..5ef4c4d3f 100644 --- a/src/display/sp-ctrlcurve.cpp +++ b/src/display/sp-ctrlcurve.cpp @@ -1,5 +1,3 @@ -#define __INKSCAPE_CTRLCURVE_C__ - /* * Simple bezier curve used for Mesh Gradients * diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp index 119adfc79..ab525e482 100644 --- a/src/gradient-chemistry.cpp +++ b/src/gradient-chemistry.cpp @@ -7,7 +7,9 @@ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> * Jon A. Cruz <jon@joncruz.org> * Abhishek Sharma + * Tavmjong Bah <tavmjong@free.fr> * + * Copyright (C) 2012 Tavmjong Bah * Copyright (C) 2010 Authors * Copyright (C) 2007 Johan Engelen * Copyright (C) 2001-2005 authors @@ -27,8 +29,10 @@ #include "sp-gradient-vector.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" +#include "sp-mesh-gradient.h" #include "sp-stop.h" #include "widgets/gradient-vector.h" +#include "gradient-drag.h" #include "sp-text.h" #include "sp-tspan.h" @@ -130,14 +134,18 @@ static SPGradient *sp_gradient_get_private_normalized(SPDocument *document, SPGr Inkscape::XML::Node *repr; if (type == SP_GRADIENT_TYPE_LINEAR) { repr = xml_doc->createElement("svg:linearGradient"); - } else { + } else if(type == SP_GRADIENT_TYPE_RADIAL) { repr = xml_doc->createElement("svg:radialGradient"); + } else { + // Rows/patches added in sp_gradient_reset_to_userspace for new meshes. + repr = xml_doc->createElement("svg:meshGradient"); } // privates are garbage-collectable repr->setAttribute("inkscape:collect", "always"); // link to vector + // MESH FIXME: Meshes don't used vector... but meshes simulating gradient across/along path might. sp_gradient_repr_set_link(repr, vector); /* Append the new private gradient to defs */ @@ -248,11 +256,13 @@ SPGradient *sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *ve repr_new->setAttribute("fx", repr->attribute("fx")); repr_new->setAttribute("fy", repr->attribute("fy")); repr_new->setAttribute("r", repr->attribute("r")); - } else { + } else if (SP_IS_LINEARGRADIENT(gr)) { repr_new->setAttribute("x1", repr->attribute("x1")); repr_new->setAttribute("y1", repr->attribute("y1")); repr_new->setAttribute("x2", repr->attribute("x2")); repr_new->setAttribute("y2", repr->attribute("y2")); + } else { + std::cout << "sp_gradient_fork_private_if_necessary: mesh not implemented" << std::endl; } return gr_new; @@ -344,11 +354,19 @@ SPGradient *sp_gradient_reset_to_userspace(SPGradient *gr, SPItem *item) gr->getRepr()->setAttribute("gradientTransform", c); g_free(c); } - } else { + } else if (SP_IS_LINEARGRADIENT(gr)) { sp_repr_set_svg_double(repr, "x1", (center - Geom::Point(width/2, 0))[Geom::X]); sp_repr_set_svg_double(repr, "y1", (center - Geom::Point(width/2, 0))[Geom::Y]); sp_repr_set_svg_double(repr, "x2", (center + Geom::Point(width/2, 0))[Geom::X]); sp_repr_set_svg_double(repr, "y2", (center + Geom::Point(width/2, 0))[Geom::Y]); + } else { + // Mesh + // THIS IS BEING CALLED TWICE WHENEVER A NEW GRADIENT IS CREATED, WRITING HERE CAUSES PROBLEMS + // IN SPMeshNodeArray::create() + //sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]); + //sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]); + SPMeshGradient* mg = SP_MESHGRADIENT( gr ); + mg->array.create( mg, item, bbox ); } // set the gradientUnits @@ -633,6 +651,7 @@ void sp_item_gradient_edit_stop(SPItem *item, GrPointType point_type, guint poin } break; default: + g_warning( "Unhandled gradient handle" ); break; } } @@ -644,49 +663,78 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, if (!gradient || !SP_IS_GRADIENT(gradient)) return 0; - SPGradient *vector = gradient->getVector(); + if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { - if (!vector) // orphan! - return 0; // what else to do? + SPGradient *vector = gradient->getVector(); - switch (point_type) { - case POINT_LG_BEGIN: - case POINT_RG_CENTER: - case POINT_RG_FOCUS: - { - SPStop *first = vector->getFirstStop(); - if (first) { - return sp_stop_get_rgba32(first); + if (!vector) // orphan! + return 0; // what else to do? + + switch (point_type) { + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = vector->getFirstStop(); + if (first) { + return sp_stop_get_rgba32(first); + } } - } - break; + break; - case POINT_LG_END: - case POINT_RG_R1: - case POINT_RG_R2: - { - SPStop *last = sp_last_stop (vector); - if (last) { - return sp_stop_get_rgba32(last); + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + return sp_stop_get_rgba32(last); + } } - } - break; + break; - case POINT_LG_MID: - case POINT_RG_MID1: - case POINT_RG_MID2: - { - SPStop *stopi = sp_get_stop_i (vector, point_i); - if (stopi) { - return sp_stop_get_rgba32(stopi); + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + { + SPStop *stopi = sp_get_stop_i (vector, point_i); + if (stopi) { + return sp_stop_get_rgba32(stopi); + } } + break; + + default: + g_warning( "Bad linear/radial gradient handle type" ); + break; } - break; + return 0; + } else { - default: - break; + // Mesh gradient + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + + switch (point_type) { + case POINT_MG_CORNER: { + + SPColor color = mg->array.corners[ point_i ]->color; + double opacity = mg->array.corners[ point_i ]->opacity; + return color.toRGBA32( opacity ); + break; + } + + case POINT_MG_HANDLE: + case POINT_MG_TENSOR: + { + // Do nothing. Handles and tensors don't have color + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + return 0; } - return 0; } void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop) @@ -699,52 +747,102 @@ void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint if (!gradient || !SP_IS_GRADIENT(gradient)) return; - SPGradient *vector = gradient->getVector(); + if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { - if (!vector) // orphan! - return; + SPGradient *vector = gradient->getVector(); - vector = sp_gradient_fork_vector_if_necessary (vector); - if ( gradient != vector && gradient->ref->getObject() != vector ) { - sp_gradient_repr_set_link(gradient->getRepr(), vector); - } + if (!vector) // orphan! + return; - switch (point_type) { - case POINT_LG_BEGIN: - case POINT_RG_CENTER: - case POINT_RG_FOCUS: - { - SPStop *first = vector->getFirstStop(); - if (first) { - sp_repr_css_change(first->getRepr(), stop, "style"); - } + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(gradient->getRepr(), vector); } - break; - case POINT_LG_END: - case POINT_RG_R1: - case POINT_RG_R2: - { - SPStop *last = sp_last_stop (vector); - if (last) { - sp_repr_css_change(last->getRepr(), stop, "style"); + switch (point_type) { + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = vector->getFirstStop(); + if (first) { + sp_repr_css_change(first->getRepr(), stop, "style"); + } } - } - break; + break; - case POINT_LG_MID: - case POINT_RG_MID1: - case POINT_RG_MID2: - { - SPStop *stopi = sp_get_stop_i (vector, point_i); - if (stopi) { - sp_repr_css_change(stopi->getRepr(), stop, "style"); + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + sp_repr_css_change(last->getRepr(), stop, "style"); + } } - } - break; + break; - default: + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + { + SPStop *stopi = sp_get_stop_i (vector, point_i); + if (stopi) { + sp_repr_css_change(stopi->getRepr(), stop, "style"); + } + } break; + + default: + g_warning( "Bad linear/radial gradient handle type" ); + break; + } + } else { + + // Mesh gradient + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + + bool changed = false; + switch (point_type) { + case POINT_MG_CORNER: { + + gchar const* color_str = sp_repr_css_property( stop, "stop-color", NULL ); + if( color_str ) { + SPColor color( 0 ); + SPStyle* style = sp_style_new(0); + SPIPaint paint; + paint.read( color_str, *style ); + if( paint.isColor() ) { + color = paint.value.color; + } + mg->array.corners[ point_i ]->color = color; + changed = true; + } + gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", NULL ); + if( opacity_str ) { + std::stringstream os( opacity_str ); + double opacity = 1.0; + os >> opacity; + mg->array.corners[ point_i ]->opacity = opacity; + changed = true; + } + if( changed ) { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + mg->array.write( mg ); + } + break; + } + + case POINT_MG_HANDLE: + case POINT_MG_TENSOR: + { + // Do nothing. Handles and tensors don't have colors. + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } } } @@ -820,6 +918,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi if (!gradient || !SP_IS_GRADIENT(gradient)) return; + // Needed only if units are set to SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX gradient = sp_gradient_convert_to_userspace(gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke"); Geom::Affine i2d (item->i2dt_affine ()); @@ -887,6 +986,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi } break; default: + g_warning( "Bad linear gradient handle type" ); break; } } else if (SP_IS_RADIALGRADIENT(gradient)) { @@ -963,7 +1063,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi break; } - case POINT_RG_MID1: + case POINT_RG_MID1: { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); @@ -980,7 +1080,8 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi } break; } - case POINT_RG_MID2: + case POINT_RG_MID2: + { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); double offset = Geom::LineSegment(start, end).nearestPoint(p); @@ -995,6 +1096,10 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } break; + } + default: + g_warning( "Bad radial gradient handle type" ); + break; } if (transform_set) { @@ -1008,7 +1113,41 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } } + } else if (SP_IS_MESHGRADIENT(gradient)) { + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + //Geom::Affine new_transform; + //bool transform_set = false; + + switch (point_type) { + case POINT_MG_CORNER: + { + mg->array.corners[ point_i ]->p = p; + // Handles are moved in gradient-drag.cpp + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + case POINT_MG_HANDLE: { + mg->array.handles[ point_i ]->p = p; + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + case POINT_MG_TENSOR: { + mg->array.tensors[ point_i ]->p = p; + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + if( write_repr ) { + //std::cout << "Write mesh repr" << std::endl; + sp_meshgradient_repr_write( mg ); + } } + } SPGradient *sp_item_gradient_get_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke) @@ -1033,6 +1172,10 @@ SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget } +/** +Returns the position of point point_type of the gradient applied to item (either fill_or_stroke), +in desktop coordinates. +*/ Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { #ifdef SP_GR_VERBOSE @@ -1060,6 +1203,9 @@ Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_ p = (1-offset) * Geom::Point(lg->x1.computed, lg->y1.computed) + offset * Geom::Point(lg->x2.computed, lg->y2.computed); } break; + default: + g_warning( "Bad linear gradient handle type" ); + break; } } else if (SP_IS_RADIALGRADIENT(gradient)) { SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); @@ -1088,9 +1234,34 @@ Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_ p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed, rg->cy.computed - rg->r.computed); } break; + default: + g_warning( "Bad radial gradient handle type" ); + break; + } + } else if (SP_IS_MESHGRADIENT(gradient)) { + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + switch (point_type) { + + case POINT_MG_CORNER: + p = mg->array.corners[ point_i ]->p; + break; + + case POINT_MG_HANDLE: { + p = mg->array.handles[ point_i ]->p; + break; + } + + case POINT_MG_TENSOR: { + p = mg->array.tensors[ point_i ]->p; + break; + } + + default: + g_warning( "Bad mesh handle type" ); } } + if (SP_GRADIENT(gradient)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { item->document->ensureUpToDate(); Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine @@ -1104,6 +1275,11 @@ Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_ return p; } +/** + * Sets item fill or stroke to the gradient of the specified type with given vector, creating + * new private gradient, if needed. + * gr has to be a normalized vector. + */ SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke) { @@ -1168,6 +1344,8 @@ SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType ty } else { /* Current fill style is not a gradient or wrong type, so construct everything */ + /* This is where mesh gradients are constructed. */ + g_assert(SP_IS_GRADIENT(gr)); // TEMP SPGradient *constructed = sp_gradient_get_private_normalized(item->document, gr, type); constructed = sp_gradient_reset_to_userspace(constructed, item); sp_style_set_property_url(item, ( (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke" ), constructed, true); diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index 6ea430585..64d82c87d 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -6,6 +6,7 @@ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> * Jon A. Cruz <jon@joncruz.org> * Abhishek Sharma + * Tavmjong Bah <tavmjong@free.fr> * * Copyright (C) 2007 Johan Engelen * Copyright (C) 2005,2010 Authors @@ -20,7 +21,7 @@ #include <glibmm/i18n.h> #include <cstring> #include <string> -#include <2geom/bezier-curve.h> +//#include <2geom/bezier-curve.h> #include "desktop-handles.h" #include "selection.h" @@ -30,6 +31,7 @@ #include "document.h" #include "document-undo.h" #include "display/sp-ctrlline.h" +#include "display/sp-ctrlcurve.h" #include "display/sp-canvas-util.h" #include "xml/repr.h" #include "svg/css-ostringstream.h" @@ -40,6 +42,9 @@ #include "knot.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" +#include "sp-mesh-gradient.h" +#include "sp-mesh-row.h" +#include "sp-mesh-patch.h" #include "gradient-chemistry.h" #include "gradient-drag.h" #include "sp-stop.h" @@ -61,23 +66,29 @@ using Inkscape::CTLINE_SECONDARY; #define GR_KNOT_COLOR_MOUSEOVER 0xff000000 #define GR_KNOT_COLOR_SELECTED 0x0000ff00 +#define GR_LINE_COLOR_FILL 0x0000ff7f +#define GR_LINE_COLOR_STROKE 0x9999007f + // screen pixels between knots when they snap: #define SNAP_DIST 5 // absolute distance between gradient points for them to become a single dragger when the drag is created: #define MERGE_DIST 0.1 -// knot shapes corresponding to GrPointType enum +// knot shapes corresponding to GrPointType enum (in sp-gradient.h) SPKnotShapeType gr_knot_shapes [] = { - SP_KNOT_SHAPE_SQUARE, //POINT_LG_BEGIN - SP_KNOT_SHAPE_CIRCLE, //POINT_LG_END - SP_KNOT_SHAPE_DIAMOND, //POINT_LG_MID + SP_KNOT_SHAPE_SQUARE, // POINT_LG_BEGIN + SP_KNOT_SHAPE_CIRCLE, // POINT_LG_END + SP_KNOT_SHAPE_DIAMOND, // POINT_LG_MID SP_KNOT_SHAPE_SQUARE, // POINT_RG_CENTER SP_KNOT_SHAPE_CIRCLE, // POINT_RG_R1 SP_KNOT_SHAPE_CIRCLE, // POINT_RG_R2 - SP_KNOT_SHAPE_CROSS, // POINT_RG_FOCUS - SP_KNOT_SHAPE_DIAMOND, //POINT_RG_MID1 - SP_KNOT_SHAPE_DIAMOND //POINT_RG_MID2 + SP_KNOT_SHAPE_CROSS, // POINT_RG_FOCUS + SP_KNOT_SHAPE_DIAMOND, // POINT_RG_MID1 + SP_KNOT_SHAPE_DIAMOND, // POINT_RG_MID2 + SP_KNOT_SHAPE_DIAMOND, // POINT_MG_CORNER + SP_KNOT_SHAPE_CIRCLE, // POINT_MG_HANDLE + SP_KNOT_SHAPE_SQUARE // POINT_MG_TENSOR }; const gchar *gr_knot_descr [] = { @@ -89,7 +100,10 @@ const gchar *gr_knot_descr [] = { N_("Radial gradient <b>radius</b>"), N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS N_("Radial gradient <b>mid stop</b>"), - N_("Radial gradient <b>mid stop</b>") + N_("Radial gradient <b>mid stop</b>"), + N_("Mesh gradient <b>corner</b>"), + N_("Mesh gradient <b>handle</b>"), + N_("Mesh gradient <b>tensor</b>") }; static void @@ -347,6 +361,11 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler SPGradient *gradient = 0; //bool r1_knot = false; + // For Mesh + int divide_row = -1; + int divide_column = -1; + double divide_coord = 0.5; + bool addknot = false; for (std::vector<Inkscape::PaintTarget>::const_iterator it = allPaintTargets().begin(); (it != allPaintTargets().end()) && !addknot; ++it) @@ -393,36 +412,159 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler //r1_knot = false; } } - } + } else if (SP_IS_MESHGRADIENT(gradient)) { + + // add_stop_near_point() + // Find out which curve pointer is over and use that curve to determine + // which row or column will be divided. + // This is silly as we already should know which line we are over... + // but that information is not saved (sp_gradient_context_is_over_line). + + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + Geom::Affine transform = Geom::Affine(mg->gradientTransform)*(Geom::Affine)item->i2dt_affine(); + + uint rows = mg->array.patch_rows(); + uint columns = mg->array.patch_columns(); + + double closest = 1e10; + for( uint i = 0; i < rows; ++i ) { + for( uint j = 0; j < columns; ++j ) { + + SPMeshPatchI patch( &(mg->array.nodes), i, j ); + Geom::Point p[4]; + + // Top line + { + p[0] = patch.getPoint( 0, 0 ) * transform; + p[1] = patch.getPoint( 0, 1 ) * transform; + p[2] = patch.getPoint( 0, 2 ) * transform; + p[3] = patch.getPoint( 0, 3 ) * transform; + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Point nearest = b( coord ); + double dist_screen = Geom::L2 ( mouse_p - nearest ); + if ( dist_screen < closest ) { + closest = dist_screen; + divide_row = -1; + divide_column = j; + divide_coord = coord; + } + } + + // Right line (only for last column) + if( j == columns - 1 ) { + p[0] = patch.getPoint( 1, 0 ) * transform; + p[1] = patch.getPoint( 1, 1 ) * transform; + p[2] = patch.getPoint( 1, 2 ) * transform; + p[3] = patch.getPoint( 1, 3 ) * transform; + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Point nearest = b( coord ); + double dist_screen = Geom::L2 ( mouse_p - nearest ); + if ( dist_screen < closest ) { + closest = dist_screen; + divide_row = i; + divide_column = -1; + divide_coord = coord; + } + } + + // Bottom line (only for last row) + if( i == rows - 1 ) { + p[0] = patch.getPoint( 2, 0 ) * transform; + p[1] = patch.getPoint( 2, 1 ) * transform; + p[2] = patch.getPoint( 2, 2 ) * transform; + p[3] = patch.getPoint( 2, 3 ) * transform; + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Point nearest = b( coord ); + double dist_screen = Geom::L2 ( mouse_p - nearest ); + if ( dist_screen < closest ) { + closest = dist_screen; + divide_row = -1; + divide_column = j; + divide_coord = 1.0 - coord; + } + } + + // Left line + { + p[0] = patch.getPoint( 3, 0 ) * transform; + p[1] = patch.getPoint( 3, 1 ) * transform; + p[2] = patch.getPoint( 3, 2 ) * transform; + p[3] = patch.getPoint( 3, 3 ) * transform; + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Point nearest = b( coord ); + double dist_screen = Geom::L2 ( mouse_p - nearest ); + if ( dist_screen < closest ) { + closest = dist_screen; + divide_row = i; + divide_column = -1; + divide_coord = 1.0 - coord; + } + } + + } // End loop over columns + } // End loop rows + + if( closest < tolerance ) { + addknot = true; + } + + } // End if mesh + } if (addknot) { - SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); - SPStop* prev_stop = vector->getFirstStop(); - SPStop* next_stop = prev_stop->getNextStop(); - guint i = 1; - while ( (next_stop) && (next_stop->offset < new_stop_offset) ) { - prev_stop = next_stop; - next_stop = next_stop->getNextStop(); - i++; - } - if (!next_stop) { - // logical error: the endstop should have offset 1 and should always be more than this offset here - return NULL; - } + if( SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT( gradient ) ) { + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); + SPStop* prev_stop = vector->getFirstStop(); + SPStop* next_stop = prev_stop->getNextStop(); + guint i = 1; + while ( (next_stop) && (next_stop->offset < new_stop_offset) ) { + prev_stop = next_stop; + next_stop = next_stop->getNextStop(); + i++; + } + if (!next_stop) { + // logical error: the endstop should have offset 1 and should always be more than this offset here + return NULL; + } + + + SPStop *newstop = sp_vector_add_stop (vector, prev_stop, next_stop, new_stop_offset); + gradient->ensureVector(); + updateDraggers(); + + // so that it does not automatically update draggers in idle loop, as this would deselect + local_change = true; + + // select the newly created stop + selectByStop(newstop); + + return newstop; - SPStop *newstop = sp_vector_add_stop (vector, prev_stop, next_stop, new_stop_offset); - gradient->ensureVector(); - updateDraggers(); + } else { + + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); - // so that it does not automatically update draggers in idle loop, as this would deselect - local_change = true; + if( divide_row > -1 ) { + mg->array.split_row( divide_row, divide_coord ); + } else { + mg->array.split_column( divide_column, divide_coord ); + } - // select the newly created stop - selectByStop(newstop); + // Update repr + sp_meshgradient_repr_write( mg ); + mg->array.built = false; + mg->ensureArray(); + // How do we do this? + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, + _("Added patch row or column")); - return newstop; + } // Mesh } return NULL; @@ -703,6 +845,10 @@ static void gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, gui } else if (draggable->point_type == POINT_RG_CENTER) { // radial center snaps to hor/vert relative to its original position dr_snap = dragger->point_original; + } else if (draggable->point_type == POINT_MG_CORNER || + draggable->point_type == POINT_MG_HANDLE || + draggable->point_type == POINT_MG_TENSOR ) { + // std::cout << " gr_knot_moved_handler: Got mesh point!" << std::endl; } // dr_snap contains the origin of the gradient, whereas p will be the new endpoint which we will try to snap now @@ -746,11 +892,12 @@ static void gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, gui Geom::Point diff = p - dragger->point; drag->selected_move_nowrite (diff[Geom::X], diff[Geom::Y], scale_radial); } else { + Geom::Point p_old = dragger->point; dragger->point = p; dragger->fireDraggables (false, scale_radial); dragger->updateDependencies(false); + dragger->updateHandles( p_old, MG_NODE_NO_SCALE ); } - } @@ -928,6 +1075,7 @@ static void gr_knot_ungrabbed_handler(SPKnot *knot, unsigned int state, gpointer } else { dragger->fireDraggables (true); } + dragger->updateHandles( dragger->point_original, MG_NODE_NO_SCALE ); for (GList *i = dragger->parent->selected; i != NULL; i = i->next) { GrDragger *d = (GrDragger *) i->data; @@ -966,34 +1114,41 @@ static void gr_knot_clicked_handler(SPKnot */*knot*/, guint state, gpointer data if (gradient->vector.stops.size() > 2) { // 2 is the minimum SPStop *stop = NULL; switch (draggable->point_type) { // if we delete first or last stop, move the next/previous to the edge - case POINT_LG_BEGIN: - case POINT_RG_CENTER: - stop = gradient->getFirstStop(); - { - SPStop *next = stop->getNextStop(); - if (next) { - next->offset = 0; - sp_repr_set_css_double(next->getRepr(), "offset", 0); + + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + stop = gradient->getFirstStop(); + { + SPStop *next = stop->getNextStop(); + if (next) { + next->offset = 0; + sp_repr_set_css_double(next->getRepr(), "offset", 0); + } } - } - break; - case POINT_LG_END: - case POINT_RG_R1: - case POINT_RG_R2: - stop = sp_last_stop(gradient); - { - SPStop *prev = stop->getPrevStop(); - if (prev) { - prev->offset = 1; - sp_repr_set_css_double(prev->getRepr(), "offset", 1); + break; + + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + stop = sp_last_stop(gradient); + { + SPStop *prev = stop->getPrevStop(); + if (prev) { + prev->offset = 1; + sp_repr_set_css_double(prev->getRepr(), "offset", 1); + } } - } - break; - case POINT_LG_MID: - case POINT_RG_MID1: - case POINT_RG_MID2: - stop = sp_get_stop_i(gradient, draggable->point_i); - break; + break; + + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + stop = sp_get_stop_i(gradient, draggable->point_i); + break; + + default: + break; + } gradient->getRepr()->removeChild(stop->getRepr()); @@ -1002,6 +1157,7 @@ static void gr_knot_clicked_handler(SPKnot */*knot*/, guint state, gpointer data } } else { // select the dragger + dragger->point_original = dragger->point; if ( state & GDK_SHIFT_MASK ) { @@ -1140,6 +1296,110 @@ bool GrDragger::mayMerge(GrDraggable *da2) } /** + * Update mesh handles when mesh corner is moved. + * pc_old: old position of corner (could be changed to dp if we figure out transforms). + * op: how other nodes (handles, tensors) should be moved. + * Scaling takes place only between a selected and an unselected corner, + * other wise a handle is displaced the same distance as the adjacent corner. + * If a side is a line, then the handles are always placed 1/3 of side length + * from each corner. + * + * Ooops, needs to be reimplemented. + */ +void +GrDragger::updateHandles ( Geom::Point pc_old, MeshNodeOperation op ) +{ + + // This routine might more properly be in mesh-context.cpp but moving knots is + // handled here rather than there. + + // We need to update two places: + // 1. In SPMeshArrayI with object coordinates + // 2. In Drager/Knots with desktop coordinates. + + // This routine is more complicated than it might need to be inorder to allow + // corner points to be selected in multiple meshes at the same time... with some + // sharing the same dragger (overkill, perhaps?). + + // If no corner point in GrDragger then do nothing. + if( !isA (POINT_MG_CORNER ) ) return; + + GrDrag *drag = this->parent; + + // We need a list of selected corners per mesh if scaling. + std::map<SPGradient*, std::vector<uint> > selected_corners; + bool scale = false; + if( scale == true ) { + + for ( GList *i = drag->selected; i != NULL; i = i->next ) { + GrDragger *dragger = (GrDragger *) i->data; + for ( GSList *j = dragger->draggables; j != NULL; j = j->next ) { + GrDraggable *draggable = (GrDraggable *) j->data; + + // Check draggable is of type POINT_MG_CORNER (don't allow selection of POINT_MG_HANDLE) + if( draggable->point_type != POINT_MG_CORNER ) continue; + + // Must be a mesh gradient + SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke); + if ( !SP_IS_MESHGRADIENT( gradient ) ) continue; + + selected_corners[ gradient ].push_back( draggable->point_i ); + } + } + } + + // Now we do the handle moves. + + // Loop over all draggables in moved corner + std::map<SPGradient*, std::vector<uint> > dragger_corners; + for ( GSList *j = draggables; j != NULL; j = j->next ) { + GrDraggable *draggable = (GrDraggable *) j->data; + + SPItem *item = draggable->item; + gint point_type = draggable->point_type; + gint point_i = draggable->point_i; + Inkscape::PaintTarget + fill_or_stroke = draggable->fill_or_stroke; + + // Check draggable is of type POINT_MG_CORNER (don't allow selection of POINT_MG_HANDLE) + if( point_type != POINT_MG_CORNER ) continue; + + // Must be a mesh gradient + SPGradient *gradient = getGradient(item, fill_or_stroke); + if ( !SP_IS_MESHGRADIENT( gradient ) ) continue; + SPMeshGradient *mg = SP_MESHGRADIENT( gradient ); + + // pc_old is the old corner position in desktop coordinates, we need it in gradient coordinate. + gradient = sp_gradient_convert_to_userspace (gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke"); + Geom::Affine i2d ( item->i2dt_affine() ); + Geom::Point pcg_old = pc_old * i2d.inverse(); + pcg_old *= (gradient->gradientTransform).inverse(); + + mg->array.update_handles( point_i, selected_corners[ gradient ], pcg_old, op ); + + // Move on-screen knots + for( uint i = 0; i < mg->array.handles.size(); ++i ) { + GrDragger *handle = drag->getDraggerFor( item, POINT_MG_HANDLE, i, fill_or_stroke ); + SPKnot *knot = handle->knot; + Geom::Point pk = getGradientCoords( item, POINT_MG_HANDLE, i, fill_or_stroke ); + sp_knot_moveto( knot, pk ); + + } + + for( uint i = 0; i < mg->array.tensors.size(); ++i ) { + + GrDragger *handle = drag->getDraggerFor( item, POINT_MG_TENSOR, i, fill_or_stroke ); + SPKnot *knot = handle->knot; + Geom::Point pk = getGradientCoords( item, POINT_MG_TENSOR, i, fill_or_stroke ); + sp_knot_moveto( knot, pk ); + + } + + } // Loop over draggables. +} + + +/** * Updates the statusbar tip of the dragger knot, based on its draggables. */ void GrDragger::updateTip() @@ -1225,6 +1485,7 @@ void GrDragger::moveThisToDraggable(SPItem *item, GrPointType point_type, gint p (point_type == -1 || da->point_type == point_type) && (point_i == -1 || da->point_i == point_i) && (da->fill_or_stroke == fill_or_stroke) ) { + // Don't move initial draggable continue; } sp_item_gradient_set_coords(da->item, da->point_type, da->point_i, this->point, da->fill_or_stroke, write_repr, false); @@ -1361,7 +1622,10 @@ GrDragger::GrDragger(GrDrag *parent, Geom::Point p, GrDraggable *draggable) GrDragger::~GrDragger() { // unselect if it was selected - this->parent->setDeselected(this); + // Hmm, this causes a race condition as it triggers a call to gradient_selection_changed which + // can be executed while a list of draggers is being deleted. It doesn't acutally seem to be + // necessary. + //this->parent->setDeselected(this); // disconnect signals g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_moved_handler), this); @@ -1418,6 +1682,13 @@ void GrDragger::select() { this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED; g_object_set (G_OBJECT (this->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL); + //if( isA(POINT_MG_CORNER) ) { + // for (GSList * drgble = this->draggables; drgble != NULL; drgble = drgble->next) { + // GrDraggable *draggable = (GrDraggable*) drgble->data; + // //if( draggable != NULL ) std::cout << " draggable" << std::endl; + // // MESH FIXME: TURN ON CORRESPONDING SIDE/TENSOR NODE VISIBILITY + // } + //} } /** @@ -1427,6 +1698,7 @@ void GrDragger::deselect() { this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL; g_object_set (G_OBJECT (this->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL); + // MESH FIXME: TURN OFF CORRESPONDING SIDE/TENSOR NODE VISIBILITY } bool @@ -1525,6 +1797,10 @@ void GrDrag::setSelected(GrDragger *dragger, bool add_to_selection, bool overrid { GrDragger *seldragger = NULL; + // Don't allow selecting a mesh handle or mesh tensor. + // We might want to rethink since a dragger can have draggables of different types. + if ( dragger->isA( POINT_MG_HANDLE ) || dragger->isA( POINT_MG_TENSOR ) ) return; + if (add_to_selection) { if (!dragger) return; if (override) { @@ -1588,6 +1864,23 @@ void GrDrag::addLine(SPItem *item, Geom::Point p1, Geom::Point p2, Inkscape::Pai this->lines = g_slist_append(this->lines, line); } + + +/** + * Create a curve from p0 to p3 and add it to the lines list. Used for mesh sides. + */ +void GrDrag::addCurve(SPItem *item, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, Inkscape::PaintTarget fill_or_stroke) +{ + CtrlLineType type = (fill_or_stroke == Inkscape::FOR_FILL) ? CTLINE_PRIMARY : CTLINE_SECONDARY; + SPCtrlCurve *line = ControlManager::getManager().createControlCurve(sp_desktop_controls(this->desktop), p0, p1, p2, p3, type); + + sp_canvas_item_move_to_z(line, 0); + line->item = item; + sp_canvas_item_show (line); + this->lines = g_slist_append (this->lines, line); +} + + /** * If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create * new dragger and add it to draggers list. @@ -1651,7 +1944,94 @@ void GrDrag::addDraggersLinear(SPLinearGradient *lg, SPItem *item, Inkscape::Pai } /** + *Add draggers for the mesh gradient mg on item + */ +void GrDrag::addDraggersMesh(SPMeshGradient *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ + std::vector< std::vector< SPMeshNode* > > nodes = mg->array.nodes; + + // Show/hide mesh on fill/stroke. This doesn't work at the moment... and prevents node color updating. + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool edit_fill = true; //abs(prefs->getBool("/tools/mesh/edit_fill", true)); + bool edit_stroke = true; //abs(prefs->getBool("/tools/mesh/edit_stroke", true)); + bool show_handles = true; //abs(prefs->getBool("/tools/mesh/show_handles", true)); + + if( (fill_or_stroke == Inkscape::FOR_FILL && !edit_fill) || + (fill_or_stroke == Inkscape::FOR_STROKE && !edit_stroke) ) { + return; + } + + // Make sure we have at least one patch defined. + if( mg->array.patch_rows() == 0 || mg->array.patch_columns() == 0 ) { + + std::cout << "Empty Mesh Gradient, No Draggers to Add" << std::endl; + return; + } + + uint icorner = 0; + uint ihandle = 0; + uint itensor = 0; + mg->array.corners.clear(); + mg->array.handles.clear(); + mg->array.tensors.clear(); + + + for( uint i = 0; i < nodes.size(); ++i ) { + for( uint j = 0; j < nodes[i].size(); ++j ) { + + // std::cout << " Draggers: " << i << " " << j << " " << nodes[i][j]->node_type << std::endl; + + if( nodes[i][j]->set ) { + switch ( nodes[i][j]->node_type ) { + + case MG_NODE_TYPE_CORNER: + { + mg->array.corners.push_back( nodes[i][j] ); + GrDraggable *corner = new GrDraggable (item, POINT_MG_CORNER, icorner, fill_or_stroke); + addDragger ( corner ); + nodes[i][j]->draggable = icorner; + ++icorner; + break; + } + + case MG_NODE_TYPE_HANDLE: + { + if( show_handles ) { + mg->array.handles.push_back( nodes[i][j] ); + GrDraggable *handle = new GrDraggable (item, POINT_MG_HANDLE, ihandle, fill_or_stroke); + addDragger ( handle ); + nodes[i][j]->draggable = ihandle; + ++ihandle; + break; + } + } + + case MG_NODE_TYPE_TENSOR: + { + if( show_handles ) { + mg->array.tensors.push_back( nodes[i][j] ); + GrDraggable *tensor = new GrDraggable (item, POINT_MG_TENSOR, itensor, fill_or_stroke); + addDragger ( tensor ); + nodes[i][j]->draggable = itensor; + ++itensor; + break; + } + } + + default: + std::cout << "Bad Mesh Gradient draggable type" << std::endl; + break; + } + } + } + } + + mg->array.drag_valid = true; +} + +/** * Artificially grab the knot of this dragger; used by the gradient context. + * Not used at the moment. */ void GrDrag::grabKnot(GrDragger *dragger, gint x, gint y, guint32 etime) { @@ -1662,6 +2042,7 @@ void GrDrag::grabKnot(GrDragger *dragger, gint x, gint y, guint32 etime) /** * Artificially grab the knot of the dragger with this draggable; used by the gradient context. + * This allows setting the final point from the end of the drag when creating a new gradient. */ void GrDrag::grabKnot(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, gint x, gint y, guint32 etime) { @@ -1701,7 +2082,10 @@ void GrDrag::updateDraggers() addDraggersLinear( SP_LINEARGRADIENT(server), item, Inkscape::FOR_FILL ); } else if ( SP_IS_RADIALGRADIENT(server) ) { addDraggersRadial( SP_RADIALGRADIENT(server), item, Inkscape::FOR_FILL ); + } else if ( SP_IS_MESHGRADIENT(server) ) { + addDraggersMesh( SP_MESHGRADIENT(server), item, Inkscape::FOR_FILL ); } + } if (style && (style->stroke.isPaintserver())) { @@ -1712,6 +2096,8 @@ void GrDrag::updateDraggers() addDraggersLinear( SP_LINEARGRADIENT(server), item, Inkscape::FOR_STROKE ); } else if ( SP_IS_RADIALGRADIENT(server) ) { addDraggersRadial( SP_RADIALGRADIENT(server), item, Inkscape::FOR_STROKE ); + } else if ( SP_IS_MESHGRADIENT(server) ) { + addDraggersMesh( SP_MESHGRADIENT(server), item, Inkscape::FOR_STROKE ); } } } @@ -1763,6 +2149,52 @@ void GrDrag::updateLines() Geom::Point center = getGradientCoords(item, POINT_RG_CENTER, 0, Inkscape::FOR_FILL); addLine(item, center, getGradientCoords(item, POINT_RG_R1, 0, Inkscape::FOR_FILL), Inkscape::FOR_FILL); addLine(item, center, getGradientCoords(item, POINT_RG_R2, 0, Inkscape::FOR_FILL), Inkscape::FOR_FILL); + } else if ( SP_IS_MESHGRADIENT(server) ) { + + SPMeshGradient *mg = SP_MESHGRADIENT(server); + + uint rows = mg->array.patch_rows(); + uint columns = mg->array.patch_columns(); + for ( uint i = 0; i < rows; ++i ) { + for ( uint j = 0; j < columns; ++j ) { + + std::vector<Geom::Point> h; + + SPMeshPatchI patch( &(mg->array.nodes), i, j ); + + // Top line + h = patch.getPointsForSide( 0 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_FILL ); + + // Right line + if( j == columns - 1 ) { + h = patch.getPointsForSide( 1 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_FILL ); + } + + // Bottom line + if( i == rows - 1 ) { + h = patch.getPointsForSide( 2 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_FILL ); + } + + // Left line + h = patch.getPointsForSide( 3 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_FILL ); + } + } } } @@ -1776,6 +2208,53 @@ void GrDrag::updateLines() Geom::Point center = getGradientCoords(item, POINT_RG_CENTER, 0, Inkscape::FOR_STROKE); addLine(item, center, getGradientCoords(item, POINT_RG_R1, 0, Inkscape::FOR_STROKE), Inkscape::FOR_STROKE); addLine(item, center, getGradientCoords(item, POINT_RG_R2, 0, Inkscape::FOR_STROKE), Inkscape::FOR_STROKE); + } else if ( SP_IS_MESHGRADIENT(server) ) { + + // MESH FIXME: TURN ROUTINE INTO FUNCTION AND CALL FOR BOTH FILL AND STROKE. + SPMeshGradient *mg = SP_MESHGRADIENT(server); + + uint rows = mg->array.patch_rows(); + uint columns = mg->array.patch_columns(); + for ( uint i = 0; i < rows; ++i ) { + for ( uint j = 0; j < columns; ++j ) { + + std::vector<Geom::Point> h; + + SPMeshPatchI patch( &(mg->array.nodes), i, j ); + + // Top line + h = patch.getPointsForSide( 0 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_STROKE ); + + // Right line + if( j == columns - 1 ) { + h = patch.getPointsForSide( 1 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_STROKE ); + } + + // Bottom line + if( i == rows - 1 ) { + h = patch.getPointsForSide( 2 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_STROKE ); + } + + // Left line + h = patch.getPointsForSide( 3 ); + for( uint p = 0; p < 4; ++p ) { + h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + } + addCurve (item, h[0], h[1], h[2], h[3], Inkscape::FOR_STROKE ); + } + } } } } @@ -1783,6 +2262,7 @@ void GrDrag::updateLines() /** * Regenerates the levels list from the current selection. + * Levels correspond to bounding box edges and midpoints. */ void GrDrag::updateLevels() { @@ -1857,12 +2337,13 @@ void GrDrag::selected_move(double x, double y, bool write_repr, bool scale_radia } did = true; + Geom::Point p_old = d->point; d->point += Geom::Point (x, y); d->point_original = d->point; sp_knot_moveto (d->knot, d->point); d->fireDraggables (write_repr, scale_radial); - + d->updateHandles( p_old, MG_NODE_NO_SCALE ); d->updateDependencies(write_repr); } } @@ -2027,6 +2508,7 @@ void GrDrag::deleteSelected(bool just_one) } } break; + default: break; } @@ -2128,7 +2610,8 @@ void GrDrag::deleteSelected(bool just_one) break; case POINT_RG_R1: case POINT_RG_R2: - stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr()); + { + stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr()); SPRadialGradient *rg = SP_RADIALGRADIENT(stopinfo->gradient); double oldradius = rg->r.computed; @@ -2150,7 +2633,10 @@ void GrDrag::deleteSelected(bool just_one) sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset); stop = stop->getNextStop(); } - break; + } + break; + default: + break; } } else diff --git a/src/gradient-drag.h b/src/gradient-drag.h index 2150ef3d9..7fb5c5de2 100644 --- a/src/gradient-drag.h +++ b/src/gradient-drag.h @@ -8,6 +8,7 @@ * bulia byak <bulia@users.sf.net> * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> * Jon A. Cruz <jon@joncruz.org> + * Tavmjong Bah <tavmjong@free.fr> * * Copyright (C) 2012 Authors * Copyright (C) 2007 Johan Engelen @@ -26,12 +27,14 @@ #include "knot-enums.h" #include "sp-gradient.h" // TODO refactor enums to external .h file +#include "sp-mesh-array.h" struct SPKnot; class SPDesktop; class SPCSSAttr; class SPLinearGradient; +class SPMeshGradient; class SPItem; class SPObject; class SPRadialGradient; @@ -98,11 +101,15 @@ struct GrDragger { void deselect(); bool isSelected(); + /* Given one GrDraggable, these all update other draggables belonging to same GrDragger */ void moveThisToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr); void moveOtherToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr); void updateMidstopDependencies(GrDraggable *draggable, bool write_repr); void updateDependencies(bool write_repr); + /* Update handles/tensors when mesh corner moved */ + void updateHandles( Geom::Point pc_old, MeshNodeOperation op ); + bool mayMerge(GrDragger *other); bool mayMerge(GrDraggable *da2); @@ -184,12 +191,14 @@ public: // FIXME: make more of this private! private: void deselect_all(); - void addLine(SPItem *item, Geom::Point p1, Geom::Point p2, Inkscape::PaintTarget fill_or_stroke); + void addLine( SPItem *item, Geom::Point p1, Geom::Point p2, Inkscape::PaintTarget fill_or_stroke); + void addCurve(SPItem *item, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, Inkscape::PaintTarget fill_or_stroke); void addDragger(GrDraggable *draggable); void addDraggersRadial(SPRadialGradient *rg, SPItem *item, Inkscape::PaintTarget fill_or_stroke); void addDraggersLinear(SPLinearGradient *lg, SPItem *item, Inkscape::PaintTarget fill_or_stroke); + void addDraggersMesh( SPMeshGradient *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke); bool styleSet( const SPCSSAttr *css ); diff --git a/src/mesh-context.cpp b/src/mesh-context.cpp new file mode 100644 index 000000000..d34782e12 --- /dev/null +++ b/src/mesh-context.cpp @@ -0,0 +1,1045 @@ +/* + * Mesh drawing and editing tool + * + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Abhishek Sharma + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +//#define DEBUG_MESH + + +// Libraries +#include <gdk/gdkkeysyms.h> +#include <glibmm/i18n.h> + +// General +#include "desktop.h" +#include "desktop-handles.h" +#include "document.h" +#include "document-undo.h" +#include "macros.h" +#include "message-context.h" +#include "message-stack.h" +#include "preferences.h" +#include "rubberband.h" +#include "selection.h" +#include "snap.h" +#include "sp-namedview.h" +#include "verbs.h" + +// Gradient specific +#include "gradient-drag.h" +#include "gradient-chemistry.h" +#include "pixmaps/cursor-gradient.xpm" +#include "pixmaps/cursor-gradient-add.xpm" + +// Mesh specific +#include "mesh-context.h" +#include "sp-mesh-gradient.h" +#include "display/sp-ctrlcurve.h" + +#if !GTK_CHECK_VERSION(2,22,0) +#include "compat-key-syms.h" +#endif + +using Inkscape::DocumentUndo; + +static void sp_mesh_context_class_init(SPMeshContextClass *klass); +static void sp_mesh_context_init(SPMeshContext *gr_context); +static void sp_mesh_context_dispose(GObject *object); + +static void sp_mesh_context_setup(SPEventContext *ec); + +static gint sp_mesh_context_root_handler(SPEventContext *event_context, GdkEvent *event); + +static void sp_mesh_drag(SPMeshContext &rc, Geom::Point const pt, guint state, guint32 etime); + +static SPEventContextClass *parent_class; + + +GType sp_mesh_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPMeshContextClass), + NULL, NULL, + (GClassInitFunc) sp_mesh_context_class_init, + NULL, NULL, + sizeof(SPMeshContext), + 4, + (GInstanceInitFunc) sp_mesh_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPMeshContext", &info, (GTypeFlags) 0); + } + return type; +} + +static void sp_mesh_context_class_init(SPMeshContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass *) g_type_class_peek_parent(klass); + + object_class->dispose = sp_mesh_context_dispose; + + event_context_class->setup = sp_mesh_context_setup; + event_context_class->root_handler = sp_mesh_context_root_handler; +} + +static void sp_mesh_context_init(SPMeshContext *gr_context) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(gr_context); + + gr_context->cursor_addnode = false; + event_context->cursor_shape = cursor_gradient_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + event_context->xp = 0; + event_context->yp = 0; + event_context->tolerance = 6; + event_context->within_tolerance = false; + event_context->item_to_select = NULL; +} + +static void sp_mesh_context_dispose(GObject *object) +{ + SPMeshContext *rc = SP_MESH_CONTEXT(object); + SPEventContext *ec = SP_EVENT_CONTEXT(object); + + ec->enableGrDrag(false); + + if (rc->_message_context) { + delete rc->_message_context; + } + + rc->selcon->disconnect(); + delete rc->selcon; + rc->subselcon->disconnect(); + delete rc->subselcon; + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +const gchar *ms_handle_descr [] = { + N_("Mesh gradient <b>corner</b>"), + N_("Mesh gradient <b>handle</b>"), + N_("Mesh gradient <b>tensor</b>") +}; + +static void +mesh_selection_changed (Inkscape::Selection *, gpointer data) +{ + SPMeshContext *rc = (SPMeshContext *) data; + + GrDrag *drag = rc->_grdrag; + Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(rc)->desktop); + if (selection == NULL) { + return; + } + guint n_obj = g_slist_length((GSList *) selection->itemList()); + + if (!drag->isNonEmpty() || selection->isEmpty()) + return; + guint n_tot = drag->numDraggers(); + guint n_sel = drag->numSelected(); + + //The use of ngettext in the following code is intentional even if the English singular form would never be used + if (n_sel == 1) { + if (drag->singleSelectedDraggerNumDraggables() == 1) { + gchar * message = g_strconcat( + //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message + _("%s selected"), + //TRANSLATORS: Mind the space in front. This is part of a compound message + ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + message,_(ms_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj); + } else { + gchar * message = + g_strconcat( + //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count) + ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected", + "One handle merging %d stops (drag with <b>Shift</b> to separate) selected", + drag->singleSelectedDraggerNumDraggables()), + ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->_message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj); + } + } else if (n_sel > 1) { + //TRANSLATORS: The plural refers to number of selected mesh handles. This is part of a compound message (part two indicates selected object count) + gchar * message = + g_strconcat(ngettext("<b>%d</b> mesh handle selected out of %d","<b>%d</b> mesh handles selected out of %d",n_sel), + //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->_message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj); + } else if (n_sel == 0) { + rc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + //TRANSLATORS: The plural refers to number of selected objects + ngettext("<b>No</b> mesh handles selected out of %d on %d selected object", + "<b>No</b> mesh handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj); + } + + // FIXME + // We need to update mesh gradient handles. + // Get gradient this drag belongs too.. + // std::cout << "mesh_selection_changed: selection: objects: " << n_obj << std::endl; + // GSList *itemList = (GSList *) selection->itemList(); + // while( itemList ) { + + // SPItem *item = SP_ITEM( itemList->data ); + // // std::cout << " item: " << SP_OBJECT(item)->getId() << std::endl; + + // SPStyle *style = item->style; + // if (style && (style->fill.isPaintserver())) { + + // SPPaintServer *server = item->style->getFillPaintServer(); + // if ( SP_IS_MESHGRADIENT(server) ) { + + // SPMeshGradient *mg = SP_MESHGRADIENT(server); + + // uint rows = 0;//mg->array.patches.size(); + // for ( uint i = 0; i < rows; ++i ) { + // uint columns = 0;//mg->array.patches[0].size(); + // for ( uint j = 0; j < columns; ++j ) { + // } + // } + // } + // } + // itemList = itemList->next; + // } + + // GList* dragger_ptr = drag->draggers; // Points to GrDragger class (group of GrDraggable) + // uint count = 0; + // while( dragger_ptr ) { + + // std::cout << "mesh_selection_changed: dragger: " << ++count << std::endl; + // GSList* draggable_ptr = ((GrDragger *) dragger_ptr->data)->draggables; + + // while( draggable_ptr ) { + + // std::cout << "mesh_selection_changed: draggable: " << draggable_ptr << std::endl; + // GrDraggable *draggable = (GrDraggable *) draggable_ptr->data; + + // gint point_type = draggable->point_type; + // gint point_i = draggable->point_i; + // bool fill_or_stroke = draggable->fill_or_stroke; + + // if( point_type == POINT_MG_CORNER ) { + + // //std::cout << "mesh_selection_changed: POINT_MG_CORNER: " << point_i << std::endl; + // // Now we must create or destroy corresponding handles. + + // if( g_list_find( drag->selected, dragger_ptr->data ) ) { + // //std::cout << "gradient_selection_changed: Selected: " << point_i << std::endl; + // // Which meshes does this point belong to? + + // } else { + // //std::cout << "mesh_selection_changed: Not Selected: " << point_i << std::endl; + // } + // } + + // draggable_ptr = draggable_ptr->next; + + // } + + // dragger_ptr = dragger_ptr->next; + // } +} + + +static void +mesh_subselection_changed (gpointer, gpointer data) +{ + mesh_selection_changed (NULL, data); +} + + +static void sp_mesh_context_setup(SPEventContext *ec) +{ + SPMeshContext *rc = SP_MESH_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/mesh/selcue", true)) { + ec->enableSelectionCue(); + } + + ec->enableGrDrag(); + Inkscape::Selection *selection = sp_desktop_selection(ec->desktop); + + rc->_message_context = new Inkscape::MessageContext(sp_desktop_message_stack(ec->desktop)); + + rc->selcon = new sigc::connection (selection->connectChanged( sigc::bind (sigc::ptr_fun(&mesh_selection_changed), rc))); + rc->subselcon = new sigc::connection (ec->desktop->connectToolSubselectionChanged(sigc::bind (sigc::ptr_fun(&mesh_subselection_changed), rc))); + mesh_selection_changed(selection, rc); +} + +void +sp_mesh_context_select_next (SPEventContext *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_next(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +void +sp_mesh_context_select_prev (SPEventContext *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_prev(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +/** +Returns true if mouse cursor over mesh edge. +*/ +static bool +sp_mesh_context_is_over_line (SPMeshContext *rc, SPItem *item, Geom::Point event_p) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + //Translate mouse point into proper coord system + rc->mousepoint_doc = desktop->w2d(event_p); + + SPCtrlCurve *curve = SP_CTRLCURVE(item); + Geom::BezierCurveN<3> b( curve->p0, curve->p1, curve->p2, curve->p3 ); + Geom::Coord coord = b.nearestPoint( rc->mousepoint_doc ); // Coord == double + Geom::Point nearest = b( coord ); + + double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); + + double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; + + bool close = (dist_screen < tolerance); + + return close; +} + + +/** +Split row/column near the mouse point. +*/ +static void +sp_mesh_context_split_near_point (SPMeshContext *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/) +{ + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_split_near_point: entrance: " << mouse_p << std::endl; +#endif + + // item is the selected item. mouse_p the location in doc coordinates of where to add the stop + + SPEventContext *ec = SP_EVENT_CONTEXT(rc); + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + double tolerance = (double) ec->tolerance; + + ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom()); + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, + _("Split mesh row/column")); + + ec->get_drag()->updateDraggers(); +} + +/** +Wrapper for various mesh operations that require a list of selected corner nodes. + */ +static void +sp_mesh_context_corner_operation (SPMeshContext *rc, MeshCornerOperation operation ) +{ + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_corner_operation: entrance: " << operation << std::endl; +#endif + + SPDocument *doc = NULL; + GrDrag *drag = rc->_grdrag; + + std::map<SPMeshGradient*, std::vector<uint> > points; + std::map<SPMeshGradient*, SPItem*> items; + + // Get list of selected draggers for each mesh. + // For all selected draggers + for (GList *i = drag->selected; i != NULL; i = i->next) { + GrDragger *dragger = (GrDragger *) i->data; + // For all draggables of dragger + for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { + GrDraggable *d = (GrDraggable *) j->data; + + // Only mesh corners + if( d->point_type != POINT_MG_CORNER ) continue; + + // Find the gradient + SPMeshGradient *gradient = SP_MESHGRADIENT( getGradient (d->item, d->fill_or_stroke) ); + + // Collect points together for same gradient + points[gradient].push_back( d->point_i ); + items[gradient] = d->item; + } + } + + // Loop over meshes. + for( std::map<SPMeshGradient*, std::vector<uint> >::const_iterator iter = points.begin(); iter != points.end(); ++iter) { + SPMeshGradient *mg = SP_MESHGRADIENT( iter->first ); + if( iter->second.size() > 0 ) { + uint noperation = 0; + switch (operation) { + + case MG_CORNER_SIDE_TOGGLE: + // std::cout << "SIDE_TOGGLE" << std::endl; + noperation += mg->array.side_toggle( iter->second ); + break; + + case MG_CORNER_SIDE_ARC: + // std::cout << "SIDE_ARC" << std::endl; + noperation += mg->array.side_arc( iter->second ); + break; + + case MG_CORNER_TENSOR_TOGGLE: + // std::cout << "TENSOR_TOGGLE" << std::endl; + noperation += mg->array.tensor_toggle( iter->second ); + break; + + case MG_CORNER_COLOR_SMOOTH: + // std::cout << "COLOR_SMOOTH" << std::endl; + noperation += mg->array.color_smooth( iter->second ); + break; + + case MG_CORNER_COLOR_PICK: + // std::cout << "COLOR_PICK" << std::endl; + noperation += mg->array.color_pick( iter->second, items[iter->first] ); + break; + + default: + std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; + } + + if( noperation > 0 ) { + mg->array.write( mg ); + mg->requestModified(SP_OBJECT_MODIFIED_FLAG); + doc = mg->document; + + switch (operation) { + + case MG_CORNER_SIDE_TOGGLE: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh path type.")); + break; + + case MG_CORNER_SIDE_ARC: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Approximated arc for mesh side.")); + break; + + case MG_CORNER_TENSOR_TOGGLE: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh tensors.")); + break; + + case MG_CORNER_COLOR_SMOOTH: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Smoothed mesh corner color.")); + break; + + case MG_CORNER_COLOR_PICK: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Picked mesh corner color.")); + break; + + default: + std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; + } + } + } + } + drag->updateDraggers(); + +} + + +/** +Handles all keyboard and mouse input for meshs. +*/ +static gint +sp_mesh_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + // static int count = 0; + // std::cout << "sp_mesh_context_root_handler: " << count++ << std::endl; + static bool dragging; + + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = sp_desktop_selection (desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + SPMeshContext *rc = SP_MESH_CONTEXT(event_context); + + event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px + + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + gint ret = FALSE; + switch (event->type) { + + case GDK_2BUTTON_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_2BUTTON_PRESS" << std::endl; +#endif + + // Double click: + // If over a mesh line, divide mesh row/column + // If not over a line, create new gradients for selected objects. + + if ( event->button.button == 1 ) { + + // Are we over a mesh line? + bool over_line = false; + SPCtrlCurve *line = NULL; + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlCurve*) l->data; + over_line |= sp_mesh_context_is_over_line (rc, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (over_line) { + // We take the first item in selection, because with doubleclick, the first click + // always resets selection to the single object under cursor + sp_mesh_context_split_near_point(rc, SP_ITEM(selection->itemList()->data), rc->mousepoint_doc, event->button.time); + + } else { + // Create a new gradient with default coordinates. + + for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { + + SPItem *item = SP_ITEM(i->data); + SPGradientType new_type = SP_GRADIENT_TYPE_MESH; + Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: creating new mesh on: " << (fsmode == Inkscape::FOR_FILL ? "Fill" : "Stroke") << std::endl; +#endif + SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode); + + SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode); + sp_gradient_reset_to_userspace(priv, item); + } + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, + _("Create default mesh")); + } + ret = TRUE; + } + break; + + case GDK_BUTTON_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_PRESS" << std::endl; +#endif + // Button down + // If Shift key down: do rubber band selection + // Else set origin for drag. A drag creates a new gradient if one does not exist + + if ( event->button.button == 1 && !event_context->space_panning ) { + Geom::Point button_w(event->button.x, event->button.y); + + // save drag origin + event_context->xp = (gint) button_w[Geom::X]; + event_context->yp = (gint) button_w[Geom::Y]; + event_context->within_tolerance = true; + + dragging = true; + + Geom::Point button_dt = desktop->w2d(button_w); + if (event->button.state & GDK_SHIFT_MASK) { + Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + } else { + // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to + // enable Ctrl+doubleclick of exactly the selected item(s) + if (!(event->button.state & GDK_CONTROL_MASK)) + event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + + if (!selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + rc->origin = button_dt; + } + + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + + // Mouse move + + if ( dragging + && ( event->motion.state & GDK_BUTTON1_MASK ) && !event_context->space_panning ) + { + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_MOTION_NOTIFY: Dragging" << std::endl; +#endif + if ( event_context->within_tolerance + && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) + && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + event_context->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point const motion_dt = event_context->desktop->w2d(motion_w); + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(motion_dt); + event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them")); + } else { + // Create new gradient with coordinates determined by drag. + sp_mesh_drag(*rc, motion_dt, event->motion.state, event->motion.time); + } + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + + } else { + // Not dragging + + // Do snapping + if (!drag->mouseOver() && !selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt = event_context->desktop->w2d(motion_w); + + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); + m.unSetup(); + } + + // Highlight corner node corresponding to side or tensor node + if( drag->mouseOver() ) { + // MESH FIXME: Light up corresponding corner node corresponding to node we are over. + // See "pathflash" in ui/tools/node-tool.cpp for ideas. + // Use desktop->add_temporary_canvasitem( SPCanvasItem, milliseconds ); + } + + // Change cursor shape if over line + bool over_line = false; + if (drag->lines) { + for (GSList *l = drag->lines; l != NULL; l = l->next) { + over_line |= sp_mesh_context_is_over_line (rc, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (rc->cursor_addnode && !over_line) { + event_context->cursor_shape = cursor_gradient_xpm; + sp_event_context_update_cursor(event_context); + rc->cursor_addnode = false; + } else if (!rc->cursor_addnode && over_line) { + event_context->cursor_shape = cursor_gradient_add_xpm; + sp_event_context_update_cursor(event_context); + rc->cursor_addnode = true; + } + } + break; + + case GDK_BUTTON_RELEASE: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_RELEASE" << std::endl; +#endif + + event_context->xp = event_context->yp = 0; + if ( event->button.button == 1 && !event_context->space_panning ) { + + // Check if over line + bool over_line = false; + SPCtrlLine *line = NULL; + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlLine*) l->data; + over_line = sp_mesh_context_is_over_line (rc, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + if (over_line) + break; + } + } + + if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) { + if (over_line && line) { + sp_mesh_context_split_near_point(rc, line->item, rc->mousepoint_doc, 0); + ret = TRUE; + } + + } else { + + dragging = false; + + // unless clicked with Ctrl (to enable Ctrl+doubleclick). + if (event->button.state & GDK_CONTROL_MASK) { + ret = TRUE; + break; + } + + if (!event_context->within_tolerance) { + // we've been dragging, either do nothing (grdrag handles that), + // or rubberband-select if we have rubberband + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + if (r->is_started() && !event_context->within_tolerance) { + // this was a rubberband drag + if (r->getMode() == RUBBERBAND_MODE_RECT) { + Geom::OptRect const b = r->getRectangle(); + drag->selectRect(*b); + } + } + + } else if (event_context->item_to_select) { + if (over_line && line) { + // Clicked on an existing mesh line, don't change selection. This stops + // possible change in selection during a double click with overlapping objects + } + else { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(event_context->item_to_select); + } else { + selection->set(event_context->item_to_select); + } + } + } else { + // click in an empty space; do the same as Esc + if (drag->selected) { + drag->deselectAll(); + } else { + selection->clear(); + } + } + + event_context->item_to_select = NULL; + ret = TRUE; + } + Inkscape::Rubberband::get(desktop)->stop(); + } + break; + + case GDK_KEY_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_KEY_PRESS" << std::endl; +#endif + + // FIXME: tip + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + sp_event_show_modifier_tip (event_context->defaultMessageContext(), event, + _("FIXME<b>Ctrl</b>: snap mesh angle"), + _("FIXME<b>Shift</b>: draw mesh around the starting point"), + NULL); + break; + + case GDK_KEY_A: + case GDK_KEY_a: + if (MOD__CTRL_ONLY && drag->isNonEmpty()) { + drag->selectAll(); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (drag->selected) { + drag->deselectAll(); + } else { + selection->clear(); + } + ret = TRUE; + //TODO: make dragging escapable by Esc + break; + + case GDK_KEY_Left: // move handle left + case GDK_KEY_KP_Left: + case GDK_KEY_KP_4: + if (!MOD__CTRL) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(mul*-10, 0); // shift + else drag->selected_move_screen(mul*-1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(mul*-10*nudge, 0); // shift + else drag->selected_move(mul*-nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_KEY_Up: // move handle up + case GDK_KEY_KP_Up: + case GDK_KEY_KP_8: + if (!MOD__CTRL) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(0, mul*10); // shift + else drag->selected_move_screen(0, mul*1); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(0, mul*10*nudge); // shift + else drag->selected_move(0, mul*nudge); // no shift + } + ret = TRUE; + } + break; + + case GDK_KEY_Right: // move handle right + case GDK_KEY_KP_Right: + case GDK_KEY_KP_6: + if (!MOD__CTRL) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(mul*10, 0); // shift + else drag->selected_move_screen(mul*1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(mul*10*nudge, 0); // shift + else drag->selected_move(mul*nudge, 0); // no shift + } + ret = TRUE; + } + break; + + case GDK_KEY_Down: // move handle down + case GDK_KEY_KP_Down: + case GDK_KEY_KP_2: + if (!MOD__CTRL) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(0, mul*-10); // shift + else drag->selected_move_screen(0, mul*-1); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(0, mul*-10*nudge); // shift + else drag->selected_move(0, mul*-nudge); // no shift + } + ret = TRUE; + } + break; + + case GDK_KEY_Insert: + case GDK_KEY_KP_Insert: + // with any modifiers: + //sp_gradient_context_add_stops_between_selected_stops (rc); + std::cout << "Inserting stops between selected stops not implemented yet" << std::endl; + ret = TRUE; + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + if ( drag->selected ) { + std::cout << "Deleting mesh stops not implemented yet" << std::endl; + ret = TRUE; + } + break; + + // Mesh Operations -------------------------------------------- + + case GDK_b: // Toggle mesh side between lineto and curveto. + case GDK_B: + if (MOD__ALT && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( rc, MG_CORNER_SIDE_TOGGLE ); + ret = TRUE; + } + break; + + case GDK_c: // Convert mesh side from generic Bezier to Bezier approximating arc, + case GDK_C: // preserving handle direction. + if (MOD__ALT && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( rc, MG_CORNER_SIDE_ARC ); + ret = TRUE; + } + break; + + case GDK_g: // Toggle mesh tensor points on/off + case GDK_G: + if (MOD__ALT && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( rc, MG_CORNER_TENSOR_TOGGLE ); + ret = TRUE; + } + break; + + case GDK_j: // Smooth corner color + case GDK_J: + if (MOD__ALT && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( rc, MG_CORNER_COLOR_SMOOTH ); + ret = TRUE; + } + break; + + case GDK_k: // Pick corner color + case GDK_K: + if (MOD__ALT && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( rc, MG_CORNER_COLOR_PICK ); + ret = TRUE; + } + break; + + default: + break; + } + + break; + + case GDK_KEY_RELEASE: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_KEY_RELEASE" << std::endl; +#endif + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + event_context->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + + +static void sp_mesh_drag(SPMeshContext &rc, Geom::Point const pt, guint /*state*/, guint32 etime) +{ + + SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; + Inkscape::Selection *selection = sp_desktop_selection(desktop); + SPDocument *document = sp_desktop_document(desktop); + SPEventContext *ec = SP_EVENT_CONTEXT(&rc); + + if (!selection->isEmpty()) { + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int type = SP_GRADIENT_TYPE_MESH; + Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + + SPGradient *vector; + if (ec->item_to_select) { + // pick color from the object where drag started + vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); + } else { + // Starting from empty space: + // Sort items so that the topmost comes last + GSList *items = g_slist_copy ((GSList *) selection->itemList()); + items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); + // take topmost + vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke); + g_slist_free (items); + } + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + + //FIXME: see above + sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style"); + + sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); + + // We don't need to do anything. Mesh is already sized appropriately. + + SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + // if (ec->_grdrag) { + // ec->_grdrag->updateDraggers(); + // // prevent regenerating draggers by selection modified signal, which sometimes + // // comes too late and thus destroys the knot which we will now grab: + // ec->_grdrag->local_change = true; + // // give the grab out-of-bounds values of xp/yp because we're already dragging + // // and therefore are already out of tolerance + // ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), + // type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1, + // -1, // ignore number (though it is always 1) + // fill_or_stroke, 99999, 99999, etime); + // } + // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released + + // status text; we do not track coords because this branch is run once, not all the time + // during drag + int n_objects = g_slist_length((GSList *) selection->itemList()); + rc._message_context->setF(Inkscape::NORMAL_MESSAGE, + ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle", + "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects), + n_objects); + } else { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient.")); + } + +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/mesh-context.h b/src/mesh-context.h new file mode 100644 index 000000000..b43a187e2 --- /dev/null +++ b/src/mesh-context.h @@ -0,0 +1,71 @@ +#ifndef __SP_MESH_CONTEXT_H__ +#define __SP_MESH_CONTEXT_H__ + +/* + * Mesh drawing and editing tool + * + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Jon A. Cruz <jon@joncruz.org. + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005,2010 Authors + * + * Released under GNU GPL + */ + +#include <stddef.h> +#include <sigc++/sigc++.h> +#include "event-context.h" + +#define SP_TYPE_MESH_CONTEXT (sp_mesh_context_get_type()) +#define SP_MESH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_MESH_CONTEXT, SPMeshContext)) +#define SP_MESH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_MESH_CONTEXT, SPMeshContextClass)) +#define SP_IS_MESH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_MESH_CONTEXT)) +#define SP_IS_MESH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_MESH_CONTEXT)) + +class SPMeshContext; +class SPMeshContextClass; + +struct SPMeshContext : public SPEventContext { + + Geom::Point origin; + + bool cursor_addnode; + + bool node_added; + + Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords + + Inkscape::MessageContext *_message_context; + + sigc::connection *selcon; + sigc::connection *subselcon; +}; + +struct SPMeshContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ +GType sp_mesh_context_get_type(); + +void sp_mesh_context_select_next (SPEventContext *event_context); +void sp_mesh_context_select_prev (SPEventContext *event_context); +static void sp_mesh_context_split_near_point (SPMeshContext *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/); +#endif + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 85c895dcb..2cd391150 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -120,6 +120,7 @@ static char const preferences_skeleton[] = " <eventcontext id=\"tweak\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n" " <eventcontext id=\"spray\" selcue=\"1\" gradientdrag=\"0\" usepressure=\"1\" width=\"15\" population=\"70\" mode=\"1\" rotation_variation=\"0\" scale_variation=\"0\" standard_deviation=\"70\" mean=\"0\"/>\n" " <eventcontext id=\"gradient\" selcue=\"1\"/>\n" +" <eventcontext id=\"mesh\" selcue=\"1\"/>\n" " <eventcontext id=\"zoom\" selcue=\"1\" gradientdrag=\"0\"/>\n" " <eventcontext id=\"dropper\" selcue=\"1\" gradientdrag=\"1\" pick=\"1\" setalpha=\"1\"/>\n" " <eventcontext id=\"select\" selcue=\"1\" gradientdrag=\"0\"/>\n" diff --git a/src/sp-gradient-fns.h b/src/sp-gradient-fns.h index f408affd1..e57877256 100644 --- a/src/sp-gradient-fns.h +++ b/src/sp-gradient-fns.h @@ -12,6 +12,7 @@ #include "sp-gradient-units.h" class SPGradient; +class SPMeshGradient; SPGradientSpread sp_gradient_get_spread (SPGradient *gradient); @@ -19,6 +20,8 @@ SPGradientSpread sp_gradient_get_spread (SPGradient *gradient); void sp_gradient_repr_write_vector(SPGradient *gr); void sp_gradient_repr_clear_vector(SPGradient *gr); +void sp_meshgradient_repr_write(SPMeshGradient *mg); + cairo_pattern_t *sp_gradient_create_preview_pattern(SPGradient *gradient, double width); /** Transforms to/from gradient position space in given environment */ diff --git a/src/sp-gradient.cpp b/src/sp-gradient.cpp index 1a99485f6..d86b73e78 100644 --- a/src/sp-gradient.cpp +++ b/src/sp-gradient.cpp @@ -1,5 +1,6 @@ /** \file - * SPGradient, SPStop, SPLinearGradient, SPRadialGradient. + * SPGradient, SPStop, SPLinearGradient, SPRadialGradient, + * SPMeshGradient, SPMeshRow, SPMeshPatch */ /* * Authors: @@ -8,11 +9,13 @@ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl> * Jon A. Cruz <jon@joncruz.org> * Abhishek Sharma + * Tavmjong Bah <tavmjong@free.fr> * * Copyright (C) 1999-2002 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. * Copyright (C) 2004 David Turner * Copyright (C) 2009 Jasper van de Gronde + * Copyright (C) 2011 Tavmjong Bah * * Released under GNU GPL, read the file 'COPYING' for more information * @@ -25,6 +28,8 @@ #include <2geom/transforms.h> +#include <cairo.h> + #include <sigc++/functors/ptr_fun.h> #include <sigc++/adaptors/bind.h> @@ -39,6 +44,9 @@ #include "sp-gradient-reference.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" +#include "sp-mesh-gradient.h" +#include "sp-mesh-row.h" +#include "sp-mesh-patch.h" #include "sp-stop.h" #include "streq.h" #include "uri.h" @@ -49,9 +57,10 @@ #define SP_MACROS_SILENT #include "macros.h" -/// Has to be power of 2 -#define NCOLORS NR_GRADIENT_VECTOR_LENGTH +/// Has to be power of 2 Seems to be unused. +//#define NCOLORS NR_GRADIENT_VECTOR_LENGTH +// SPStop static void sp_stop_class_init(SPStopClass *klass); static void sp_stop_init(SPStop *stop); @@ -61,6 +70,29 @@ static Inkscape::XML::Node *sp_stop_write(SPObject *object, Inkscape::XML::Docum static SPObjectClass *stop_parent_class; + +// SPMeshRow +static void sp_meshrow_class_init(SPMeshRowClass *klass); +static void sp_meshrow_init(SPMeshRow *meshrow); + +static void sp_meshrow_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_meshrow_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_meshrow_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *meshrow_parent_class; + + +// SPMeshPatch +static void sp_meshpatch_class_init(SPMeshPatchClass *klass); +static void sp_meshpatch_init(SPMeshPatch *meshpatch); + +static void sp_meshpatch_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_meshpatch_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_meshpatch_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *meshpatch_parent_class; + + class SPGradientImpl { friend class SPGradient; @@ -145,6 +177,7 @@ static void sp_stop_build(SPObject *object, SPDocument *document, Inkscape::XML: object->readAttr( "stop-color" ); object->readAttr( "stop-opacity" ); object->readAttr( "style" ); + object->readAttr( "path" ); // For mesh } /** @@ -210,6 +243,18 @@ sp_stop_set(SPObject *object, unsigned key, gchar const *value) object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); break; } + case SP_PROP_STOP_PATH: { + if (value) { + stop->path_string = new Glib::ustring( value ); + //Geom::PathVector pv = sp_svg_read_pathv(value); + //SPCurve *curve = new SPCurve(pv); + //if( curve ) { + // std::cout << "Got Curve" << std::endl; + //curve->unref(); + //} + } + break; + } default: { if (((SPObjectClass *) stop_parent_class)->set) (* ((SPObjectClass *) stop_parent_class)->set)(object, key, value); @@ -265,6 +310,11 @@ bool SPGradient::hasStops() const return has_stops; } +bool SPGradient::hasPatches() const +{ + return has_patches; +} + bool SPGradient::isUnitsSet() const { return units_set; @@ -321,6 +371,198 @@ sp_stop_get_rgba32(SPStop const *const stop) } /* + * Mesh Row + */ + +/** + * Registers SPMeshRow class and returns its type. + */ +GType +sp_meshrow_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPMeshRowClass), + NULL, NULL, + (GClassInitFunc) sp_meshrow_class_init, + NULL, NULL, + sizeof(SPMeshRow), + 16, + (GInstanceInitFunc) sp_meshrow_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPMeshRow", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Callback to initialize SPMeshRow vtable. + */ +static void sp_meshrow_class_init(SPMeshRowClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + meshrow_parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = sp_meshrow_build; + sp_object_class->set = sp_meshrow_set; + sp_object_class->write = sp_meshrow_write; +} + +/** + * Callback to initialize SPMeshRow object. + */ +static void +sp_meshrow_init(SPMeshRow *meshrow) +{ + // Do nothing +} + +/** + * Virtual build: set meshrow attributes from its associated XML node. + */ +static void sp_meshrow_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) meshrow_parent_class)->build) + (* ((SPObjectClass *) meshrow_parent_class)->build)(object, document, repr); + + // No attributes +} + +/** + * Virtual set: set attribute to value. + */ +static void +sp_meshrow_set(SPObject *object, unsigned key, gchar const *value) +{ + // Do nothing +} + +/** + * Virtual write: write object attributes to repr. + */ +static Inkscape::XML::Node * +sp_meshrow_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + //SPMeshRow *meshrow = SP_MESHROW(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshRow"); + } + + if (((SPObjectClass *) meshrow_parent_class)->write) { + (* ((SPObjectClass *) meshrow_parent_class)->write)(object, xml_doc, repr, flags); + } + + return repr; +} + +/* + * Mesh Patch + */ + +/** + * Registers SPMeshPatch class and returns its type. + */ +GType +sp_meshpatch_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPMeshPatchClass), + NULL, NULL, + (GClassInitFunc) sp_meshpatch_class_init, + NULL, NULL, + sizeof(SPMeshPatch), + 16, + (GInstanceInitFunc) sp_meshpatch_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPMeshPatch", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Callback to initialize SPMeshPatch vtable. + */ +static void sp_meshpatch_class_init(SPMeshPatchClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + meshpatch_parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = sp_meshpatch_build; + sp_object_class->set = sp_meshpatch_set; + sp_object_class->write = sp_meshpatch_write; +} + +/** + * Callback to initialize SPMeshPatch object. + */ +static void +sp_meshpatch_init(SPMeshPatch *meshpatch) +{ + // Do nothing +} + +/** + * Virtual build: set meshpatch attributes from its associated XML node. + */ +static void sp_meshpatch_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) meshpatch_parent_class)->build) + (* ((SPObjectClass *) meshpatch_parent_class)->build)(object, document, repr); + + object->readAttr( "tensor" ); +} + +/** + * Virtual set: set attribute to value. + */ +static void +sp_meshpatch_set(SPObject *object, unsigned key, gchar const *value) +{ + SPMeshPatch *patch = SP_MESHPATCH(object); + + switch (key) { + case SP_ATTR_TENSOR: { + if (value) { + patch->tensor_string = new Glib::ustring( value ); + // std::cout << "sp_meshpatch_set: Tensor string: " << patch->tensor_string->c_str() << std::endl; + } + break; + } + default: { + // Do nothing + } + } +} + +/** + * Virtual write: write object attributes to repr. + */ +static Inkscape::XML::Node * +sp_meshpatch_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + //SPMeshPatch *meshpatch = SP_MESHPATCH(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshPatch"); + } + + if (((SPObjectClass *) meshpatch_parent_class)->write) { + (* ((SPObjectClass *) meshpatch_parent_class)->write)(object, xml_doc, repr, flags); + } + + return repr; +} + + +/* * Gradient */ @@ -333,6 +575,7 @@ GType SPGradient::getType() { static GType gradient_type = 0; if (!gradient_type) { + GTypeInfo gradient_info = { sizeof(SPGradientClass), NULL, NULL, @@ -647,11 +890,19 @@ void SPGradientImpl::modified(SPObject *object, guint flags) SPGradient *gr = SP_GRADIENT(object); if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { - gr->invalidateVector(); + if( gr->get_type() != SP_GRADIENT_TYPE_MESH ) { + gr->invalidateVector(); + } else { + gr->invalidateArray(); + } } if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { - gr->ensureVector(); + if( gr->get_type() != SP_GRADIENT_TYPE_MESH ) { + gr->ensureVector(); + } else { + gr->ensureArray(); + } } if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; @@ -788,6 +1039,19 @@ void SPGradient::ensureVector() } /** + * Forces the array to be built, if not present (i.e., changed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +void SPGradient::ensureArray() +{ + //std::cout << "SPGradient::ensureArray()" << std::endl; + if ( !array.built ) { + rebuildArray(); + } +} + +/** * Set units property of gradient and emit modified. */ void SPGradient::setUnits(SPGradientUnits units) @@ -1004,6 +1268,20 @@ bool SPGradient::invalidateVector() return ret; } +/** Return true if change made. */ +bool SPGradient::invalidateArray() +{ + bool ret = false; + + if (array.built) { + array.built = false; + array.clear(); + ret = true; + } + + return ret; +} + /** Creates normalized color vector */ void SPGradient::rebuildVector() { @@ -1102,6 +1380,46 @@ void SPGradient::rebuildVector() vector.built = true; } +/** Creates normalized color mesh patch array */ +void SPGradient::rebuildArray() +{ + // std::cout << "SPGradient::rebuildArray()" << std::endl; + + if( !SP_IS_MESHGRADIENT(this) ) { + g_warning( "SPGradient::rebuildArray() called for non-mesh gradient" ); + return; + } + + array.read( SP_MESHGRADIENT( this ) ); + + has_patches = false; + for ( SPObject *ro = firstChild() ; ro ; ro = ro->getNext() ) { + if (SP_IS_MESHROW(ro)) { + has_patches = true; + // std::cout << " Has Patches" << std::endl; + break; + } + } + + // MESH_FIXME: TO PROPERLY COPY + SPGradient *reffed = ref->getObject(); + if ( !hasPatches() && reffed ) { + std::cout << "SPGradient::rebuildArray(): reffed array NOT IMPLEMENTED!!!" << std::endl; + /* Copy array from referenced gradient */ + array.built = true; // Prevent infinite recursion. + reffed->ensureArray(); + // if (!reffed->array.nodes.empty()) { + // array.built = reffed->array.built; + // for( uint i = 0; i < reffed->array.nodes.size(); ++i ) { + // array.nodes[i].assign(reffed->array.nodes[i].begin(), reffed->array.nodes[i].end()); + + // // FILL ME + // } + // return; + // } + } +} + Geom::Affine sp_gradient_get_g2d_matrix(SPGradient const *gr, Geom::Affine const &ctm, Geom::Rect const &bbox) { @@ -1489,6 +1807,161 @@ sp_radialgradient_set_position(SPRadialGradient *rg, rg->requestModified(SP_OBJECT_MODIFIED_FLAG); } +/* + * Mesh Gradient + */ + +//#define MESH_DEBUG + +static void sp_meshgradient_class_init(SPMeshGradientClass *klass); +static void sp_meshgradient_init(SPMeshGradient *mg); + +static void sp_meshgradient_build(SPObject *object, + SPDocument *document, + Inkscape::XML::Node *repr); +static void sp_meshgradient_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_meshgradient_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, + guint flags); +static cairo_pattern_t *sp_meshgradient_create_pattern(SPPaintServer *ps, cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +static SPGradientClass *mg_parent_class; + +/** + * Register SPMeshGradient class and return its type. + */ +GType +sp_meshgradient_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPMeshGradientClass), + NULL, NULL, + (GClassInitFunc) sp_meshgradient_class_init, + NULL, NULL, + sizeof(SPMeshGradient), + 16, + (GInstanceInitFunc) sp_meshgradient_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GRADIENT, "SPMeshGradient", &info, (GTypeFlags)0); + } + return type; +} + +/** + * SPMeshGradient vtable initialization. + */ +static void sp_meshgradient_class_init(SPMeshGradientClass *klass) +{ +#ifdef MESH_DEBUG + std::cout << "sp_meshgradient_class_init()" << std::endl; +#endif + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPPaintServerClass *ps_class = (SPPaintServerClass *) klass; + + mg_parent_class = (SPGradientClass*)g_type_class_ref(SP_TYPE_GRADIENT); + + sp_object_class->build = sp_meshgradient_build; + sp_object_class->set = sp_meshgradient_set; + sp_object_class->write = sp_meshgradient_write; + + ps_class->pattern_new = sp_meshgradient_create_pattern; +} + +/** + * Callback for SPMeshGradient object initialization. + */ +static void +sp_meshgradient_init(SPMeshGradient *mg) +{ + // Start coordinate of mesh + mg->x.unset(SVGLength::NONE, 0.0, 0.0); + mg->y.unset(SVGLength::NONE, 0.0, 0.0); +} + +/** + * Set mesh gradient attributes from associated repr. + */ +static void +sp_meshgradient_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) mg_parent_class)->build) + (* ((SPObjectClass *) mg_parent_class)->build)(object, document, repr); + + // Start coordinate of mesh + object->readAttr( "x" ); + object->readAttr( "y" ); +} + +/** + * Set mesh gradient attribute. + */ +static void +sp_meshgradient_set(SPObject *object, unsigned key, gchar const *value) +{ + SPMeshGradient *mg = SP_MESHGRADIENT(object); + + switch (key) { + case SP_ATTR_X: + if (!mg->x.read(value)) { + mg->x.unset(SVGLength::NONE, 0.0, 0.0); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + if (!mg->y.read(value)) { + mg->y.unset(SVGLength::NONE, 0.0, 0.0); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) mg_parent_class)->set) + ((SPObjectClass *) mg_parent_class)->set(object, key, value); + break; + } +} + +/** + * Write mesh gradient attributes to associated repr. + */ +static Inkscape::XML::Node * +sp_meshgradient_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + +#ifdef MESH_DEBUG + std::cout << "sp_meshgradient_write() ***************************" << std::endl; +#endif + SPMeshGradient *mg = SP_MESHGRADIENT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshGradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || mg->x._set) sp_repr_set_svg_double(repr, "x", mg->x.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || mg->y._set) sp_repr_set_svg_double(repr, "y", mg->y.computed); + + if (((SPObjectClass *) mg_parent_class)->write) + (* ((SPObjectClass *) mg_parent_class)->write)(object, xml_doc, repr, flags); + + return repr; +} + +/** + * Directly set properties of mesh gradient and request modified. + */ +void +sp_meshgradient_set_position(SPMeshGradient *mg, gdouble x, gdouble y) +{ + g_return_if_fail(mg != NULL); + g_return_if_fail(SP_IS_MESHGRADIENT(mg)); + + mg->x.set(SVGLength::NONE, x, x); + mg->y.set(SVGLength::NONE, y, y); + + mg->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + /* CAIRO RENDERING STUFF */ static void @@ -1571,6 +2044,126 @@ sp_radialgradient_create_pattern(SPPaintServer *ps, } static cairo_pattern_t * +sp_meshgradient_create_pattern(SPPaintServer *ps, + cairo_t */* ct */, + Geom::OptRect const &bbox, + double opacity) +{ + + using Geom::X; + using Geom::Y; + +#ifdef MESH_DEBUG + std::cout << "sp_meshgradient_create_pattern: (" << bbox->x0 << "," << bbox->y0 << ") (" << bbox->x1 << "," << bbox->y1 << ") " << opacity << std::endl; +#endif + //SPMeshGradient *mg = SP_MESHGRADIENT(ps); + SPGradient *gr = SP_GRADIENT(ps); + + gr->ensureArray(); + + SPMeshNodeArray* array = &(gr->array); + + cairo_pattern_t *cp = NULL; + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 4) + + cp = cairo_pattern_create_mesh(); + + for( uint i = 0; i < array->patch_rows(); ++i ) { + for( uint j = 0; j < array->patch_columns(); ++j ) { + + SPMeshPatchI patch( &(array->nodes), i, j ); + + cairo_mesh_pattern_begin_patch( cp ); + cairo_mesh_pattern_move_to( cp, patch.getPoint( 0, 0 )[X], patch.getPoint( 0, 0 )[Y] ); + + for( uint k = 0; k < 4; ++k ) { +#ifdef DEBUG_MESH + std::cout << i << " " << j << " " + << patch.getPathType( k ) << " ("; + for( int p = 0; p < 4; ++p ) { + std::cout << patch.getPoint( k, p ); + } + std::cout << ") " + << patch.getColor( k ).toString() << std::endl; +#endif + + switch ( patch.getPathType( k ) ) { + case 'l': + case 'L': + case 'z': + case 'Z': + cairo_mesh_pattern_line_to( cp, + patch.getPoint( k, 3 )[X], + patch.getPoint( k, 3 )[Y] ); + break; + case 'c': + case 'C': + { + std::vector< Geom::Point > pts = patch.getPointsForSide( k ); + cairo_mesh_pattern_curve_to( cp, + pts[1][X], pts[1][Y], + pts[2][X], pts[2][Y], + pts[3][X], pts[3][Y] ); + break; + } + default: + // Shouldn't happen + std::cout << "sp_meshgradient_create_pattern: path error" << std::endl; + } + + if( patch.tensorIsSet(k) ) { + // Tensor point defined relative to corner. + Geom::Point t = patch.getTensorPoint(k); + cairo_mesh_pattern_set_control_point( cp, k, t[X], t[Y] ); + //std::cout << " sp_meshgradient_create_pattern: tensor " << k + // << " set to " << t << "." << std::endl; + } else { + // Geom::Point t = patch.coonsTensorPoint(k); + //std::cout << " sp_meshgradient_create_pattern: tensor " << k + // << " calculated as " << t << "." <<std::endl; + } + + cairo_mesh_pattern_set_corner_color_rgba( + cp, k, + patch.getColor( k ).v.c[0], + patch.getColor( k ).v.c[1], + patch.getColor( k ).v.c[2], + patch.getOpacity( k ) * opacity ); + } + + cairo_mesh_pattern_end_patch( cp ); + } + } + + // set pattern matrix + Geom::Affine gs2user = gr->gradientTransform; + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + Geom::Affine bbox2user(bbox->width(), 0, 0, bbox->height(), bbox->left(), bbox->top()); + gs2user *= bbox2user; + } + ink_cairo_pattern_set_matrix(cp, gs2user.inverse()); + +#else + static bool shown = false; + if( !shown ) { + std::cout << "sp_meshgradient_create_pattern: needs cairo >= 1.11.4, using " + << cairo_version_string() << std::endl; + shown = true; + } +#endif + +/* + cairo_pattern_t *cp = cairo_pattern_create_radial( + rg->fx.computed, rg->fy.computed, 0, + rg->cx.computed, rg->cy.computed, rg->r.computed); + sp_gradient_pattern_common_setup(cp, gr, bbox, opacity); +*/ + + return cp; +} + +static cairo_pattern_t * sp_lineargradient_create_pattern(SPPaintServer *ps, cairo_t */* ct */, Geom::OptRect const &bbox, @@ -1593,20 +2186,31 @@ sp_lineargradient_create_pattern(SPPaintServer *ps, cairo_pattern_t * sp_gradient_create_preview_pattern(SPGradient *gr, double width) { - gr->ensureVector(); + cairo_pattern_t *pat = NULL; - cairo_pattern_t *pat = cairo_pattern_create_linear(0, 0, width, 0); + if( gr->get_type() != SP_GRADIENT_TYPE_MESH ) { - for (std::vector<SPGradientStop>::iterator i = gr->vector.stops.begin(); - i != gr->vector.stops.end(); ++i) - { - cairo_pattern_add_color_stop_rgba(pat, i->offset, - i->color.v.c[0], i->color.v.c[1], i->color.v.c[2], i->opacity); + gr->ensureVector(); + + pat = cairo_pattern_create_linear(0, 0, width, 0); + + for (std::vector<SPGradientStop>::iterator i = gr->vector.stops.begin(); + i != gr->vector.stops.end(); ++i) + { + cairo_pattern_add_color_stop_rgba(pat, i->offset, + i->color.v.c[0], i->color.v.c[1], i->color.v.c[2], i->opacity); + } } return pat; } +void +sp_meshgradient_repr_write(SPMeshGradient *mg) +{ + mg->array.write( mg ); +} + /* Local Variables: mode:c++ diff --git a/src/sp-gradient.h b/src/sp-gradient.h index 9322b3f43..ba9c32d17 100644 --- a/src/sp-gradient.h +++ b/src/sp-gradient.h @@ -22,6 +22,7 @@ #include "sp-gradient-spread.h" #include "sp-gradient-units.h" #include "sp-gradient-vector.h" +#include "sp-mesh-array.h" #include <stddef.h> #include <sigc++/connection.h> @@ -38,7 +39,14 @@ class SPStop; enum SPGradientType { SP_GRADIENT_TYPE_UNKNOWN, SP_GRADIENT_TYPE_LINEAR, - SP_GRADIENT_TYPE_RADIAL + SP_GRADIENT_TYPE_RADIAL, + SP_GRADIENT_TYPE_MESH +}; + +enum SPGradientMeshType { + SP_GRADIENT_MESH_TYPE_UNKNOWN, + SP_GRADIENT_MESH_TYPE_NORMAL, + SP_GRADIENT_MESH_TYPE_CONICAL }; enum SPGradientState { @@ -57,6 +65,9 @@ enum GrPointType { POINT_RG_FOCUS, POINT_RG_MID1, POINT_RG_MID2, + POINT_MG_CORNER, + POINT_MG_HANDLE, + POINT_MG_TENSOR, // insert new point types here. POINT_G_INVALID @@ -108,8 +119,14 @@ private: /** Gradient stops */ guint has_stops : 1; + + /** Gradient patches */ + guint has_patches : 1; + public: + /** Linear and Radial Gradients */ + /** Composed vector */ SPGradientVector vector; @@ -121,6 +138,15 @@ public: int getStopCount() const; + /** Mesh Gradients **************/ + + /** Composed array (for mesh gradients) */ + SPMeshNodeArray array; + + bool hasPatches() const; + + + /** All Gradients **************/ bool isUnitsSet() const; SPGradientUnits getUnits() const; void setUnits(SPGradientUnits units); @@ -143,6 +169,9 @@ public: /** Forces vector to be built, if not present (i.e. changed) */ void ensureVector(); + /** Forces array (mesh) to be built, if not present (i.e. changed) */ + void ensureArray(); + /** Ensures that color array is populated */ void ensureColors(); @@ -158,11 +187,13 @@ public: private: bool invalidateVector(); + bool invalidateArray(); void rebuildVector(); + void rebuildArray(); friend class SPGradientImpl; - friend class SPLGPainter; - friend class SPRGPainter; +// friend class SPLGPainter; +// friend class SPRGPainter; }; /** diff --git a/src/sp-mesh-array.cpp b/src/sp-mesh-array.cpp new file mode 100644 index 000000000..cc439395b --- /dev/null +++ b/src/sp-mesh-array.cpp @@ -0,0 +1,2508 @@ +/** \file + A group of classes and functions for manipulating mesh gradients. + + A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can + be shared between two patches and the corners between up to four. + + The order of the points for each side always goes from left to right or top to bottom. + For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions). + + Two patches: (C=corner, S=side, H=handle, T=tensor) + + C0 H1 H2 C1 C0 H1 H2 C1 + + ---------- + ---------- + + | S0 | S0 | + H1 | T0 T1 |H1 T0 T1 | H1 + |S3 S1|S3 S1| + H2 | T3 T2 |H2 T3 T2 | H2 + | S2 | S2 | + + ---------- + ---------- + + C3 H1 H2 C2 C3 H1 H2 C2 + + The mesh is stored internally as an array of nodes that includes the tensor nodes. + + Note: This code uses tensor points which are not part of the SVG2 plan at the moment. + Including tensor points was motivated by a desire to experiment with their usefulness + in smoothing color transitions. There doesn't seem to be much advantage for that + purpose. However including them internally allows for storing all the points in + an array which simplifies things like inserting new rows or columns. +*/ + +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyrigt (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-mesh-array.h" +#include "sp-mesh-gradient.h" +#include "sp-mesh-row.h" +#include "sp-mesh-patch.h" +#include "sp-stop.h" + +// For new mesh creation +#include "preferences.h" +#include "sp-ellipse.h" +#include "sp-star.h" + +// For writing color/opacity to style +#include "svg/css-ostringstream.h" + +// For color picking +#include "display/drawing.h" +#include "display/drawing-context.h" +#include "display/cairo-utils.h" +#include "document.h" +#include "sp-root.h" + +// For default color +#include "style.h" +#include "svg/svg-color.h" + + +// Includes bezier-curve.h, ray.h, crossing.h +#include "2geom/line.h" + +#include "xml/repr.h" +#include <cmath> +#include <algorithm> + +enum { ROW, COL }; + +void swap_p( Geom::Point *p1, Geom::Point *p2 ) { + Geom::Point temp = *p1; + *p1 = *p2; + *p2 = temp; +}; + +SPMeshPatchI::SPMeshPatchI( std::vector<std::vector< SPMeshNode* > > * n, int r, int c ) { + + nodes = n; + row = r*3; // Convert from patch array to node array + col = c*3; + + uint i = 0; + if( row != 0 ) i = 1; + for( ; i < 4; ++i ) { + if( nodes->size() < row+i+1 ) { + std::vector< SPMeshNode* > row; + nodes->push_back( row ); + } + + uint j = 0; + if( col != 0 ) j = 1; + for( ; j < 4; ++j ) { + if( (*nodes)[row+i].size() < col+j+1 ){ + SPMeshNode* node = new SPMeshNode; + // Ensure all nodes know their type. + node->node_type = MG_NODE_TYPE_HANDLE; + if( (i == 0 || i == 3) && (j == 0 || j == 3 ) ) node->node_type = MG_NODE_TYPE_CORNER; + if( (i == 1 || i == 2) && (j == 1 || j == 2 ) ) node->node_type = MG_NODE_TYPE_TENSOR; + (*nodes)[row+i].push_back( node ); + } + } + } +} + +/** + Returns point for side in proper order for patch +*/ +Geom::Point SPMeshPatchI::getPoint( uint s, uint pt ) { + + assert( s < 4 ); + assert( pt < 4 ); + + Geom::Point p; + switch ( s ) { + case 0: + p = (*nodes)[ row ][ col+pt ]->p; + break; + case 1: + p = (*nodes)[ row+pt ][ col+3 ]->p; + break; + case 2: + p = (*nodes)[ row+3 ][ col+3-pt ]->p; + break; + case 3: + p = (*nodes)[ row+3-pt ][ col ]->p; + break; + } + return p; + +}; + +/** + Returns vector of points for a side in proper order for a patch (clockwise order). +*/ +std::vector< Geom::Point > SPMeshPatchI::getPointsForSide( uint i ) { + + assert( i < 4 ); + + std::vector< Geom::Point> points; + points.push_back( getPoint( i, 0 ) ); + points.push_back( getPoint( i, 1 ) ); + points.push_back( getPoint( i, 2 ) ); + points.push_back( getPoint( i, 3 ) ); + return points; +}; + + +/** + Set point for side in proper order for patch +*/ +void SPMeshPatchI::setPoint( uint s, uint pt, Geom::Point p, bool set ) { + + assert( s < 4 ); + assert( pt < 4 ); + + NodeType node_type = MG_NODE_TYPE_CORNER; + if( pt == 1 || pt == 2 ) node_type = MG_NODE_TYPE_HANDLE; + + // std::cout << "SPMeshPatchI::setPoint: s: " << s + // << " pt: " << pt + // << " p: " << p + // << " node_type: " << node_type + // << " set: " << set + // << " row: " << row + // << " col: " << col << std::endl; + switch ( s ) { + case 0: + (*nodes)[ row ][ col+pt ]->p = p; + (*nodes)[ row ][ col+pt ]->set = set; + (*nodes)[ row ][ col+pt ]->node_type = node_type; + break; + case 1: + (*nodes)[ row+pt ][ col+3 ]->p = p; + (*nodes)[ row+pt ][ col+3 ]->set = set; + (*nodes)[ row+pt ][ col+3 ]->node_type = node_type; + break; + case 2: + (*nodes)[ row+3 ][ col+3-pt ]->p = p; + (*nodes)[ row+3 ][ col+3-pt ]->set = set; + (*nodes)[ row+3 ][ col+3-pt ]->node_type = node_type; + break; + case 3: + (*nodes)[ row+3-pt ][ col ]->p = p; + (*nodes)[ row+3-pt ][ col ]->set = set; + (*nodes)[ row+3-pt ][ col ]->node_type = node_type; + break; + } + +}; + +/** + Get path type for side (stored in handle nodes). +*/ +gchar SPMeshPatchI::getPathType( uint s ) { + + assert( s < 4 ); + + gchar type = 'x'; + + switch ( s ) { + case 0: + type = (*nodes)[ row ][ col+1 ]->path_type; + break; + case 1: + type = (*nodes)[ row+1 ][ col+3 ]->path_type; + break; + case 2: + type = (*nodes)[ row+3 ][ col+2 ]->path_type; + break; + case 3: + type = (*nodes)[ row+2 ][ col ]->path_type; + break; + } + + return type; +}; + +/** + Set path type for side (stored in handle nodes). +*/ +void SPMeshPatchI::setPathType( uint s, gchar t ) { + + assert( s < 4 ); + + switch ( s ) { + case 0: + (*nodes)[ row ][ col+1 ]->path_type = t; + (*nodes)[ row ][ col+2 ]->path_type = t; + break; + case 1: + (*nodes)[ row+1 ][ col+3 ]->path_type = t; + (*nodes)[ row+2 ][ col+3 ]->path_type = t; + break; + case 2: + (*nodes)[ row+3 ][ col+1 ]->path_type = t; + (*nodes)[ row+3 ][ col+2 ]->path_type = t; + break; + case 3: + (*nodes)[ row+1 ][ col ]->path_type = t; + (*nodes)[ row+2 ][ col ]->path_type = t; + break; + } + +}; + +/** + Set tensor control point for "corner" i. + */ +void SPMeshPatchI::setTensorPoint( uint i, Geom::Point p ) { + + assert( i < 4 ); + switch ( i ) { + case 0: + (*nodes)[ row + 1 ][ col + 1 ]->p = p; + (*nodes)[ row + 1 ][ col + 1 ]->set = true; + (*nodes)[ row + 1 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 1: + (*nodes)[ row + 1 ][ col + 2 ]->p = p; + (*nodes)[ row + 1 ][ col + 2 ]->set = true; + (*nodes)[ row + 1 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 2: + (*nodes)[ row + 2 ][ col + 2 ]->p = p; + (*nodes)[ row + 2 ][ col + 2 ]->set = true; + (*nodes)[ row + 2 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 3: + (*nodes)[ row + 2 ][ col + 1 ]->p = p; + (*nodes)[ row + 2 ][ col + 1 ]->set = true; + (*nodes)[ row + 2 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + } +} + +/** + Return if any tensor control point is set. + */ +bool SPMeshPatchI::tensorIsSet() { + for( uint i = 0; i < 4; ++i ) { + if( tensorIsSet( i ) ) { + return true; + } + } + return false; +} + +/** + Return if tensor control point for "corner" i is set. + */ +bool SPMeshPatchI::tensorIsSet( uint i ) { + + assert( i < 4 ); + + bool set = false; + switch ( i ) { + case 0: + set = (*nodes)[ row + 1 ][ col + 1 ]->set; + break; + case 1: + set = (*nodes)[ row + 1 ][ col + 2 ]->set; + break; + case 2: + set = (*nodes)[ row + 2 ][ col + 2 ]->set; + break; + case 3: + set = (*nodes)[ row + 2 ][ col + 1 ]->set; + break; + } + return set; +} + +/** + Return tensor control point for "corner" i. + If not sest, returns calculated (Coons) point. + */ +Geom::Point SPMeshPatchI::getTensorPoint( uint k ) { + + assert( k < 4 ); + + uint i, j; + + + switch ( k ) { + case 0: + i = 1; j = 1; + break; + case 1: + i = 1; j = 2; + break; + case 2: + i = 2; j = 2; + break; + case 3: + i = 2; j = 1; + break; + } + + Geom::Point p; + if( (*nodes)[ row + i ][ col + j ]->set ) { + p = (*nodes)[ row + i ][ col + j ]->p; + } else { + p = coonsTensorPoint( k ); + } + return p; + +} + +/** + Find default tensor point (equivalent point to Coons Patch). + Formulas defined in PDF spec. + Equivalent to 1/3 of side length from corner for square patch. + */ +Geom::Point SPMeshPatchI::coonsTensorPoint( uint i ) { + + Geom::Point t; + Geom::Point p[4][4]; // Points in PDF notation + + p[0][0] = getPoint( 0, 0 ); + p[0][1] = getPoint( 0, 1 ); + p[0][2] = getPoint( 0, 2 ); + p[0][3] = getPoint( 0, 3 ); + p[1][0] = getPoint( 3, 2 ); + p[1][3] = getPoint( 1, 1 ); + p[2][0] = getPoint( 3, 1 ); + p[2][3] = getPoint( 1, 2 ); + p[3][0] = getPoint( 2, 3 ); + p[3][1] = getPoint( 2, 2 ); + p[3][2] = getPoint( 2, 1 ); + p[3][3] = getPoint( 2, 0 ); + + switch ( i ) { + case 0: + t = ( -4.0 * p[0][0] + + 6.0 * ( p[0][1] + p[1][0] ) + + -2.0 * ( p[0][3] + p[3][0] ) + + 3.0 * ( p[3][1] + p[1][3] ) + + -1.0 * p[3][3] ) / 9.0; + break; + + case 1: + t = ( -4.0 * p[0][3] + + 6.0 * ( p[0][2] + p[1][3] ) + + -2.0 * ( p[0][0] + p[3][3] ) + + 3.0 * ( p[3][2] + p[1][0] ) + + -1.0 * p[3][0] ) / 9.0; + break; + + case 2: + t = ( -4.0 * p[3][3] + + 6.0 * ( p[3][2] + p[2][3] ) + + -2.0 * ( p[3][0] + p[0][3] ) + + 3.0 * ( p[0][2] + p[2][0] ) + + -1.0 * p[0][0] ) / 9.0; + break; + + case 3: + t = ( -4.0 * p[3][0] + + 6.0 * ( p[3][1] + p[2][0] ) + + -2.0 * ( p[3][3] + p[0][0] ) + + 3.0 * ( p[0][1] + p[2][3] ) + + -1.0 * p[0][3] ) / 9.0; + break; + + default: + + g_warning( "Impossible!" ); + + } + return t; +} + +/** + Update default values for handle and tensor nodes. +*/ +void SPMeshPatchI::updateNodes() { + + // std::cout << "SPMeshPatchI::updateNodes: " << row << "," << col << std::endl; + // Handles first (tensors require update handles). + for( uint i = 0; i < 4; ++i ) { + for( uint j = 0; j < 4; ++j ) { + if( (*nodes)[ row + i ][ col + j ]->set == false ) { + + if( (*nodes)[ row + i ][ col + j ]->node_type == MG_NODE_TYPE_HANDLE ) { + + // If a handle is not set it is because the side is a line. + // Set node points 1/3 of the way between corners. + + if( i == 0 || i == 3 ) { + Geom::Point p0 = ( (*nodes)[ row + i ][ col ]->p ); + Geom::Point p3 = ( (*nodes)[ row + i ][ col + 3 ]->p ); + Geom::Point dp = (p3 - p0)/3.0; + if( j == 2 ) dp *= 2.0; + (*nodes)[ row + i ][ col + j ]->p = p0 + dp; + } + + if( j == 0 || j == 3 ) { + Geom::Point p0 = ( (*nodes)[ row ][ col + j ]->p ); + Geom::Point p3 = ( (*nodes)[ row + 3 ][ col + j ]->p ); + Geom::Point dp = (p3 - p0)/3.0; + if( i == 2 ) dp *= 2.0; + (*nodes)[ row + i ][ col + j ]->p = p0 + dp; + } + } + } + } + } + + // Update tensor nodes + for( uint i = 1; i < 3; ++i ) { + for( uint j = 1; j < 3; ++j ) { + if( (*nodes)[ row + i ][ col + j ]->set == false ) { + + (*nodes)[ row + i ][ col + j ]->node_type = MG_NODE_TYPE_TENSOR; + + uint t = 0; + if( i == 1 && j == 2 ) t = 1; + if( i == 2 && j == 2 ) t = 2; + if( i == 2 && j == 1 ) t = 3; + (*nodes)[ row + i ][ col + j ]->p = coonsTensorPoint( t ); + // std::cout << "Update node: " << i << ", " << j << " " << coonsTensorPoint( t ) << std::endl; + + } + } + } +} + +/** + Return color for corner of patch. +*/ +SPColor SPMeshPatchI::getColor( uint i ) { + + assert( i < 4 ); + + SPColor color; + switch ( i ) { + case 0: + color = (*nodes)[ row ][ col ]->color; + break; + case 1: + color = (*nodes)[ row ][ col+3 ]->color; + break; + case 2: + color = (*nodes)[ row+3 ][ col+3 ]->color; + break; + case 3: + color = (*nodes)[ row+3 ][ col ]->color; + break; + + } + + return color; + +}; + +/** + Set color for corner of patch. +*/ +void SPMeshPatchI::setColor( uint i, SPColor color ) { + + assert( i < 4 ); + + switch ( i ) { + case 0: + (*nodes)[ row ][ col ]->color = color; + break; + case 1: + (*nodes)[ row ][ col+3 ]->color = color; + break; + case 2: + (*nodes)[ row+3 ][ col+3 ]->color = color; + break; + case 3: + (*nodes)[ row+3 ][ col ]->color = color; + break; + } +}; + +/** + Return opacity for corner of patch. +*/ +gdouble SPMeshPatchI::getOpacity( uint i ) { + + assert( i < 4 ); + + gdouble opacity; + switch ( i ) { + case 0: + opacity = (*nodes)[ row ][ col ]->opacity; + break; + case 1: + opacity = (*nodes)[ row ][ col+3 ]->opacity; + break; + case 2: + opacity = (*nodes)[ row+3 ][ col+3 ]->opacity; + break; + case 3: + opacity = (*nodes)[ row+3 ][ col ]->opacity; + break; + } + + return opacity; +}; + + +/** + Set opacity for corner of patch. +*/ +void SPMeshPatchI::setOpacity( uint i, gdouble opacity ) { + + assert( i < 4 ); + + switch ( i ) { + case 0: + (*nodes)[ row ][ col ]->opacity = opacity; + break; + case 1: + (*nodes)[ row ][ col+3 ]->opacity = opacity; + break; + case 2: + (*nodes)[ row+3 ][ col+3 ]->opacity = opacity; + break; + case 3: + (*nodes)[ row+3 ][ col ]->opacity = opacity; + break; + + } + +}; + + +SPMeshNodeArray::SPMeshNodeArray( SPMeshGradient *mg ) { + + read( mg ); + +}; + +void SPMeshNodeArray::read( SPMeshGradient *mg_in ) { + + mg = mg_in; + + clear(); + + Geom::Point current_p( mg->x.computed, mg->y.computed ); + // std::cout << "SPMeshNodeArray::read: p: " << current_p << std::endl; + + uint max_column = 0; + uint irow = 0; // Corresponds to top of patch being read in. + for ( SPObject *ro = mg->firstChild() ; ro ; ro = ro->getNext() ) { + + if (SP_IS_MESHROW(ro)) { + + uint icolumn = 0; // Corresponds to left of patch being read in. + for ( SPObject *po = ro->firstChild() ; po ; po = po->getNext() ) { + + if (SP_IS_MESHPATCH(po)) { + + SPMeshPatch *patch = SP_MESHPATCH(po); + + // std::cout << "SPMeshNodeArray::read: row size: " << nodes.size() << std::endl; + SPMeshPatchI new_patch( &nodes, irow, icolumn ); // Adds new nodes. + // std::cout << " after: " << nodes.size() << std::endl; + + gint istop = 0; + + // Only 'top' side defined for first row. + if( irow != 0 ) ++istop; + + for ( SPObject *so = po->firstChild() ; so ; so = so->getNext() ) { + if (SP_IS_STOP(so)) { + + if( istop > 3 ) { + // std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl; + break; + } + + SPStop *stop = SP_STOP(so); + + // Handle top of first row. + if( istop == 0 && icolumn == 0 ) { + // First patch in mesh. + new_patch.setPoint( 0, 0, current_p ); + } + // First point is always already defined by previous side (stop). + current_p = new_patch.getPoint( istop, 0 ); + + // If side closes patch, then we read one less point. + bool closed = false; + if( icolumn == 0 && istop == 3 ) closed = true; + if( icolumn > 0 && istop == 2 ) closed = true; + + + // Copy path and then replace commas by spaces so we can use stringstream to parse + std::string path_string = *(stop->path_string); + std::replace(path_string.begin(),path_string.end(),',',' '); + + // std::cout << " path_string: " << path_string << std::endl; + // std::cout << " current_p: " << current_p << std::endl; + + std::stringstream os( path_string ); + + // Determine type of path + char path_type; + os >> path_type; + new_patch.setPathType( istop, path_type ); + + gdouble x, y; + Geom::Point p, dp; + uint max; + switch ( path_type ) { + case 'l': + if( !closed ) { + os >> x >> y; + if( !os.fail() ) { + dp = Geom::Point( x, y ); + new_patch.setPoint( istop, 3, current_p + dp ); + } else { + std::cout << "Failed to read l" << std::endl; + } + } + // To facilitate some side operations, set handles to 1/3 and + // 2/3 distance between corner points but flag as unset. + p = new_patch.getPoint( istop, 3 ); + dp = (p - current_p)/3.0; // Calculate since may not be set if closed. + // std::cout << " istop: " << istop + // << " dp: " << dp + // << " p: " << p + // << " current_p: " << current_p + // << std::endl; + new_patch.setPoint( istop, 1, current_p + dp, false ); + new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false ); + break; + case 'L': + if( !closed ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + new_patch.setPoint( istop, 3, p ); + } else { + std::cout << "Failed to read L" << std::endl; + } + } + // To facilitate some side operations, set handles to 1/3 and + // 2/3 distance between corner points but flag as unset. + p = new_patch.getPoint( istop, 3 ); + dp = (p - current_p)/3.0; + new_patch.setPoint( istop, 1, current_p + dp, false ); + new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false ); + break; + case 'c': + max = 4; + if( closed ) max = 3; + for( uint i = 1; i < max; ++i ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + p += current_p; + new_patch.setPoint( istop, i, p ); + } else { + std::cout << "Failed to read c: " << i << std::endl; + } + } + break; + case 'C': + max = 4; + if( closed ) max = 3; + for( uint i = 1; i < max; ++i ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + new_patch.setPoint( istop, i, p ); + } else { + std::cout << "Failed to read C: " << i << std::endl; + } + } + break; + default: + // should not reach + std::cout << "Path Error: unhandled path type: " << path_type << std::endl; + } + current_p = new_patch.getPoint( istop, 3 ); + + // Color + if( (istop == 0 && irow == 0 && icolumn > 0) || (istop == 1 && irow > 0 ) ) { + // skip + } else { + SPColor color = stop->getEffectiveColor(); + double opacity = stop->opacity; + new_patch.setColor( istop, color ); + new_patch.setOpacity( istop, opacity ); + } + + } + ++istop; + } // Loop over stops + + // Read in tensor string after stops since tensor nodes defined relative to corner nodes. + + // Copy string and then replace commas by spaces so we can use stringstream to parse XXXX + if( patch->tensor_string ) { + std::string tensor_string = *(patch->tensor_string); + std::replace(tensor_string.begin(),tensor_string.end(),',',' '); + + // std::cout << " tensor_string: " << tensor_string << std::endl; + + std::stringstream os( tensor_string ); + for( uint i = 0; i < 4; ++i ) { + double x = 0.0; + double y = 0.0; + os >> x >> y; + if( !os.fail() ) { + new_patch.setTensorPoint( i, new_patch.getPoint( i, 0 ) + Geom::Point( x, y ) ); + } else { + std::cout << "Failed to read p: " << i << std::endl; + break; + } + } + } + } + + ++icolumn; + if( max_column < icolumn ) max_column = icolumn; + } + } + ++irow; + } + + // Insure we have a true array. + for( uint i = 0; i < nodes.size(); ++i ) { + nodes[ i ].resize( max_column * 3 + 1 ); + } + + // Set node edge. + for( uint i = 0; i < nodes.size(); ++i ) { + for( uint j = 0; j < nodes[i].size(); ++j ) { + nodes[i][j]->node_edge = MG_NODE_EDGE_NONE; + if( i == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_TOP; + if( i == nodes.size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_BOTTOM; + if( j == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_RIGHT; + if( j == nodes[i].size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_LEFT; + } + } + + // std::cout << "SPMeshNodeArray::Read: result:" << std::endl; + // print(); + + drag_valid = false; + built = true; + +}; + +/** + Write repr using our array. +*/ +void SPMeshNodeArray::write( SPMeshGradient *mg ) { + + // std::cout << "SPMeshNodeArray::write: entrance:" << std::endl; + // print(); + using Geom::X; + using Geom::Y; + + // First we must delete reprs for old mesh rows and patches. + GSList *descendant_reprs = NULL; + GSList *descendant_objects = NULL; + for ( SPObject *row = mg->firstChild(); row; row = row->getNext() ) { + descendant_reprs = g_slist_prepend (descendant_reprs, row->getRepr()); + descendant_objects = g_slist_prepend (descendant_objects, row ); + for ( SPObject *patch = row->firstChild(); patch; patch = patch->getNext() ) { + descendant_reprs = g_slist_prepend (descendant_reprs, patch->getRepr()); + descendant_objects = g_slist_prepend (descendant_objects, patch ); + for ( SPObject *stop = patch->firstChild(); stop; stop = stop->getNext() ) { + descendant_reprs = g_slist_prepend (descendant_reprs, stop->getRepr()); + descendant_objects = g_slist_prepend (descendant_objects, stop ); + } + } + } + + for ( GSList *i = descendant_objects; i != NULL; i = i->next ) { + SPObject *descendant = SP_OBJECT (i->data); + descendant->deleteObject(); + } + + for ( GSList *i = descendant_reprs; i != NULL; i = i->next ) { + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) i->data; + sp_repr_unparent( repr ); + } + + + // Now we build new reprs + Inkscape::XML::Node *mesh = mg->getRepr(); + + SPMeshNodeArray* array = &(mg->array); + SPMeshPatchI patch0( &(array->nodes), 0, 0 ); + Geom::Point current_p = patch0.getPoint( 0, 0 ); // Side 0, point 0 + + sp_repr_set_svg_double( mesh, "x", current_p[X] ); + sp_repr_set_svg_double( mesh, "y", current_p[Y] ); + + Geom::Point current_p2( mg->x.computed, mg->y.computed ); + + Inkscape::XML::Document *xml_doc = mesh->document(); + uint rows = array->patch_rows(); + for( uint i = 0; i < rows; ++i ) { + + // Write row + Inkscape::XML::Node *row = xml_doc->createElement("svg:meshRow"); + mesh->appendChild( row ); // No attributes + + uint columns = array->patch_columns(); + for( uint j = 0; j < columns; ++j ) { + + // Write patch + Inkscape::XML::Node *patch = xml_doc->createElement("svg:meshPatch"); + + SPMeshPatchI patchi( &(array->nodes), i, j ); + + // Add tensor + if( patchi.tensorIsSet() ) { + + std::stringstream is; + + for( uint k = 0; k < 4; ++k ) { + Geom::Point p = patchi.getTensorPoint( k ) - patchi.getPoint( k, 0 ); + is << p[X] << "," << p[Y]; + if( k < 3 ) is << " "; + } + + patch->setAttribute("tensor", is.str().c_str()); + // std::cout << " SPMeshNodeArray::write: tensor: " << is.str() << std::endl; + } + + row->appendChild( patch ); + + // Write sides + for( uint k = 0; k < 4; ++k ) { + + // Only first row has top stop + if( k == 0 && i != 0 ) continue; + + // Only first column has left stop + if( k == 3 && j != 0 ) continue; + + Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop"); + + // Add path + std::stringstream is; + char path_type = patchi.getPathType( k ); + is << path_type; + + std::vector< Geom::Point> p = patchi.getPointsForSide( k ); + current_p = patchi.getPoint( k, 0 ); + + switch ( path_type ) { + case 'l': + is << " " + << ( p[3][X] - current_p[X] ) << "," + << ( p[3][Y] - current_p[Y] ); + break; + case 'L': + is << " " + << p[3][X] << "," + << p[3][Y]; + break; + case 'c': + is << " " + << ( p[1][X] - current_p[X] ) << "," + << ( p[1][Y] - current_p[Y] ) << " " + << ( p[2][X] - current_p[X] ) << "," + << ( p[2][Y] - current_p[Y] ) << " " + << ( p[3][X] - current_p[X] ) << "," + << ( p[3][Y] - current_p[Y] ); + break; + case 'C': + is << " " + << p[1][X] << "," + << p[1][Y] << " " + << p[2][X] << "," + << p[2][Y] << " " + << p[3][X] << "," + << p[3][Y]; + break; + case 'z': + case 'Z': + std::cout << "sp_meshgradient_repr_write: bad path type" << path_type << std::endl; + break; + default: + std::cout << "sp_meshgradient_repr_write: unhandled path type" << path_type << std::endl; + } + stop->setAttribute("path", is.str().c_str()); + // std::cout << "SPMeshNodeArray::write: path: " << is.str().c_str() << std::endl; + // Add stop-color + if( ( k == 0 && i == 0 && j == 0 ) || + ( k == 1 && i == 0 ) || + ( k == 2 ) || + ( k == 3 && j == 0 ) ) { + + // Why are we setting attribute and not style? + //stop->setAttribute("stop-color", patchi.getColor(k).toString().c_str() ); + //stop->setAttribute("stop-opacity", patchi.getOpacity(k) ); + + Inkscape::CSSOStringStream os; + os << "stop-color:" << patchi.getColor(k).toString() << ";stop-opacity:" << patchi.getOpacity(k); + stop->setAttribute("style", os.str().c_str()); + } + patch->appendChild( stop ); + } + } + } +} + +/** + Find default color based on color of first stop in "vector" gradient. + This should be rewritten if dependence on "vector" is removed. +*/ +SPColor default_color( SPItem *item ) { + + // Set initial color to the color of the object before adding the mesh. + // This is a bit tricky as at the moment, a "vector" gradient is created + // before reaching here, replacing the original solid color. But the first + // stop will be that of the original object color. + SPColor color( 0.5, 0.0, 0.5 ); + if ( item->style ) { + SPStyle const &style = *(item->style); + SPIPaint const &paint = ( style.fill ); // Could pick between style.fill/style.stroke + if ( paint.isColor() ) { + color = paint.value.color; + } else if ( paint.isPaintserver() ) { + SPObject const *server = style.getFillPaintServer(); + if ( SP_IS_GRADIENT(server) ) { + SPGradient *vector = SP_GRADIENT( server )->getVector(); + SPStop *firstStop = (vector) ? + vector->getFirstStop() : SP_GRADIENT( server )->getFirstStop(); + if ( firstStop ) { + if (firstStop->currentColor) { + Glib::ustring str = firstStop->getStyleProperty("color", NULL); + if( !str.empty() ) { + guint32 rgb = sp_svg_read_color( str.c_str(), 0 ); + color = SPColor( rgb ); + } + } else { + color = firstStop->specified_color; + } + } + } + } + } else { + std::cout << " SPMeshNodeArray: No style" << std::endl; + } + + return color; +} + +/** + Create a default mesh. +*/ +void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) { + + // std::cout << "SPMeshNodeArray::create: Entrance" << std::endl; + + if( !bbox ) { + // Set default size to bounding box if size not given. + std::cout << "SPMeshNodeArray::create(): bbox empty" << std::endl; + Geom::OptRect bbox = item->geometricBounds(); + } + if( !bbox ) { + std::cout << "SPMeshNodeArray::create: ERROR: No bounding box!" << std::endl; + return; + } + + Geom::Coord const width = bbox->dimensions()[Geom::X]; + Geom::Coord const height = bbox->dimensions()[Geom::Y]; + Geom::Point center = bbox->midpoint(); + + // Must keep repr and array in sync. We have two choices: + // Build the repr first and the "read" it. + // Construct the array and the "write" it. + // We'll do the second. + + // Remove any existing mesh. We could chose to simply scale an existing mesh... + //clear(); + + // We get called twice when a new mesh is created...WHY? + // return if we've already constructed the mesh. + if( nodes.size() != 0 ) return; + + // Get default color + SPColor color = default_color( item ); + + // Get preferences + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + uint prows = prefs->getInt("/tools/mesh/mesh_rows", 1); + uint pcols = prefs->getInt("/tools/mesh/mesh_cols", 1); + + SPGradientMeshType mesh_type = + (SPGradientMeshType) prefs->getInt("/tools/mesh/mesh_type", SP_GRADIENT_MESH_TYPE_NORMAL); + + if( mesh_type == SP_GRADIENT_MESH_TYPE_CONICAL ) { + + // Conical gradient.. for any shape/path using geometric bounding box. + + gdouble rx = width/2.0; + gdouble ry = height/2.0; + + // Start and end angles + gdouble start = 0.0; + gdouble end = 2.0 * M_PI; + + if ( SP_IS_STAR( item ) ) { + // But if it is a star... use star parameters! + SPStar* star = SP_STAR( item ); + center = star->center; + rx = star->r[0]; + ry = star->r[0]; + start = star->arg[0]; + end = start + 2.0 * M_PI; + } + + if ( SP_IS_ARC( item ) ) { + // For arcs use set start/stop + SPArc* arc = SP_ARC( item ); + center[Geom::X] = arc->cx.computed; + center[Geom::Y] = arc->cy.computed; + rx = arc->rx.computed; + ry = arc->ry.computed; + start = arc->start; + end = arc->end; + } + + // std::cout << " start: " << start << " end: " << end << std::endl; + + // IS THIS NECESSARY? + Inkscape::XML::Node *repr = mg->getRepr(); + sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(start) ); + sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(start) ); + + uint sections = pcols; + + // If less sections, arc approximation error too great. (Check!) + if( sections < 4 ) sections = 4; + + double arc = (end - start) / (double)sections; + + // See: http://en.wikipedia.org/wiki/B%C3%A9zier_curve + gdouble kappa = 4.0/3.0 * tan(arc/4.0); + gdouble lenx = rx * kappa; + gdouble leny = ry * kappa; + + gdouble s = start; + for( uint i = 0; i < sections; ++i ) { + + SPMeshPatchI patch( &nodes, 0, i ); + + gdouble x0 = center[Geom::X] + rx * cos(s); + gdouble y0 = center[Geom::Y] + ry * sin(s); + gdouble x1 = x0 - lenx * sin(s); + gdouble y1 = y0 + leny * cos(s); + + s += arc; + gdouble x3 = center[Geom::X] + rx * cos(s); + gdouble y3 = center[Geom::Y] + ry * sin(s); + gdouble x2 = x3 + lenx * sin(s); + gdouble y2 = y3 - leny * cos(s); + + patch.setPoint( 0, 0, Geom::Point( x0, y0 ) ); + patch.setPoint( 0, 1, Geom::Point( x1, y1 ) ); + patch.setPoint( 0, 2, Geom::Point( x2, y2 ) ); + patch.setPoint( 0, 3, Geom::Point( x3, y3 ) ); + + patch.setPoint( 2, 0, center ); + patch.setPoint( 3, 0, center ); + + for( uint k = 0; k < 4; ++k ) { + patch.setPathType( k, 'l' ); + patch.setColor( k, color ); + patch.setOpacity( k, 1.0 ); + } + patch.setPathType( 0, 'c' ); + + // Set handle and tensor nodes. + patch.updateNodes(); + + } + + split_row( 0, prows ); + + } else { + + // Normal grid meshes + + if( SP_IS_ARC( item ) ) { + + // std::cout << "We've got ourselves an arc!" << std::endl; + + SPArc* arc = SP_ARC( item ); + center[Geom::X] = arc->cx.computed; + center[Geom::Y] = arc->cy.computed; + gdouble rx = arc->rx.computed; + gdouble ry = arc->ry.computed; + + gdouble s = -3.0/2.0 * M_PI_2; + + Inkscape::XML::Node *repr = mg->getRepr(); + sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(s) ); + sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(s) ); + + gdouble lenx = rx * 4*tan(M_PI_2/4)/3; + gdouble leny = ry * 4*tan(M_PI_2/4)/3; + + SPMeshPatchI patch( &nodes, 0, 0 ); + for( uint i = 0; i < 4; ++i ) { + + gdouble x0 = center[Geom::X] + rx * cos(s); + gdouble y0 = center[Geom::Y] + ry * sin(s); + gdouble x1 = x0 + lenx * cos(s + M_PI_2); + gdouble y1 = y0 + leny * sin(s + M_PI_2); + + s += M_PI_2; + gdouble x3 = center[Geom::X] + rx * cos(s); + gdouble y3 = center[Geom::Y] + ry * sin(s); + gdouble x2 = x3 + lenx * cos(s - M_PI_2); + gdouble y2 = y3 + leny * sin(s - M_PI_2); + + Geom::Point p1( x1, y1 ); + Geom::Point p2( x2, y2 ); + Geom::Point p3( x3, y3 ); + patch.setPoint( i, 1, p1 ); + patch.setPoint( i, 2, p2 ); + patch.setPoint( i, 3, p3 ); + + patch.setPathType( i, 'c' ); + + patch.setColor( i, color ); + patch.setOpacity( i, 1.0 ); + } + + // Fill out tensor points + patch.updateNodes(); + + split_row( 0, prows ); + split_column( 0, pcols ); + + // END Arc + + } else if ( SP_IS_STAR( item ) ) { + + // Do simplest thing... assume star is not rounded or randomized. + // (It should be easy to handle the rounded/randomized cases by making + // the appropriate star class function public.) + SPStar* star = SP_STAR( item ); + uint sides = star->sides; + + // std::cout << "We've got ourselves an star! Sides: " << sides << std::endl; + + Geom::Point p0 = sp_star_get_xy( star, SP_STAR_POINT_KNOT1, 0 ); + Inkscape::XML::Node *repr = mg->getRepr(); + sp_repr_set_svg_double( repr, "x", p0[Geom::X] ); + sp_repr_set_svg_double( repr, "y", p0[Geom::Y] ); + + for( uint i = 0; i < sides; ++i ) { + + if( star->flatsided ) { + + SPMeshPatchI patch( &nodes, 0, i ); + + patch.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) ); + uint ii = i+1; + if( ii == sides ) ii = 0; + patch.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) ); + patch.setPoint( 2, 0, star->center ); + patch.setPoint( 3, 0, star->center ); + + for( uint s = 0; s < 4; ++s ) { + patch.setPathType( s, 'l' ); + patch.setColor( s, color ); + patch.setOpacity( s, 1.0 ); + } + + // Set handle and tensor nodes. + patch.updateNodes(); + + } else { + + SPMeshPatchI patch0( &nodes, 0, 2*i ); + + patch0.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) ); + patch0.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) ); + patch0.setPoint( 2, 0, star->center ); + patch0.setPoint( 3, 0, star->center ); + + uint ii = i+1; + if( ii == sides ) ii = 0; + + SPMeshPatchI patch1( &nodes, 0, 2*i+1 ); + + patch1.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) ); + patch1.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) ); + patch1.setPoint( 2, 0, star->center ); + patch1.setPoint( 3, 0, star->center ); + + for( uint s = 0; s < 4; ++s ) { + patch0.setPathType( s, 'l' ); + patch0.setColor( s, color ); + patch0.setOpacity( s, 1.0 ); + patch1.setPathType( s, 'l' ); + patch1.setColor( s, color ); + patch1.setOpacity( s, 1.0 ); + } + + // Set handle and tensor nodes. + patch0.updateNodes(); + patch1.updateNodes(); + + } + } + + //print(); + + split_row( 0, prows ); + //split_column( 0, pcols ); + + } else { + + // Generic + + Inkscape::XML::Node *repr = mg->getRepr(); + sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]); + sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]); + + // Get node array size + uint nrows = prows * 3 + 1; + uint ncols = pcols * 3 + 1; + + gdouble dx = width / (gdouble)(ncols-1.0); + gdouble dy = height / (gdouble)(nrows-1.0); + + Geom::Point p0( mg->x.computed, mg->y.computed ); + + for( uint i = 0; i < nrows; ++i ) { + std::vector< SPMeshNode* > row; + for( uint j = 0; j < ncols; ++j ) { + SPMeshNode* node = new SPMeshNode; + node->p = p0 + Geom::Point( j * dx, i * dy ); + + node->node_edge = MG_NODE_EDGE_NONE; + if( i == 0 ) node->node_edge |= MG_NODE_EDGE_TOP; + if( i == nrows -1 ) node->node_edge |= MG_NODE_EDGE_BOTTOM; + if( j == 0 ) node->node_edge |= MG_NODE_EDGE_LEFT; + if( j == ncols -1 ) node->node_edge |= MG_NODE_EDGE_RIGHT; + + if( i%3 == 0 ) { + + if( j%3 == 0) { + // Corner + node->node_type = MG_NODE_TYPE_CORNER; + node->set = true; + node->color = color; + node->opacity = 1.0; + + } else { + // Side + node->node_type = MG_NODE_TYPE_HANDLE; + node->set = true; + node->path_type = 'c'; + } + + } else { + + if( j%3 == 0) { + // Side + node->node_type = MG_NODE_TYPE_HANDLE; + node->set = true; + node->path_type = 'c'; + } else { + // Tensor + node->node_type = MG_NODE_TYPE_TENSOR; + node->set = false; + } + + } + + row.push_back( node ); + } + nodes.push_back( row ); + } + // End normal + } + + } // If conical + + //print(); + + // Write repr + write( mg ); +} + + +/** + Clear mesh gradient. +*/ +void SPMeshNodeArray::clear() { + + for( uint i = 0; i < nodes.size(); ++i ) { + for( uint j = 0; j < nodes[i].size(); ++j ) { + if( nodes[i][j] ) { + delete nodes[i][j]; + } + } + for( uint i = 0; i < nodes.size(); ++i ) { + nodes[i].clear(); + } + nodes.clear(); + } +}; + + +/** + Print mesh gradient (for debugging). +*/ +void SPMeshNodeArray::print() { + for( uint i = 0; i < nodes.size(); ++i ) { + std::cout << "New node row:" << std::endl; + for( uint j = 0; j < nodes[i].size(); ++j ) { + if( nodes[i][j] ) { + std::cout.width(4); + std::cout << " Node: " << i << "," << j << ": " + << nodes[i][j]->p + << " Node type: " << nodes[i][j]->node_type + << " Node edge: " << nodes[i][j]->node_edge + << " Set: " << nodes[i][j]->set + << " Path type: " << nodes[i][j]->path_type + << std::endl; + } else { + std::cout << "Error: missing mesh node." << std::endl; + } + } // Loop over patches + } // Loop over rows +}; + + +/** + Number of patch rows. +*/ +uint SPMeshNodeArray::patch_rows() { + + return nodes.size()/3; +} + +/** + Number of patch columns. +*/ +uint SPMeshNodeArray::patch_columns() { + + return nodes[0].size()/3; +} + +/** + Inputs: + i, j: Corner draggable indices. + Returns: + true if corners adjacent. + n[] is array of nodes in top/bottom or left/right order. +*/ +bool SPMeshNodeArray::adjacent_corners( uint i, uint j, SPMeshNode* n[4] ) { + + // This works as all corners have indices and they + // are numbered in order by row and column (and + // the node array is rectangular). + + bool adjacent = false; + + uint c1 = i; + uint c2 = j; + if( j < i ) { + c1 = j; + c2 = i; + } + + // Number of corners in a row of patches. + uint ncorners = patch_columns() + 1; + + uint crow1 = c1 / ncorners; + uint crow2 = c2 / ncorners; + uint ccol1 = c1 % ncorners; + uint ccol2 = c2 % ncorners; + + uint nrow = crow1 * 3; + uint ncol = ccol1 * 3; + + // std::cout << " i: " << i + // << " j: " << j + // << " ncorners: " << ncorners + // << " c1: " << c1 + // << " crow1: " << crow1 + // << " ccol1: " << ccol1 + // << " c2: " << c2 + // << " crow2: " << crow2 + // << " ccol2: " << ccol2 + // << " nrow: " << nrow + // << " ncol: " << ncol + // << std::endl; + + // Check for horizontal neighbors + if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) { + adjacent = true; + for( uint k = 0; k < 4; ++k ) { + n[k] = nodes[nrow][ncol+k]; + } + } + + // Check for vertical neighbors + if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) { + adjacent = true; + for( uint k = 0; k < 4; ++k ) { + n[k] = nodes[nrow+k][ncol]; + } + } + + return adjacent; +} + +/** + Toggle sides between lineto and curve to if both corners selected. + Input is a list of selected corner draggable indices. +*/ +uint SPMeshNodeArray::side_toggle( std::vector<uint> corners ) { + + uint toggled = 0; + + if( corners.size() < 2 ) return 0; + + for( uint i = 0; i < corners.size()-1; ++i ) { + for( uint j = i+1; j < corners.size(); ++j ) { + + SPMeshNode* n[4]; + if( adjacent_corners( corners[i], corners[j], n ) ) { + + gchar path_type = n[1]->path_type; + switch (path_type) + { + case 'L': + n[1]->path_type = 'C'; + n[2]->path_type = 'C'; + n[1]->set = true; + n[2]->set = true; + break; + + case 'l': + n[1]->path_type = 'c'; + n[2]->path_type = 'c'; + n[1]->set = true; + n[2]->set = true; + break; + + case 'C': { + n[1]->path_type = 'L'; + n[2]->path_type = 'L'; + n[1]->set = false; + n[2]->set = false; + // 'L' acts as if handles are 1/3 of path length from corners. + Geom::Point dp = (n[3]->p - n[0]->p)/3.0; + n[1]->p = n[0]->p + dp; + n[2]->p = n[3]->p - dp; + break; + } + case 'c': { + n[1]->path_type = 'l'; + n[2]->path_type = 'l'; + n[1]->set = false; + n[2]->set = false; + // 'l' acts as if handles are 1/3 of path length from corners. + Geom::Point dp = (n[3]->p - n[0]->p)/3.0; + n[1]->p = n[0]->p + dp; + n[2]->p = n[3]->p - dp; + // std::cout << "Toggle sides: " + // << n[0]->p << " " + // << n[1]->p << " " + // << n[2]->p << " " + // << n[3]->p << " " + // << dp << std::endl; + break; + } + default: + std::cout << "Toggle sides: Invalid path type: " << path_type << std::endl; + } + ++toggled; + } + } + } + if( toggled > 0 ) built = false; + return toggled; +} + +/** + * Converts generic Beziers to Beziers approximating elliptical arcs, preserving handle direction. + * There are infinite possible solutions. The solution chosen here is to generate a section of an + * ellipse that is centered on the intersection of the two lines passing through the two nodes but + * parallel to the other node's handle direction. This is the section of an ellipse that + * corresponds to a quarter of a circle squished and then skewed. + */ +uint SPMeshNodeArray::side_arc( std::vector<uint> corners ) { + + if( corners.size() < 2 ) return 0; + + uint arced = 0; + for( uint i = 0; i < corners.size()-1; ++i ) { + for( uint j = i+1; j < corners.size(); ++j ) { + + SPMeshNode* n[4]; + if( adjacent_corners( corners[i], corners[j], n ) ) { + + gchar path_type = n[1]->path_type; + switch (path_type) + { + case 'L': + case 'l': + std::cout << "SPMeshNodeArray::arc_sides: Can't convert straight lines to arcs."; + break; + + case 'C': + case 'c': { + + Geom::Ray ray1( n[0]->p, n[1]->p ); + Geom::Ray ray2( n[3]->p, n[2]->p ); + if( !are_parallel( (Geom::Line)ray1, (Geom::Line)ray2 ) ) { + + Geom::OptCrossing crossing = intersection( ray1, ray2 ); + + if( crossing ) { + + Geom::Point intersection = ray1.pointAt( (*crossing).ta ); + + const double f = 4.0/3.0 * tan( M_PI/2.0/4.0 ); + + Geom::Point h1 = intersection - n[0]->p; + Geom::Point h2 = intersection - n[3]->p; + + n[1]->p = n[0]->p + f*h1; + n[2]->p = n[3]->p + f*h2; + ++arced; + + } else { + std::cout << "SPMeshNodeArray::arc_sides: No crossing, can't turn into arc." << std::endl; + } + } else { + std::cout << "SPMeshNodeArray::arc_sides: Handles parallel, can't turn into arc." << std::endl; + } + break; + } + default: + std::cout << "SPMeshNodeArray::arc_sides: Invalid path type: " << n[1]->path_type << std::endl; + } + } + } + } + if( arced > 0 ) built = false; + return arced; +} + +/** + Toggle sides between lineto and curve to if both corners selected. + Input is a list of selected corner draggable indices. +*/ +uint SPMeshNodeArray::tensor_toggle( std::vector<uint> corners ) { + + // std::cout << "SPMeshNodeArray::tensor_toggle" << std::endl; + + if( corners.size() < 4 ) return 0; + + uint toggled = 0; + + // Number of corners in a row of patches. + uint ncorners = patch_columns() + 1; + + for( uint i = 0; i < corners.size()-3; ++i ) { + for( uint j = i+1; j < corners.size()-2; ++j ) { + for( uint k = j+1; k < corners.size()-1; ++k ) { + for( uint l = k+1; l < corners.size(); ++l ) { + + uint c[4]; + c[0] = corners[i]; + c[1] = corners[j]; + c[2] = corners[k]; + c[3] = corners[l]; + std::sort( c, c+4 ); + + // Check we have four corners of one patch selected + if( c[1]-c[0] == 1 && + c[3]-c[2] == 1 && + c[2]-c[0] == ncorners && + c[3]-c[1] == ncorners && + c[0] % ncorners < ncorners - 1 ) { + + // Patch + uint prow = c[0] / ncorners; + uint pcol = c[0] % ncorners; + + // Upper left node of patch + uint irow = prow * 3; + uint jcol = pcol * 3; + + // std::cout << "tensor::toggle: " + // << c[0] << ", " + // << c[1] << ", " + // << c[2] << ", " + // << c[3] << std::endl; + + // std::cout << "tensor::toggle: " + // << " irow: " << irow + // << " jcol: " << jcol + // << " prow: " << prow + // << " pcol: " << pcol + // << std::endl; + + SPMeshPatchI patch( &nodes, prow, pcol ); + patch.updateNodes(); + + if( patch.tensorIsSet() ) { + // Unset tensor points + nodes[irow+1][jcol+1]->set = false; + nodes[irow+1][jcol+2]->set = false; + nodes[irow+2][jcol+1]->set = false; + nodes[irow+2][jcol+2]->set = false; + } else { + // Set tensor points + nodes[irow+1][jcol+1]->set = true; + nodes[irow+1][jcol+2]->set = true; + nodes[irow+2][jcol+1]->set = true; + nodes[irow+2][jcol+2]->set = true; + } + + ++toggled; + } + } + } + } + } + if( toggled > 0 ) built = false; + return toggled; +} + +/** + Atempts to smooth color transitions across corners. + Input is a list of selected corner draggable indices. +*/ +uint SPMeshNodeArray::color_smooth( std::vector<uint> corners ) { + + // std::cout << "SPMeshNodeArray::color_smooth" << std::endl; + + uint smoothed = 0; + + // Number of corners in a row of patches. + uint ncorners = patch_columns() + 1; + + // Number of node rows and columns + uint ncols = patch_columns() * 3 + 1; + uint nrows = patch_rows() * 3 + 1; + + for( uint i = 0; i < corners.size(); ++i ) { + + uint corner = corners[i]; + // std::cout << "SPMeshNodeArray::color_smooth: " << i << " " << corner << std::endl; + + // Node row & col + uint nrow = (corner / ncorners) * 3; + uint ncol = (corner % ncorners) * 3; + + SPMeshNode* n[7]; + for( uint s = 0; s < 2; ++s ) { + + bool smooth = false; + + // Find neighboring nodes + if( s == 0 ) { + + // Horizontal + if( ncol > 2 && ncol+3 < ncols) { + for( uint j = 0; j < 7; ++j ) { + n[j] = nodes[ nrow ][ ncol - 3 + j ]; + } + smooth = true; + } + + } else { + + // Vertical + if( nrow > 2 && nrow+3 < nrows) { + for( uint j = 0; j < 7; ++j ) { + n[j] = nodes[ nrow - 3 + j ][ ncol ]; + } + smooth = true; + } + } + + if( smooth ) { + + // Let the smoothing begin + // std::cout << " checking: " << ncol << " " << nrow << std::endl; + + // Get initial slopes using closest handles. + double slope[2][3]; + double slope_ave[3]; + double slope_diff[3]; + + // Color of corners + SPColor color0 = n[0]->color; + SPColor color3 = n[3]->color; + SPColor color6 = n[6]->color; + + // Distance nodes from selected corner + Geom::Point d[7]; + for( uint k = 0; k < 7; ++k ) { + d[k]= n[k]->p - n[3]->p; + // std::cout << " d[" << k << "]: " << d[k].length() << std::endl; + } + + double sdm = -1.0; // Slope Diff Max + uint cdm = 0; // Color Diff Max (Which color has the maximum difference in slopes) + for( uint c = 0; c < 3; ++c ) { + if( d[2].length() != 0.0 ) { + slope[0][c] = (color3.v.c[c] - color0.v.c[c]) / d[2].length(); + } + if( d[4].length() != 0.0 ) { + slope[1][c] = (color6.v.c[c] - color3.v.c[c]) / d[4].length(); + } + slope_ave[c] = (slope[0][c]+slope[1][c]) / 2.0; + slope_diff[c] = (slope[0][c]-slope[1][c]); + // std::cout << " color: " << c << " :" + // << color0.v.c[c] << " " + // << color3.v.c[c] << " " + // << color6.v.c[c] + // << " slope: " + // << slope[0][c] << " " + // << slope[1][c] + // << " slope_ave: " << slope_ave[c] + // << " slope_diff: " << slope_diff[c] + // << std::endl; + + // Find color with maximum difference + if( std::abs( slope_diff[c] ) > sdm ) { + sdm = std::abs( slope_diff[c] ); + cdm = c; + } + } + // std::cout << " cdm: " << cdm << std::endl; + + // Find new handle positions: + double length_left = d[0].length(); + double length_right = d[6].length(); + if( slope_ave[ cdm ] != 0.0 ) { + length_left = std::abs( (color3.v.c[cdm] - color0.v.c[cdm]) / slope_ave[ cdm ] ); + length_right = std::abs( (color6.v.c[cdm] - color3.v.c[cdm]) / slope_ave[ cdm ] ); + } + + // Move closest handle a maximum of mid point... but don't shorten + double max = 0.8; + if( length_left > max * d[0].length() && length_left > d[2].length() ) { + std::cout << " Can't smooth left side" << std::endl; + length_left = std::max( max * d[0].length(), d[2].length() ); + } + if( length_right > max * d[6].length() && length_right > d[4].length() ) { + std::cout << " Can't smooth right side" << std::endl; + length_right = std::max( max * d[6].length(), d[4].length() ); + } + + if( d[2].length() != 0.0 ) d[2] *= length_left/d[2].length(); + if( d[4].length() != 0.0 ) d[4] *= length_right/d[4].length(); + + // std::cout << " length_left: " << length_left + // << " d[0]: " << d[0].length() + // << " length_right: " << length_right + // << " d[6]: " << d[6].length() + // << std::endl; + + n[2]->p = n[3]->p + d[2]; + n[4]->p = n[3]->p + d[4]; + + ++smoothed; + } + } + + } + + if( smoothed > 0 ) built = false; + return smoothed; +} + +/** + Pick color from background for selected corners. +*/ +uint SPMeshNodeArray::color_pick( std::vector<uint> icorners, SPItem* item ) { + + // std::cout << "SPMeshNodeArray::color_pick" << std::endl; + + uint picked = 0; + + // Code inspired from clone tracing + + // Setup... + + // We need a copy of the drawing so we can hide the mesh. + Inkscape::Drawing *pick_drawing = new Inkscape::Drawing(); + unsigned pick_visionkey = SPItem::display_key_new(1); + + SPDocument *pick_doc = mg->document; + + pick_drawing->setRoot(pick_doc->getRoot()->invoke_show(*pick_drawing, pick_visionkey, SP_ITEM_SHOW_DISPLAY)); + + item->invoke_hide(pick_visionkey); + + pick_doc->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + pick_doc->ensureUpToDate(); + + //gdouble pick_zoom = 1.0; // zoom; + //pick_drawing->root()->setTransform(Geom::Scale(pick_zoom)); + pick_drawing->update(); + + // std::cout << " transform: " << std::endl; + // std::cout << item->transform << std::endl; + // std::cout << " i2doc: " << std::endl; + // std::cout << item->i2doc_affine() << std::endl; + // std::cout << " i2dt: " << std::endl; + // std::cout << item->i2dt_affine() << std::endl; + // std::cout << " dt2i: " << std::endl; + // std::cout << item->dt2i_affine() << std::endl; + SPGradient* gr = SP_GRADIENT( mg ); + // if( gr->gradientTransform_set ) { + // std::cout << " gradient transform set: " << std::endl; + // std::cout << gr->gradientTransform << std::endl; + // } else { + // std::cout << " gradient transform not set! " << std::endl; + // } + + // Do picking + for( uint i = 0; i < icorners.size(); ++i ) { + + uint corner = icorners[i]; + + SPMeshNode* n = corners[ corner ]; + + // Region to average over + Geom::Point p = n->p; + // std::cout << " p: " << p << std::endl; + p *= gr->gradientTransform; + // std::cout << " p: " << p << std::endl; + + // If on edge, move inward + uint cols = patch_columns()+1; + uint rows = patch_rows()+1; + uint col = corner % cols; + uint row = corner / cols; + uint ncol = col * 3; + uint nrow = row * 3; + + double size = 3.0; + + // Top edge + if( row == 0 ) { + Geom::Point dp = nodes[nrow+1][ncol]->p - p; + p += unit_vector( dp ) * size; + } + // Right edge + if( col == cols-1 ) { + Geom::Point dp = nodes[nrow][ncol-1]->p - p; + p += unit_vector( dp ) * size; + } + // Bottom edge + if( row == rows-1 ) { + Geom::Point dp = nodes[nrow-1][ncol]->p - p; + p += unit_vector( dp ) * size; + } + // Left edge + if( col == 0 ) { + Geom::Point dp = nodes[nrow][ncol+1]->p - p; + p += unit_vector( dp ) * size; + } + + Geom::Rect box( p[Geom::X]-size/2.0, p[Geom::Y]-size/2.0, + p[Geom::X]+size/2.0, p[Geom::Y]+size/2.0 ); + + /* Item integer bbox in points */ + Geom::IntRect ibox = box.roundOutwards(); + + /* Find visible area */ + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ibox.width(), ibox.height()); + Inkscape::DrawingContext ct(s, ibox.min()); + + /* Render copy and pick color */ + pick_drawing->render(ct, ibox); + double R = 0, G = 0, B = 0, A = 0; + ink_cairo_surface_average_color(s, R, G, B, A); + cairo_surface_destroy(s); + + // std::cout << " p: " << p + // << " box: " << ibox + // << " R: " << R + // << " G: " << G + // << " B: " << B + // << std::endl; + n->color.set( R, G, B ); + } + + pick_doc->getRoot()->invoke_hide(pick_visionkey); + delete pick_drawing; + + if( picked > 0 ) built = false; + return picked; +} + +/** + Moves handles in response to a corner node move. + p_old: orignal position of moved corner node. + corner: the corner node moved (draggable index, i.e. point_i). + selected: list of all corners selected (draggable indices). + op: how other corners should be moved. +*/ +void SPMeshNodeArray::update_handles( uint corner, std::vector< uint > selected, Geom::Point p_old, MeshNodeOperation op ) { + + assert( drag_valid ); + + // std::cout << "SPMeshNodeArray::update_handles: " + // << " corner: " << corner + // << " op: " << op + // << std::endl; + + // Find number of patch rows and columns + uint mrow = patch_rows(); + uint mcol = patch_columns(); + + // Number of corners in a row of patches. + uint ncorners = mcol + 1; + + // Find corner row/column + uint crow = corner / ncorners; + uint ccol = corner % ncorners; + + // Find node row/column + uint nrow = crow * 3; + uint ncol = ccol * 3; + + // std::cout << " mrow: " << mrow + // << " mcol: " << mcol + // << " crow: " << crow + // << " ccol: " << ccol + // << " ncorners: " << ncorners + // << " nrow: " << nrow + // << " ncol: " << ncol + // << std::endl; + + // New corner mesh coordinate. + Geom::Point p_new = nodes[nrow][ncol]->p; + + // Corner point move dpg in mesh coordinate system. + Geom::Point dp = p_new - p_old; + + // std::cout << " p_old: " << p_old << std::endl; + // std::cout << " p_new: " << p_new << std::endl; + // std::cout << " dp: " << dp << std::endl; + + // STEP 1: ONLY DO DIRECT MOVE + bool patch[4]; + patch[0] = patch[1] = patch[2] = patch[3] = false; + if( ccol > 0 && crow > 0 ) patch[0] = true; + if( ccol < mcol && crow > 0 ) patch[1] = true; + if( ccol < mcol && crow < mrow ) patch[2] = true; + if( ccol > 0 && crow < mrow ) patch[3] = true; + + // std::cout << patch[0] << " " + // << patch[1] << " " + // << patch[2] << " " + // << patch[3] << std::endl; + + // Move handles + if( patch[0] || patch[1] ) { + if( nodes[nrow-1][ncol]->path_type == 'l' || + nodes[nrow-1][ncol]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow-3][ncol]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow-1][ncol ]->p = nodes[nrow][ncol]->p + s; + nodes[nrow-2][ncol ]->p = nodes[nrow-3][ncol]->p - s; + } else { + nodes[nrow-1][ncol ]->p += dp; + } + } + + if( patch[1] || patch[2] ) { + if( nodes[nrow ][ncol+1]->path_type == 'l' || + nodes[nrow ][ncol+1]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow][ncol+3]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow ][ncol+1]->p = nodes[nrow][ncol]->p + s; + nodes[nrow ][ncol+2]->p = nodes[nrow][ncol+3]->p - s; + } else { + nodes[nrow ][ncol+1]->p += dp; + } + } + + if( patch[2] || patch[3] ) { + if( nodes[nrow+1][ncol ]->path_type == 'l' || + nodes[nrow+1][ncol ]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow+3][ncol]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow+1][ncol ]->p = nodes[nrow][ncol]->p + s; + nodes[nrow+2][ncol ]->p = nodes[nrow+3][ncol]->p - s; + } else { + nodes[nrow+1][ncol ]->p += dp; + } + } + + if( patch[3] || patch[0] ) { + if( nodes[nrow ][ncol-1]->path_type == 'l' || + nodes[nrow ][ncol-1]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow][ncol-3]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow ][ncol-1]->p = nodes[nrow][ncol]->p + s; + nodes[nrow ][ncol-2]->p = nodes[nrow][ncol-3]->p - s; + } else { + nodes[nrow ][ncol-1]->p += dp; + } + } + + + // Move tensors + if( patch[0] ) nodes[nrow-1][ncol-1]->p += dp; + if( patch[1] ) nodes[nrow-1][ncol+1]->p += dp; + if( patch[2] ) nodes[nrow+1][ncol+1]->p += dp; + if( patch[3] ) nodes[nrow+1][ncol-1]->p += dp; + + // // Check if neighboring corners are selected. + + // bool do_scale = false; + + // bool do_scale_xp = do_scale; + // bool do_scale_xn = do_scale; + // bool do_scale_yp = do_scale; + // bool do_scale_yn = do_scale; + + // if( ccol < mcol+1 ) { + // if( std::find( sc.begin(), sc.end(), point_i + 1 ) != sc.end() ) { + // do_scale_xp = false; + // std::cout << " Not scaling x+" << std::endl; + // } + // } + + // if( ccol > 0 ) { + // if( std::find( sc.begin(), sc.end(), point_i - 1 ) != sc.end() ) { + // do_scale_xn = false; + // std::cout << " Not scaling x-" << std::endl; + // } + // } + + // if( crow < mrow+1 ) { + // if( std::find( sc.begin(), sc.end(), point_i + ncorners ) != sc.end() ) { + // do_scale_yp = false; + // std::cout << " Not scaling y+" << std::endl; + // } + // } + + // if( crow > 0 ) { + // if( std::find( sc.begin(), sc.end(), point_i - ncorners ) != sc.end() ) { + // do_scale_yn = false; + // std::cout << " Not scaling y-" << std::endl; + // } + // } + + // // We have four patches to adjust... + // for ( uint k = 0; k < 4; ++k ) { + + // bool do_scale_x = do_scale; + // bool do_scale_y = do_scale; + + // SPMeshNode* pnodes[4][4]; + + // // Load up matrix + // switch (k) { + + // case 0: + // if( crow < mrow+1 && ccol < mcol+1 ) { + // // Bottom right patch + + // do_scale_x = do_scale_xp; + // do_scale_y = do_scale_yp; + + // for( uint i = 0; i < 4; ++i ) { + // for( uint j = 0; j< 4; ++j ) { + // pnodes[i][j] = mg->array.nodes[nrow+i][nrow+j]; + // } + // } + // } + // break; + + // case 1: + // if( crow < mrow+1 && ccol > 0 ) { + // // Bottom left patch (note x, y swapped) + + // do_scale_y = do_scale_xn; + // do_scale_x = do_scale_yp; + + // for( uint i = 0; i < 4; ++i ) { + // for( uint j = 0; j< 4; ++j ) { + // pnodes[j][i] = mg->array.nodes[nrow+i][nrow-j]; + // } + // } + // } + // break; + + // case 2: + // if( crow > 0 && ccol > 0 ) { + // // Top left patch + + // do_scale_x = do_scale_xn; + // do_scale_y = do_scale_yn; + + // for( uint i = 0; i < 4; ++i ) { + // for( uint j = 0; j< 4; ++j ) { + // pnodes[i][j] = mg->array.nodes[nrow-i][nrow-j]; + // } + // } + // } + // break; + + // case 3: + // if( crow > 0 && ccol < mcol+1 ) { + // // Top right patch (note x, y swapped) + + // do_scale_y = do_scale_xp; + // do_scale_x = do_scale_yn; + + // for( uint i = 0; i < 4; ++i ) { + // for( uint j = 0; j< 4; ++j ) { + // pnodes[j][i] = mg->array.nodes[nrow-i][nrow+j]; + // } + // } + // } + // break; + // } + + // // Now we must move points in both x and y. + // // There are upto six points to move: P01, P02, P11, P12, P21, P22. + // // (The points P10, P20 will be moved in another branch of the loop. + // // The points P03, P13, P23, P33, P32, P31, P30 are not moved.) + // // + // // P00 P01 P02 P03 + // // P10 P11 P12 P13 + // // P20 P21 P22 P23 + // // P30 P31 P32 P33 + // // + // // The goal is to preserve the direction of the handle! + + + // Geom::Point dsx_new = pnodes[0][3]->p - pnodes[0][0]->p; // New side x + // Geom::Point dsy_new = pnodes[3][0]->p - pnodes[0][0]->p; // New side y + // Geom::Point dsx_old = pnodes[0][3]->p - pcg_old; // Old side x + // Geom::Point dsy_old = pnodes[3][0]->p - pcg_old; // Old side y + + + // double scale_factor_x = 1.0; + // if( dsx_old.length() != 0.0 ) scale_factor_x = dsx_new.length()/dsx_old.length(); + + // double scale_factor_y = 1.0; + // if( dsy_old.length() != 0.0 ) scale_factor_y = dsy_new.length()/dsy_old.length(); + + + // if( do_scalex && do_scaley ) { + + // // We have six point to move. + + // // P01 + // Geom::Point dp01 = pnodes[0][1] - pcg_old; + // dp01 *= scale_factor_x; + // pnodes[0][1] = pnodes[0][0] + dp01; + + // // P02 + // Geom::Point dp02 = pnodes[0][2] - pnodes[0][3]; + // dp02 *= scale_factor_x; + // pnodes[0][2] = pnodes[0][3] + dp02; + + // // P11 + // Geom::Point dp11 = pnodes[1][1] - pcg_old; + // dp11 *= scale_factor_x; + // pnodes[1][1] = pnodes[0][0] + dp11; + + + + // // P21 + // Geom::Point dp21 = pnodes[2][1] - pnodes[3][0]; + // dp21 *= scale_factor_x; + // dp21 *= scale_factor_y; + // pnodes[2][1] = pnodes[3][0] + dp21; + + + // Geom::Point dsx1 = pnodes[0][1]->p - + + + +} + + +// Defined in gradient-chemistry.cpp +guint32 average_color(guint32 c1, guint32 c2, gdouble p); + +/** + Split a row into n equal parts. +*/ +void SPMeshNodeArray::split_row( uint row, uint n ) { + + double nn = n; + if( n > 1 ) split_row( row, (nn-1)/nn ); + if( n > 2 ) split_row( row, n-1 ); +} + +/** + Split a column into n equal parts. +*/ +void SPMeshNodeArray::split_column( uint col, uint n ) { + + double nn = n; + if( n > 1 ) split_column( col, (nn-1)/nn ); + if( n > 2 ) split_column( col, n-1 ); +} + +/** + Split a row into two rows at coord (fraction of row height). +*/ +void SPMeshNodeArray::split_row( uint row, double coord ) { + + // std::cout << "Splitting row: " << row << " at " << coord << std::endl; + // print(); + assert( coord >= 0.0 && coord <= 1.0 ); + assert( row < patch_rows() ); + + built = false; + + // First step is to ensure that handle and tensor points are up-to-date if they are not set. + // (We can't do this on the fly as we overwrite the necessary points to do the calculation + // during the update.) + for( uint j = 0; j < patch_columns(); ++ j ) { + SPMeshPatchI patch( &nodes, row, j ); + patch.updateNodes(); + } + + // Add three new rows of empty nodes + for( uint i = 0; i < 3; ++i ) { + std::vector< SPMeshNode* > new_row; + for( uint j = 0; j < nodes[0].size(); ++j ) { + SPMeshNode* new_node = new SPMeshNode; + new_row.push_back( new_node ); + } + nodes.insert( nodes.begin()+3*(row+1), new_row ); + } + + uint i = 3 * row; // Convert from patch row to node row + for( uint j = 0; j < nodes[i].size(); ++j ) { + + // std::cout << "Splitting row: column: " << j << std::endl; + + Geom::Point p[4]; + for( uint k = 0; k < 4; ++k ) { + uint n = k; + if( k == 3 ) n = 6; // Bottom patch row has been shifted by new rows + p[k] = nodes[i+n][j]->p; + // std::cout << p[k] << std::endl; + } + + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + + std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new = + b.subdivide( coord ); + + // Update points + for( uint n = 0; n < 4; ++n ) { + nodes[i+n ][j]->p = b_new.first[n]; + nodes[i+n+3][j]->p = b_new.second[n]; + // std::cout << b_new.first[n] << " " << b_new.second[n] << std::endl; + } + + if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) { + // We are splitting a side + + // Path type stored in handles. + gchar path_type = nodes[i+1][j]->path_type; + nodes[i+4][j]->path_type = path_type; + nodes[i+5][j]->path_type = path_type; + bool set = nodes[i+1][j]->set; + nodes[i+4][j]->set = set; + nodes[i+5][j]->set = set; + nodes[i+4][j]->node_type = MG_NODE_TYPE_HANDLE; + nodes[i+5][j]->node_type = MG_NODE_TYPE_HANDLE; + + // Color stored in corners + guint c0 = nodes[i ][j]->color.toRGBA32( 1.0 ); + guint c1 = nodes[i+6][j]->color.toRGBA32( 1.0 ); + gdouble o0 = nodes[i ][j]->opacity; + gdouble o1 = nodes[i+6][j]->opacity; + guint cnew = average_color( c0, c1, coord ); + gdouble onew = o0 * (1.0 - coord) + o1 * coord; + nodes[i+3][j]->color.set( cnew ); + nodes[i+3][j]->opacity = onew; + nodes[i+3][j]->node_type = MG_NODE_TYPE_CORNER; + nodes[i+3][j]->set = true; + + } else { + // We are splitting a middle + + bool set = nodes[i+1][j]->set || nodes[i+1][j]->set; + nodes[i+4][j]->set = set; + nodes[i+5][j]->set = set; + nodes[i+4][j]->node_type = MG_NODE_TYPE_TENSOR; + nodes[i+5][j]->node_type = MG_NODE_TYPE_TENSOR; + + // Path type, if different, choose l -> L -> c -> C. + gchar path_type0 = nodes[i ][j]->path_type; + gchar path_type1 = nodes[i+6][j]->path_type; + gchar path_type = 'l'; + if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L'; + if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c'; + if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C'; + nodes[i+3][j]->path_type = path_type; + nodes[i+3][j]->node_type = MG_NODE_TYPE_HANDLE; + if( path_type == 'c' || path_type == 'C' ) nodes[i+3][j]->set = true; + + } + + nodes[i+3][j]->node_edge = MG_NODE_EDGE_NONE; + nodes[i+4][j]->node_edge = MG_NODE_EDGE_NONE; + nodes[i+5][j]->node_edge = MG_NODE_EDGE_NONE;; + if( j == 0 ) { + nodes[i+3][j]->node_edge |= MG_NODE_EDGE_LEFT; + nodes[i+4][j]->node_edge |= MG_NODE_EDGE_LEFT; + nodes[i+5][j]->node_edge |= MG_NODE_EDGE_LEFT; + } + if( j == nodes[i].size() - 1 ) { + nodes[i+3][j]->node_edge |= MG_NODE_EDGE_RIGHT; + nodes[i+4][j]->node_edge |= MG_NODE_EDGE_RIGHT; + nodes[i+5][j]->node_edge |= MG_NODE_EDGE_RIGHT; + } + } + + // std::cout << "Splitting row: result:" << std::endl; + // print(); +} + + + +/** + Split a column into two columns at coord (fraction of column width). +*/ +void SPMeshNodeArray::split_column( uint col, double coord ) { + + // std::cout << "Splitting column: " << col << " at " << coord << std::endl; + // print(); + assert( coord >= 0.0 && coord <= 1.0 ); + assert( col < patch_columns() ); + + built = false; + + // First step is to ensure that handle and tensor points are up-to-date if they are not set. + // (We can't do this on the fly as we overwrite the necessary points to do the calculation + // during the update.) + for( uint i = 0; i < patch_rows(); ++ i ) { + SPMeshPatchI patch( &nodes, i, col ); + patch.updateNodes(); + } + + uint j = 3 * col; // Convert from patch column to node column + for( uint i = 0; i < nodes.size(); ++i ) { + + // std::cout << "Splitting column: row: " << i << std::endl; + + Geom::Point p[4]; + for( uint k = 0; k < 4; ++k ) { + p[k] = nodes[i][j+k]->p; + } + + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + + std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new = + b.subdivide( coord ); + + // Add three new nodes + for( uint n = 0; n < 3; ++n ) { + SPMeshNode* new_node = new SPMeshNode; + nodes[i].insert( nodes[i].begin()+j+3, new_node ); + } + + // Update points + for( uint n = 0; n < 4; ++n ) { + nodes[i][j+n]->p = b_new.first[n]; + nodes[i][j+n+3]->p = b_new.second[n]; + } + + if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) { + // We are splitting a side + + // Path type stored in handles. + gchar path_type = nodes[i][j+1]->path_type; + nodes[i][j+4]->path_type = path_type; + nodes[i][j+5]->path_type = path_type; + bool set = nodes[i][j+1]->set; + nodes[i][j+4]->set = set; + nodes[i][j+5]->set = set; + nodes[i][j+4]->node_type = MG_NODE_TYPE_HANDLE; + nodes[i][j+5]->node_type = MG_NODE_TYPE_HANDLE; + + // Color stored in corners + guint c0 = nodes[i][j ]->color.toRGBA32( 1.0 ); + guint c1 = nodes[i][j+6]->color.toRGBA32( 1.0 ); + gdouble o0 = nodes[i][j ]->opacity; + gdouble o1 = nodes[i][j+6]->opacity; + guint cnew = average_color( c0, c1, coord ); + gdouble onew = o0 * (1.0 - coord) + o1 * coord; + nodes[i][j+3]->color.set( cnew ); + nodes[i][j+3]->opacity = onew; + nodes[i][j+3]->node_type = MG_NODE_TYPE_CORNER; + nodes[i][j+3]->set = true; + + } else { + // We are splitting a middle + + bool set = nodes[i][j+1]->set || nodes[i][j+2]->set; + nodes[i][j+4]->set = set; + nodes[i][j+5]->set = set; + nodes[i][j+4]->node_type = MG_NODE_TYPE_TENSOR; + nodes[i][j+5]->node_type = MG_NODE_TYPE_TENSOR; + + // Path type, if different, choose l -> L -> c -> C. + gchar path_type0 = nodes[i][j ]->path_type; + gchar path_type1 = nodes[i][j+6]->path_type; + gchar path_type = 'l'; + if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L'; + if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c'; + if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C'; + nodes[i][j+3]->path_type = path_type; + nodes[i][j+3]->node_type = MG_NODE_TYPE_HANDLE; + if( path_type == 'c' || path_type == 'C' ) nodes[i][j+3]->set = true; + + } + + nodes[i][j+3]->node_edge = MG_NODE_EDGE_NONE; + nodes[i][j+4]->node_edge = MG_NODE_EDGE_NONE; + nodes[i][j+5]->node_edge = MG_NODE_EDGE_NONE;; + if( i == 0 ) { + nodes[i][j+3]->node_edge |= MG_NODE_EDGE_TOP; + nodes[i][j+4]->node_edge |= MG_NODE_EDGE_TOP; + nodes[i][j+5]->node_edge |= MG_NODE_EDGE_TOP; + } + if( i == nodes.size() - 1 ) { + nodes[i][j+3]->node_edge |= MG_NODE_EDGE_BOTTOM; + nodes[i][j+4]->node_edge |= MG_NODE_EDGE_BOTTOM; + nodes[i][j+5]->node_edge |= MG_NODE_EDGE_BOTTOM; + } + + } + + // std::cout << "Splitting col: result:" << std::endl; + // print(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-array.h b/src/sp-mesh-array.h new file mode 100644 index 000000000..1771f3d1f --- /dev/null +++ b/src/sp-mesh-array.h @@ -0,0 +1,197 @@ +#ifndef SEEN_SP_MESH_ARRAY_H +#define SEEN_SP_MESH_ARRAY_H +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyrigt (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** + A group of classes and functions for manipulating mesh gradients. + + A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can + be shared between two patches and the corners between up to four. + + The order of the points for each side always goes from left to right or top to bottom. + For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions). + + Two patches: (C=corner, S=side, H=handle, T=tensor) + + C0 H1 H2 C1 C0 H1 H2 C1 + + ---------- + ---------- + + | S0 | S0 | + H1 | T0 T1 |H1 T0 T1 | H1 + |S3 S1|S3 S1| + H2 | T3 T2 |H2 T3 T2 | H2 + | S2 | S2 | + + ---------- + ---------- + + C3 H1 H2 C2 C3 H1 H2 C2 + + The mesh is stored internally as an array of nodes that includes the tensor nodes. + + Note: This code uses tensor points which are not part of the SVG2 plan at the moment. + Including tensor points was motivated by a desire to experiment with their usefulness + in smoothing color transitions. There doesn't seem to be much advantage for that + purpose. However including them internally allows for storing all the points in + an array which simplifies things like inserting new rows or columns. +*/ + +#include <gdk/gdk.h> +#include <glibmm/ustring.h> +#include <2geom/point.h> +#include "color.h" + +// For color picking +#include "sp-item.h" + +enum NodeType { + MG_NODE_TYPE_UNKNOWN, + MG_NODE_TYPE_CORNER, + MG_NODE_TYPE_HANDLE, + MG_NODE_TYPE_TENSOR +}; + +// Is a node along an edge? +enum NodeEdge { + MG_NODE_EDGE_NONE, + MG_NODE_EDGE_TOP = 1, + MG_NODE_EDGE_LEFT = 2, + MG_NODE_EDGE_BOTTOM = 4, + MG_NODE_EDGE_RIGHT = 8 +}; + +enum MeshCornerOperation { + MG_CORNER_SIDE_TOGGLE, + MG_CORNER_SIDE_ARC, + MG_CORNER_TENSOR_TOGGLE, + MG_CORNER_COLOR_SMOOTH, + MG_CORNER_COLOR_PICK +}; + +enum MeshNodeOperation { + MG_NODE_NO_SCALE, + MG_NODE_SCALE, + MG_NODE_SCALE_HANDLE +}; + + +class SPMeshNode { +public: + SPMeshNode() { + node_type = MG_NODE_TYPE_UNKNOWN; + node_edge = MG_NODE_EDGE_NONE; + set = false; + draggable = -1; + path_type = 'u'; + opacity = 0.0; + } + NodeType node_type; + uint node_edge; + bool set; + Geom::Point p; + uint draggable; // index of on-screen node + gchar path_type; + SPColor color; + gdouble opacity; +}; + + +// I for Internal to distinguish it from the Object class +// This is a convenience class... +class SPMeshPatchI { + +private: + std::vector<std::vector< SPMeshNode* > > *nodes; + int row; + int col; + +public: + SPMeshPatchI( std::vector<std::vector< SPMeshNode* > > *n, int r, int c ); + Geom::Point getPoint( uint side, uint point ); + std::vector< Geom::Point > getPointsForSide( uint i ); + void setPoint( uint side, uint point, Geom::Point p, bool set = true ); + gchar getPathType( uint i ); + void setPathType( uint, gchar t ); + Geom::Point getTensorPoint( uint i ); + void setTensorPoint( uint i, Geom::Point p ); + bool tensorIsSet(); + bool tensorIsSet( uint i ); + Geom::Point coonsTensorPoint( uint i ); + void updateNodes(); + SPColor getColor( uint i ); + void setColor( uint i, SPColor c ); + gdouble getOpacity( uint i ); + void setOpacity( uint i, gdouble o ); +}; + +struct SPMeshGradient; + +// An array of mesh nodes. +class SPMeshNodeArray { + +// Should be private +public: + SPMeshGradient *mg; + std::vector< std::vector< SPMeshNode* > > nodes; + +public: + // Draggables to nodes + bool drag_valid; + std::vector< SPMeshNode* > corners; + std::vector< SPMeshNode* > handles; + std::vector< SPMeshNode* > tensors; + +public: + + friend class SPMeshPatchI; + + SPMeshNodeArray() { built = false; mg = NULL; drag_valid = false; }; + SPMeshNodeArray( SPMeshGradient *mg ); + ~SPMeshNodeArray() { clear(); }; + bool built; + + void read( SPMeshGradient *mg ); + void write( SPMeshGradient *mg ); + void create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ); + void clear(); + void print(); + + // Get size of patch + uint patch_rows(); + uint patch_columns(); + + SPMeshNode * node( uint i, uint j ) { return nodes[i][j]; } + + // Operations on corners + bool adjacent_corners( uint i, uint j, SPMeshNode* n[4] ); + uint side_toggle( std::vector< uint > ); + uint side_arc( std::vector< uint > ); + uint tensor_toggle( std::vector< uint > ); + uint color_smooth( std::vector< uint > ); + uint color_pick( std::vector< uint >, SPItem* ); + + // Update other nodes in response to a node move. + void update_handles( uint corner, std::vector< uint > selected_corners, Geom::Point old_p, MeshNodeOperation op ); + + void split_row( uint i, uint n ); + void split_column( uint j, uint n ); + void split_row( uint i, double coord ); + void split_column( uint j, double coord ); +}; + +#endif /* !SEEN_SP_MESH_ARRAY_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + c-basic-offset:2 + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-gradient-fns.h b/src/sp-mesh-gradient-fns.h new file mode 100644 index 000000000..18f66128a --- /dev/null +++ b/src/sp-mesh-gradient-fns.h @@ -0,0 +1,41 @@ +#ifndef SP_MESH_GRADIENT_FNS_H +#define SP_MESH_GRADIENT_FNS_H + +/** \file + * Macros and fn definitions related to mesh gradients. + */ + +#include <glib-object.h> +#include <glib.h> + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class SPMeshGradient; + +#define SP_TYPE_MESHGRADIENT (sp_meshgradient_get_type()) +#define SP_MESHGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_MESHGRADIENT, SPMeshGradient)) +#define SP_MESHGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_MESHGRADIENT, SPMeshGradientClass)) +#define SP_IS_MESHGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_MESHGRADIENT)) +#define SP_IS_MESHGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_MESHGRADIENT)) + + +GType sp_meshgradient_get_type(); + +void sp_meshgradient_set_position(SPMeshGradient *mg, gdouble x, gdouble y ); + +#endif /* !SP_MESH_GRADIENT_FNS_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-gradient.h b/src/sp-mesh-gradient.h new file mode 100644 index 000000000..44d6c0e4c --- /dev/null +++ b/src/sp-mesh-gradient.h @@ -0,0 +1,35 @@ +#ifndef SP_MESH_GRADIENT_H +#define SP_MESH_GRADIENT_H + +/** \file + * SPMeshGradient: SVG <meshgradient> implementation. + */ + +#include "svg/svg-length.h" +#include "sp-gradient.h" +#include "sp-mesh-gradient-fns.h" + +/** Mesh gradient. */ +struct SPMeshGradient : public SPGradient { + SVGLength x; // Upper left corner of mesh + SVGLength y; // Upper right corner of mesh +}; + +/// The SPMeshGradient vtable. +struct SPMeshGradientClass { + SPGradientClass parent_class; +}; + + +#endif /* !SP_MESH_GRADIENT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-patch-fns.h b/src/sp-mesh-patch-fns.h new file mode 100644 index 000000000..37fb5a70a --- /dev/null +++ b/src/sp-mesh-patch-fns.h @@ -0,0 +1,39 @@ +#ifndef SP_MESH_PATCH_FNS_H +#define SP_MESH_PATCH_FNS_H + +/** \file + * Macros and fn definitions related to mesh patchs. + */ + +#include <glib-object.h> + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class SPMeshPatch; + +#define SP_TYPE_MESHPATCH (sp_meshpatch_get_type()) +#define SP_MESHPATCH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_MESHPATCH, SPMeshPatch)) +#define SP_MESHPATCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_MESHPATCH, SPMeshPatchClass)) +#define SP_IS_MESHPATCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_MESHPATCH)) +#define SP_IS_MESHPATCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_MESHPATCH)) + + +GType sp_meshpatch_get_type(); + + +#endif /* !SP_MESH_PATCH_FNS_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-patch.cpp b/src/sp-mesh-patch.cpp new file mode 100644 index 000000000..ff1a18a01 --- /dev/null +++ b/src/sp-mesh-patch.cpp @@ -0,0 +1,63 @@ +/** @file + * @gradient meshpatch class. + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Jon A. Cruz <jon@joncruz.org> + * Tavmjong Bah <tavjong@free.fr> + * + * Copyright (C) 1999,2005 authors + * Copyright (C) 2010 Jon A. Cruz + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "sp-mesh-patch.h" +#include "style.h" + +SPMeshPatch* SPMeshPatch::getNextMeshPatch() +{ + SPMeshPatch *result = 0; + + for (SPObject* obj = getNext(); obj && !result; obj = obj->getNext()) { + if (SP_IS_MESHPATCH(obj)) { + result = SP_MESHPATCH(obj); + } + } + + return result; +} + +SPMeshPatch* SPMeshPatch::getPrevMeshPatch() +{ + SPMeshPatch *result = 0; + + for (SPObject* obj = getPrev(); obj; obj = obj->getPrev()) { + // The closest previous SPObject that is an SPMeshPatch *should* be ourself. + if (SP_IS_MESHPATCH(obj)) { + SPMeshPatch* meshpatch = SP_MESHPATCH(obj); + // Sanity check to ensure we have a proper sibling structure. + if (meshpatch->getNextMeshPatch() == this) { + result = meshpatch; + } else { + g_warning("SPMeshPatch previous/next relationship broken"); + } + break; + } + } + + return result; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-patch.h b/src/sp-mesh-patch.h new file mode 100644 index 000000000..b56a0b95f --- /dev/null +++ b/src/sp-mesh-patch.h @@ -0,0 +1,59 @@ +#ifndef SEEN_SP_MESHPATCH_H +#define SEEN_SP_MESHPATCH_H + +/** \file + * SPMeshPatch: SVG <meshpatch> implementation. + */ +/* + * Authors: Tavmjong Bah + * + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include <glibmm/ustring.h> +//#include "svg/svg-length.h" +#include "sp-object.h" + +class SPObjectClass; + +struct SPMeshPatch; +struct SPMeshPatchClass; + +#define SP_TYPE_MESHPATCH (sp_meshpatch_get_type()) +#define SP_MESHPATCH(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_MESHPATCH, SPMeshPatch)) +#define SP_MESHPATCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_MESHPATCH, SPMeshPatchClass)) +#define SP_IS_MESHPATCH(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_MESHPATCH)) +#define SP_IS_MESHPATCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_MESHPATCH)) + +GType sp_meshpatch_get_type(); + +/** Gradient MeshPatch. */ +struct SPMeshPatch : public SPObject { + + SPMeshPatch* getNextMeshPatch(); + SPMeshPatch* getPrevMeshPatch(); + Glib::ustring * tensor_string; + //SVGLength tx[4]; // Tensor points + //SVGLength ty[4]; // Tensor points +}; + +/// The SPMeshPatch vtable. +struct SPMeshPatchClass { + SPObjectClass parent_class; +}; + +#endif /* !SEEN_SP_MESHPATCH_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-row-fns.h b/src/sp-mesh-row-fns.h new file mode 100644 index 000000000..5d85fdfea --- /dev/null +++ b/src/sp-mesh-row-fns.h @@ -0,0 +1,39 @@ +#ifndef SP_MESH_ROW_FNS_H +#define SP_MESH_ROW_FNS_H + +/** \file + * Macros and fn definitions related to mesh rows. + */ + +#include <glib-object.h> + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class SPMeshRow; + +#define SP_TYPE_MESHROW (sp_meshrow_get_type()) +#define SP_MESHROW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_MESHROW, SPMeshRow)) +#define SP_MESHROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_MESHROW, SPMeshRowClass)) +#define SP_IS_MESHROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_MESHROW)) +#define SP_IS_MESHROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_MESHROW)) + + +GType sp_meshrow_get_type(); + + +#endif /* !SP_MESH_ROW_FNS_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-row.cpp b/src/sp-mesh-row.cpp new file mode 100644 index 000000000..bc0c59776 --- /dev/null +++ b/src/sp-mesh-row.cpp @@ -0,0 +1,63 @@ +/** @file + * @gradient meshpatch class. + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Jon A. Cruz <jon@joncruz.org> + * Tavmjong Bah <tavjong@free.fr> + * + * Copyright (C) 1999,2005 authors + * Copyright (C) 2010 Jon A. Cruz + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "sp-mesh-row.h" +#include "style.h" + +SPMeshRow* SPMeshRow::getNextMeshRow() +{ + SPMeshRow *result = 0; + + for (SPObject* obj = getNext(); obj && !result; obj = obj->getNext()) { + if (SP_IS_MESHROW(obj)) { + result = SP_MESHROW(obj); + } + } + + return result; +} + +SPMeshRow* SPMeshRow::getPrevMeshRow() +{ + SPMeshRow *result = 0; + + for (SPObject* obj = getPrev(); obj; obj = obj->getPrev()) { + // The closest previous SPObject that is an SPMeshRow *should* be ourself. + if (SP_IS_MESHROW(obj)) { + SPMeshRow* meshrow = SP_MESHROW(obj); + // Sanity check to ensure we have a proper sibling structure. + if (meshrow->getNextMeshRow() == this) { + result = meshrow; + } else { + g_warning("SPMeshRow previous/next relationship broken"); + } + break; + } + } + + return result; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-mesh-row.h b/src/sp-mesh-row.h new file mode 100644 index 000000000..53e311bef --- /dev/null +++ b/src/sp-mesh-row.h @@ -0,0 +1,53 @@ +#ifndef SEEN_SP_MESHROW_H +#define SEEN_SP_MESHROW_H + +/** \file + * SPMeshRow: SVG <meshRow> implementation. + */ +/* + * Authors: Tavmjong Bah + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include "sp-object.h" + +class SPObjectClass; + +struct SPMeshRow; +struct SPMeshRowClass; + +#define SP_TYPE_MESHROW (sp_meshrow_get_type()) +#define SP_MESHROW(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_MESHROW, SPMeshRow)) +#define SP_MESHROW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_MESHROW, SPMeshRowClass)) +#define SP_IS_MESHROW(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_MESHROW)) +#define SP_IS_MESHROW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_MESHROW)) + +GType sp_meshrow_get_type(); + +/** Gradient MeshRow. */ +struct SPMeshRow : public SPObject { + + SPMeshRow* getNextMeshRow(); + SPMeshRow* getPrevMeshRow(); +}; + +/// The SPMeshRow vtable. +struct SPMeshRowClass { + SPObjectClass parent_class; +}; + +#endif /* !SEEN_SP_MESHROW_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/sp-object-repr.cpp b/src/sp-object-repr.cpp index b40017e65..16e64ff49 100644 --- a/src/sp-object-repr.cpp +++ b/src/sp-object-repr.cpp @@ -17,6 +17,9 @@ #include "sp-root.h" #include "sp-image.h" #include "sp-linear-gradient-fns.h" +#include "sp-mesh-gradient-fns.h" +#include "sp-mesh-row-fns.h" +#include "sp-mesh-patch-fns.h" #include "sp-path.h" #include "sp-radial-gradient-fns.h" #include "sp-rect.h" @@ -186,6 +189,9 @@ populate_dtables() { "svg:linearGradient", SP_TYPE_LINEARGRADIENT }, { "svg:marker", SP_TYPE_MARKER }, { "svg:mask", SP_TYPE_MASK }, + { "svg:meshGradient", SP_TYPE_MESHGRADIENT }, + { "svg:meshRow", SP_TYPE_MESHROW }, + { "svg:meshPatch", SP_TYPE_MESHPATCH }, { "svg:metadata", SP_TYPE_METADATA }, { "svg:path", SP_TYPE_PATH }, { "svg:pattern", SP_TYPE_PATTERN }, diff --git a/src/sp-stop.h b/src/sp-stop.h index ec6c4525f..c3b1e2753 100644 --- a/src/sp-stop.h +++ b/src/sp-stop.h @@ -43,6 +43,8 @@ struct SPStop : public SPObject { /// \todo fixme: Implement SPSVGNumber or something similar. gfloat opacity; + Glib::ustring * path_string; + //SPCurve path; static SPColor readStopColor( Glib::ustring const &styleStr, guint32 dfl = 0 ); @@ -50,6 +52,7 @@ struct SPStop : public SPObject { SPStop* getPrevStop(); SPColor getEffectiveColor() const; + }; /// The SPStop vtable. diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index 42eaf4474..cbf269387 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -49,6 +49,7 @@ #include "sp-text.h" #include "sp-flowtext.h" #include "gradient-context.h" +#include "mesh-context.h" #include "zoom-context.h" #include "measure-context.h" #include "dropper-context.h" @@ -75,6 +76,7 @@ static char const *const tool_names[] = { "/tools/calligraphic", "/tools/text", "/tools/gradient", + "/tools/mesh", "/tools/zoom", "/tools/measure", "/tools/dropper", @@ -204,7 +206,14 @@ tools_switch(SPDesktop *dt, int num) inkscape_eventcontext_set(sp_desktop_event_context(dt)); dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> or <b>double click</b> to create a gradient on selected objects, <b>drag handles</b> to adjust gradients.")); break; + case TOOLS_MESH: + dt->set_event_context(SP_TYPE_MESH_CONTEXT, tool_names[num]); + dt->activate_guides(false); + inkscape_eventcontext_set(sp_desktop_event_context(dt)); + dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> or <b>double click</b> to create a mesh on selected objects, <b>drag handles</b> to adjust meshes.")); + break; case TOOLS_ZOOM: + std::cout << "tools-switch.cpp: TOOLS_ZOOM" << std::endl; dt->set_event_context(SP_TYPE_ZOOM_CONTEXT, tool_names[num]); dt->activate_guides(false); inkscape_eventcontext_set(sp_desktop_event_context(dt)); diff --git a/src/tools-switch.h b/src/tools-switch.h index 9765def92..77fe370c6 100644 --- a/src/tools-switch.h +++ b/src/tools-switch.h @@ -34,6 +34,7 @@ enum { TOOLS_CALLIGRAPHIC, TOOLS_TEXT, TOOLS_GRADIENT, + TOOLS_MESH, TOOLS_ZOOM, TOOLS_MEASURE, TOOLS_DROPPER, diff --git a/src/verbs.cpp b/src/verbs.cpp index f2ff61293..96a6b76a1 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -1509,6 +1509,9 @@ void ContextVerb::perform(SPAction *action, void *data) case SP_VERB_CONTEXT_GRADIENT: tools_switch(dt, TOOLS_GRADIENT); break; + case SP_VERB_CONTEXT_MESH: + tools_switch(dt, TOOLS_MESH); + break; case SP_VERB_CONTEXT_ZOOM: tools_switch(dt, TOOLS_ZOOM); break; @@ -1587,6 +1590,10 @@ void ContextVerb::perform(SPAction *action, void *data) prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_GRADIENT); dt->_dlg_mgr->showDialog("InkscapePreferences"); break; + case SP_VERB_CONTEXT_MESH_PREFS: + prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_GRADIENT); + dt->_dlg_mgr->showDialog("InkscapePreferences"); + break; case SP_VERB_CONTEXT_ZOOM_PREFS: prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_ZOOM); dt->_dlg_mgr->showDialog("InkscapePreferences"); @@ -2512,6 +2519,8 @@ Verb *Verb::_base_verbs[] = { N_("Create and edit text objects"), INKSCAPE_ICON("draw-text")), new ContextVerb(SP_VERB_CONTEXT_GRADIENT, "ToolGradient", N_("Gradient"), N_("Create and edit gradients"), INKSCAPE_ICON("color-gradient")), + new ContextVerb(SP_VERB_CONTEXT_MESH, "ToolMesh", N_("Mesh"), + N_("Create and edit meshes"), INKSCAPE_ICON("mesh-gradient")), new ContextVerb(SP_VERB_CONTEXT_ZOOM, "ToolZoom", N_("Zoom"), N_("Zoom in or out"), INKSCAPE_ICON("zoom")), new ContextVerb(SP_VERB_CONTEXT_MEASURE, "ToolMeasure", NC_("Measurement tool", "Measure"), @@ -2557,6 +2566,8 @@ Verb *Verb::_base_verbs[] = { N_("Open Preferences for the Text tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_GRADIENT_PREFS, "GradientPrefs", N_("Gradient Preferences"), N_("Open Preferences for the Gradient tool"), NULL), + new ContextVerb(SP_VERB_CONTEXT_MESH_PREFS, "Mesh_Prefs", N_("Mesh Preferences"), + N_("Open Preferences for the Mesh tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_ZOOM_PREFS, "ZoomPrefs", N_("Zoom Preferences"), N_("Open Preferences for the Zoom tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_MEASURE_PREFS, "MeasurePrefs", N_("Measure Preferences"), diff --git a/src/verbs.h b/src/verbs.h index 1a9efdb81..8d65db642 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -178,6 +178,7 @@ enum { SP_VERB_CONTEXT_CALLIGRAPHIC, SP_VERB_CONTEXT_TEXT, SP_VERB_CONTEXT_GRADIENT, + SP_VERB_CONTEXT_MESH, SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_MEASURE, SP_VERB_CONTEXT_DROPPER, @@ -201,6 +202,7 @@ enum { SP_VERB_CONTEXT_CALLIGRAPHIC_PREFS, SP_VERB_CONTEXT_TEXT_PREFS, SP_VERB_CONTEXT_GRADIENT_PREFS, + SP_VERB_CONTEXT_MESH_PREFS, SP_VERB_CONTEXT_ZOOM_PREFS, SP_VERB_CONTEXT_MEASURE_PREFS, SP_VERB_CONTEXT_DROPPER_PREFS, diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 8ae6441c3..38180bd69 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -9,6 +9,7 @@ set(widgets_SRC erasor-toolbar.cpp lpe-toolbar.cpp measure-toolbar.cpp + mesh-toolbar.cpp node-toolbar.cpp paintbucket-toolbar.cpp pencil-toolbar.cpp @@ -65,6 +66,7 @@ set(widgets_SRC erasor-toolbar.h lpe-toolbar.h measure-toolbar.h + mesh-toolbar.h node-toolbar.h paintbucket-toolbar.h pencil-toolbar.h diff --git a/src/widgets/Makefile_insert b/src/widgets/Makefile_insert index 439b5c3d4..7d6b413bd 100644 --- a/src/widgets/Makefile_insert +++ b/src/widgets/Makefile_insert @@ -42,6 +42,8 @@ ink_common_sources += \ widgets/lpe-toolbar.h \ widgets/measure-toolbar.cpp \ widgets/measure-toolbar.h \ + widgets/mesh-toolbar.cpp \ + widgets/mesh-toolbar.h \ widgets/node-toolbar.cpp \ widgets/node-toolbar.h \ widgets/paint-selector.cpp \ diff --git a/src/widgets/gradient-toolbar.h b/src/widgets/gradient-toolbar.h index 938f45dc6..980a41a83 100644 --- a/src/widgets/gradient-toolbar.h +++ b/src/widgets/gradient-toolbar.h @@ -17,4 +17,4 @@ struct SPDesktop; void sp_gradient_toolbox_prep(SPDesktop * /*desktop*/, GtkActionGroup* mainActions, GObject* holder); -#endif /* !SEEN_SELECT_TOOLBAR_H */ +#endif /* !SEEN_GRADIENT_TOOLBAR_H */ diff --git a/src/widgets/mesh-toolbar.cpp b/src/widgets/mesh-toolbar.cpp new file mode 100644 index 000000000..7c37d757c --- /dev/null +++ b/src/widgets/mesh-toolbar.cpp @@ -0,0 +1,373 @@ +/* + * Gradient aux toolbar + * + * Authors: + * bulia byak <bulia@dr.com> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Abhishek Sharma + * Tavmjong Bah <tavjong@free.fr> + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +// REVIEW THESE AT END OF REWRITE +#include "toolbox.h" +#include "mesh-toolbar.h" + +#include "ui/widget/color-preview.h" +#include "verbs.h" + +#include "macros.h" +#include "widgets/button.h" +#include "widgets/widget-sizes.h" +#include "widgets/spw-utilities.h" +#include "widgets/spinbutton-events.h" +#include "widgets/gradient-vector.h" +#include "widgets/gradient-image.h" +#include "style.h" + +#include "preferences.h" +#include "document-private.h" +#include "document-undo.h" +#include "desktop.h" +#include "desktop-handles.h" +#include <glibmm/i18n.h> + +#include "gradient-context.h" +#include "gradient-drag.h" +#include "sp-mesh-gradient.h" +#include "gradient-chemistry.h" +#include "gradient-selector.h" +#include "selection.h" +#include "ui/icon-names.h" + +#include "../ege-adjustment-action.h" +#include "../ege-output-action.h" +#include "../ege-select-one-action.h" +#include "../ink-action.h" +#include "../ink-comboboxentry-action.h" + +#include "sp-stop.h" +#include "svg/css-ostringstream.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "gradient-context.h" + +#include "toolbox.h" + +using Inkscape::DocumentUndo; +using Inkscape::UI::ToolboxFactory; +using Inkscape::UI::PrefPusher; + +static gboolean blocked = FALSE; + +//######################## +//## Mesh ## +//######################## + +/* + * Core function, setup all the widgets whenever something changes on the desktop + */ +static void ms_tb_selection_changed(Inkscape::Selection * /*selection*/, gpointer data) +{ + // DOES NOTHING AT MOMENT + + // std::cout << "ms_tb_selection_changed" << std::endl; + + // if (blocked) + // return; + + // GtkWidget *widget = GTK_WIDGET(data); + + // SPDesktop *desktop = static_cast<SPDesktop *>(g_object_get_data(G_OBJECT(widget), "desktop")); + // if (!desktop) { + // return; + // } + + // Inkscape::Selection *selection = sp_desktop_selection(desktop); // take from desktop, not from args + // if (selection) { + // SPEventContext *ev = sp_desktop_event_context(desktop); + // GrDrag *drag = NULL; + // if (ev) { + // drag = ev->get_drag(); + // // Hide/show handles? + // } + + // } +} + + +static void ms_tb_selection_modified(Inkscape::Selection *selection, guint /*flags*/, gpointer data) +{ + ms_tb_selection_changed(selection, data); +} + +static void ms_drag_selection_changed(gpointer /*dragger*/, gpointer data) +{ + ms_tb_selection_changed(NULL, data); + +} + +static void ms_defs_release(SPObject * defs, GtkWidget *widget) +{ + ms_tb_selection_changed(NULL, (gpointer) widget); +} + +static void ms_defs_modified(SPObject * /*defs*/, guint /*flags*/, GtkWidget *widget) +{ + ms_tb_selection_changed(NULL, (gpointer) widget); +} + +static void ms_disconnect_sigc(GObject * /*obj*/, sigc::connection *connection) { + connection->disconnect(); + delete connection; +} + + +/* + * Callback functions for user actions + */ + +static void ms_new_type_changed( EgeSelectOneAction *act, GObject * /*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint typemode = ege_select_one_action_get_active( act ) == 0 ? SP_GRADIENT_MESH_TYPE_NORMAL : SP_GRADIENT_MESH_TYPE_CONICAL; + prefs->setInt("/tools/mesh/mesh_type", typemode); +} + +static void ms_new_fillstroke_changed( EgeSelectOneAction *act, GObject * /*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Inkscape::PaintTarget fsmode = (ege_select_one_action_get_active( act ) == 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + prefs->setInt("/tools/gradient/newfillorstroke", (fsmode == Inkscape::FOR_FILL) ? 1 : 0); +} + +static void ms_row_changed (GtkAdjustment *adj, GObject *tbl ) { + + if (blocked) { + return; + } + + blocked = TRUE; + + int rows = gtk_adjustment_get_value(adj); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + prefs->setInt("/tools/mesh/mesh_rows", rows); + + blocked = FALSE; +} + +static void ms_col_changed (GtkAdjustment *adj, GObject *tbl ) { + + if (blocked) { + return; + } + + blocked = TRUE; + + int cols = gtk_adjustment_get_value(adj); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + prefs->setInt("/tools/mesh/mesh_cols", cols); + + blocked = FALSE; +} + +static void ms_edit_fillstroke_changed( EgeSelectOneAction *act, GObject * /*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Inkscape::PaintTarget fsmode = (ege_select_one_action_get_active( act ) == 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + prefs->setInt("/tools/gradient/editfillorstroke", (fsmode == Inkscape::FOR_FILL) ? 1 : 0); +} + +/** + * Mesh auxiliary toolbar construction and setup. + * + */ +void sp_mesh_toolbox_prep(SPDesktop * desktop, GtkActionGroup* mainActions, GObject* holder) +{ + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); + + EgeAdjustmentAction* eact = 0; + + /* New mesh: normal or conical */ + { + GtkListStore* model = gtk_list_store_new( 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING ); + + GtkTreeIter iter; + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("normal"), 1, _("Create mesh gradient"), 2, INKSCAPE_ICON("paint-gradient-mesh"), -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("conical"), 1, _("Create conical gradient"), 2, INKSCAPE_ICON("paint-gradient-conical"), -1 ); + + EgeSelectOneAction* act = ege_select_one_action_new( "MeshNewTypeAction", (""), (""), NULL, GTK_TREE_MODEL(model) ); + g_object_set( act, "short_label", _("New:"), NULL ); + gtk_action_group_add_action( mainActions, GTK_ACTION(act) ); + g_object_set_data( holder, "mesh_new_type_action", act ); + + ege_select_one_action_set_appearance( act, "full" ); + ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); + g_object_set( G_OBJECT(act), "icon-property", "iconId", NULL ); + ege_select_one_action_set_icon_column( act, 2 ); + ege_select_one_action_set_tooltip_column( act, 1 ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint mode = prefs->getInt("/tools/mesh/mesh_type", SP_GRADIENT_MESH_TYPE_NORMAL) != SP_GRADIENT_MESH_TYPE_NORMAL; + ege_select_one_action_set_active( act, mode ); + g_signal_connect_after( G_OBJECT(act), "changed", G_CALLBACK(ms_new_type_changed), holder ); + } + + /* New gradient on fill or stroke*/ + { + GtkListStore* model = gtk_list_store_new( 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING ); + + GtkTreeIter iter; + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("fill"), 1, _("Create gradient in the fill"), 2, INKSCAPE_ICON("object-fill"), -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("stroke"), 1, _("Create gradient in the stroke"), 2, INKSCAPE_ICON("object-stroke"), -1 ); + + EgeSelectOneAction* act = ege_select_one_action_new( "MeshNewFillStrokeAction", (""), (""), NULL, GTK_TREE_MODEL(model) ); + g_object_set( act, "short_label", _("on:"), NULL ); + gtk_action_group_add_action( mainActions, GTK_ACTION(act) ); + g_object_set_data( holder, "mesh_new_fillstroke_action", act ); + + ege_select_one_action_set_appearance( act, "full" ); + ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); + g_object_set( G_OBJECT(act), "icon-property", "iconId", NULL ); + ege_select_one_action_set_icon_column( act, 2 ); + ege_select_one_action_set_tooltip_column( act, 1 ); + + /// @todo Convert to boolean? + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool fillstrokemode = !prefs->getBool("/tools/gradient/newfillorstroke", true); + ege_select_one_action_set_active( act, fillstrokemode ); + g_signal_connect_after( G_OBJECT(act), "changed", G_CALLBACK(ms_new_fillstroke_changed), holder ); + } + + /* Number of mesh rows */ + { + gchar const* labels[] = {}; + gdouble values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + eact = create_adjustment_action( "MeshRowAction", + _("Rows"), _("Rows:"), _("Number of rows in new mesh"), + "/tools/mesh/mesh_rows", 1, + GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL, + 1, 20, 1, 1, + labels, values, G_N_ELEMENTS(labels), + ms_row_changed, + 1.0, 0 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + /* Number of mesh columns */ + { + gchar const* labels[] = {}; + gdouble values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + eact = create_adjustment_action( "MeshColumnAction", + _("Columns"), _("Columns:"), _("Number of columns in new mesh"), + "/tools/mesh/mesh_cols", 1, + GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL, + 1, 20, 1, 1, + labels, values, G_N_ELEMENTS(labels), + ms_col_changed, + 1.0, 0 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + /* Edit fill mesh */ + { + InkToggleAction* act = ink_toggle_action_new( "MeshEditFillAction", + _("Edit Fill"), + _("Edit fill mesh"), + INKSCAPE_ICON("object-fill"), + secondarySize ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + PrefPusher *pusher = new PrefPusher(GTK_TOGGLE_ACTION(act), "/tools/mesh/edit_fill"); + g_signal_connect( holder, "destroy", G_CALLBACK(delete_prefspusher), pusher); + } + + /* Edit stroke mesh */ + { + InkToggleAction* act = ink_toggle_action_new( "MeshEditStrokeAction", + _("Edit Stroke"), + _("Edit stroke mesh"), + INKSCAPE_ICON("object-stroke"), + secondarySize ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + PrefPusher *pusher = new PrefPusher(GTK_TOGGLE_ACTION(act), "/tools/mesh/edit_stroke"); + g_signal_connect( holder, "destroy", G_CALLBACK(delete_prefspusher), pusher); + } + + /* Show/hide side and tensor handles */ + { + InkToggleAction* act = ink_toggle_action_new( "MeshShowHandlesAction", + _("Show Handles"), + _("Show side and tensor handles"), + INKSCAPE_ICON("show-node-handles"), + secondarySize ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + PrefPusher *pusher = new PrefPusher(GTK_TOGGLE_ACTION(act), "/tools/mesh/show_handles"); + g_signal_connect( holder, "destroy", G_CALLBACK(delete_prefspusher), pusher); + } + + + Inkscape::Selection *selection = sp_desktop_selection (desktop); + SPDocument *document = sp_desktop_document (desktop); + + g_object_set_data(holder, "desktop", desktop); + + // connect to selection modified and changed signals + sigc::connection *conn1 = new sigc::connection( + selection->connectChanged(sigc::bind(sigc::ptr_fun(&ms_tb_selection_changed), (gpointer) holder))); + sigc::connection *conn2 = new sigc::connection( + selection->connectModified(sigc::bind(sigc::ptr_fun(&ms_tb_selection_modified), (gpointer) holder))); + sigc::connection *conn3 = new sigc::connection( + desktop->connectToolSubselectionChanged( sigc::bind(sigc::ptr_fun(&ms_drag_selection_changed), (gpointer) holder))); + + // when holder is destroyed, disconnect + g_signal_connect(G_OBJECT(holder), "destroy", G_CALLBACK(ms_disconnect_sigc), conn1); + g_signal_connect(G_OBJECT(holder), "destroy", G_CALLBACK(ms_disconnect_sigc), conn2); + g_signal_connect(G_OBJECT(holder), "destroy", G_CALLBACK(ms_disconnect_sigc), conn3); + + // connect to release and modified signals of the defs (i.e. when someone changes mesh) + sigc::connection *release_connection = new sigc::connection(); + *release_connection = document->getDefs()->connectRelease(sigc::bind<1>(sigc::ptr_fun(&ms_defs_release), GTK_WIDGET(holder))); + sigc::connection *modified_connection = new sigc::connection(); + *modified_connection = document->getDefs()->connectModified(sigc::bind<2>(sigc::ptr_fun(&ms_defs_modified), GTK_WIDGET(holder))); + + // when holder is destroyed, disconnect + g_signal_connect(G_OBJECT(holder), "destroy", G_CALLBACK(ms_disconnect_sigc), release_connection); + g_signal_connect(G_OBJECT(holder), "destroy", G_CALLBACK(ms_disconnect_sigc), modified_connection); + +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/widgets/mesh-toolbar.h b/src/widgets/mesh-toolbar.h new file mode 100644 index 000000000..277525804 --- /dev/null +++ b/src/widgets/mesh-toolbar.h @@ -0,0 +1,22 @@ +#ifndef SEEN_MESH_TOOLBAR_H +#define SEEN_MESH_TOOLBAR_H + +/* + * Mesh aux toolbar + * + * Authors: + * bulia byak <bulia@dr.com> + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2012 authors + * Copyright (C) 2005 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtk/gtk.h> +struct SPDesktop; + +void sp_mesh_toolbox_prep( SPDesktop * /*desktop*/, GtkActionGroup* mainActions, GObject* holder); + +#endif /* !SEEN_MESH_TOOLBAR_H */ diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index 78d0683f3..f8aed71a8 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -76,6 +76,7 @@ #include "erasor-toolbar.h" #include "gradient-toolbar.h" #include "lpe-toolbar.h" +#include "mesh-toolbar.h" #include "measure-toolbar.h" #include "node-toolbar.h" #include "rect-toolbar.h" @@ -157,6 +158,7 @@ static struct { { "SPTextContext", "text_tool", SP_VERB_CONTEXT_TEXT, SP_VERB_CONTEXT_TEXT_PREFS }, { "SPConnectorContext","connector_tool", SP_VERB_CONTEXT_CONNECTOR, SP_VERB_CONTEXT_CONNECTOR_PREFS }, { "SPGradientContext", "gradient_tool", SP_VERB_CONTEXT_GRADIENT, SP_VERB_CONTEXT_GRADIENT_PREFS }, + { "SPMeshContext", "mesh_tool", SP_VERB_CONTEXT_MESH, SP_VERB_CONTEXT_MESH_PREFS }, { "SPDropperContext", "dropper_tool", SP_VERB_CONTEXT_DROPPER, SP_VERB_CONTEXT_DROPPER_PREFS }, { NULL, NULL, 0, 0 } }; @@ -209,8 +211,10 @@ static struct { SP_VERB_INVALID, 0, 0}, { "SPConnectorContext", "connector_toolbox", 0, sp_connector_toolbox_prep, "ConnectorToolbar", SP_VERB_INVALID, 0, 0}, - { "SPGradientContext", "gradient_toolbox", 0, sp_gradient_toolbox_prep, "GradientToolbar", - SP_VERB_INVALID, 0, 0}, + { "SPGradientContext", "gradient_toolbox", 0, sp_gradient_toolbox_prep, "GradientToolbar", + SP_VERB_INVALID, 0, 0}, + { "SPMeshContext", "mesh_toolbox", 0, sp_mesh_toolbox_prep, "MeshToolbar", + SP_VERB_INVALID, 0, 0}, { "SPFloodContext", "paintbucket_toolbox", 0, sp_paintbucket_toolbox_prep, "PaintbucketToolbar", SP_VERB_CONTEXT_PAINTBUCKET_PREFS, "/tools/paintbucket", N_("Style of Paint Bucket fill objects")}, { NULL, NULL, NULL, NULL, NULL, SP_VERB_INVALID, NULL, NULL } @@ -506,6 +510,18 @@ static gchar const * ui_descr = " <toolitem action='GradientEditDeleteAction' />" " </toolbar>" + " <toolbar name='MeshToolbar'>" + " <toolitem action='MeshNewTypeAction' />" + " <toolitem action='MeshNewFillStrokeAction' />" + " <toolitem action='MeshRowAction' />" + " <toolitem action='MeshColumnAction' />" + " <separator />" +// " <toolitem action='MeshEditFillAction' />" +// " <toolitem action='MeshEditStrokeAction' />" +// " <toolitem action='MeshShowHandlesAction' />" + " <separator />" + " </toolbar>" + " <toolbar name='DropperToolbar'>" " <toolitem action='DropperOpacityAction' />" " <toolitem action='DropperPickAlphaAction' />" @@ -1330,6 +1346,9 @@ void setup_tool_toolbox(GtkWidget *toolbox, SPDesktop *desktop) " <!-- Fill -->" " <toolitem action='ToolPaintBucket' />" " <toolitem action='ToolGradient' />" +#ifdef WITH_MESH + " <toolitem action='ToolMesh' />" +#endif " <toolitem action='ToolDropper' />" " <toolitem action='ToolConnector' />" |
