Code

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