Code

639ad93a48ee34ecf60eeb1e1d8028e14f2b7365
[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-lattice.h"
47 #include "live_effects/lpe-envelope.h"
48 #include "live_effects/lpe-constructgrid.h"
49 #include "live_effects/lpe-perp_bisector.h"
50 #include "live_effects/lpe-tangent_to_curve.h"
51 // end of includes
53 #include "nodepath.h"
55 namespace Inkscape {
57 namespace LivePathEffect {
59 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
60     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
61     {BEND_PATH,             N_("Bend"),                  "bend_path"},
62     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
63     {SKETCH,                N_("Sketch"),                "sketch"},
64     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
65     {KNOT,                  N_("Knot"),                  "knot"},
66 #ifdef LPE_ENABLE_TEST_EFFECTS
67     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
68 #endif
69     {GEARS,                 N_("Gears"),                 "gears"},
70     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
71     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
72     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
73     {SPIRO,      N_("Spiro spline"),      "spiro"},
74     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
75     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
76     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
77     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
78     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}
79 };
80 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
82 Effect*
83 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
84 {
85     Effect* neweffect = NULL;
86     switch (lpenr) {
87         case PATTERN_ALONG_PATH:
88             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
89             break;
90         case BEND_PATH:
91             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
92             break;
93         case SKETCH:
94             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
95             break;
96         case VONKOCH:
97             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
98             break;
99         case KNOT:
100             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
101             break;
102 #ifdef LPE_ENABLE_TEST_EFFECTS
103         case DOEFFECTSTACK_TEST:
104             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
105             break;
106 #endif
107         case GEARS:
108             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
109             break;
110         case CURVE_STITCH:
111             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
112             break;
113         case LATTICE:
114             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
115             break;
116         case ENVELOPE:
117             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
118             break;
119         case CIRCLE_WITH_RADIUS:
120             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
121             break;
122         case PERSPECTIVE_PATH:
123             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
124             break;
125         case SPIRO:
126             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
127             break;
128         case CONSTRUCT_GRID:
129             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
130             break;
131         case PERP_BISECTOR:
132             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
133             break;
134         case TANGENT_TO_CURVE:
135             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
136             break;
137         default:
138             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
139             neweffect = NULL;
140             break;
141     }
143     if (neweffect) {
144         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
145     }
147     return neweffect;
150 Effect::Effect(LivePathEffectObject *lpeobject)
151     : oncanvasedit_it(0),
152       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
153       lpeobj(lpeobject),
154       concatenate_before_pwd2(false)
156     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
159 Effect::~Effect()
163 Glib::ustring
164 Effect::getName()
166     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
167         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
168     else
169         return Glib::ustring( _("No effect") );
172 EffectType
173 Effect::effectType() {
174     return lpeobj->effecttype;
177 void
178 Effect::doOnApply (SPLPEItem */*lpeitem*/)
180     // This is performed once when the effect is freshly applied to a path
183 void
184 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
186     //Do nothing for simple effects
190 /*
191  *  Here be the doEffect function chain:
192  */
193 void
194 Effect::doEffect (SPCurve * curve)
196     NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
198     curve->set_bpath(new_bpath);
201 NArtBpath *
202 Effect::doEffect_nartbpath (NArtBpath const * path_in)
204     try {
205         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
207         std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
209         NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
211         return new_bpath;
212     }
213     catch (std::exception & e) {
214         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
215         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
216             _("An exception occurred during execution of the Path Effect.") );
218         NArtBpath *path_out;
220         unsigned ret = 0;
221         while ( path_in[ret].code != NR_END ) {
222             ++ret;
223         }
224         unsigned len = ++ret;
226         path_out = g_new(NArtBpath, len);
227         memcpy(path_out, path_in, len * sizeof(NArtBpath));
228         return path_out;
229     }
232 std::vector<Geom::Path>
233 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
235     std::vector<Geom::Path> path_out;
237     if ( !concatenate_before_pwd2 ) {
238         // default behavior
239         for (unsigned int i=0; i < path_in.size(); i++) {
240             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
241             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
242             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
243             // add the output path vector to the already accumulated vector:
244             for (unsigned int j=0; j < path.size(); j++) {
245                 path_out.push_back(path[j]);
246             }
247         }
248     } else {
249       // concatenate the path into possibly discontinuous pwd2
250         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
251         for (unsigned int i=0; i < path_in.size(); i++) {
252             pwd2_in.concat( path_in[i].toPwSb() );
253         }
254         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
255         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
256     }
258     return path_out;
261 Geom::Piecewise<Geom::D2<Geom::SBasis> >
262 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
264     g_warning("Effect has no doEffect implementation");
265     return pwd2_in;
268 void
269 Effect::readallParameters(Inkscape::XML::Node * repr)
271     std::vector<Parameter *>::iterator it = param_vector.begin();
272     while (it != param_vector.end()) {
273         Parameter * param = *it;
274         const gchar * key = param->param_key.c_str();
275         const gchar * value = repr->attribute(key);
276         if (value) {
277             bool accepted = param->param_readSVGValue(value);
278             if (!accepted) {
279                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
280             }
281         } else {
282             // set default value
283             param->param_set_default();
284         }
286         it++;
287     }
290 /* This function does not and SHOULD NOT write to XML */
291 void
292 Effect::setParameter(const gchar * key, const gchar * new_value)
294     Parameter * param = getParameter(key);
295     if (param) {
296         if (new_value) {
297             bool accepted = param->param_readSVGValue(new_value);
298             if (!accepted) {
299                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
300             }
301         } else {
302             // set default value
303             param->param_set_default();
304         }
305     }
308 void
309 Effect::registerParameter(Parameter * param)
311     param_vector.push_back(param);
314 void
315 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
317     knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
320 // TODO: allow for adding click_functions and description strings, too
321 void
322 Effect::addHandles(KnotHolder *knotholder) {
323     std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
324     for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
325         //knotholder->add(i->first, i->second, NULL, (""));
326     }
329 /**
330  * This *creates* a new widget, management of deletion should be done by the caller
331  */
332 Gtk::Widget *
333 Effect::newWidget(Gtk::Tooltips * tooltips)
335     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
336     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
338     vbox->set_border_width(5);
340     std::vector<Parameter *>::iterator it = param_vector.begin();
341     while (it != param_vector.end()) {
342         Parameter * param = *it;
343         Gtk::Widget * widg = param->param_newWidget(tooltips);
344         Glib::ustring * tip = param->param_getTooltip();
345         if (widg) {
346            vbox->pack_start(*widg, true, true, 2);
347             if (tip != NULL) {
348                 tooltips->set_tip(*widg, *tip);
349             }
350         }
352         it++;
353     }
355     return dynamic_cast<Gtk::Widget *>(vbox);
359 Inkscape::XML::Node *
360 Effect::getRepr()
362     return SP_OBJECT_REPR(lpeobj);
365 SPDocument *
366 Effect::getSPDoc()
368     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
369     return SP_OBJECT_DOCUMENT(lpeobj);
372 Parameter *
373 Effect::getParameter(const char * key)
375     Glib::ustring stringkey(key);
377     std::vector<Parameter *>::iterator it = param_vector.begin();
378     while (it != param_vector.end()) {
379         Parameter * param = *it;
380         if ( param->param_key == key) {
381             return param;
382         }
384         it++;
385     }
387     return NULL;
390 Parameter *
391 Effect::getNextOncanvasEditableParam()
393     if (param_vector.size() == 0) // no parameters
394         return NULL;
396     oncanvasedit_it++;
397     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
398         oncanvasedit_it = 0;
399     }
400     int old_it = oncanvasedit_it;
402     do {
403         Parameter * param = param_vector[oncanvasedit_it];
404         if(param && param->oncanvas_editable) {
405             return param;
406         } else {
407             oncanvasedit_it++;
408             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
409                 oncanvasedit_it = 0;
410             }
411         }
412     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
414     return NULL;
417 void
418 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
420     if (!desktop) return;
422     Parameter * param = getNextOncanvasEditableParam();
423     if (param) {
424         param->param_editOncanvas(item, desktop);
425         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
426         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
427         g_free(message);
428     } else {
429         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
430                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
431     }
434 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
435 * 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!
436 */
437 void
438 Effect::resetDefaults(SPItem * /*item*/)
440     // do nothing for simple effects
443 void
444 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
446     np->helperpath_rgba = 0xff0000ff;
447     np->helperpath_width = 1.0;
450 void
451 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
453     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
454     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
455         Parameter * param = *it;
456         param->param_transform_multiply(postmul, set);
457     }
460 } /* namespace LivePathEffect */
462 } /* namespace Inkscape */
464 /*
465   Local Variables:
466   mode:c++
467   c-file-style:"stroustrup"
468   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
469   indent-tabs-mode:nil
470   fill-column:99
471   End:
472 */
473 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :