Code

b08077e795050b11275748281085bdbdcff4f638
[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 const *bpath_in = curve->get_bpath();
226     std::vector<Geom::Path> result_pathv;
228     try {
229         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
231         result_pathv = doEffect_path(orig_pathv);
232     }
233     catch (std::exception & e) {
234         g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
235         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
236             _("An exception occurred during execution of the Path Effect.") );
237     }
239     curve->set_pathv(result_pathv);
242 std::vector<Geom::Path>
243 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
245     std::vector<Geom::Path> path_out;
247     if ( !concatenate_before_pwd2 ) {
248         // default behavior
249         for (unsigned int i=0; i < path_in.size(); i++) {
250             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
251             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
252             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
253             // add the output path vector to the already accumulated vector:
254             for (unsigned int j=0; j < path.size(); j++) {
255                 path_out.push_back(path[j]);
256             }
257         }
258     } else {
259       // concatenate the path into possibly discontinuous pwd2
260         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
261         for (unsigned int i=0; i < path_in.size(); i++) {
262             pwd2_in.concat( path_in[i].toPwSb() );
263         }
264         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
265         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
266     }
268     return path_out;
271 Geom::Piecewise<Geom::D2<Geom::SBasis> >
272 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
274     g_warning("Effect has no doEffect implementation");
275     return pwd2_in;
278 void
279 Effect::readallParameters(Inkscape::XML::Node * repr)
281     std::vector<Parameter *>::iterator it = param_vector.begin();
282     while (it != param_vector.end()) {
283         Parameter * param = *it;
284         const gchar * key = param->param_key.c_str();
285         const gchar * value = repr->attribute(key);
286         if (value) {
287             bool accepted = param->param_readSVGValue(value);
288             if (!accepted) {
289                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
290             }
291         } else {
292             // set default value
293             param->param_set_default();
294         }
296         it++;
297     }
300 /* This function does not and SHOULD NOT write to XML */
301 void
302 Effect::setParameter(const gchar * key, const gchar * new_value)
304     Parameter * param = getParameter(key);
305     if (param) {
306         if (new_value) {
307             bool accepted = param->param_readSVGValue(new_value);
308             if (!accepted) {
309                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
310             }
311         } else {
312             // set default value
313             param->param_set_default();
314         }
315     }
318 void
319 Effect::registerParameter(Parameter * param)
321     param_vector.push_back(param);
324 // TODO: should we provide a way to alter the handle's appearance?
325 void
326 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
328     kh_entity_vector.push_back(std::make_pair(entity, descr));
331 /**
332  * Add all registered LPE knotholder handles to the knotholder
333  */
334 void
335 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
336     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
337     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
338         KnotHolderEntity *entity = i->first;
339         const char *descr = i->second;
341         entity->create(desktop, item, knotholder, descr);
342         knotholder->add(entity);
343     }
346 void
347 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
348     using namespace std;
349     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
350         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
351             g_print ("Parameter is of type PointParam\n");
352             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
353             e->create(desktop, item, knotholder);
354             knotholder->add(e);
355         } else {
356             g_print ("Parameter is *not* of type PointParam\n");
357         }
358     }
361 /**
362  * This *creates* a new widget, management of deletion should be done by the caller
363  */
364 Gtk::Widget *
365 Effect::newWidget(Gtk::Tooltips * tooltips)
367     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
368     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
370     vbox->set_border_width(5);
372     std::vector<Parameter *>::iterator it = param_vector.begin();
373     while (it != param_vector.end()) {
374         Parameter * param = *it;
375         Gtk::Widget * widg = param->param_newWidget(tooltips);
376         Glib::ustring * tip = param->param_getTooltip();
377         if (widg) {
378            vbox->pack_start(*widg, true, true, 2);
379             if (tip != NULL) {
380                 tooltips->set_tip(*widg, *tip);
381             }
382         }
384         it++;
385     }
387     return dynamic_cast<Gtk::Widget *>(vbox);
391 Inkscape::XML::Node *
392 Effect::getRepr()
394     return SP_OBJECT_REPR(lpeobj);
397 SPDocument *
398 Effect::getSPDoc()
400     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
401     return SP_OBJECT_DOCUMENT(lpeobj);
404 Parameter *
405 Effect::getParameter(const char * key)
407     Glib::ustring stringkey(key);
409     std::vector<Parameter *>::iterator it = param_vector.begin();
410     while (it != param_vector.end()) {
411         Parameter * param = *it;
412         if ( param->param_key == key) {
413             return param;
414         }
416         it++;
417     }
419     return NULL;
422 Parameter *
423 Effect::getNextOncanvasEditableParam()
425     if (param_vector.size() == 0) // no parameters
426         return NULL;
428     oncanvasedit_it++;
429     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
430         oncanvasedit_it = 0;
431     }
432     int old_it = oncanvasedit_it;
434     do {
435         Parameter * param = param_vector[oncanvasedit_it];
436         if(param && param->oncanvas_editable) {
437             return param;
438         } else {
439             oncanvasedit_it++;
440             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
441                 oncanvasedit_it = 0;
442             }
443         }
444     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
446     return NULL;
449 void
450 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
452     if (!desktop) return;
454     Parameter * param = getNextOncanvasEditableParam();
455     if (param) {
456         param->param_editOncanvas(item, desktop);
457         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
458         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
459         g_free(message);
460     } else {
461         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
462                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
463     }
466 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
467 * 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!
468 */
469 void
470 Effect::resetDefaults(SPItem * /*item*/)
472     // do nothing for simple effects
475 void
476 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
478     np->helperpath_rgba = 0xff0000ff;
479     np->helperpath_width = 1.0;
482 void
483 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
485     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
486     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
487         Parameter * param = *it;
488         param->param_transform_multiply(postmul, set);
489     }
492 } /* namespace LivePathEffect */
494 } /* namespace Inkscape */
496 /*
497   Local Variables:
498   mode:c++
499   c-file-style:"stroustrup"
500   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
501   indent-tabs-mode:nil
502   fill-column:99
503   End:
504 */
505 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :