Code

164447387a2a80c3989419dc4eb961b089a3b3e1
[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>
22 #include "pen-context.h"
23 #include "tools-switch.h"
24 #include "message-stack.h"
25 #include "desktop.h"
26 #include "nodepath.h"
28 #include "live_effects/lpeobject.h"
29 #include "live_effects/parameter/parameter.h"
30 #include <glibmm/ustring.h>
31 #include "libnr/n-art-bpath-2geom.h"
32 #include "display/curve.h"
33 #include <gtkmm.h>
35 #include <exception>
37 #include <2geom/sbasis-to-bezier.h>
38 #include <2geom/matrix.h>
41 // include effects:
42 #include "live_effects/lpe-patternalongpath.h"
43 #include "live_effects/lpe-bendpath.h"
44 #include "live_effects/lpe-sketch.h"
45 #include "live_effects/lpe-vonkoch.h"
46 #include "live_effects/lpe-knot.h"
47 #include "live_effects/lpe-test-doEffect-stack.h"
48 #include "live_effects/lpe-gears.h"
49 #include "live_effects/lpe-curvestitch.h"
50 #include "live_effects/lpe-circle_with_radius.h"
51 #include "live_effects/lpe-perspective_path.h"
52 #include "live_effects/lpe-spiro.h"
53 #include "live_effects/lpe-lattice.h"
54 #include "live_effects/lpe-envelope.h"
55 #include "live_effects/lpe-constructgrid.h"
56 #include "live_effects/lpe-perp_bisector.h"
57 #include "live_effects/lpe-tangent_to_curve.h"
58 #include "live_effects/lpe-mirror_reflect.h"
59 #include "live_effects/lpe-circle_3pts.h"
60 #include "live_effects/lpe-angle_bisector.h"
61 // end of includes
63 namespace Inkscape {
65 namespace LivePathEffect {
67 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
68     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
69     {BEND_PATH,             N_("Bend"),                  "bend_path"},
70     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
71     {SKETCH,                N_("Sketch"),                "sketch"},
72     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
73     {KNOT,                  N_("Knot"),                  "knot"},
74 #ifdef LPE_ENABLE_TEST_EFFECTS
75     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
76 #endif
77     {GEARS,                 N_("Gears"),                 "gears"},
78     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
79     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
80     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
81     {SPIRO,      N_("Spiro spline"),      "spiro"},
82     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
83     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
84     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
85     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
86     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
87     {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
88     {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
89     {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
90 };
91 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
93 Effect*
94 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
95 {
96     Effect* neweffect = NULL;
97     switch (lpenr) {
98         case PATTERN_ALONG_PATH:
99             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
100             break;
101         case BEND_PATH:
102             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
103             break;
104         case SKETCH:
105             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
106             break;
107         case VONKOCH:
108             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
109             break;
110         case KNOT:
111             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
112             break;
113 #ifdef LPE_ENABLE_TEST_EFFECTS
114         case DOEFFECTSTACK_TEST:
115             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
116             break;
117 #endif
118         case GEARS:
119             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
120             break;
121         case CURVE_STITCH:
122             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
123             break;
124         case LATTICE:
125             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
126             break;
127         case ENVELOPE:
128             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
129             break;
130         case CIRCLE_WITH_RADIUS:
131             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
132             break;
133         case PERSPECTIVE_PATH:
134             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
135             break;
136         case SPIRO:
137             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
138             break;
139         case CONSTRUCT_GRID:
140             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
141             break;
142         case PERP_BISECTOR:
143             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
144             break;
145         case TANGENT_TO_CURVE:
146             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
147             break;
148         case MIRROR_REFLECT:
149             neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
150             break;
151         case CIRCLE_3PTS:
152             neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
153             break;
154         case ANGLE_BISECTOR:
155             neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
156             break;
157         default:
158             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
159             neweffect = NULL;
160             break;
161     }
163     if (neweffect) {
164         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
165     }
167     return neweffect;
170 void
171 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
173     // Path effect definition
174     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
175     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
176     repr->setAttribute("effect", name);
178     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
179     const gchar * repr_id = repr->attribute("id");
180     Inkscape::GC::release(repr);
182     gchar *href = g_strdup_printf("#%s", repr_id);
183     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
184     g_free(href);
186     sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
187                      _("Create and apply path effect"));
190 void
191 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
193     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
196 Effect::Effect(LivePathEffectObject *lpeobject)
197     : oncanvasedit_it(0),
198       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
199       done_pathparam_set(false),
200       show_orig_path(false),
201       lpeobj(lpeobject),
202       concatenate_before_pwd2(false),
203       provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
205     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
208 Effect::~Effect()
212 Glib::ustring
213 Effect::getName()
215     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
216         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
217     else
218         return Glib::ustring( _("No effect") );
221 EffectType
222 Effect::effectType() {
223     return lpeobj->effecttype;
226 /**
227  * Is performed a single time when the effect is freshly applied to a path
228  */
229 void
230 Effect::doOnApply (SPLPEItem */*lpeitem*/)
234 /**
235  * Is performed each time before the effect is updated.
236  */
237 void
238 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
240     //Do nothing for simple effects
243 /**
244  * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
245  * clicks. This method activates the pen context, which waits for the specified number of clicks.
246  * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
247  */
248 void
249 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
251     // switch to pen context
252     SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
253     if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
254         tools_switch(desktop, TOOLS_FREEHAND_PEN);
255     }
257     SPEventContext *ec = desktop->event_context;
258     SPPenContext *pc = SP_PEN_CONTEXT(ec);
259     pc->expecting_clicks_for_LPE = this->acceptsNumParams();
260     pc->waiting_LPE = this;
261     pc->waiting_item = lpeitem;
262     pc->polylines_only = true;
264     ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
265         g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
266                         getName().c_str(), acceptsNumParams()));
269 void
270 Effect::writeParamsToSVG() {
271     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
272     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
273         (*p)->write_to_SVG();
274     }
277 /**
278  * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
279  * applied, this is the method that processes the resulting path. Override it to customize it for
280  * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
281  */
282 void
283 Effect::acceptParamPath (SPPath *param_path) {
284     done_pathparam_set = true;
287 /*
288  *  Here be the doEffect function chain:
289  */
290 void
291 Effect::doEffect (SPCurve * curve)
293     std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
295     std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
297     curve->set_pathv(result_pathv);
300 std::vector<Geom::Path>
301 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
303     std::vector<Geom::Path> path_out;
305     if ( !concatenate_before_pwd2 ) {
306         // default behavior
307         for (unsigned int i=0; i < path_in.size(); i++) {
308             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
309             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
310             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
311             // add the output path vector to the already accumulated vector:
312             for (unsigned int j=0; j < path.size(); j++) {
313                 path_out.push_back(path[j]);
314             }
315         }
316     } else {
317       // concatenate the path into possibly discontinuous pwd2
318         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
319         for (unsigned int i=0; i < path_in.size(); i++) {
320             pwd2_in.concat( path_in[i].toPwSb() );
321         }
322         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
323         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
324     }
326     return path_out;
329 Geom::Piecewise<Geom::D2<Geom::SBasis> >
330 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
332     g_warning("Effect has no doEffect implementation");
333     return pwd2_in;
336 void
337 Effect::readallParameters(Inkscape::XML::Node * repr)
339     std::vector<Parameter *>::iterator it = param_vector.begin();
340     while (it != param_vector.end()) {
341         Parameter * param = *it;
342         const gchar * key = param->param_key.c_str();
343         const gchar * value = repr->attribute(key);
344         if (value) {
345             bool accepted = param->param_readSVGValue(value);
346             if (!accepted) {
347                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
348             }
349         } else {
350             // set default value
351             param->param_set_default();
352         }
354         it++;
355     }
358 /* This function does not and SHOULD NOT write to XML */
359 void
360 Effect::setParameter(const gchar * key, const gchar * new_value)
362     Parameter * param = getParameter(key);
363     if (param) {
364         if (new_value) {
365             bool accepted = param->param_readSVGValue(new_value);
366             if (!accepted) {
367                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
368             }
369         } else {
370             // set default value
371             param->param_set_default();
372         }
373     }
376 void
377 Effect::registerParameter(Parameter * param)
379     param_vector.push_back(param);
382 // TODO: should we provide a way to alter the handle's appearance?
383 void
384 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
386     kh_entity_vector.push_back(std::make_pair(entity, descr));
389 /**
390  * Add all registered LPE knotholder handles to the knotholder
391  */
392 void
393 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
394     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
395     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
396         KnotHolderEntity *entity = i->first;
397         const char *descr = i->second;
399         entity->create(desktop, item, knotholder, descr);
400         knotholder->add(entity);
401     }
404 void
405 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
406     using namespace std;
407     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
408         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
409             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
410             e->create(desktop, item, knotholder);
411             knotholder->add(e);
412         }
413     }
416 void
417 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
419     g_return_if_fail(SP_IS_PATH(lpeitem));
421     if (providesKnotholder() && showOrigPath()) {
422         // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
423         // must create the helper curve for the original path manually; when we allow nodepaths and
424         // knotholders alongside each other, this needs to be rethought!
425         SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
426         // TODO: Make sure the tempitem doesn't get destroyed when the mouse leaves the item
427         Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
428         lpeitem->lpe_helperpaths.push_back(tmpitem);
429     }
431     addHelperPathsImpl(lpeitem, desktop);
434 void
435 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
437     // if this method is overloaded in derived classes, provides_own_flash_paths will be true
438     provides_own_flash_paths = false;
441 /**
442  * This *creates* a new widget, management of deletion should be done by the caller
443  */
444 Gtk::Widget *
445 Effect::newWidget(Gtk::Tooltips * tooltips)
447     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
448     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
450     vbox->set_border_width(5);
452     std::vector<Parameter *>::iterator it = param_vector.begin();
453     while (it != param_vector.end()) {
454         Parameter * param = *it;
455         Gtk::Widget * widg = param->param_newWidget(tooltips);
456         Glib::ustring * tip = param->param_getTooltip();
457         if (widg) {
458            vbox->pack_start(*widg, true, true, 2);
459             if (tip != NULL) {
460                 tooltips->set_tip(*widg, *tip);
461             }
462         }
464         it++;
465     }
467     return dynamic_cast<Gtk::Widget *>(vbox);
471 Inkscape::XML::Node *
472 Effect::getRepr()
474     return SP_OBJECT_REPR(lpeobj);
477 SPDocument *
478 Effect::getSPDoc()
480     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
481     return SP_OBJECT_DOCUMENT(lpeobj);
484 Parameter *
485 Effect::getParameter(const char * key)
487     Glib::ustring stringkey(key);
489     std::vector<Parameter *>::iterator it = param_vector.begin();
490     while (it != param_vector.end()) {
491         Parameter * param = *it;
492         if ( param->param_key == key) {
493             return param;
494         }
496         it++;
497     }
499     return NULL;
502 Parameter *
503 Effect::getNextOncanvasEditableParam()
505     if (param_vector.size() == 0) // no parameters
506         return NULL;
508     oncanvasedit_it++;
509     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
510         oncanvasedit_it = 0;
511     }
512     int old_it = oncanvasedit_it;
514     do {
515         Parameter * param = param_vector[oncanvasedit_it];
516         if(param && param->oncanvas_editable) {
517             return param;
518         } else {
519             oncanvasedit_it++;
520             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
521                 oncanvasedit_it = 0;
522             }
523         }
524     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
526     return NULL;
529 void
530 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
532     if (!desktop) return;
534     Parameter * param = getNextOncanvasEditableParam();
535     if (param) {
536         param->param_editOncanvas(item, desktop);
537         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
538         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
539         g_free(message);
540     } else {
541         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
542                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
543     }
546 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
547 * 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!
548 */
549 void
550 Effect::resetDefaults(SPItem * /*item*/)
552     // do nothing for simple effects
555 void
556 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
558     np->helperpath_rgba = 0xff0000ff;
559     np->helperpath_width = 1.0;
562 void
563 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
565     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
566     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
567         Parameter * param = *it;
568         param->param_transform_multiply(postmul, set);
569     }
572 } /* namespace LivePathEffect */
574 } /* namespace Inkscape */
576 /*
577   Local Variables:
578   mode:c++
579   c-file-style:"stroustrup"
580   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
581   indent-tabs-mode:nil
582   fill-column:99
583   End:
584 */
585 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :