Code

New LPE: Tangent to curve (draws a tangent of fixed length which can be dragged along...
[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 <glibmm/i18n.h>
21 #include "live_effects/lpeobject.h"
22 #include "live_effects/parameter/parameter.h"
23 #include <glibmm/ustring.h>
24 #include "libnr/n-art-bpath-2geom.h"
25 #include "display/curve.h"
26 #include <gtkmm.h>
28 #include <exception>
30 #include <2geom/sbasis-to-bezier.h>
31 #include <2geom/matrix.h>
34 // include effects:
35 #include "live_effects/lpe-patternalongpath.h"
36 #include "live_effects/lpe-bendpath.h"
37 #include "live_effects/lpe-sketch.h"
38 #include "live_effects/lpe-vonkoch.h"
39 #include "live_effects/lpe-knot.h"
40 #include "live_effects/lpe-test-doEffect-stack.h"
41 #include "live_effects/lpe-gears.h"
42 #include "live_effects/lpe-curvestitch.h"
43 #include "live_effects/lpe-circle_with_radius.h"
44 #include "live_effects/lpe-perspective_path.h"
45 #include "live_effects/lpe-spiro.h"
46 #include "live_effects/lpe-constructgrid.h"
47 #include "live_effects/lpe-envelope.h"
48 #include "live_effects/lpe-perp_bisector.h"
49 #include "live_effects/lpe-tangent_to_curve.h"
50 // end of includes
52 #include "nodepath.h"
54 namespace Inkscape {
56 namespace LivePathEffect {
58 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
59     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
60     {BEND_PATH,             N_("Bend"),                  "bend_path"},
61     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
62     {SKETCH,                N_("Sketch"),                "sketch"},
63     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
64     {KNOT,                  N_("Knot"),                  "knot"},
65 #ifdef LPE_ENABLE_TEST_EFFECTS
66     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
67 #endif
68     {GEARS,                 N_("Gears"),                 "gears"},
69     {CURVE_STITCH,          N_("Stitch Sub-Paths"),      "curvestitching"},
70     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
71     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
72     {SPIRO,      N_("Spiro spline"),      "spiro"},
73     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
74     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
75     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
76     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
77 };
78 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
80 Effect*
81 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
82 {
83     Effect* neweffect = NULL;
84     switch (lpenr) {
85         case PATTERN_ALONG_PATH:
86             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
87             break;
88         case BEND_PATH:
89             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
90             break;
91         case SKETCH:
92             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
93             break;
94         case VONKOCH:
95             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
96             break;
97         case KNOT:
98             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
99             break;
100 #ifdef LPE_ENABLE_TEST_EFFECTS
101         case DOEFFECTSTACK_TEST:
102             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
103             break;
104 #endif
105         case GEARS:
106             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
107             break;
108         case CURVE_STITCH:
109             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
110             break;
111         case CIRCLE_WITH_RADIUS:
112             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
113             break;
114         case PERSPECTIVE_PATH:
115             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
116             break;
117         case SPIRO:
118             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
119             break;
120         case CONSTRUCT_GRID:
121             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
122             break;
123         case ENVELOPE:
124             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
125             break;
126         case PERP_BISECTOR:
127             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
128             break;
129         case TANGENT_TO_CURVE:
130             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
131             break;
132         default:
133             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
134             neweffect = NULL;
135             break;
136     }
138     if (neweffect) {
139         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
140     }
142     return neweffect;
145 Effect::Effect(LivePathEffectObject *lpeobject)
146     : oncanvasedit_it(0),
147       lpeobj(lpeobject),
148       concatenate_before_pwd2(false)
152 Effect::~Effect()
156 Glib::ustring
157 Effect::getName()
159     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
160         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
161     else
162         return Glib::ustring( _("No effect") );
165 EffectType
166 Effect::effectType() {
167     return lpeobj->effecttype;
170 void
171 Effect::doOnApply (SPLPEItem */*lpeitem*/)
173     // This is performed once when the effect is freshly applied to a path
176 void
177 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
179     //Do nothing for simple effects
183 /*
184  *  Here be the doEffect function chain:
185  */
186 void
187 Effect::doEffect (SPCurve * curve)
189     NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
191     curve->set_bpath(new_bpath);
194 NArtBpath *
195 Effect::doEffect_nartbpath (NArtBpath const * path_in)
197     try {
198         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
200         std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
202         NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
204         return new_bpath;
205     }
206     catch (std::exception & e) {
207         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
208         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
209             _("An exception occurred during execution of the Path Effect.") );
211         NArtBpath *path_out;
213         unsigned ret = 0;
214         while ( path_in[ret].code != NR_END ) {
215             ++ret;
216         }
217         unsigned len = ++ret;
219         path_out = g_new(NArtBpath, len);
220         memcpy(path_out, path_in, len * sizeof(NArtBpath));
221         return path_out;
222     }
225 std::vector<Geom::Path>
226 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
228     std::vector<Geom::Path> path_out;
230     if ( !concatenate_before_pwd2 ) {
231         // default behavior
232         for (unsigned int i=0; i < path_in.size(); i++) {
233             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
234             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
235             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
236             // add the output path vector to the already accumulated vector:
237             for (unsigned int j=0; j < path.size(); j++) {
238                 path_out.push_back(path[j]);
239             }
240         }
241     } else {
242       // concatenate the path into possibly discontinuous pwd2
243         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
244         for (unsigned int i=0; i < path_in.size(); i++) {
245             pwd2_in.concat( path_in[i].toPwSb() );
246         }
247         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
248         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
249     }
251     return path_out;
254 Geom::Piecewise<Geom::D2<Geom::SBasis> >
255 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
257     g_warning("Effect has no doEffect implementation");
258     return pwd2_in;
261 void
262 Effect::readallParameters(Inkscape::XML::Node * repr)
264     std::vector<Parameter *>::iterator it = param_vector.begin();
265     while (it != param_vector.end()) {
266         Parameter * param = *it;
267         const gchar * key = param->param_key.c_str();
268         const gchar * value = repr->attribute(key);
269         if (value) {
270             bool accepted = param->param_readSVGValue(value);
271             if (!accepted) {
272                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
273             }
274         } else {
275             // set default value
276             param->param_set_default();
277         }
279         it++;
280     }
283 /* This function does not and SHOULD NOT write to XML */
284 void
285 Effect::setParameter(const gchar * key, const gchar * new_value)
287     Parameter * param = getParameter(key);
288     if (param) {
289         if (new_value) {
290             bool accepted = param->param_readSVGValue(new_value);
291             if (!accepted) {
292                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
293             }
294         } else {
295             // set default value
296             param->param_set_default();
297         }
298     }
301 void
302 Effect::registerParameter(Parameter * param)
304     param_vector.push_back(param);
307 void
308 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
310     knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
313 // TODO: allow for adding click_functions and description strings, too
314 void
315 Effect::addHandles(SPKnotHolder *knotholder) {
316     std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
317     for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
318         sp_knot_holder_add(knotholder, i->first, i->second, NULL, (""));
319     }
322 /**
323  * This *creates* a new widget, management of deletion should be done by the caller
324  */
325 Gtk::Widget *
326 Effect::newWidget(Gtk::Tooltips * tooltips)
328     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
329     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
331     vbox->set_border_width(5);
333     std::vector<Parameter *>::iterator it = param_vector.begin();
334     while (it != param_vector.end()) {
335         Parameter * param = *it;
336         Gtk::Widget * widg = param->param_newWidget(tooltips);
337         Glib::ustring * tip = param->param_getTooltip();
338         if (widg) {
339            vbox->pack_start(*widg, true, true, 2);
340             if (tip != NULL) {
341                 tooltips->set_tip(*widg, *tip);
342             }
343         }
345         it++;
346     }
348     return dynamic_cast<Gtk::Widget *>(vbox);
352 Inkscape::XML::Node *
353 Effect::getRepr()
355     return SP_OBJECT_REPR(lpeobj);
358 SPDocument *
359 Effect::getSPDoc()
361     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
362     return SP_OBJECT_DOCUMENT(lpeobj);
365 Parameter *
366 Effect::getParameter(const char * key)
368     Glib::ustring stringkey(key);
370     std::vector<Parameter *>::iterator it = param_vector.begin();
371     while (it != param_vector.end()) {
372         Parameter * param = *it;
373         if ( param->param_key == key) {
374             return param;
375         }
377         it++;
378     }
380     return NULL;
383 Parameter *
384 Effect::getNextOncanvasEditableParam()
386     if (param_vector.size() == 0) // no parameters
387         return NULL;
389     oncanvasedit_it++;
390     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
391         oncanvasedit_it = 0;
392     }
393     int old_it = oncanvasedit_it;
395     do {
396         Parameter * param = param_vector[oncanvasedit_it];
397         if(param && param->oncanvas_editable) {
398             return param;
399         } else {
400             oncanvasedit_it++;
401             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
402                 oncanvasedit_it = 0;
403             }
404         }
405     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
407     return NULL;
410 void
411 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
413     if (!desktop) return;
415     Parameter * param = getNextOncanvasEditableParam();
416     if (param) {
417         param->param_editOncanvas(item, desktop);
418         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
419         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
420         g_free(message);
421     } else {
422         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
423                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
424     }
427 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
428 * 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!
429 */
430 void
431 Effect::resetDefaults(SPItem * /*item*/)
433     // do nothing for simple effects
436 void
437 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
439     np->helperpath_rgba = 0xff0000ff;
440     np->helperpath_width = 1.0;
443 void
444 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
446     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
447     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
448         Parameter * param = *it;
449         param->param_transform_multiply(postmul, set);
450     }
453 } /* namespace LivePathEffect */
455 } /* namespace Inkscape */
457 /*
458   Local Variables:
459   mode:c++
460   c-file-style:"stroustrup"
461   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
462   indent-tabs-mode:nil
463   fill-column:99
464   End:
465 */
466 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :