Code

Add checkbox for LPEs to temporarily disable them on canvas (but keep them applied...
[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       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
148       lpeobj(lpeobject),
149       concatenate_before_pwd2(false)
151     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
154 Effect::~Effect()
158 Glib::ustring
159 Effect::getName()
161     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
162         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
163     else
164         return Glib::ustring( _("No effect") );
167 EffectType
168 Effect::effectType() {
169     return lpeobj->effecttype;
172 void
173 Effect::doOnApply (SPLPEItem */*lpeitem*/)
175     // This is performed once when the effect is freshly applied to a path
178 void
179 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
181     //Do nothing for simple effects
185 /*
186  *  Here be the doEffect function chain:
187  */
188 void
189 Effect::doEffect (SPCurve * curve)
191     NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
193     curve->set_bpath(new_bpath);
196 NArtBpath *
197 Effect::doEffect_nartbpath (NArtBpath const * path_in)
199     try {
200         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
202         std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
204         NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
206         return new_bpath;
207     }
208     catch (std::exception & e) {
209         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
210         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
211             _("An exception occurred during execution of the Path Effect.") );
213         NArtBpath *path_out;
215         unsigned ret = 0;
216         while ( path_in[ret].code != NR_END ) {
217             ++ret;
218         }
219         unsigned len = ++ret;
221         path_out = g_new(NArtBpath, len);
222         memcpy(path_out, path_in, len * sizeof(NArtBpath));
223         return path_out;
224     }
227 std::vector<Geom::Path>
228 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
230     std::vector<Geom::Path> path_out;
232     if ( !concatenate_before_pwd2 ) {
233         // default behavior
234         for (unsigned int i=0; i < path_in.size(); i++) {
235             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
236             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
237             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
238             // add the output path vector to the already accumulated vector:
239             for (unsigned int j=0; j < path.size(); j++) {
240                 path_out.push_back(path[j]);
241             }
242         }
243     } else {
244       // concatenate the path into possibly discontinuous pwd2
245         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
246         for (unsigned int i=0; i < path_in.size(); i++) {
247             pwd2_in.concat( path_in[i].toPwSb() );
248         }
249         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
250         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
251     }
253     return path_out;
256 Geom::Piecewise<Geom::D2<Geom::SBasis> >
257 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
259     g_warning("Effect has no doEffect implementation");
260     return pwd2_in;
263 void
264 Effect::readallParameters(Inkscape::XML::Node * repr)
266     std::vector<Parameter *>::iterator it = param_vector.begin();
267     while (it != param_vector.end()) {
268         Parameter * param = *it;
269         const gchar * key = param->param_key.c_str();
270         const gchar * value = repr->attribute(key);
271         if (value) {
272             bool accepted = param->param_readSVGValue(value);
273             if (!accepted) {
274                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
275             }
276         } else {
277             // set default value
278             param->param_set_default();
279         }
281         it++;
282     }
285 /* This function does not and SHOULD NOT write to XML */
286 void
287 Effect::setParameter(const gchar * key, const gchar * new_value)
289     Parameter * param = getParameter(key);
290     if (param) {
291         if (new_value) {
292             bool accepted = param->param_readSVGValue(new_value);
293             if (!accepted) {
294                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
295             }
296         } else {
297             // set default value
298             param->param_set_default();
299         }
300     }
303 void
304 Effect::registerParameter(Parameter * param)
306     param_vector.push_back(param);
309 void
310 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
312     knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
315 // TODO: allow for adding click_functions and description strings, too
316 void
317 Effect::addHandles(SPKnotHolder *knotholder) {
318     std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
319     for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
320         sp_knot_holder_add(knotholder, i->first, i->second, NULL, (""));
321     }
324 /**
325  * This *creates* a new widget, management of deletion should be done by the caller
326  */
327 Gtk::Widget *
328 Effect::newWidget(Gtk::Tooltips * tooltips)
330     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
331     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
333     vbox->set_border_width(5);
335     std::vector<Parameter *>::iterator it = param_vector.begin();
336     while (it != param_vector.end()) {
337         Parameter * param = *it;
338         Gtk::Widget * widg = param->param_newWidget(tooltips);
339         Glib::ustring * tip = param->param_getTooltip();
340         if (widg) {
341            vbox->pack_start(*widg, true, true, 2);
342             if (tip != NULL) {
343                 tooltips->set_tip(*widg, *tip);
344             }
345         }
347         it++;
348     }
350     return dynamic_cast<Gtk::Widget *>(vbox);
354 Inkscape::XML::Node *
355 Effect::getRepr()
357     return SP_OBJECT_REPR(lpeobj);
360 SPDocument *
361 Effect::getSPDoc()
363     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
364     return SP_OBJECT_DOCUMENT(lpeobj);
367 Parameter *
368 Effect::getParameter(const char * key)
370     Glib::ustring stringkey(key);
372     std::vector<Parameter *>::iterator it = param_vector.begin();
373     while (it != param_vector.end()) {
374         Parameter * param = *it;
375         if ( param->param_key == key) {
376             return param;
377         }
379         it++;
380     }
382     return NULL;
385 Parameter *
386 Effect::getNextOncanvasEditableParam()
388     if (param_vector.size() == 0) // no parameters
389         return NULL;
391     oncanvasedit_it++;
392     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
393         oncanvasedit_it = 0;
394     }
395     int old_it = oncanvasedit_it;
397     do {
398         Parameter * param = param_vector[oncanvasedit_it];
399         if(param && param->oncanvas_editable) {
400             return param;
401         } else {
402             oncanvasedit_it++;
403             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
404                 oncanvasedit_it = 0;
405             }
406         }
407     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
409     return NULL;
412 void
413 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
415     if (!desktop) return;
417     Parameter * param = getNextOncanvasEditableParam();
418     if (param) {
419         param->param_editOncanvas(item, desktop);
420         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
421         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
422         g_free(message);
423     } else {
424         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
425                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
426     }
429 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
430 * 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!
431 */
432 void
433 Effect::resetDefaults(SPItem * /*item*/)
435     // do nothing for simple effects
438 void
439 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
441     np->helperpath_rgba = 0xff0000ff;
442     np->helperpath_width = 1.0;
445 void
446 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
448     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
449     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
450         Parameter * param = *it;
451         param->param_transform_multiply(postmul, set);
452     }
455 } /* namespace LivePathEffect */
457 } /* namespace Inkscape */
459 /*
460   Local Variables:
461   mode:c++
462   c-file-style:"stroustrup"
463   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
464   indent-tabs-mode:nil
465   fill-column:99
466   End:
467 */
468 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :