Code

LPE knotholder refactoring: PointParams are not knotholder entities any more; instead...
[inkscape.git] / src / live_effects / effect.cpp
index 9513f37b17bedf3d3b293fadd5c85cf2bee5eaa7..c4bbc31e10d57198c30bfed72b856171908a3257 100644 (file)
@@ -6,6 +6,8 @@
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
+#include "live_effects/effect.h"
+
 #include "display/display-forward.h"
 #include "xml/node-event-vector.h"
 #include "sp-object.h"
 #include "desktop.h"
 #include "inkscape.h"
 #include "document.h"
+#include "document-private.h"
+#include "xml/document.h"
 #include <glibmm/i18n.h>
+#include "pen-context.h"
+#include "tools-switch.h"
+#include "message-stack.h"
+#include "desktop.h"
+#include "nodepath.h"
 
-#include "live_effects/effect.h"
 #include "live_effects/lpeobject.h"
 #include "live_effects/parameter/parameter.h"
 #include <glibmm/ustring.h>
-#include "live_effects/n-art-bpath-2geom.h"
+#include "libnr/n-art-bpath-2geom.h"
 #include "display/curve.h"
-#include <2geom/sbasis-to-bezier.h>
 #include <gtkmm.h>
 
 #include <exception>
 
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/matrix.h>
+
+
 // 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<EffectType> LPETypeData[INVALID_LPE] = {
+const Util::EnumData<EffectType> 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 subpaths"),       "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<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
+const Util::EnumDataConverter<EffectType> 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<Effect*> ( new LPEPatternAlongPath(lpeobj) );
             break;
-        case PATH_ALONG_PATH:
-            neweffect = (Effect*) new LPEPathAlongPath(lpeobj);
+        case FREEHAND_SHAPE:
+            neweffect = static_cast<Effect*> ( new LPEFreehandShape(lpeobj) );
             break;
-#ifdef LPE_ENABLE_TEST_EFFECTS
-            case SLANT:
-            neweffect = (Effect*) new LPESlant(lpeobj);
+        case BEND_PATH:
+            neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
+            break;
+        case SKETCH:
+            neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
             break;
+        case VONKOCH:
+            neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
+            break;
+        case KNOT:
+            neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
+            break;
+#ifdef LPE_ENABLE_TEST_EFFECTS
         case DOEFFECTSTACK_TEST:
-            neweffect = (Effect*) new LPEdoEffectStackTest(lpeobj);
+            neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
             break;
 #endif
         case GEARS:
-            neweffect = (Effect*) new LPEGears(lpeobj);
+            neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
             break;
         case CURVE_STITCH:
-            neweffect = (Effect*) new LPECurveStitch(lpeobj);
+            neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
+            break;
+        case LATTICE:
+            neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
+            break;
+        case ENVELOPE:
+            neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
+            break;
+        case CIRCLE_WITH_RADIUS:
+            neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
+            break;
+        case PERSPECTIVE_PATH:
+            neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
+            break;
+        case SPIRO:
+            neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
+            break;
+        case CONSTRUCT_GRID:
+            neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
+            break;
+        case PERP_BISECTOR:
+            neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
+            break;
+        case TANGENT_TO_CURVE:
+            neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
+            break;
+        case MIRROR_SYMMETRY:
+            neweffect = static_cast<Effect*> ( new LPEMirrorSymmetry(lpeobj) );
+            break;
+        case CIRCLE_3PTS:
+            neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
+            break;
+        case ANGLE_BISECTOR:
+            neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
+            break;
+        case PARALLEL:
+            neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
+            break;
+        case COPY_ROTATE:
+            neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
+            break;
+        case OFFSET:
+            neweffect = static_cast<Effect*> ( new LPEOffset(lpeobj) );
+            break;
+        case RULER:
+            neweffect = static_cast<Effect*> ( new LPERuler(lpeobj) );
+            break;
+        case BOOLOPS:
+            neweffect = static_cast<Effect*> ( new LPEBoolops(lpeobj) );
+            break;
+        case INTERPOLATE:
+            neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) );
             break;
         default:
             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
@@ -92,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 <defs> 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 = param_map.begin();
+    registerParameter( dynamic_cast<Parameter *>(&is_visible) );
 }
 
 Effect::~Effect()
 {
-    if (tooltips) {
-        delete tooltips;
-    }
 }
 
-Glib::ustring 
+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<Geom::Path> 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<Geom::Path> 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<Inkscape::LivePathEffect::Parameter *>::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<Geom::Path> orig_pathv = curve->get_pathvector();
 
-        path_out = g_new(NArtBpath, len);
-        memcpy(path_out, path_in, len * sizeof(NArtBpath));
-        return path_out;
-    }
+    std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
+
+    curve->set_pathvector(result_pathv);
 }
 
 std::vector<Geom::Path>
-Effect::doEffect_path (std::vector<Geom::Path> & path_in)
+Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
 {
-    Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
-
-    for (unsigned int i=0; i < path_in.size(); i++) {
-        pwd2_in.concat( path_in[i].toPwSb() );
+    std::vector<Geom::Path> path_out;
+
+    if ( !concatenate_before_pwd2 ) {
+        // default behavior
+        for (unsigned int i=0; i < path_in.size(); i++) {
+            Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
+            Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
+            std::vector<Geom::Path> 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<Geom::D2<Geom::SBasis> > pwd2_in;
+        for (unsigned int i=0; i < path_in.size(); i++) {
+            pwd2_in.concat( path_in[i].toPwSb() );
+        }
+        Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
+        path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
     }
 
-    Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
-
-    std::vector<Geom::Path> path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
-
     return path_out;
 }
 
 Geom::Piecewise<Geom::D2<Geom::SBasis> >
-Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
 {
     g_warning("Effect has no doEffect implementation");
     return pwd2_in;
@@ -189,13 +367,21 @@ Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
 void
 Effect::readallParameters(Inkscape::XML::Node * repr)
 {
-    param_map_type::iterator it = param_map.begin();
-    while (it != param_map.end()) {
-        const gchar * key = (*it).first.c_str();
+    std::vector<Parameter *>::iterator it = param_vector.begin();
+    while (it != param_vector.end()) {
+        Parameter * param = *it;
+        const gchar * key = param->param_key.c_str();
         const gchar * value = repr->attribute(key);
-        if(value) {
-            setParameter(key, value);
+        if (value) {
+            bool accepted = param->param_readSVGValue(value);
+            if (!accepted) {
+                g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
+            }
+        } else {
+            // set default value
+            param->param_set_default();
         }
+
         it++;
     }
 }
@@ -204,18 +390,16 @@ Effect::readallParameters(Inkscape::XML::Node * repr)
 void
 Effect::setParameter(const gchar * key, const gchar * new_value)
 {
-    Glib::ustring stringkey(key);
-
-    param_map_type::iterator it = param_map.find(stringkey);
-    if (it != param_map.end()) {
+    Parameter * param = getParameter(key);
+    if (param) {
         if (new_value) {
-            bool accepted = it->second->param_readSVGValue(new_value);
-            if (!accepted) { 
+            bool accepted = param->param_readSVGValue(new_value);
+            if (!accepted) {
                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
             }
         } else {
             // set default value
-            it->second->param_set_default();
+            param->param_set_default();
         }
     }
 }
@@ -223,33 +407,99 @@ Effect::setParameter(const gchar * key, const gchar * new_value)
 void
 Effect::registerParameter(Parameter * param)
 {
-    param_map[param->param_key] = param; // inserts or updates
+    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<std::pair<KnotHolderEntity*, const char*> >::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<Parameter *>::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<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
+        if ( Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam*>(*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);
-
-        param_map_type::iterator it = param_map.begin();
-        while (it != param_map.end()) {
-            Parameter * param = it->second;
-            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() );
+
+    vbox->set_border_width(5);
+
+    std::vector<Parameter *>::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++;
         }
+
+        it++;
     }
 
     return dynamic_cast<Gtk::Widget *>(vbox);
@@ -274,31 +524,39 @@ Effect::getParameter(const char * key)
 {
     Glib::ustring stringkey(key);
 
-    param_map_type::iterator it = param_map.find(stringkey);
-    if (it != param_map.end()) {
-        return it->second;
-    } else {
-        return NULL;
+    std::vector<Parameter *>::iterator it = param_vector.begin();
+    while (it != param_vector.end()) {
+        Parameter * param = *it;
+        if ( param->param_key == key) {
+            return param;
+        }
+
+        it++;
     }
+
+    return NULL;
 }
 
 Parameter *
 Effect::getNextOncanvasEditableParam()
 {
+    if (param_vector.size() == 0) // no parameters
+        return NULL;
+
     oncanvasedit_it++;
-    if (oncanvasedit_it == param_map.end()) {
-        oncanvasedit_it = param_map.begin();
+    if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
+        oncanvasedit_it = 0;
     }
-    param_map_type::iterator old_it = oncanvasedit_it;
+    int old_it = oncanvasedit_it;
 
     do {
-        Parameter * param = oncanvasedit_it->second;
-        if(param->oncanvas_editable) {
+        Parameter * param = param_vector[oncanvasedit_it];
+        if(param && param->oncanvas_editable) {
             return param;
         } else {
             oncanvasedit_it++;
-            if (oncanvasedit_it == param_map.end()) {  // loop round the map
-                oncanvasedit_it = param_map.begin();
+            if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
+                oncanvasedit_it = 0;
             }
         }
     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
@@ -333,13 +591,39 @@ Effect::resetDefaults(SPItem * /*item*/)
 }
 
 void
-Effect::setup_notepath(Inkscape::NodePath::Path *np)
+Effect::setup_nodepath(Inkscape::NodePath::Path *np)
 {
-    np->show_helperpath = true;
     np->helperpath_rgba = 0xff0000ff;
     np->helperpath_width = 1.0;
 }
 
+void
+Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
+{
+    // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
+    for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
+        Parameter * param = *it;
+        param->param_transform_multiply(postmul, 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<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
+        if ( Inkscape::LivePathEffect::PointParam *pointparam = dynamic_cast<Inkscape::LivePathEffect::PointParam*>(*p) ) {
+            return true;
+        }
+    }
+
+    return false;
+}
 
 } /* namespace LivePathEffect */