Code

Rename LPE: mirror reflect --> mirror symmetry
[inkscape.git] / src / live_effects / effect.cpp
1 #define INKSCAPE_LIVEPATHEFFECT_CPP
3 /*
4  * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
5  *
6  * Released under GNU GPL, read the file 'COPYING' for more information
7  */
9 #include "live_effects/effect.h"
11 #include "display/display-forward.h"
12 #include "xml/node-event-vector.h"
13 #include "sp-object.h"
14 #include "attributes.h"
15 #include "message-stack.h"
16 #include "desktop.h"
17 #include "inkscape.h"
18 #include "document.h"
19 #include "document-private.h"
20 #include "xml/document.h"
21 #include <glibmm/i18n.h>
22 #include "pen-context.h"
23 #include "tools-switch.h"
24 #include "message-stack.h"
25 #include "desktop.h"
26 #include "nodepath.h"
28 #include "live_effects/lpeobject.h"
29 #include "live_effects/parameter/parameter.h"
30 #include <glibmm/ustring.h>
31 #include "libnr/n-art-bpath-2geom.h"
32 #include "display/curve.h"
33 #include <gtkmm.h>
35 #include <exception>
37 #include <2geom/sbasis-to-bezier.h>
38 #include <2geom/matrix.h>
41 // include effects:
42 #include "live_effects/lpe-patternalongpath.h"
43 #include "live_effects/lpe-bendpath.h"
44 #include "live_effects/lpe-sketch.h"
45 #include "live_effects/lpe-vonkoch.h"
46 #include "live_effects/lpe-knot.h"
47 #include "live_effects/lpe-test-doEffect-stack.h"
48 #include "live_effects/lpe-gears.h"
49 #include "live_effects/lpe-curvestitch.h"
50 #include "live_effects/lpe-circle_with_radius.h"
51 #include "live_effects/lpe-perspective_path.h"
52 #include "live_effects/lpe-spiro.h"
53 #include "live_effects/lpe-lattice.h"
54 #include "live_effects/lpe-envelope.h"
55 #include "live_effects/lpe-constructgrid.h"
56 #include "live_effects/lpe-perp_bisector.h"
57 #include "live_effects/lpe-tangent_to_curve.h"
58 #include "live_effects/lpe-mirror_symmetry.h"
59 #include "live_effects/lpe-circle_3pts.h"
60 #include "live_effects/lpe-angle_bisector.h"
61 #include "live_effects/lpe-parallel.h"
62 #include "live_effects/lpe-copy_rotate.h"
63 #include "live_effects/lpe-offset.h"
64 // end of includes
66 namespace Inkscape {
68 namespace LivePathEffect {
70 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
71     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
72     {BEND_PATH,             N_("Bend"),                  "bend_path"},
73     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
74     {SKETCH,                N_("Sketch"),                "sketch"},
75     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
76     {KNOT,                  N_("Knot"),                  "knot"},
77 #ifdef LPE_ENABLE_TEST_EFFECTS
78     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
79 #endif
80     {GEARS,                 N_("Gears"),                 "gears"},
81     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
82     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
83     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
84     {SPIRO,      N_("Spiro spline"),      "spiro"},
85     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
86     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
87     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
88     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
89     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
90     {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"},
91     {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
92     {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
93     {PARALLEL, N_("Parallel"), "parallel"},
94     {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
95     {OFFSET, N_("Offset"), "offset"},
96 };
97 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
99 Effect*
100 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
102     Effect* neweffect = NULL;
103     switch (lpenr) {
104         case PATTERN_ALONG_PATH:
105             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
106             break;
107         case BEND_PATH:
108             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
109             break;
110         case SKETCH:
111             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
112             break;
113         case VONKOCH:
114             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
115             break;
116         case KNOT:
117             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
118             break;
119 #ifdef LPE_ENABLE_TEST_EFFECTS
120         case DOEFFECTSTACK_TEST:
121             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
122             break;
123 #endif
124         case GEARS:
125             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
126             break;
127         case CURVE_STITCH:
128             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
129             break;
130         case LATTICE:
131             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
132             break;
133         case ENVELOPE:
134             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
135             break;
136         case CIRCLE_WITH_RADIUS:
137             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
138             break;
139         case PERSPECTIVE_PATH:
140             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
141             break;
142         case SPIRO:
143             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
144             break;
145         case CONSTRUCT_GRID:
146             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
147             break;
148         case PERP_BISECTOR:
149             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
150             break;
151         case TANGENT_TO_CURVE:
152             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
153             break;
154         case MIRROR_SYMMETRY:
155             neweffect = static_cast<Effect*> ( new LPEMirrorSymmetry(lpeobj) );
156             break;
157         case CIRCLE_3PTS:
158             neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
159             break;
160         case ANGLE_BISECTOR:
161             neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
162             break;
163         case PARALLEL:
164             neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
165             break;
166         case COPY_ROTATE:
167             neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
168             break;
169         case OFFSET:
170             neweffect = static_cast<Effect*> ( new LPEOffset(lpeobj) );
171             break;
172         default:
173             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
174             neweffect = NULL;
175             break;
176     }
178     if (neweffect) {
179         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
180     }
182     return neweffect;
185 void
186 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
188     // Path effect definition
189     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
190     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
191     repr->setAttribute("effect", name);
193     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
194     const gchar * repr_id = repr->attribute("id");
195     Inkscape::GC::release(repr);
197     gchar *href = g_strdup_printf("#%s", repr_id);
198     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
199     g_free(href);
202 void
203 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
205     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
208 Effect::Effect(LivePathEffectObject *lpeobject)
209     : oncanvasedit_it(0),
210       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
211       done_pathparam_set(false),
212       show_orig_path(false),
213       lpeobj(lpeobject),
214       concatenate_before_pwd2(false),
215       provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
217     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
220 Effect::~Effect()
224 Glib::ustring
225 Effect::getName()
227     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
228         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
229     else
230         return Glib::ustring( _("No effect") );
233 EffectType
234 Effect::effectType() {
235     return lpeobj->effecttype;
238 /**
239  * Is performed a single time when the effect is freshly applied to a path
240  */
241 void
242 Effect::doOnApply (SPLPEItem */*lpeitem*/)
246 /**
247  * Is performed each time before the effect is updated.
248  */
249 void
250 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
252     //Do nothing for simple effects
255 /**
256  * Effects can have a parameter path set before they are applied by accepting a nonzero number of
257  * mouse clicks. This method activates the pen context, which waits for the specified number of
258  * clicks. Override Effect::acceptsNumParams() to return the number of expected mouse clicks.
259  */
260 void
261 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
263     // switch to pen context
264     SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
265     if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
266         tools_switch(desktop, TOOLS_FREEHAND_PEN);
267     }
269     SPEventContext *ec = desktop->event_context;
270     SPPenContext *pc = SP_PEN_CONTEXT(ec);
271     pc->expecting_clicks_for_LPE = this->acceptsNumParams();
272     pc->waiting_LPE = this;
273     pc->waiting_item = lpeitem;
274     pc->polylines_only = true;
276     ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
277         g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
278                         getName().c_str(), acceptsNumParams()));
281 void
282 Effect::writeParamsToSVG() {
283     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
284     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
285         (*p)->write_to_SVG();
286     }
289 /**
290  * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
291  * applied, this is the method that processes the resulting path. Override it to customize it for
292  * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
293  */
294 void
295 Effect::acceptParamPath (SPPath */*param_path*/) {
296     done_pathparam_set = true;
299 /*
300  *  Here be the doEffect function chain:
301  */
302 void
303 Effect::doEffect (SPCurve * curve)
305     std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
307     std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
309     curve->set_pathvector(result_pathv);
312 std::vector<Geom::Path>
313 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
315     std::vector<Geom::Path> path_out;
317     if ( !concatenate_before_pwd2 ) {
318         // default behavior
319         for (unsigned int i=0; i < path_in.size(); i++) {
320             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
321             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
322             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
323             // add the output path vector to the already accumulated vector:
324             for (unsigned int j=0; j < path.size(); j++) {
325                 path_out.push_back(path[j]);
326             }
327         }
328     } else {
329       // concatenate the path into possibly discontinuous pwd2
330         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
331         for (unsigned int i=0; i < path_in.size(); i++) {
332             pwd2_in.concat( path_in[i].toPwSb() );
333         }
334         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
335         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
336     }
338     return path_out;
341 Geom::Piecewise<Geom::D2<Geom::SBasis> >
342 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
344     g_warning("Effect has no doEffect implementation");
345     return pwd2_in;
348 void
349 Effect::readallParameters(Inkscape::XML::Node * repr)
351     std::vector<Parameter *>::iterator it = param_vector.begin();
352     while (it != param_vector.end()) {
353         Parameter * param = *it;
354         const gchar * key = param->param_key.c_str();
355         const gchar * value = repr->attribute(key);
356         if (value) {
357             bool accepted = param->param_readSVGValue(value);
358             if (!accepted) {
359                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
360             }
361         } else {
362             // set default value
363             param->param_set_default();
364         }
366         it++;
367     }
370 /* This function does not and SHOULD NOT write to XML */
371 void
372 Effect::setParameter(const gchar * key, const gchar * new_value)
374     Parameter * param = getParameter(key);
375     if (param) {
376         if (new_value) {
377             bool accepted = param->param_readSVGValue(new_value);
378             if (!accepted) {
379                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
380             }
381         } else {
382             // set default value
383             param->param_set_default();
384         }
385     }
388 void
389 Effect::registerParameter(Parameter * param)
391     param_vector.push_back(param);
394 // TODO: should we provide a way to alter the handle's appearance?
395 void
396 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
398     kh_entity_vector.push_back(std::make_pair(entity, descr));
401 /**
402  * Add all registered LPE knotholder handles to the knotholder
403  */
404 void
405 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
406     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
407     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
408         KnotHolderEntity *entity = i->first;
409         const char *descr = i->second;
411         entity->create(desktop, item, knotholder, descr);
412         knotholder->add(entity);
413     }
416 void
417 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
418     using namespace Inkscape::LivePathEffect;
419     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
420         if ((*p)->paramType() == POINT_PARAM) {
421             PointParam *pparam = static_cast<PointParam *>(*p);
422             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
423             e->create(desktop, item, knotholder, pparam->handleTip(),
424                       pparam->knotShape(), pparam->knotMode(), pparam->knotColor());
425             knotholder->add(e);
426         }
427     }
430 void
431 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
433     g_return_if_fail(desktop);
434     g_return_if_fail(SP_IS_PATH(lpeitem));
436     if (providesKnotholder() && showOrigPath()) {
437         // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
438         // must create the helper curve for the original path manually; once we allow nodepaths and
439         // knotholders alongside each other, this needs to be rethought!
440         SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
441         Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
442         lpeitem->lpe_helperpaths.push_back(tmpitem);
443     }
445     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
446         if ((*p)->paramType() == Inkscape::LivePathEffect::PATH_PARAM) {
447             SPCurve *c = new SPCurve(static_cast<Inkscape::LivePathEffect::PathParam*>(*p)->get_pathvector());
449             // TODO: factor this out (also the copied code above); see also lpe-lattice.cpp
450             SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, c, SP_ITEM(lpeitem), 0x009000ff);
451             Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
452             lpeitem->lpe_helperpaths.push_back(tmpitem);
453         }
454     }
456     addHelperPathsImpl(lpeitem, desktop);
459 void
460 Effect::addHelperPathsImpl(SPLPEItem */*lpeitem*/, SPDesktop */*desktop*/)
462     // if this method is overloaded in derived classes, provides_own_flash_paths will be true
463     provides_own_flash_paths = false;
466 /**
467  * This *creates* a new widget, management of deletion should be done by the caller
468  */
469 Gtk::Widget *
470 Effect::newWidget(Gtk::Tooltips * tooltips)
472     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
473     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
475     vbox->set_border_width(5);
477     std::vector<Parameter *>::iterator it = param_vector.begin();
478     while (it != param_vector.end()) {
479         Parameter * param = *it;
480         Gtk::Widget * widg = param->param_newWidget(tooltips);
481         Glib::ustring * tip = param->param_getTooltip();
482         if (widg) {
483            vbox->pack_start(*widg, true, true, 2);
484             if (tip != NULL) {
485                 tooltips->set_tip(*widg, *tip);
486             }
487         }
489         it++;
490     }
492     return dynamic_cast<Gtk::Widget *>(vbox);
496 Inkscape::XML::Node *
497 Effect::getRepr()
499     return SP_OBJECT_REPR(lpeobj);
502 SPDocument *
503 Effect::getSPDoc()
505     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
506     return SP_OBJECT_DOCUMENT(lpeobj);
509 Parameter *
510 Effect::getParameter(const char * key)
512     Glib::ustring stringkey(key);
514     std::vector<Parameter *>::iterator it = param_vector.begin();
515     while (it != param_vector.end()) {
516         Parameter * param = *it;
517         if ( param->param_key == key) {
518             return param;
519         }
521         it++;
522     }
524     return NULL;
527 Parameter *
528 Effect::getNextOncanvasEditableParam()
530     if (param_vector.size() == 0) // no parameters
531         return NULL;
533     oncanvasedit_it++;
534     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
535         oncanvasedit_it = 0;
536     }
537     int old_it = oncanvasedit_it;
539     do {
540         Parameter * param = param_vector[oncanvasedit_it];
541         if(param && param->oncanvas_editable) {
542             return param;
543         } else {
544             oncanvasedit_it++;
545             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
546                 oncanvasedit_it = 0;
547             }
548         }
549     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
551     return NULL;
554 void
555 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
557     if (!desktop) return;
559     Parameter * param = getNextOncanvasEditableParam();
560     if (param) {
561         param->param_editOncanvas(item, desktop);
562         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
563         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
564         g_free(message);
565     } else {
566         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
567                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
568     }
571 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
572 * The nice thing about this is that this function can use knowledge of the original path and set things accordingly for example to the size or origin of the original path!
573 */
574 void
575 Effect::resetDefaults(SPItem * /*item*/)
577     // do nothing for simple effects
580 void
581 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
583     np->helperpath_rgba = 0xff0000ff;
584     np->helperpath_width = 1.0;
587 void
588 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
590     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
591     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
592         Parameter * param = *it;
593         param->param_transform_multiply(postmul, set);
594     }
597 bool
598 Effect::providesKnotholder()
600     // does the effect actively provide any knotholder entities of its own?
601     if (kh_entity_vector.size() > 0)
602         return true;
604     // otherwise: are there any PointParams?
605     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
606         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
607             return true;
608         }
609     }
611     return false;
614 } /* namespace LivePathEffect */
616 } /* namespace Inkscape */
618 /*
619   Local Variables:
620   mode:c++
621   c-file-style:"stroustrup"
622   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
623   indent-tabs-mode:nil
624   fill-column:99
625   End:
626 */
627 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :