Code

c46a9dd2397dc864fc46f382a596866e4fcaa63a
[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 void
219 Effect::writeParamsToSVG() {
220     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
221     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
222         (*p)->write_to_SVG();
223     }
226 /*
227  *  Here be the doEffect function chain:
228  */
229 void
230 Effect::doEffect (SPCurve * curve)
232     NArtBpath const *bpath_in = curve->get_bpath();
234     std::vector<Geom::Path> result_pathv;
236     try {
237         std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
239         result_pathv = doEffect_path(orig_pathv);
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.") );
245     }
247     curve->set_pathv(result_pathv);
250 std::vector<Geom::Path>
251 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
253     std::vector<Geom::Path> path_out;
255     if ( !concatenate_before_pwd2 ) {
256         // default behavior
257         for (unsigned int i=0; i < path_in.size(); i++) {
258             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
259             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
260             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
261             // add the output path vector to the already accumulated vector:
262             for (unsigned int j=0; j < path.size(); j++) {
263                 path_out.push_back(path[j]);
264             }
265         }
266     } else {
267       // concatenate the path into possibly discontinuous pwd2
268         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
269         for (unsigned int i=0; i < path_in.size(); i++) {
270             pwd2_in.concat( path_in[i].toPwSb() );
271         }
272         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
273         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
274     }
276     return path_out;
279 Geom::Piecewise<Geom::D2<Geom::SBasis> >
280 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
282     g_warning("Effect has no doEffect implementation");
283     return pwd2_in;
286 void
287 Effect::readallParameters(Inkscape::XML::Node * repr)
289     std::vector<Parameter *>::iterator it = param_vector.begin();
290     while (it != param_vector.end()) {
291         Parameter * param = *it;
292         const gchar * key = param->param_key.c_str();
293         const gchar * value = repr->attribute(key);
294         if (value) {
295             bool accepted = param->param_readSVGValue(value);
296             if (!accepted) {
297                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
298             }
299         } else {
300             // set default value
301             param->param_set_default();
302         }
304         it++;
305     }
308 /* This function does not and SHOULD NOT write to XML */
309 void
310 Effect::setParameter(const gchar * key, const gchar * new_value)
312     Parameter * param = getParameter(key);
313     if (param) {
314         if (new_value) {
315             bool accepted = param->param_readSVGValue(new_value);
316             if (!accepted) {
317                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
318             }
319         } else {
320             // set default value
321             param->param_set_default();
322         }
323     }
326 void
327 Effect::registerParameter(Parameter * param)
329     param_vector.push_back(param);
332 // TODO: should we provide a way to alter the handle's appearance?
333 void
334 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
336     kh_entity_vector.push_back(std::make_pair(entity, descr));
339 /**
340  * Add all registered LPE knotholder handles to the knotholder
341  */
342 void
343 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
344     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
345     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
346         KnotHolderEntity *entity = i->first;
347         const char *descr = i->second;
349         entity->create(desktop, item, knotholder, descr);
350         knotholder->add(entity);
351     }
354 void
355 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
356     using namespace std;
357     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
358         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
359             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
360             e->create(desktop, item, knotholder);
361             knotholder->add(e);
362         }
363     }
366 /**
367  * This *creates* a new widget, management of deletion should be done by the caller
368  */
369 Gtk::Widget *
370 Effect::newWidget(Gtk::Tooltips * tooltips)
372     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
373     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
375     vbox->set_border_width(5);
377     std::vector<Parameter *>::iterator it = param_vector.begin();
378     while (it != param_vector.end()) {
379         Parameter * param = *it;
380         Gtk::Widget * widg = param->param_newWidget(tooltips);
381         Glib::ustring * tip = param->param_getTooltip();
382         if (widg) {
383            vbox->pack_start(*widg, true, true, 2);
384             if (tip != NULL) {
385                 tooltips->set_tip(*widg, *tip);
386             }
387         }
389         it++;
390     }
392     return dynamic_cast<Gtk::Widget *>(vbox);
396 Inkscape::XML::Node *
397 Effect::getRepr()
399     return SP_OBJECT_REPR(lpeobj);
402 SPDocument *
403 Effect::getSPDoc()
405     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
406     return SP_OBJECT_DOCUMENT(lpeobj);
409 Parameter *
410 Effect::getParameter(const char * key)
412     Glib::ustring stringkey(key);
414     std::vector<Parameter *>::iterator it = param_vector.begin();
415     while (it != param_vector.end()) {
416         Parameter * param = *it;
417         if ( param->param_key == key) {
418             return param;
419         }
421         it++;
422     }
424     return NULL;
427 Parameter *
428 Effect::getNextOncanvasEditableParam()
430     if (param_vector.size() == 0) // no parameters
431         return NULL;
433     oncanvasedit_it++;
434     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
435         oncanvasedit_it = 0;
436     }
437     int old_it = oncanvasedit_it;
439     do {
440         Parameter * param = param_vector[oncanvasedit_it];
441         if(param && param->oncanvas_editable) {
442             return param;
443         } else {
444             oncanvasedit_it++;
445             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
446                 oncanvasedit_it = 0;
447             }
448         }
449     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
451     return NULL;
454 void
455 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
457     if (!desktop) return;
459     Parameter * param = getNextOncanvasEditableParam();
460     if (param) {
461         param->param_editOncanvas(item, desktop);
462         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
463         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
464         g_free(message);
465     } else {
466         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
467                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
468     }
471 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
472 * 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!
473 */
474 void
475 Effect::resetDefaults(SPItem * /*item*/)
477     // do nothing for simple effects
480 void
481 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
483     np->helperpath_rgba = 0xff0000ff;
484     np->helperpath_width = 1.0;
487 void
488 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
490     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
491     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
492         Parameter * param = *it;
493         param->param_transform_multiply(postmul, set);
494     }
497 } /* namespace LivePathEffect */
499 } /* namespace Inkscape */
501 /*
502   Local Variables:
503   mode:c++
504   c-file-style:"stroustrup"
505   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
506   indent-tabs-mode:nil
507   fill-column:99
508   End:
509 */
510 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :