X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Flive_effects%2Feffect.cpp;h=c4bbc31e10d57198c30bfed72b856171908a3257;hb=797bee69297bbdd86c5cff2e0771a71d1e2ac69d;hp=543d60fc043220c46947c7f3ad29c7dfe46d2b1d;hpb=ca216080e46a0592cff832a87d2ab9ebd38095eb;p=inkscape.git diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 543d60fc0..c4bbc31e1 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -16,12 +16,19 @@ #include "desktop.h" #include "inkscape.h" #include "document.h" +#include "document-private.h" +#include "xml/document.h" #include +#include "pen-context.h" +#include "tools-switch.h" +#include "message-stack.h" +#include "desktop.h" +#include "nodepath.h" #include "live_effects/lpeobject.h" #include "live_effects/parameter/parameter.h" #include -#include "live_effects/n-art-bpath-2geom.h" +#include "libnr/n-art-bpath-2geom.h" #include "display/curve.h" #include @@ -32,56 +39,154 @@ // include effects: -#include "live_effects/lpe-skeletalstrokes.h" -#include "live_effects/lpe-pathalongpath.h" -#include "live_effects/lpe-slant.h" +#include "live_effects/lpe-patternalongpath.h" +#include "live_effects/lpe-bendpath.h" +#include "live_effects/lpe-sketch.h" +#include "live_effects/lpe-vonkoch.h" +#include "live_effects/lpe-knot.h" #include "live_effects/lpe-test-doEffect-stack.h" #include "live_effects/lpe-gears.h" #include "live_effects/lpe-curvestitch.h" - -#include "nodepath.h" +#include "live_effects/lpe-circle_with_radius.h" +#include "live_effects/lpe-perspective_path.h" +#include "live_effects/lpe-spiro.h" +#include "live_effects/lpe-lattice.h" +#include "live_effects/lpe-envelope.h" +#include "live_effects/lpe-constructgrid.h" +#include "live_effects/lpe-perp_bisector.h" +#include "live_effects/lpe-tangent_to_curve.h" +#include "live_effects/lpe-mirror_symmetry.h" +#include "live_effects/lpe-circle_3pts.h" +#include "live_effects/lpe-angle_bisector.h" +#include "live_effects/lpe-parallel.h" +#include "live_effects/lpe-copy_rotate.h" +#include "live_effects/lpe-offset.h" +#include "live_effects/lpe-ruler.h" +#include "live_effects/lpe-boolops.h" +#include "live_effects/lpe-interpolate.h" +// end of includes namespace Inkscape { namespace LivePathEffect { -const Util::EnumData LPETypeData[INVALID_LPE] = { +const Util::EnumData LPETypeData[] = { // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"} - {PATH_ALONG_PATH, N_("Bend Path"), "bend_path"}, - {SKELETAL_STROKES, N_("Pattern Along Path"), "skeletal"}, + {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"}, + {BEND_PATH, N_("Bend"), "bend_path"}, + {BOOLOPS, N_("Boolops"), "boolops"}, + {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"}, + {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"}, + {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"}, #ifdef LPE_ENABLE_TEST_EFFECTS - {SLANT, N_("Slant"), "slant"}, - {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"}, + {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"}, #endif - {GEARS, N_("Gears"), "gears"}, - {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"}, + {ENVELOPE, N_("Envelope Deformation"), "envelope"}, + {FREEHAND_SHAPE, N_("Freehand Shape"), "freehand_shape"}, // this is actually a special type of PatternAlongPath, used to paste shapes in pen/pencil tool + {GEARS, N_("Gears"), "gears"}, + {INTERPOLATE, N_("Interpolate Sub-Paths"), "interpolate"}, + {KNOT, N_("Knot"), "knot"}, + {LATTICE, N_("Lattice Deformation"), "lattice"}, + {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"}, + {OFFSET, N_("Offset"), "offset"}, + {PARALLEL, N_("Parallel"), "parallel"}, + {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG + {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"}, + {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"}, + {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"}, + {RULER, N_("Ruler"), "ruler"}, + {SKETCH, N_("Sketch"), "sketch"}, + {SPIRO, N_("Spiro spline"), "spiro"}, + {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"}, + {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}, + {VONKOCH, N_("VonKoch"), "vonkoch"}, }; -const Util::EnumDataConverter LPETypeConverter(LPETypeData, INVALID_LPE); +const Util::EnumDataConverter LPETypeConverter(LPETypeData, sizeof(LPETypeData)/sizeof(*LPETypeData)); Effect* Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) { Effect* neweffect = NULL; switch (lpenr) { - case SKELETAL_STROKES: - neweffect = (Effect*) new LPESkeletalStrokes(lpeobj); + case PATTERN_ALONG_PATH: + neweffect = static_cast ( new LPEPatternAlongPath(lpeobj) ); break; - case PATH_ALONG_PATH: - neweffect = (Effect*) new LPEPathAlongPath(lpeobj); + case FREEHAND_SHAPE: + neweffect = static_cast ( new LPEFreehandShape(lpeobj) ); break; -#ifdef LPE_ENABLE_TEST_EFFECTS - case SLANT: - neweffect = (Effect*) new LPESlant(lpeobj); + case BEND_PATH: + neweffect = static_cast ( new LPEBendPath(lpeobj) ); + break; + case SKETCH: + neweffect = static_cast ( new LPESketch(lpeobj) ); + break; + case VONKOCH: + neweffect = static_cast ( new LPEVonKoch(lpeobj) ); break; + case KNOT: + neweffect = static_cast ( new LPEKnot(lpeobj) ); + break; +#ifdef LPE_ENABLE_TEST_EFFECTS case DOEFFECTSTACK_TEST: - neweffect = (Effect*) new LPEdoEffectStackTest(lpeobj); + neweffect = static_cast ( new LPEdoEffectStackTest(lpeobj) ); break; #endif case GEARS: - neweffect = (Effect*) new LPEGears(lpeobj); + neweffect = static_cast ( new LPEGears(lpeobj) ); break; case CURVE_STITCH: - neweffect = (Effect*) new LPECurveStitch(lpeobj); + neweffect = static_cast ( new LPECurveStitch(lpeobj) ); + break; + case LATTICE: + neweffect = static_cast ( new LPELattice(lpeobj) ); + break; + case ENVELOPE: + neweffect = static_cast ( new LPEEnvelope(lpeobj) ); + break; + case CIRCLE_WITH_RADIUS: + neweffect = static_cast ( new LPECircleWithRadius(lpeobj) ); + break; + case PERSPECTIVE_PATH: + neweffect = static_cast ( new LPEPerspectivePath(lpeobj) ); + break; + case SPIRO: + neweffect = static_cast ( new LPESpiro(lpeobj) ); + break; + case CONSTRUCT_GRID: + neweffect = static_cast ( new LPEConstructGrid(lpeobj) ); + break; + case PERP_BISECTOR: + neweffect = static_cast ( new LPEPerpBisector(lpeobj) ); + break; + case TANGENT_TO_CURVE: + neweffect = static_cast ( new LPETangentToCurve(lpeobj) ); + break; + case MIRROR_SYMMETRY: + neweffect = static_cast ( new LPEMirrorSymmetry(lpeobj) ); + break; + case CIRCLE_3PTS: + neweffect = static_cast ( new LPECircle3Pts(lpeobj) ); + break; + case ANGLE_BISECTOR: + neweffect = static_cast ( new LPEAngleBisector(lpeobj) ); + break; + case PARALLEL: + neweffect = static_cast ( new LPEParallel(lpeobj) ); + break; + case COPY_ROTATE: + neweffect = static_cast ( new LPECopyRotate(lpeobj) ); + break; + case OFFSET: + neweffect = static_cast ( new LPEOffset(lpeobj) ); + break; + case RULER: + neweffect = static_cast ( new LPERuler(lpeobj) ); + break; + case BOOLOPS: + neweffect = static_cast ( new LPEBoolops(lpeobj) ); + break; + case INTERPOLATE: + neweffect = static_cast ( new LPEInterpolate(lpeobj) ); break; default: g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); @@ -96,95 +201,164 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) return neweffect; } +void +Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item) +{ + // Path effect definition + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); + Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect"); + repr->setAttribute("effect", name); + + SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to and assigns the 'id' attribute + const gchar * repr_id = repr->attribute("id"); + Inkscape::GC::release(repr); + + gchar *href = g_strdup_printf("#%s", repr_id); + sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true); + g_free(href); +} + +void +Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item) +{ + createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item); +} + Effect::Effect(LivePathEffectObject *lpeobject) + : oncanvasedit_it(0), + is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true), + done_pathparam_set(false), + show_orig_path(false), + lpeobj(lpeobject), + concatenate_before_pwd2(false), + provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden { - vbox = NULL; - tooltips = NULL; - lpeobj = lpeobject; - oncanvasedit_it = 0; + registerParameter( dynamic_cast(&is_visible) ); } Effect::~Effect() { - if (tooltips) { - delete tooltips; - } } Glib::ustring Effect::getName() { - if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE) + if (lpeobj->effecttype_set && LPETypeConverter.is_valid_id(lpeobj->effecttype) ) return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) ); else return Glib::ustring( _("No effect") ); } -/* - * Here be the doEffect function chain: +EffectType +Effect::effectType() { + return lpeobj->effecttype; +} + +/** + * Is performed a single time when the effect is freshly applied to a path */ void -Effect::doEffect (SPCurve * curve) +Effect::doOnApply (SPLPEItem */*lpeitem*/) { - NArtBpath *new_bpath = doEffect_nartbpath(SP_CURVE_BPATH(curve)); +} - if (new_bpath && new_bpath != SP_CURVE_BPATH(curve)) { // FIXME, add function to SPCurve to change bpath? or a copy function? - if (curve->_bpath) { - g_free(curve->_bpath); //delete old bpath - } - curve->_bpath = new_bpath; - } +/** + * Is performed each time before the effect is updated. + */ +void +Effect::doBeforeEffect (SPLPEItem */*lpeitem*/) +{ + //Do nothing for simple effects } -NArtBpath * -Effect::doEffect_nartbpath (NArtBpath * path_in) +/** + * Effects can have a parameter path set before they are applied by accepting a nonzero number of + * mouse clicks. This method activates the pen context, which waits for the specified number of + * clicks. Override Effect::acceptsNumParams() to return the number of expected mouse clicks. + */ +void +Effect::doAcceptPathPreparations(SPLPEItem *lpeitem) { - try { - std::vector orig_pathv = BPath_to_2GeomPath(path_in); + // switch to pen context + SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop? + if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) { + tools_switch(desktop, TOOLS_FREEHAND_PEN); + } - std::vector result_pathv = doEffect_path(orig_pathv); + SPEventContext *ec = desktop->event_context; + SPPenContext *pc = SP_PEN_CONTEXT(ec); + pc->expecting_clicks_for_LPE = this->acceptsNumParams(); + pc->waiting_LPE = this; + pc->waiting_item = lpeitem; + pc->polylines_only = true; - NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv); + ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, + g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"), + getName().c_str(), acceptsNumParams())); +} - return new_bpath; +void +Effect::writeParamsToSVG() { + std::vector::iterator p; + for (p = param_vector.begin(); p != param_vector.end(); ++p) { + (*p)->write_to_SVG(); } - catch (std::exception & e) { - g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what()); - SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE, - _("An exception occurred during execution of the Path Effect.") ); +} - NArtBpath *path_out; +/** + * If the effect expects a path parameter (specified by a number of mouse clicks) before it is + * applied, this is the method that processes the resulting path. Override it to customize it for + * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true! + */ +void +Effect::acceptParamPath (SPPath */*param_path*/) { + done_pathparam_set = true; +} - unsigned ret = 0; - while ( path_in[ret].code != NR_END ) { - ++ret; - } - unsigned len = ++ret; +/* + * Here be the doEffect function chain: + */ +void +Effect::doEffect (SPCurve * curve) +{ + std::vector orig_pathv = curve->get_pathvector(); - path_out = g_new(NArtBpath, len); - memcpy(path_out, path_in, len * sizeof(NArtBpath)); - return path_out; - } + std::vector result_pathv = doEffect_path(orig_pathv); + + curve->set_pathvector(result_pathv); } std::vector -Effect::doEffect_path (std::vector & path_in) +Effect::doEffect_path (std::vector const & path_in) { - Geom::Piecewise > pwd2_in; - - for (unsigned int i=0; i < path_in.size(); i++) { - pwd2_in.concat( path_in[i].toPwSb() ); + std::vector path_out; + + if ( !concatenate_before_pwd2 ) { + // default behavior + for (unsigned int i=0; i < path_in.size(); i++) { + Geom::Piecewise > pwd2_in = path_in[i].toPwSb(); + Geom::Piecewise > pwd2_out = doEffect_pwd2(pwd2_in); + std::vector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + // add the output path vector to the already accumulated vector: + for (unsigned int j=0; j < path.size(); j++) { + path_out.push_back(path[j]); + } + } + } else { + // concatenate the path into possibly discontinuous pwd2 + Geom::Piecewise > pwd2_in; + for (unsigned int i=0; i < path_in.size(); i++) { + pwd2_in.concat( path_in[i].toPwSb() ); + } + Geom::Piecewise > pwd2_out = doEffect_pwd2(pwd2_in); + path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); } - Geom::Piecewise > pwd2_out = doEffect_pwd2(pwd2_in); - - std::vector path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); - return path_out; } Geom::Piecewise > -Effect::doEffect_pwd2 (Geom::Piecewise > & pwd2_in) +Effect::doEffect_pwd2 (Geom::Piecewise > const & pwd2_in) { g_warning("Effect has no doEffect implementation"); return pwd2_in; @@ -200,7 +374,7 @@ Effect::readallParameters(Inkscape::XML::Node * repr) const gchar * value = repr->attribute(key); if (value) { bool accepted = param->param_readSVGValue(value); - if (!accepted) { + if (!accepted) { g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key); } } else { @@ -220,7 +394,7 @@ Effect::setParameter(const gchar * key, const gchar * new_value) if (param) { if (new_value) { bool accepted = param->param_readSVGValue(new_value); - if (!accepted) { + if (!accepted) { g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key); } } else { @@ -236,30 +410,96 @@ Effect::registerParameter(Parameter * param) param_vector.push_back(param); } +// TODO: should we provide a way to alter the handle's appearance? +void +Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr) +{ + kh_entity_vector.push_back(std::make_pair(entity, descr)); +} + +/** + * Add all registered LPE knotholder handles to the knotholder + */ +void +Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) { + using namespace Inkscape::LivePathEffect; + + // add handles provided by the effect itself + std::vector >::iterator i; + for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) { + KnotHolderEntity *entity = i->first; + const char *descr = i->second; + + entity->create(desktop, item, knotholder, descr); + knotholder->add(entity); + } + + // add handles provided by the effect's parameters (if any) + for (std::vector::iterator p = param_vector.begin(); p != param_vector.end(); ++p) { + (*p)->addKnotHolderEntities(knotholder, desktop, item); + } +} + +void +Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop) +{ + g_return_if_fail(desktop); + g_return_if_fail(SP_IS_PATH(lpeitem)); + + if (providesKnotholder() && showOrigPath()) { + // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we + // must create the helper curve for the original path manually; once we allow nodepaths and + // knotholders alongside each other, this needs to be rethought! + SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem)); + Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0); + lpeitem->lpe_helperpaths.push_back(tmpitem); + } + + for (std::vector::iterator p = param_vector.begin(); p != param_vector.end(); ++p) { + if ( Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast(*p) ) { + SPCurve *c = new SPCurve(pathparam->get_pathvector()); + + // TODO: factor this out (also the copied code above); see also lpe-lattice.cpp + SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, c, SP_ITEM(lpeitem), 0x009000ff); + Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0); + lpeitem->lpe_helperpaths.push_back(tmpitem); + } + } + + addHelperPathsImpl(lpeitem, desktop); +} + +void +Effect::addHelperPathsImpl(SPLPEItem */*lpeitem*/, SPDesktop */*desktop*/) +{ + // if this method is overloaded in derived classes, provides_own_flash_paths will be true + provides_own_flash_paths = false; +} + +/** + * This *creates* a new widget, management of deletion should be done by the caller + */ Gtk::Widget * -Effect::getWidget() +Effect::newWidget(Gtk::Tooltips * tooltips) { - if (!vbox) { - vbox = Gtk::manage( new Gtk::VBox() ); // use manage here, because after deletion of Effect object, others might still be pointing to this widget. - //if (!tooltips) - tooltips = new Gtk::Tooltips(); - - vbox->set_border_width(5); - - std::vector::iterator it = param_vector.begin(); - while (it != param_vector.end()) { - Parameter * param = *it; - Gtk::Widget * widg = param->param_getWidget(); - Glib::ustring * tip = param->param_getTooltip(); - if (widg) { - vbox->pack_start(*widg, true, true, 2); - if (tip != NULL) { - tooltips->set_tip(*widg, *tip); - } - } + // use manage here, because after deletion of Effect object, others might still be pointing to this widget. + Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() ); - it++; + vbox->set_border_width(5); + + std::vector::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + Parameter * param = *it; + Gtk::Widget * widg = param->param_newWidget(tooltips); + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + vbox->pack_start(*widg, true, true, 2); + if (tip != NULL) { + tooltips->set_tip(*widg, *tip); + } } + + it++; } return dynamic_cast(vbox); @@ -300,8 +540,11 @@ Effect::getParameter(const char * key) Parameter * Effect::getNextOncanvasEditableParam() { + if (param_vector.size() == 0) // no parameters + return NULL; + oncanvasedit_it++; - if (oncanvasedit_it == static_cast(param_vector.size())) { + if (oncanvasedit_it >= static_cast(param_vector.size())) { oncanvasedit_it = 0; } int old_it = oncanvasedit_it; @@ -350,7 +593,6 @@ Effect::resetDefaults(SPItem * /*item*/) void Effect::setup_nodepath(Inkscape::NodePath::Path *np) { - np->show_helperpath = true; np->helperpath_rgba = 0xff0000ff; np->helperpath_width = 1.0; } @@ -365,6 +607,24 @@ Effect::transform_multiply(Geom::Matrix const& postmul, bool set) } } +// TODO: take _all_ parameters into account, not only PointParams +bool +Effect::providesKnotholder() +{ + // does the effect actively provide any knotholder entities of its own? + if (kh_entity_vector.size() > 0) + return true; + + // otherwise: are there any PointParams? + for (std::vector::iterator p = param_vector.begin(); p != param_vector.end(); ++p) { + if ( Inkscape::LivePathEffect::PointParam *pointparam = dynamic_cast(*p) ) { + return true; + } + } + + return false; +} + } /* namespace LivePathEffect */ } /* namespace Inkscape */