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