Code

New LPE: Copy rotate
[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 #include "live_effects/lpe-parallel.h"
62 #include "live_effects/lpe-copy_rotate.h"
63 // end of includes
65 namespace Inkscape {
67 namespace LivePathEffect {
69 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
70     // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
71     {BEND_PATH,             N_("Bend"),                  "bend_path"},
72     {PATTERN_ALONG_PATH,    N_("Pattern Along Path"),    "skeletal"},   // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
73     {SKETCH,                N_("Sketch"),                "sketch"},
74     {VONKOCH,               N_("VonKoch"),               "vonkoch"},
75     {KNOT,                  N_("Knot"),                  "knot"},
76 #ifdef LPE_ENABLE_TEST_EFFECTS
77     {DOEFFECTSTACK_TEST,    N_("doEffect stack test"),   "doeffectstacktest"},
78 #endif
79     {GEARS,                 N_("Gears"),                 "gears"},
80     {CURVE_STITCH,          N_("Stitch Sub-Paths"),       "curvestitching"},
81     {CIRCLE_WITH_RADIUS,    N_("Circle (center+radius)"), "circle_with_radius"},
82     {PERSPECTIVE_PATH,      N_("Perspective path"),      "perspective_path"},
83     {SPIRO,      N_("Spiro spline"),      "spiro"},
84     {LATTICE,               N_("Lattice Deformation"),   "lattice"},
85     {ENVELOPE,              N_("Envelope Deformation"),  "envelope"},
86     {CONSTRUCT_GRID,        N_("Construct grid"),        "construct_grid"},
87     {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
88     {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
89     {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
90     {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
91     {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
92     {PARALLEL, N_("Parallel"), "parallel"},
93     {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
94 };
95 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
97 Effect*
98 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
99 {
100     Effect* neweffect = NULL;
101     switch (lpenr) {
102         case PATTERN_ALONG_PATH:
103             neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
104             break;
105         case BEND_PATH:
106             neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
107             break;
108         case SKETCH:
109             neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
110             break;
111         case VONKOCH:
112             neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
113             break;
114         case KNOT:
115             neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
116             break;
117 #ifdef LPE_ENABLE_TEST_EFFECTS
118         case DOEFFECTSTACK_TEST:
119             neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
120             break;
121 #endif
122         case GEARS:
123             neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
124             break;
125         case CURVE_STITCH:
126             neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
127             break;
128         case LATTICE:
129             neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
130             break;
131         case ENVELOPE:
132             neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
133             break;
134         case CIRCLE_WITH_RADIUS:
135             neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
136             break;
137         case PERSPECTIVE_PATH:
138             neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
139             break;
140         case SPIRO:
141             neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
142             break;
143         case CONSTRUCT_GRID:
144             neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
145             break;
146         case PERP_BISECTOR:
147             neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
148             break;
149         case TANGENT_TO_CURVE:
150             neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
151             break;
152         case MIRROR_REFLECT:
153             neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
154             break;
155         case CIRCLE_3PTS:
156             neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
157             break;
158         case ANGLE_BISECTOR:
159             neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
160             break;
161         case PARALLEL:
162             neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
163             break;
164         case COPY_ROTATE:
165             neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
166             break;
167         default:
168             g_warning("LivePathEffect::Effect::New   called with invalid patheffect type (%d)", lpenr);
169             neweffect = NULL;
170             break;
171     }
173     if (neweffect) {
174         neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
175     }
177     return neweffect;
180 void
181 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
183     // Path effect definition
184     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
185     Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
186     repr->setAttribute("effect", name);
188     SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
189     const gchar * repr_id = repr->attribute("id");
190     Inkscape::GC::release(repr);
192     gchar *href = g_strdup_printf("#%s", repr_id);
193     sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
194     g_free(href);
196     sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
197                      _("Create and apply path effect"));
200 void
201 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
203     createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
206 Effect::Effect(LivePathEffectObject *lpeobject)
207     : oncanvasedit_it(0),
208       is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
209       done_pathparam_set(false),
210       show_orig_path(false),
211       lpeobj(lpeobject),
212       concatenate_before_pwd2(false),
213       provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
215     registerParameter( dynamic_cast<Parameter *>(&is_visible) );
218 Effect::~Effect()
222 Glib::ustring
223 Effect::getName()
225     if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
226         return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
227     else
228         return Glib::ustring( _("No effect") );
231 EffectType
232 Effect::effectType() {
233     return lpeobj->effecttype;
236 /**
237  * Is performed a single time when the effect is freshly applied to a path
238  */
239 void
240 Effect::doOnApply (SPLPEItem */*lpeitem*/)
244 /**
245  * Is performed each time before the effect is updated.
246  */
247 void
248 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
250     //Do nothing for simple effects
253 /**
254  * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
255  * clicks. This method activates the pen context, which waits for the specified number of clicks.
256  * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
257  */
258 void
259 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
261     // switch to pen context
262     SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
263     if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
264         tools_switch(desktop, TOOLS_FREEHAND_PEN);
265     }
267     SPEventContext *ec = desktop->event_context;
268     SPPenContext *pc = SP_PEN_CONTEXT(ec);
269     pc->expecting_clicks_for_LPE = this->acceptsNumParams();
270     pc->waiting_LPE = this;
271     pc->waiting_item = lpeitem;
272     pc->polylines_only = true;
274     ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
275         g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
276                         getName().c_str(), acceptsNumParams()));
279 void
280 Effect::writeParamsToSVG() {
281     std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
282     for (p = param_vector.begin(); p != param_vector.end(); ++p) {
283         (*p)->write_to_SVG();
284     }
287 /**
288  * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
289  * applied, this is the method that processes the resulting path. Override it to customize it for
290  * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
291  */
292 void
293 Effect::acceptParamPath (SPPath *param_path) {
294     done_pathparam_set = true;
297 /*
298  *  Here be the doEffect function chain:
299  */
300 void
301 Effect::doEffect (SPCurve * curve)
303     std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
305     std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
307     curve->set_pathv(result_pathv);
310 std::vector<Geom::Path>
311 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
313     std::vector<Geom::Path> path_out;
315     if ( !concatenate_before_pwd2 ) {
316         // default behavior
317         for (unsigned int i=0; i < path_in.size(); i++) {
318             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
319             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
320             std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
321             // add the output path vector to the already accumulated vector:
322             for (unsigned int j=0; j < path.size(); j++) {
323                 path_out.push_back(path[j]);
324             }
325         }
326     } else {
327       // concatenate the path into possibly discontinuous pwd2
328         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
329         for (unsigned int i=0; i < path_in.size(); i++) {
330             pwd2_in.concat( path_in[i].toPwSb() );
331         }
332         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
333         path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
334     }
336     return path_out;
339 Geom::Piecewise<Geom::D2<Geom::SBasis> >
340 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
342     g_warning("Effect has no doEffect implementation");
343     return pwd2_in;
346 void
347 Effect::readallParameters(Inkscape::XML::Node * repr)
349     std::vector<Parameter *>::iterator it = param_vector.begin();
350     while (it != param_vector.end()) {
351         Parameter * param = *it;
352         const gchar * key = param->param_key.c_str();
353         const gchar * value = repr->attribute(key);
354         if (value) {
355             bool accepted = param->param_readSVGValue(value);
356             if (!accepted) {
357                 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
358             }
359         } else {
360             // set default value
361             param->param_set_default();
362         }
364         it++;
365     }
368 /* This function does not and SHOULD NOT write to XML */
369 void
370 Effect::setParameter(const gchar * key, const gchar * new_value)
372     Parameter * param = getParameter(key);
373     if (param) {
374         if (new_value) {
375             bool accepted = param->param_readSVGValue(new_value);
376             if (!accepted) {
377                 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
378             }
379         } else {
380             // set default value
381             param->param_set_default();
382         }
383     }
386 void
387 Effect::registerParameter(Parameter * param)
389     param_vector.push_back(param);
392 // TODO: should we provide a way to alter the handle's appearance?
393 void
394 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
396     kh_entity_vector.push_back(std::make_pair(entity, descr));
399 /**
400  * Add all registered LPE knotholder handles to the knotholder
401  */
402 void
403 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
404     std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
405     for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
406         KnotHolderEntity *entity = i->first;
407         const char *descr = i->second;
409         entity->create(desktop, item, knotholder, descr);
410         knotholder->add(entity);
411     }
414 void
415 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
416     using namespace std;
417     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
418         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
419             KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
420             e->create(desktop, item, knotholder);
421             knotholder->add(e);
422         }
423     }
426 void
427 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
429     g_return_if_fail(SP_IS_PATH(lpeitem));
431     if (providesKnotholder() && showOrigPath()) {
432         // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
433         // must create the helper curve for the original path manually; when we allow nodepaths and
434         // knotholders alongside each other, this needs to be rethought!
435         SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
436         // TODO: Make sure the tempitem doesn't get destroyed when the mouse leaves the item
437         Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
438         lpeitem->lpe_helperpaths.push_back(tmpitem);
439     }
441     addHelperPathsImpl(lpeitem, desktop);
444 void
445 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
447     // if this method is overloaded in derived classes, provides_own_flash_paths will be true
448     provides_own_flash_paths = false;
451 /**
452  * This *creates* a new widget, management of deletion should be done by the caller
453  */
454 Gtk::Widget *
455 Effect::newWidget(Gtk::Tooltips * tooltips)
457     // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
458     Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
460     vbox->set_border_width(5);
462     std::vector<Parameter *>::iterator it = param_vector.begin();
463     while (it != param_vector.end()) {
464         Parameter * param = *it;
465         Gtk::Widget * widg = param->param_newWidget(tooltips);
466         Glib::ustring * tip = param->param_getTooltip();
467         if (widg) {
468            vbox->pack_start(*widg, true, true, 2);
469             if (tip != NULL) {
470                 tooltips->set_tip(*widg, *tip);
471             }
472         }
474         it++;
475     }
477     return dynamic_cast<Gtk::Widget *>(vbox);
481 Inkscape::XML::Node *
482 Effect::getRepr()
484     return SP_OBJECT_REPR(lpeobj);
487 SPDocument *
488 Effect::getSPDoc()
490     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
491     return SP_OBJECT_DOCUMENT(lpeobj);
494 Parameter *
495 Effect::getParameter(const char * key)
497     Glib::ustring stringkey(key);
499     std::vector<Parameter *>::iterator it = param_vector.begin();
500     while (it != param_vector.end()) {
501         Parameter * param = *it;
502         if ( param->param_key == key) {
503             return param;
504         }
506         it++;
507     }
509     return NULL;
512 Parameter *
513 Effect::getNextOncanvasEditableParam()
515     if (param_vector.size() == 0) // no parameters
516         return NULL;
518     oncanvasedit_it++;
519     if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
520         oncanvasedit_it = 0;
521     }
522     int old_it = oncanvasedit_it;
524     do {
525         Parameter * param = param_vector[oncanvasedit_it];
526         if(param && param->oncanvas_editable) {
527             return param;
528         } else {
529             oncanvasedit_it++;
530             if (oncanvasedit_it == static_cast<int>(param_vector.size())) {  // loop round the map
531                 oncanvasedit_it = 0;
532             }
533         }
534     } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
536     return NULL;
539 void
540 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
542     if (!desktop) return;
544     Parameter * param = getNextOncanvasEditableParam();
545     if (param) {
546         param->param_editOncanvas(item, desktop);
547         gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
548         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
549         g_free(message);
550     } else {
551         desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
552                                         _("None of the applied path effect's parameters can be edited on-canvas.") );
553     }
556 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
557 * 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!
558 */
559 void
560 Effect::resetDefaults(SPItem * /*item*/)
562     // do nothing for simple effects
565 void
566 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
568     np->helperpath_rgba = 0xff0000ff;
569     np->helperpath_width = 1.0;
572 void
573 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
575     // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
576     for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
577         Parameter * param = *it;
578         param->param_transform_multiply(postmul, set);
579     }
582 bool
583 Effect::providesKnotholder()
585     // does the effect actively provide any knotholder entities of its own?
586     if (kh_entity_vector.size() > 0)
587         return true;
589     // otherwise: are there any PointParams?
590     for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
591         if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
592             return true;
593         }
594     }
596     return false;
599 } /* namespace LivePathEffect */
601 } /* namespace Inkscape */
603 /*
604   Local Variables:
605   mode:c++
606   c-file-style:"stroustrup"
607   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
608   indent-tabs-mode:nil
609   fill-column:99
610   End:
611 */
612 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :