Code

Factor out 'create and apply' code for LPEs so that it can be called from everywhere
[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>
23 #include "live_effects/lpeobject.h"
24 #include "live_effects/parameter/parameter.h"
25 #include <glibmm/ustring.h>
26 #include "libnr/n-art-bpath-2geom.h"
27 #include "display/curve.h"
28 #include <gtkmm.h>
30 #include <exception>
32 #include <2geom/sbasis-to-bezier.h>
33 #include <2geom/matrix.h>
36 // include effects:
37 #include "live_effects/lpe-patternalongpath.h"
38 #include "live_effects/lpe-bendpath.h"
39 #include "live_effects/lpe-sketch.h"
40 #include "live_effects/lpe-vonkoch.h"
41 #include "live_effects/lpe-knot.h"
42 #include "live_effects/lpe-test-doEffect-stack.h"
43 #include "live_effects/lpe-gears.h"
44 #include "live_effects/lpe-curvestitch.h"
45 #include "live_effects/lpe-circle_with_radius.h"
46 #include "live_effects/lpe-perspective_path.h"
47 #include "live_effects/lpe-spiro.h"
48 #include "live_effects/lpe-lattice.h"
49 #include "live_effects/lpe-envelope.h"
50 #include "live_effects/lpe-constructgrid.h"
51 #include "live_effects/lpe-perp_bisector.h"
52 #include "live_effects/lpe-tangent_to_curve.h"
53 // end of includes
55 #include "nodepath.h"
57 namespace Inkscape {
59 namespace LivePathEffect {
61 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
62     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
63     {BEND_PATH,             N_("Bend"),                  "bend_path"},
64     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
65     {SKETCH,                N_("Sketch"),                "sketch"},
66     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
67     {KNOT,                  N_("Knot"),                  "knot"},
68 #ifdef LPE_ENABLE_TEST_EFFECTS
69     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
70 #endif
71     {GEARS,                 N_("Gears"),                 "gears"},
72     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
73     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
74     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
75     {SPIRO,      N_("Spiro spline"),      "spiro"},
76     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
77     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
78     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
79     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
80     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}
81 };
82 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
84 Effect*
85 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
86 {
87     Effect* neweffect = NULL;
88     switch (lpenr) {
89         case PATTERN_ALONG_PATH:
90             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
91             break;
92         case BEND_PATH:
93             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
94             break;
95         case SKETCH:
96             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
97             break;
98         case VONKOCH:
99             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
100             break;
101         case KNOT:
102             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
103             break;
104 #ifdef LPE_ENABLE_TEST_EFFECTS
105         case DOEFFECTSTACK_TEST:
106             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
107             break;
108 #endif
109         case GEARS:
110             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
111             break;
112         case CURVE_STITCH:
113             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
114             break;
115         case LATTICE:
116             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
117             break;
118         case ENVELOPE:
119             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
120             break;
121         case CIRCLE_WITH_RADIUS:
122             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
123             break;
124         case PERSPECTIVE_PATH:
125             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
126             break;
127         case SPIRO:
128             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
129             break;
130         case CONSTRUCT_GRID:
131             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
132             break;
133         case PERP_BISECTOR:
134             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
135             break;
136         case TANGENT_TO_CURVE:
137             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
138             break;
139         default:
140             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
141             neweffect = NULL;
142             break;
143     }
145     if (neweffect) {
146         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
147     }
149     return neweffect;
152 void
153 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
155     // Path effect definition
156     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
157     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
158     repr->setAttribute("effect", name);
160     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
161     const gchar * repr_id = repr->attribute("id");
162     Inkscape::GC::release(repr);
164     gchar *href = g_strdup_printf("#%s", repr_id);
165     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
166     g_free(href);
168     sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT, 
169                      _("Create and apply path effect"));
172 void
173 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
175     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
178 Effect::Effect(LivePathEffectObject *lpeobject)
179     : oncanvasedit_it(0),
180       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
181       lpeobj(lpeobject),
182       concatenate_before_pwd2(false)
184     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
187 Effect::~Effect()
191 Glib::ustring
192 Effect::getName()
194     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
195         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
196     else
197         return Glib::ustring( _("No effect") );
200 EffectType
201 Effect::effectType() {
202     return lpeobj->effecttype;
205 void
206 Effect::doOnApply (SPLPEItem */*lpeitem*/)
208     // This is performed once when the effect is freshly applied to a path
211 void
212 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
214     //Do nothing for simple effects
218 /*
219  *  Here be the doEffect function chain:
220  */
221 void
222 Effect::doEffect (SPCurve * curve)
224     NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
226     curve->set_bpath(new_bpath);
229 NArtBpath *
230 Effect::doEffect_nartbpath (NArtBpath const * path_in)
232     try {
233         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
235         std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
237         NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
239         return new_bpath;
240     }
241     catch (std::exception & e) {
242         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
243         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
244             _("An exception occurred during execution of the Path Effect.") );
246         NArtBpath *path_out;
248         unsigned ret = 0;
249         while ( path_in[ret].code != NR_END ) {
250             ++ret;
251         }
252         unsigned len = ++ret;
254         path_out = g_new(NArtBpath, len);
255         memcpy(path_out, path_in, len * sizeof(NArtBpath));
256         return path_out;
257     }
260 std::vector<Geom::Path>
261 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
263     std::vector<Geom::Path> path_out;
265     if ( !concatenate_before_pwd2 ) {
266         // default behavior
267         for (unsigned int i=0; i < path_in.size(); i++) {
268             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
269             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
270             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
271             // add the output path vector to the already accumulated vector:
272             for (unsigned int j=0; j < path.size(); j++) {
273                 path_out.push_back(path[j]);
274             }
275         }
276     } else {
277       // concatenate the path into possibly discontinuous pwd2
278         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
279         for (unsigned int i=0; i < path_in.size(); i++) {
280             pwd2_in.concat( path_in[i].toPwSb() );
281         }
282         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
283         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
284     }
286     return path_out;
289 Geom::Piecewise<Geom::D2<Geom::SBasis> >
290 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
292     g_warning("Effect has no doEffect implementation");
293     return pwd2_in;
296 void
297 Effect::readallParameters(Inkscape::XML::Node * repr)
299     std::vector<Parameter *>::iterator it = param_vector.begin();
300     while (it != param_vector.end()) {
301         Parameter * param = *it;
302         const gchar * key = param->param_key.c_str();
303         const gchar * value = repr->attribute(key);
304         if (value) {
305             bool accepted = param->param_readSVGValue(value);
306             if (!accepted) {
307                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
308             }
309         } else {
310             // set default value
311             param->param_set_default();
312         }
314         it++;
315     }
318 /* This function does not and SHOULD NOT write to XML */
319 void
320 Effect::setParameter(const gchar * key, const gchar * new_value)
322     Parameter * param = getParameter(key);
323     if (param) {
324         if (new_value) {
325             bool accepted = param->param_readSVGValue(new_value);
326             if (!accepted) {
327                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
328             }
329         } else {
330             // set default value
331             param->param_set_default();
332         }
333     }
336 void
337 Effect::registerParameter(Parameter * param)
339     param_vector.push_back(param);
342 void
343 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
345     knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
348 // TODO: allow for adding click_functions and description strings, too
349 void
350 Effect::addHandles(KnotHolder *knotholder) {
351     std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
352     for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
353         //knotholder->add(i->first, i->second, NULL, (""));
354     }
357 /**
358  * This *creates* a new widget, management of deletion should be done by the caller
359  */
360 Gtk::Widget *
361 Effect::newWidget(Gtk::Tooltips * tooltips)
363     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
364     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
366     vbox->set_border_width(5);
368     std::vector<Parameter *>::iterator it = param_vector.begin();
369     while (it != param_vector.end()) {
370         Parameter * param = *it;
371         Gtk::Widget * widg = param->param_newWidget(tooltips);
372         Glib::ustring * tip = param->param_getTooltip();
373         if (widg) {
374            vbox->pack_start(*widg, true, true, 2);
375             if (tip != NULL) {
376                 tooltips->set_tip(*widg, *tip);
377             }
378         }
380         it++;
381     }
383     return dynamic_cast<Gtk::Widget *>(vbox);
387 Inkscape::XML::Node *
388 Effect::getRepr()
390     return SP_OBJECT_REPR(lpeobj);
393 SPDocument *
394 Effect::getSPDoc()
396     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
397     return SP_OBJECT_DOCUMENT(lpeobj);
400 Parameter *
401 Effect::getParameter(const char * key)
403     Glib::ustring stringkey(key);
405     std::vector<Parameter *>::iterator it = param_vector.begin();
406     while (it != param_vector.end()) {
407         Parameter * param = *it;
408         if ( param->param_key == key) {
409             return param;
410         }
412         it++;
413     }
415     return NULL;
418 Parameter *
419 Effect::getNextOncanvasEditableParam()
421     if (param_vector.size() == 0) // no parameters
422         return NULL;
424     oncanvasedit_it++;
425     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
426         oncanvasedit_it = 0;
427     }
428     int old_it = oncanvasedit_it;
430     do {
431         Parameter * param = param_vector[oncanvasedit_it];
432         if(param && param->oncanvas_editable) {
433             return param;
434         } else {
435             oncanvasedit_it++;
436             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
437                 oncanvasedit_it = 0;
438             }
439         }
440     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
442     return NULL;
445 void
446 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
448     if (!desktop) return;
450     Parameter * param = getNextOncanvasEditableParam();
451     if (param) {
452         param->param_editOncanvas(item, desktop);
453         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
454         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
455         g_free(message);
456     } else {
457         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
458                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
459     }
462 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
463 * 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!
464 */
465 void
466 Effect::resetDefaults(SPItem * /*item*/)
468     // do nothing for simple effects
471 void
472 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
474     np->helperpath_rgba = 0xff0000ff;
475     np->helperpath_width = 1.0;
478 void
479 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
481     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
482     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
483         Parameter * param = *it;
484         param->param_transform_multiply(postmul, set);
485     }
488 } /* namespace LivePathEffect */
490 } /* namespace Inkscape */
492 /*
493   Local Variables:
494   mode:c++
495   c-file-style:"stroustrup"
496   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
497   indent-tabs-mode:nil
498   fill-column:99
499   End:
500 */
501 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :