Code

Rename LPE: mirror reflect --> mirror symmetry
[inkscape.git] / src / live_effects / effect.cpp
index f968ec5c086e2436857300062319b754acb46f77..66d2334e018684ad2ed8b74748c9ab8853dfb012 100644 (file)
 #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/lpeobject.h"
 #include "live_effects/parameter/parameter.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"
 // end of includes
 
-#include "nodepath.h"
-
 namespace Inkscape {
 
 namespace LivePathEffect {
@@ -75,7 +86,13 @@ const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
-    {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}
+    {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
+    {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"},
+    {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
+    {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
+    {PARALLEL, N_("Parallel"), "parallel"},
+    {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
+    {OFFSET, N_("Offset"), "offset"},
 };
 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
 
@@ -134,6 +151,24 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
         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;
         default:
             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
             neweffect = NULL;
@@ -147,11 +182,37 @@ 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)
+      concatenate_before_pwd2(false),
+      provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
 {
     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
 }
@@ -174,59 +235,78 @@ Effect::effectType() {
     return lpeobj->effecttype;
 }
 
+/**
+ * Is performed a single time when the effect is freshly applied to a path
+ */
 void
 Effect::doOnApply (SPLPEItem */*lpeitem*/)
 {
-    // This is performed once when the effect is freshly applied to a path
 }
 
+/**
+ * Is performed each time before the effect is updated.
+ */
 void
 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
 {
     //Do nothing for simple effects
 }
 
-
-/*
- *  Here be the doEffect function chain:
+/**
+ * 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::doEffect (SPCurve * curve)
+Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
 {
-    NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
-
-    curve->set_bpath(new_bpath);
-}
-
-NArtBpath *
-Effect::doEffect_nartbpath (NArtBpath const * path_in)
-{
-    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.") );
+}
+
+/**
+ * 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;
+}
 
-        NArtBpath *path_out;
+/*
+ *  Here be the doEffect function chain:
+ */
+void
+Effect::doEffect (SPCurve * curve)
+{
+    std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
 
-        unsigned ret = 0;
-        while ( path_in[ret].code != NR_END ) {
-            ++ret;
-        }
-        unsigned len = ++ret;
+    std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
 
-        path_out = g_new(NArtBpath, len);
-        memcpy(path_out, path_in, len * sizeof(NArtBpath));
-        return path_out;
-    }
+    curve->set_pathvector(result_pathv);
 }
 
 std::vector<Geom::Path>
@@ -311,19 +391,76 @@ Effect::registerParameter(Parameter * param)
     param_vector.push_back(param);
 }
 
+// TODO: should we provide a way to alter the handle's appearance?
 void
-Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
+Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
 {
-    knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
+    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) {
+    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);
+    }
+}
+
+void
+Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
+    using namespace Inkscape::LivePathEffect;
+    for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
+        if ((*p)->paramType() == POINT_PARAM) {
+            PointParam *pparam = static_cast<PointParam *>(*p);
+            KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
+            e->create(desktop, item, knotholder, pparam->handleTip(),
+                      pparam->knotShape(), pparam->knotMode(), pparam->knotColor());
+            knotholder->add(e);
+        }
+    }
 }
 
-// TODO: allow for adding click_functions and description strings, too
 void
-Effect::addHandles(SPKnotHolder *knotholder) {
-    std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
-    for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
-        sp_knot_holder_add(knotholder, i->first, i->second, NULL, (""));
+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 ((*p)->paramType() == Inkscape::LivePathEffect::PATH_PARAM) {
+            SPCurve *c = new SPCurve(static_cast<Inkscape::LivePathEffect::PathParam*>(*p)->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;
 }
 
 /**
@@ -457,6 +594,23 @@ Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
     }
 }
 
+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 ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 } /* namespace LivePathEffect */
 
 } /* namespace Inkscape */