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 "xml/node-event-vector.h"
12 #include "sp-object.h"
13 #include "attributes.h"
14 #include "message-stack.h"
15 #include "desktop.h"
16 #include "inkscape.h"
17 #include "document.h"
18 #include "document-private.h"
19 #include "xml/document.h"
20 #include <glibmm/i18n.h>
21 #include "pen-context.h"
22 #include "tools-switch.h"
23 #include "message-stack.h"
24 #include "desktop.h"
25 #include "nodepath.h"
27 #include "live_effects/lpeobject.h"
28 #include "live_effects/parameter/parameter.h"
29 #include <glibmm/ustring.h>
30 #include "libnr/n-art-bpath-2geom.h"
31 #include "display/curve.h"
32 #include <gtkmm.h>
34 #include <exception>
36 #include <2geom/sbasis-to-bezier.h>
37 #include <2geom/matrix.h>
38 #include <2geom/pathvector.h>
40 // include effects:
41 #include "live_effects/lpe-patternalongpath.h"
42 #include "live_effects/lpe-bendpath.h"
43 #include "live_effects/lpe-sketch.h"
44 #include "live_effects/lpe-vonkoch.h"
45 #include "live_effects/lpe-knot.h"
46 #include "live_effects/lpe-test-doEffect-stack.h"
47 #include "live_effects/lpe-gears.h"
48 #include "live_effects/lpe-curvestitch.h"
49 #include "live_effects/lpe-circle_with_radius.h"
50 #include "live_effects/lpe-perspective_path.h"
51 #include "live_effects/lpe-spiro.h"
52 #include "live_effects/lpe-lattice.h"
53 #include "live_effects/lpe-envelope.h"
54 #include "live_effects/lpe-constructgrid.h"
55 #include "live_effects/lpe-perp_bisector.h"
56 #include "live_effects/lpe-tangent_to_curve.h"
57 #include "live_effects/lpe-mirror_symmetry.h"
58 #include "live_effects/lpe-circle_3pts.h"
59 #include "live_effects/lpe-angle_bisector.h"
60 #include "live_effects/lpe-parallel.h"
61 #include "live_effects/lpe-copy_rotate.h"
62 #include "live_effects/lpe-offset.h"
63 #include "live_effects/lpe-ruler.h"
64 #include "live_effects/lpe-boolops.h"
65 #include "live_effects/lpe-interpolate.h"
66 #include "live_effects/lpe-text_label.h"
67 #include "live_effects/lpe-path_length.h"
68 // end of includes
70 namespace Inkscape {
72 namespace LivePathEffect {
74 const Util::EnumData<EffectType> LPETypeData[] = {
75 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
76 {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
77 {BEND_PATH, N_("Bend"), "bend_path"},
78 {BOOLOPS, N_("Boolops"), "boolops"},
79 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
80 {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
81 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
82 #ifdef LPE_ENABLE_TEST_EFFECTS
83 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
84 #endif
85 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
86 {FREEHAND_SHAPE, N_("Freehand Shape"), "freehand_shape"}, // this is actually a special type of PatternAlongPath, used to paste shapes in pen/pencil tool
87 {GEARS, N_("Gears"), "gears"},
88 {INTERPOLATE, N_("Interpolate Sub-Paths"), "interpolate"},
89 {KNOT, N_("Knot"), "knot"},
90 {LATTICE, N_("Lattice Deformation"), "lattice"},
91 {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"},
92 {OFFSET, N_("Offset"), "offset"},
93 {PARALLEL, N_("Parallel"), "parallel"},
94 {PATH_LENGTH, N_("Path length"), "path_length"},
95 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
96 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
97 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
98 {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
99 {RULER, N_("Ruler"), "ruler"},
100 {SKETCH, N_("Sketch"), "sketch"},
101 {SPIRO, N_("Spiro spline"), "spiro"},
102 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
103 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
104 {TEXT_LABEL, N_("Text label"), "text_label"},
105 {VONKOCH, N_("VonKoch"), "vonkoch"},
106 };
107 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, sizeof(LPETypeData)/sizeof(*LPETypeData));
109 Effect*
110 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
111 {
112 Effect* neweffect = NULL;
113 switch (lpenr) {
114 case PATTERN_ALONG_PATH:
115 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
116 break;
117 case FREEHAND_SHAPE:
118 neweffect = static_cast<Effect*> ( new LPEFreehandShape(lpeobj) );
119 break;
120 case BEND_PATH:
121 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
122 break;
123 case SKETCH:
124 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
125 break;
126 case VONKOCH:
127 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
128 break;
129 case KNOT:
130 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
131 break;
132 #ifdef LPE_ENABLE_TEST_EFFECTS
133 case DOEFFECTSTACK_TEST:
134 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
135 break;
136 #endif
137 case GEARS:
138 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
139 break;
140 case CURVE_STITCH:
141 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
142 break;
143 case LATTICE:
144 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
145 break;
146 case ENVELOPE:
147 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
148 break;
149 case CIRCLE_WITH_RADIUS:
150 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
151 break;
152 case PERSPECTIVE_PATH:
153 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
154 break;
155 case SPIRO:
156 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
157 break;
158 case CONSTRUCT_GRID:
159 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
160 break;
161 case PERP_BISECTOR:
162 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
163 break;
164 case TANGENT_TO_CURVE:
165 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
166 break;
167 case MIRROR_SYMMETRY:
168 neweffect = static_cast<Effect*> ( new LPEMirrorSymmetry(lpeobj) );
169 break;
170 case CIRCLE_3PTS:
171 neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
172 break;
173 case ANGLE_BISECTOR:
174 neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
175 break;
176 case PARALLEL:
177 neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
178 break;
179 case COPY_ROTATE:
180 neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
181 break;
182 case OFFSET:
183 neweffect = static_cast<Effect*> ( new LPEOffset(lpeobj) );
184 break;
185 case RULER:
186 neweffect = static_cast<Effect*> ( new LPERuler(lpeobj) );
187 break;
188 case BOOLOPS:
189 neweffect = static_cast<Effect*> ( new LPEBoolops(lpeobj) );
190 break;
191 case INTERPOLATE:
192 neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) );
193 break;
194 case TEXT_LABEL:
195 neweffect = static_cast<Effect*> ( new LPETextLabel(lpeobj) );
196 break;
197 case PATH_LENGTH:
198 neweffect = static_cast<Effect*> ( new LPEPathLength(lpeobj) );
199 break;
200 default:
201 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
202 neweffect = NULL;
203 break;
204 }
206 if (neweffect) {
207 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
208 }
210 return neweffect;
211 }
213 void
214 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
215 {
216 // Path effect definition
217 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
218 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
219 repr->setAttribute("effect", name);
221 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
222 const gchar * repr_id = repr->attribute("id");
223 Inkscape::GC::release(repr);
225 gchar *href = g_strdup_printf("#%s", repr_id);
226 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
227 g_free(href);
228 }
230 void
231 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
232 {
233 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
234 }
236 Effect::Effect(LivePathEffectObject *lpeobject)
237 : oncanvasedit_it(0),
238 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
239 done_pathparam_set(false),
240 show_orig_path(false),
241 lpeobj(lpeobject),
242 concatenate_before_pwd2(false),
243 provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
244 {
245 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
246 }
248 Effect::~Effect()
249 {
250 }
252 Glib::ustring
253 Effect::getName()
254 {
255 if (lpeobj->effecttype_set && LPETypeConverter.is_valid_id(lpeobj->effecttype) )
256 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
257 else
258 return Glib::ustring( _("No effect") );
259 }
261 EffectType
262 Effect::effectType() {
263 return lpeobj->effecttype;
264 }
266 /**
267 * Is performed a single time when the effect is freshly applied to a path
268 */
269 void
270 Effect::doOnApply (SPLPEItem */*lpeitem*/)
271 {
272 }
274 /**
275 * Is performed each time before the effect is updated.
276 */
277 void
278 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
279 {
280 //Do nothing for simple effects
281 }
283 /**
284 * Effects can have a parameter path set before they are applied by accepting a nonzero number of
285 * mouse clicks. This method activates the pen context, which waits for the specified number of
286 * clicks. Override Effect::acceptsNumParams() to return the number of expected mouse clicks.
287 */
288 void
289 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
290 {
291 // switch to pen context
292 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
293 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
294 tools_switch(desktop, TOOLS_FREEHAND_PEN);
295 }
297 SPEventContext *ec = desktop->event_context;
298 SPPenContext *pc = SP_PEN_CONTEXT(ec);
299 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
300 pc->waiting_LPE = this;
301 pc->waiting_item = lpeitem;
302 pc->polylines_only = true;
304 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
305 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
306 getName().c_str(), acceptsNumParams()));
307 }
309 void
310 Effect::writeParamsToSVG() {
311 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
312 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
313 (*p)->write_to_SVG();
314 }
315 }
317 /**
318 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
319 * applied, this is the method that processes the resulting path. Override it to customize it for
320 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
321 */
322 void
323 Effect::acceptParamPath (SPPath */*param_path*/) {
324 done_pathparam_set = true;
325 }
327 /*
328 * Here be the doEffect function chain:
329 */
330 void
331 Effect::doEffect (SPCurve * curve)
332 {
333 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
335 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
337 curve->set_pathvector(result_pathv);
338 }
340 std::vector<Geom::Path>
341 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
342 {
343 std::vector<Geom::Path> path_out;
345 if ( !concatenate_before_pwd2 ) {
346 // default behavior
347 for (unsigned int i=0; i < path_in.size(); i++) {
348 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
349 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
350 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
351 // add the output path vector to the already accumulated vector:
352 for (unsigned int j=0; j < path.size(); j++) {
353 path_out.push_back(path[j]);
354 }
355 }
356 } else {
357 // concatenate the path into possibly discontinuous pwd2
358 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
359 for (unsigned int i=0; i < path_in.size(); i++) {
360 pwd2_in.concat( path_in[i].toPwSb() );
361 }
362 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
363 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
364 }
366 return path_out;
367 }
369 Geom::Piecewise<Geom::D2<Geom::SBasis> >
370 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
371 {
372 g_warning("Effect has no doEffect implementation");
373 return pwd2_in;
374 }
376 void
377 Effect::readallParameters(Inkscape::XML::Node * repr)
378 {
379 std::vector<Parameter *>::iterator it = param_vector.begin();
380 while (it != param_vector.end()) {
381 Parameter * param = *it;
382 const gchar * key = param->param_key.c_str();
383 const gchar * value = repr->attribute(key);
384 if (value) {
385 bool accepted = param->param_readSVGValue(value);
386 if (!accepted) {
387 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
388 }
389 } else {
390 // set default value
391 param->param_set_default();
392 }
394 it++;
395 }
396 }
398 /* This function does not and SHOULD NOT write to XML */
399 void
400 Effect::setParameter(const gchar * key, const gchar * new_value)
401 {
402 Parameter * param = getParameter(key);
403 if (param) {
404 if (new_value) {
405 bool accepted = param->param_readSVGValue(new_value);
406 if (!accepted) {
407 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
408 }
409 } else {
410 // set default value
411 param->param_set_default();
412 }
413 }
414 }
416 void
417 Effect::registerParameter(Parameter * param)
418 {
419 param_vector.push_back(param);
420 }
422 // TODO: should we provide a way to alter the handle's appearance?
423 void
424 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
425 {
426 kh_entity_vector.push_back(std::make_pair(entity, descr));
427 }
429 /**
430 * Add all registered LPE knotholder handles to the knotholder
431 */
432 void
433 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
434 using namespace Inkscape::LivePathEffect;
436 // add handles provided by the effect itself
437 addKnotHolderEntities(knotholder, desktop, item);
439 // add handles provided by the effect's parameters (if any)
440 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
441 (*p)->addKnotHolderEntities(knotholder, desktop, item);
442 }
443 }
445 void
446 Effect::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
447 // TODO: The entities in kh_entity_vector are already instantiated during the call
448 // to registerKnotHolderHandle(), but they are recreated here. Also, we must not
449 // delete them when the knotholder is destroyed, whence the clumsy function
450 // isDeletable(). If we could create entities of different classes dynamically,
451 // this would be much nicer. How to do this?
452 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
453 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
454 KnotHolderEntity *entity = i->first;
455 const char *descr = i->second;
457 entity->create(desktop, item, knotholder, descr);
458 knotholder->add(entity);
459 }
460 }
462 /**
463 * Return a vector of PathVectors which contain all helperpaths that should be drawn by the effect.
464 * This is the function called by external code like SPLPEItem.
465 */
466 std::vector<Geom::PathVector>
467 Effect::getHelperPaths(SPLPEItem *lpeitem)
468 {
469 std::vector<Geom::PathVector> hp_vec;
471 if (!SP_IS_SHAPE(lpeitem)) {
472 g_print ("How to handle helperpaths for non-shapes?\n");
473 return hp_vec;
474 }
476 // TODO: we can probably optimize this by using a lot more references
477 // rather than copying PathVectors all over the place
478 if (show_orig_path) {
479 // add original path to helperpaths
480 SPCurve* curve = sp_shape_get_curve (SP_SHAPE(lpeitem));
481 hp_vec.push_back(curve->get_pathvector());
482 }
484 // add other helperpaths provided by the effect itself
485 addCanvasIndicators(lpeitem, hp_vec);
487 // add helperpaths provided by the effect's parameters
488 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
489 (*p)->addCanvasIndicators(lpeitem, hp_vec);
490 }
492 return hp_vec;
493 }
495 /**
496 * Add possible canvas indicators (i.e., helperpaths other than the original path) to \a hp_vec
497 * This function should be overwritten by derived effects if they want to provide their own helperpaths.
498 */
499 void
500 Effect::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &/*hp_vec*/)
501 {
502 }
505 /**
506 * This *creates* a new widget, management of deletion should be done by the caller
507 */
508 Gtk::Widget *
509 Effect::newWidget(Gtk::Tooltips * tooltips)
510 {
511 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
512 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
514 vbox->set_border_width(5);
516 std::vector<Parameter *>::iterator it = param_vector.begin();
517 while (it != param_vector.end()) {
518 Parameter * param = *it;
519 Gtk::Widget * widg = param->param_newWidget(tooltips);
520 Glib::ustring * tip = param->param_getTooltip();
521 if (widg) {
522 vbox->pack_start(*widg, true, true, 2);
523 if (tip != NULL) {
524 tooltips->set_tip(*widg, *tip);
525 }
526 }
528 it++;
529 }
531 return dynamic_cast<Gtk::Widget *>(vbox);
532 }
535 Inkscape::XML::Node *
536 Effect::getRepr()
537 {
538 return SP_OBJECT_REPR(lpeobj);
539 }
541 SPDocument *
542 Effect::getSPDoc()
543 {
544 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
545 return SP_OBJECT_DOCUMENT(lpeobj);
546 }
548 Parameter *
549 Effect::getParameter(const char * key)
550 {
551 Glib::ustring stringkey(key);
553 std::vector<Parameter *>::iterator it = param_vector.begin();
554 while (it != param_vector.end()) {
555 Parameter * param = *it;
556 if ( param->param_key == key) {
557 return param;
558 }
560 it++;
561 }
563 return NULL;
564 }
566 Parameter *
567 Effect::getNextOncanvasEditableParam()
568 {
569 if (param_vector.size() == 0) // no parameters
570 return NULL;
572 oncanvasedit_it++;
573 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
574 oncanvasedit_it = 0;
575 }
576 int old_it = oncanvasedit_it;
578 do {
579 Parameter * param = param_vector[oncanvasedit_it];
580 if(param && param->oncanvas_editable) {
581 return param;
582 } else {
583 oncanvasedit_it++;
584 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
585 oncanvasedit_it = 0;
586 }
587 }
588 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
590 return NULL;
591 }
593 void
594 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
595 {
596 if (!desktop) return;
598 Parameter * param = getNextOncanvasEditableParam();
599 if (param) {
600 param->param_editOncanvas(item, desktop);
601 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
602 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
603 g_free(message);
604 } else {
605 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
606 _("None of the applied path effect's parameters can be edited on-canvas.") );
607 }
608 }
610 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
611 * 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!
612 */
613 void
614 Effect::resetDefaults(SPItem * /*item*/)
615 {
616 // do nothing for simple effects
617 }
619 void
620 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
621 {
622 np->helperpath_rgba = 0xff0000ff;
623 np->helperpath_width = 1.0;
624 }
626 void
627 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
628 {
629 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
630 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
631 Parameter * param = *it;
632 param->param_transform_multiply(postmul, set);
633 }
634 }
636 // TODO: take _all_ parameters into account, not only PointParams
637 bool
638 Effect::providesKnotholder()
639 {
640 // does the effect actively provide any knotholder entities of its own?
641 if (kh_entity_vector.size() > 0)
642 return true;
644 // otherwise: are there any PointParams?
645 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
646 // if ( Inkscape::LivePathEffect::PointParam *pointparam = dynamic_cast<Inkscape::LivePathEffect::PointParam*>(*p) ) {
647 if (dynamic_cast<Inkscape::LivePathEffect::PointParam*>(*p)) {
648 return true;
649 }
650 }
652 return false;
653 }
655 } /* namespace LivePathEffect */
657 } /* namespace Inkscape */
659 /*
660 Local Variables:
661 mode:c++
662 c-file-style:"stroustrup"
663 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
664 indent-tabs-mode:nil
665 fill-column:99
666 End:
667 */
668 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :