Code

fixed another typo
[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"
27 #include "live_effects/lpeobject.h"
28 #include "live_effects/parameter/parameter.h"
29 #include <glibmm/ustring.h>
30 #include "libnr/n-art-bpath-2geom.h"
31 #include "display/curve.h"
32 #include <gtkmm.h>
34 #include <exception>
36 #include <2geom/sbasis-to-bezier.h>
37 #include <2geom/matrix.h>
40 // include effects:
41 #include "live_effects/lpe-patternalongpath.h"
42 #include "live_effects/lpe-bendpath.h"
43 #include "live_effects/lpe-sketch.h"
44 #include "live_effects/lpe-vonkoch.h"
45 #include "live_effects/lpe-knot.h"
46 #include "live_effects/lpe-test-doEffect-stack.h"
47 #include "live_effects/lpe-gears.h"
48 #include "live_effects/lpe-curvestitch.h"
49 #include "live_effects/lpe-circle_with_radius.h"
50 #include "live_effects/lpe-perspective_path.h"
51 #include "live_effects/lpe-spiro.h"
52 #include "live_effects/lpe-lattice.h"
53 #include "live_effects/lpe-envelope.h"
54 #include "live_effects/lpe-constructgrid.h"
55 #include "live_effects/lpe-perp_bisector.h"
56 #include "live_effects/lpe-tangent_to_curve.h"
57 #include "live_effects/lpe-mirror_reflect.h"
58 // end of includes
60 #include "nodepath.h"
62 namespace Inkscape {
64 namespace LivePathEffect {
66 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
67     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
68     {BEND_PATH,             N_("Bend"),                  "bend_path"},
69     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
70     {SKETCH,                N_("Sketch"),                "sketch"},
71     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
72     {KNOT,                  N_("Knot"),                  "knot"},
73 #ifdef LPE_ENABLE_TEST_EFFECTS
74     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
75 #endif
76     {GEARS,                 N_("Gears"),                 "gears"},
77     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
78     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
79     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
80     {SPIRO,      N_("Spiro spline"),      "spiro"},
81     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
82     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
83     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
84     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
85     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
86     {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
87 };
88 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
90 Effect*
91 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
92 {
93     Effect* neweffect = NULL;
94     switch (lpenr) {
95         case PATTERN_ALONG_PATH:
96             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
97             break;
98         case BEND_PATH:
99             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
100             break;
101         case SKETCH:
102             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
103             break;
104         case VONKOCH:
105             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
106             break;
107         case KNOT:
108             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
109             break;
110 #ifdef LPE_ENABLE_TEST_EFFECTS
111         case DOEFFECTSTACK_TEST:
112             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
113             break;
114 #endif
115         case GEARS:
116             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
117             break;
118         case CURVE_STITCH:
119             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
120             break;
121         case LATTICE:
122             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
123             break;
124         case ENVELOPE:
125             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
126             break;
127         case CIRCLE_WITH_RADIUS:
128             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
129             break;
130         case PERSPECTIVE_PATH:
131             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
132             break;
133         case SPIRO:
134             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
135             break;
136         case CONSTRUCT_GRID:
137             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
138             break;
139         case PERP_BISECTOR:
140             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
141             break;
142         case TANGENT_TO_CURVE:
143             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
144             break;
145         case MIRROR_REFLECT:
146             neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
147             break;
148         default:
149             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
150             neweffect = NULL;
151             break;
152     }
154     if (neweffect) {
155         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
156     }
158     return neweffect;
161 void
162 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
164     // Path effect definition
165     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
166     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
167     repr->setAttribute("effect", name);
169     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
170     const gchar * repr_id = repr->attribute("id");
171     Inkscape::GC::release(repr);
173     gchar *href = g_strdup_printf("#%s", repr_id);
174     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
175     g_free(href);
177     sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
178                      _("Create and apply path effect"));
181 void
182 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
184     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
187 Effect::Effect(LivePathEffectObject *lpeobject)
188     : oncanvasedit_it(0),
189       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
190       done_pathparam_set(false),
191       lpeobj(lpeobject),
192       concatenate_before_pwd2(false)
194     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
197 Effect::~Effect()
201 Glib::ustring
202 Effect::getName()
204     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
205         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
206     else
207         return Glib::ustring( _("No effect") );
210 EffectType
211 Effect::effectType() {
212     return lpeobj->effecttype;
215 /**
216  * Is performed a single time when the effect is freshly applied to a path
217  */
218 void
219 Effect::doOnApply (SPLPEItem */*lpeitem*/)
223 /**
224  * Is performed each time before the effect is updated.
225  */
226 void
227 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
229     //Do nothing for simple effects
232 /**
233  * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
234  * clicks. This method activates the pen context, which waits for the specified number of clicks.
235  * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
236  */
237 void
238 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
240     // switch to pen context
241     SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
242     if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
243         tools_switch(desktop, TOOLS_FREEHAND_PEN);
244     }
246     SPEventContext *ec = desktop->event_context;
247     SPPenContext *pc = SP_PEN_CONTEXT(ec);
248     pc->expecting_clicks_for_LPE = this->acceptsNumParams();
249     pc->waiting_LPE = this;
250     pc->waiting_item = lpeitem;
251     pc->polylines_only = true;
253     ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
254         g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
255                         getName().c_str(), acceptsNumParams()));
258 void
259 Effect::writeParamsToSVG() {
260     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
261     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
262         (*p)->write_to_SVG();
263     }
266 /**
267  * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
268  * applied, this is the method that processes the resulting path. Override it to customize it for
269  * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
270  */
271 void
272 Effect::acceptParamPath (SPPath *param_path) {
273     done_pathparam_set = true;
276 /*
277  *  Here be the doEffect function chain:
278  */
279 void
280 Effect::doEffect (SPCurve * curve)
282     NArtBpath const *bpath_in = curve->get_bpath();
284     std::vector<Geom::Path> result_pathv;
286     try {
287         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
289         result_pathv = doEffect_path(orig_pathv);
290     }
291     catch (std::exception & e) {
292         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
293         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
294             _("An exception occurred during execution of the Path Effect.") );
295     }
297     curve->set_pathv(result_pathv);
300 std::vector<Geom::Path>
301 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
303     std::vector<Geom::Path> path_out;
305     if ( !concatenate_before_pwd2 ) {
306         // default behavior
307         for (unsigned int i=0; i < path_in.size(); i++) {
308             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
309             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
310             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
311             // add the output path vector to the already accumulated vector:
312             for (unsigned int j=0; j < path.size(); j++) {
313                 path_out.push_back(path[j]);
314             }
315         }
316     } else {
317       // concatenate the path into possibly discontinuous pwd2
318         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
319         for (unsigned int i=0; i < path_in.size(); i++) {
320             pwd2_in.concat( path_in[i].toPwSb() );
321         }
322         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
323         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
324     }
326     return path_out;
329 Geom::Piecewise<Geom::D2<Geom::SBasis> >
330 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
332     g_warning("Effect has no doEffect implementation");
333     return pwd2_in;
336 void
337 Effect::readallParameters(Inkscape::XML::Node * repr)
339     std::vector<Parameter *>::iterator it = param_vector.begin();
340     while (it != param_vector.end()) {
341         Parameter * param = *it;
342         const gchar * key = param->param_key.c_str();
343         const gchar * value = repr->attribute(key);
344         if (value) {
345             bool accepted = param->param_readSVGValue(value);
346             if (!accepted) {
347                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
348             }
349         } else {
350             // set default value
351             param->param_set_default();
352         }
354         it++;
355     }
358 /* This function does not and SHOULD NOT write to XML */
359 void
360 Effect::setParameter(const gchar * key, const gchar * new_value)
362     Parameter * param = getParameter(key);
363     if (param) {
364         if (new_value) {
365             bool accepted = param->param_readSVGValue(new_value);
366             if (!accepted) {
367                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
368             }
369         } else {
370             // set default value
371             param->param_set_default();
372         }
373     }
376 void
377 Effect::registerParameter(Parameter * param)
379     param_vector.push_back(param);
382 // TODO: should we provide a way to alter the handle's appearance?
383 void
384 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
386     kh_entity_vector.push_back(std::make_pair(entity, descr));
389 /**
390  * Add all registered LPE knotholder handles to the knotholder
391  */
392 void
393 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
394     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
395     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
396         KnotHolderEntity *entity = i->first;
397         const char *descr = i->second;
399         entity->create(desktop, item, knotholder, descr);
400         knotholder->add(entity);
401     }
404 void
405 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
406     using namespace std;
407     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
408         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
409             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
410             e->create(desktop, item, knotholder);
411             knotholder->add(e);
412         }
413     }
416 /**
417  * This *creates* a new widget, management of deletion should be done by the caller
418  */
419 Gtk::Widget *
420 Effect::newWidget(Gtk::Tooltips * tooltips)
422     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
423     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
425     vbox->set_border_width(5);
427     std::vector<Parameter *>::iterator it = param_vector.begin();
428     while (it != param_vector.end()) {
429         Parameter * param = *it;
430         Gtk::Widget * widg = param->param_newWidget(tooltips);
431         Glib::ustring * tip = param->param_getTooltip();
432         if (widg) {
433            vbox->pack_start(*widg, true, true, 2);
434             if (tip != NULL) {
435                 tooltips->set_tip(*widg, *tip);
436             }
437         }
439         it++;
440     }
442     return dynamic_cast<Gtk::Widget *>(vbox);
446 Inkscape::XML::Node *
447 Effect::getRepr()
449     return SP_OBJECT_REPR(lpeobj);
452 SPDocument *
453 Effect::getSPDoc()
455     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
456     return SP_OBJECT_DOCUMENT(lpeobj);
459 Parameter *
460 Effect::getParameter(const char * key)
462     Glib::ustring stringkey(key);
464     std::vector<Parameter *>::iterator it = param_vector.begin();
465     while (it != param_vector.end()) {
466         Parameter * param = *it;
467         if ( param->param_key == key) {
468             return param;
469         }
471         it++;
472     }
474     return NULL;
477 Parameter *
478 Effect::getNextOncanvasEditableParam()
480     if (param_vector.size() == 0) // no parameters
481         return NULL;
483     oncanvasedit_it++;
484     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
485         oncanvasedit_it = 0;
486     }
487     int old_it = oncanvasedit_it;
489     do {
490         Parameter * param = param_vector[oncanvasedit_it];
491         if(param && param->oncanvas_editable) {
492             return param;
493         } else {
494             oncanvasedit_it++;
495             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
496                 oncanvasedit_it = 0;
497             }
498         }
499     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
501     return NULL;
504 void
505 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
507     if (!desktop) return;
509     Parameter * param = getNextOncanvasEditableParam();
510     if (param) {
511         param->param_editOncanvas(item, desktop);
512         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
513         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
514         g_free(message);
515     } else {
516         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
517                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
518     }
521 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
522 * 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!
523 */
524 void
525 Effect::resetDefaults(SPItem * /*item*/)
527     // do nothing for simple effects
530 void
531 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
533     np->helperpath_rgba = 0xff0000ff;
534     np->helperpath_width = 1.0;
537 void
538 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
540     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
541     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
542         Parameter * param = *it;
543         param->param_transform_multiply(postmul, set);
544     }
547 } /* namespace LivePathEffect */
549 } /* namespace Inkscape */
551 /*
552   Local Variables:
553   mode:c++
554   c-file-style:"stroustrup"
555   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
556   indent-tabs-mode:nil
557   fill-column:99
558   End:
559 */
560 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :