Code

4f4aaff3fc88775d4ea648aad75511e99effd814
[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 // end of includes
62 namespace Inkscape {
64 namespace LivePathEffect {
66 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
67     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
68     {BEND_PATH,             N_("Bend"),                  "bend_path"},
69     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
70     {SKETCH,                N_("Sketch"),                "sketch"},
71     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
72     {KNOT,                  N_("Knot"),                  "knot"},
73 #ifdef LPE_ENABLE_TEST_EFFECTS
74     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
75 #endif
76     {GEARS,                 N_("Gears"),                 "gears"},
77     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
78     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
79     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
80     {SPIRO,      N_("Spiro spline"),      "spiro"},
81     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
82     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
83     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
84     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
85     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
86     {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
87     {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
88 };
89 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
91 Effect*
92 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
93 {
94     Effect* neweffect = NULL;
95     switch (lpenr) {
96         case PATTERN_ALONG_PATH:
97             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
98             break;
99         case BEND_PATH:
100             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
101             break;
102         case SKETCH:
103             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
104             break;
105         case VONKOCH:
106             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
107             break;
108         case KNOT:
109             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
110             break;
111 #ifdef LPE_ENABLE_TEST_EFFECTS
112         case DOEFFECTSTACK_TEST:
113             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
114             break;
115 #endif
116         case GEARS:
117             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
118             break;
119         case CURVE_STITCH:
120             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
121             break;
122         case LATTICE:
123             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
124             break;
125         case ENVELOPE:
126             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
127             break;
128         case CIRCLE_WITH_RADIUS:
129             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
130             break;
131         case PERSPECTIVE_PATH:
132             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
133             break;
134         case SPIRO:
135             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
136             break;
137         case CONSTRUCT_GRID:
138             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
139             break;
140         case PERP_BISECTOR:
141             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
142             break;
143         case TANGENT_TO_CURVE:
144             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
145             break;
146         case MIRROR_REFLECT:
147             neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
148             break;
149         case CIRCLE_3PTS:
150             neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
151             break;
152         default:
153             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
154             neweffect = NULL;
155             break;
156     }
158     if (neweffect) {
159         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
160     }
162     return neweffect;
165 void
166 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
168     // Path effect definition
169     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
170     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
171     repr->setAttribute("effect", name);
173     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
174     const gchar * repr_id = repr->attribute("id");
175     Inkscape::GC::release(repr);
177     gchar *href = g_strdup_printf("#%s", repr_id);
178     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
179     g_free(href);
181     sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
182                      _("Create and apply path effect"));
185 void
186 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
188     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
191 Effect::Effect(LivePathEffectObject *lpeobject)
192     : oncanvasedit_it(0),
193       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
194       done_pathparam_set(false),
195       provides_own_flash_paths(true), // is automatically set to false if providesOwnFlashPaths() is not overridden
196       show_orig_path(false),
197       lpeobj(lpeobject),
198       concatenate_before_pwd2(false)
200     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
203 Effect::~Effect()
207 Glib::ustring
208 Effect::getName()
210     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
211         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
212     else
213         return Glib::ustring( _("No effect") );
216 EffectType
217 Effect::effectType() {
218     return lpeobj->effecttype;
221 /**
222  * Is performed a single time when the effect is freshly applied to a path
223  */
224 void
225 Effect::doOnApply (SPLPEItem */*lpeitem*/)
229 /**
230  * Is performed each time before the effect is updated.
231  */
232 void
233 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
235     //Do nothing for simple effects
238 /**
239  * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
240  * clicks. This method activates the pen context, which waits for the specified number of clicks.
241  * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
242  */
243 void
244 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
246     // switch to pen context
247     SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
248     if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
249         tools_switch(desktop, TOOLS_FREEHAND_PEN);
250     }
252     SPEventContext *ec = desktop->event_context;
253     SPPenContext *pc = SP_PEN_CONTEXT(ec);
254     pc->expecting_clicks_for_LPE = this->acceptsNumParams();
255     pc->waiting_LPE = this;
256     pc->waiting_item = lpeitem;
257     pc->polylines_only = true;
259     ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
260         g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
261                         getName().c_str(), acceptsNumParams()));
264 void
265 Effect::writeParamsToSVG() {
266     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
267     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
268         (*p)->write_to_SVG();
269     }
272 /**
273  * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
274  * applied, this is the method that processes the resulting path. Override it to customize it for
275  * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
276  */
277 void
278 Effect::acceptParamPath (SPPath *param_path) {
279     done_pathparam_set = true;
282 /*
283  *  Here be the doEffect function chain:
284  */
285 void
286 Effect::doEffect (SPCurve * curve)
288     std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
290     std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
292     curve->set_pathv(result_pathv);
295 std::vector<Geom::Path>
296 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
298     std::vector<Geom::Path> path_out;
300     if ( !concatenate_before_pwd2 ) {
301         // default behavior
302         for (unsigned int i=0; i < path_in.size(); i++) {
303             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
304             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
305             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
306             // add the output path vector to the already accumulated vector:
307             for (unsigned int j=0; j < path.size(); j++) {
308                 path_out.push_back(path[j]);
309             }
310         }
311     } else {
312       // concatenate the path into possibly discontinuous pwd2
313         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
314         for (unsigned int i=0; i < path_in.size(); i++) {
315             pwd2_in.concat( path_in[i].toPwSb() );
316         }
317         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
318         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
319     }
321     return path_out;
324 Geom::Piecewise<Geom::D2<Geom::SBasis> >
325 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
327     g_warning("Effect has no doEffect implementation");
328     return pwd2_in;
331 void
332 Effect::readallParameters(Inkscape::XML::Node * repr)
334     std::vector<Parameter *>::iterator it = param_vector.begin();
335     while (it != param_vector.end()) {
336         Parameter * param = *it;
337         const gchar * key = param->param_key.c_str();
338         const gchar * value = repr->attribute(key);
339         if (value) {
340             bool accepted = param->param_readSVGValue(value);
341             if (!accepted) {
342                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
343             }
344         } else {
345             // set default value
346             param->param_set_default();
347         }
349         it++;
350     }
353 /* This function does not and SHOULD NOT write to XML */
354 void
355 Effect::setParameter(const gchar * key, const gchar * new_value)
357     Parameter * param = getParameter(key);
358     if (param) {
359         if (new_value) {
360             bool accepted = param->param_readSVGValue(new_value);
361             if (!accepted) {
362                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
363             }
364         } else {
365             // set default value
366             param->param_set_default();
367         }
368     }
371 void
372 Effect::registerParameter(Parameter * param)
374     param_vector.push_back(param);
377 // TODO: should we provide a way to alter the handle's appearance?
378 void
379 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
381     kh_entity_vector.push_back(std::make_pair(entity, descr));
384 /**
385  * Add all registered LPE knotholder handles to the knotholder
386  */
387 void
388 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
389     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
390     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
391         KnotHolderEntity *entity = i->first;
392         const char *descr = i->second;
394         entity->create(desktop, item, knotholder, descr);
395         knotholder->add(entity);
396     }
399 void
400 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
401     using namespace std;
402     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
403         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
404             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
405             e->create(desktop, item, knotholder);
406             knotholder->add(e);
407         }
408     }
411 void
412 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
414     g_return_if_fail(SP_IS_PATH(lpeitem));
416     if (providesKnotholder() && showOrigPath()) {
417         // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
418         // must create the helper curve for the original path manually; when we allow nodepaths and
419         // knotholders alongside each other, this needs to be rethought!
420         SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
421         // TODO: Make sure the tempitem doesn't get destroyed when the mouse leaves the item
422         Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
423         lpeitem->lpe_helperpaths.push_back(tmpitem);
424     }
426     addHelperPathsImpl(lpeitem, desktop);
429 void
430 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
432     // if this method is overloaded in derived classes, provides_own_flash_paths will be true
433     provides_own_flash_paths = false;
436 /**
437  * This *creates* a new widget, management of deletion should be done by the caller
438  */
439 Gtk::Widget *
440 Effect::newWidget(Gtk::Tooltips * tooltips)
442     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
443     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
445     vbox->set_border_width(5);
447     std::vector<Parameter *>::iterator it = param_vector.begin();
448     while (it != param_vector.end()) {
449         Parameter * param = *it;
450         Gtk::Widget * widg = param->param_newWidget(tooltips);
451         Glib::ustring * tip = param->param_getTooltip();
452         if (widg) {
453            vbox->pack_start(*widg, true, true, 2);
454             if (tip != NULL) {
455                 tooltips->set_tip(*widg, *tip);
456             }
457         }
459         it++;
460     }
462     return dynamic_cast<Gtk::Widget *>(vbox);
466 Inkscape::XML::Node *
467 Effect::getRepr()
469     return SP_OBJECT_REPR(lpeobj);
472 SPDocument *
473 Effect::getSPDoc()
475     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
476     return SP_OBJECT_DOCUMENT(lpeobj);
479 Parameter *
480 Effect::getParameter(const char * key)
482     Glib::ustring stringkey(key);
484     std::vector<Parameter *>::iterator it = param_vector.begin();
485     while (it != param_vector.end()) {
486         Parameter * param = *it;
487         if ( param->param_key == key) {
488             return param;
489         }
491         it++;
492     }
494     return NULL;
497 Parameter *
498 Effect::getNextOncanvasEditableParam()
500     if (param_vector.size() == 0) // no parameters
501         return NULL;
503     oncanvasedit_it++;
504     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
505         oncanvasedit_it = 0;
506     }
507     int old_it = oncanvasedit_it;
509     do {
510         Parameter * param = param_vector[oncanvasedit_it];
511         if(param && param->oncanvas_editable) {
512             return param;
513         } else {
514             oncanvasedit_it++;
515             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
516                 oncanvasedit_it = 0;
517             }
518         }
519     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
521     return NULL;
524 void
525 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
527     if (!desktop) return;
529     Parameter * param = getNextOncanvasEditableParam();
530     if (param) {
531         param->param_editOncanvas(item, desktop);
532         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
533         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
534         g_free(message);
535     } else {
536         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
537                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
538     }
541 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
542 * 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!
543 */
544 void
545 Effect::resetDefaults(SPItem * /*item*/)
547     // do nothing for simple effects
550 void
551 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
553     np->helperpath_rgba = 0xff0000ff;
554     np->helperpath_width = 1.0;
557 void
558 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
560     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
561     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
562         Parameter * param = *it;
563         param->param_transform_multiply(postmul, set);
564     }
567 } /* namespace LivePathEffect */
569 } /* namespace Inkscape */
571 /*
572   Local Variables:
573   mode:c++
574   c-file-style:"stroustrup"
575   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
576   indent-tabs-mode:nil
577   fill-column:99
578   End:
579 */
580 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :