74e5c29fd17010f95a6dca8f8566fff81554e626
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;
178 }
180 void
181 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
182 {
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"));
198 }
200 void
201 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
202 {
203 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
204 }
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
214 {
215 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
216 }
218 Effect::~Effect()
219 {
220 }
222 Glib::ustring
223 Effect::getName()
224 {
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") );
229 }
231 EffectType
232 Effect::effectType() {
233 return lpeobj->effecttype;
234 }
236 /**
237 * Is performed a single time when the effect is freshly applied to a path
238 */
239 void
240 Effect::doOnApply (SPLPEItem */*lpeitem*/)
241 {
242 }
244 /**
245 * Is performed each time before the effect is updated.
246 */
247 void
248 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
249 {
250 //Do nothing for simple effects
251 }
253 /**
254 * Effects can have a parameter path set before they are applied by accepting a nonzero number of
255 * mouse clicks. This method activates the pen context, which waits for the specified number of
256 * clicks. Override Effect::acceptsNumParams() to return the number of expected mouse clicks.
257 */
258 void
259 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
260 {
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()));
277 }
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 }
285 }
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;
295 }
297 /*
298 * Here be the doEffect function chain:
299 */
300 void
301 Effect::doEffect (SPCurve * curve)
302 {
303 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
305 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
307 curve->set_pathvector(result_pathv);
308 }
310 std::vector<Geom::Path>
311 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
312 {
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;
337 }
339 Geom::Piecewise<Geom::D2<Geom::SBasis> >
340 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
341 {
342 g_warning("Effect has no doEffect implementation");
343 return pwd2_in;
344 }
346 void
347 Effect::readallParameters(Inkscape::XML::Node * repr)
348 {
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 }
366 }
368 /* This function does not and SHOULD NOT write to XML */
369 void
370 Effect::setParameter(const gchar * key, const gchar * new_value)
371 {
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 }
384 }
386 void
387 Effect::registerParameter(Parameter * param)
388 {
389 param_vector.push_back(param);
390 }
392 // TODO: should we provide a way to alter the handle's appearance?
393 void
394 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
395 {
396 kh_entity_vector.push_back(std::make_pair(entity, descr));
397 }
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 }
412 }
414 void
415 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
416 using namespace Inkscape::LivePathEffect;
417 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
418 if ((*p)->paramType() == POINT_PARAM) {
419 PointParam *pparam = static_cast<PointParam *>(*p);
420 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
421 e->create(desktop, item, knotholder, pparam->handleTip());
422 knotholder->add(e);
423 }
424 }
425 }
427 void
428 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
429 {
430 g_return_if_fail(desktop);
431 g_return_if_fail(SP_IS_PATH(lpeitem));
433 if (providesKnotholder() && showOrigPath()) {
434 // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
435 // must create the helper curve for the original path manually; once we allow nodepaths and
436 // knotholders alongside each other, this needs to be rethought!
437 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
438 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
439 lpeitem->lpe_helperpaths.push_back(tmpitem);
440 }
442 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
443 if ((*p)->paramType() == Inkscape::LivePathEffect::PATH_PARAM) {
444 SPCurve *c = new SPCurve(static_cast<Inkscape::LivePathEffect::PathParam*>(*p)->get_pathvector());
446 // TODO: factor this out (also the copied code above); see also lpe-lattice.cpp
447 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, c, SP_ITEM(lpeitem), 0x009000ff);
448 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
449 lpeitem->lpe_helperpaths.push_back(tmpitem);
450 }
451 }
453 addHelperPathsImpl(lpeitem, desktop);
454 }
456 void
457 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
458 {
459 // if this method is overloaded in derived classes, provides_own_flash_paths will be true
460 provides_own_flash_paths = false;
461 }
463 /**
464 * This *creates* a new widget, management of deletion should be done by the caller
465 */
466 Gtk::Widget *
467 Effect::newWidget(Gtk::Tooltips * tooltips)
468 {
469 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
470 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
472 vbox->set_border_width(5);
474 std::vector<Parameter *>::iterator it = param_vector.begin();
475 while (it != param_vector.end()) {
476 Parameter * param = *it;
477 Gtk::Widget * widg = param->param_newWidget(tooltips);
478 Glib::ustring * tip = param->param_getTooltip();
479 if (widg) {
480 vbox->pack_start(*widg, true, true, 2);
481 if (tip != NULL) {
482 tooltips->set_tip(*widg, *tip);
483 }
484 }
486 it++;
487 }
489 return dynamic_cast<Gtk::Widget *>(vbox);
490 }
493 Inkscape::XML::Node *
494 Effect::getRepr()
495 {
496 return SP_OBJECT_REPR(lpeobj);
497 }
499 SPDocument *
500 Effect::getSPDoc()
501 {
502 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
503 return SP_OBJECT_DOCUMENT(lpeobj);
504 }
506 Parameter *
507 Effect::getParameter(const char * key)
508 {
509 Glib::ustring stringkey(key);
511 std::vector<Parameter *>::iterator it = param_vector.begin();
512 while (it != param_vector.end()) {
513 Parameter * param = *it;
514 if ( param->param_key == key) {
515 return param;
516 }
518 it++;
519 }
521 return NULL;
522 }
524 Parameter *
525 Effect::getNextOncanvasEditableParam()
526 {
527 if (param_vector.size() == 0) // no parameters
528 return NULL;
530 oncanvasedit_it++;
531 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
532 oncanvasedit_it = 0;
533 }
534 int old_it = oncanvasedit_it;
536 do {
537 Parameter * param = param_vector[oncanvasedit_it];
538 if(param && param->oncanvas_editable) {
539 return param;
540 } else {
541 oncanvasedit_it++;
542 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
543 oncanvasedit_it = 0;
544 }
545 }
546 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
548 return NULL;
549 }
551 void
552 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
553 {
554 if (!desktop) return;
556 Parameter * param = getNextOncanvasEditableParam();
557 if (param) {
558 param->param_editOncanvas(item, desktop);
559 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
560 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
561 g_free(message);
562 } else {
563 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
564 _("None of the applied path effect's parameters can be edited on-canvas.") );
565 }
566 }
568 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
569 * 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!
570 */
571 void
572 Effect::resetDefaults(SPItem * /*item*/)
573 {
574 // do nothing for simple effects
575 }
577 void
578 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
579 {
580 np->helperpath_rgba = 0xff0000ff;
581 np->helperpath_width = 1.0;
582 }
584 void
585 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
586 {
587 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
588 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
589 Parameter * param = *it;
590 param->param_transform_multiply(postmul, set);
591 }
592 }
594 bool
595 Effect::providesKnotholder()
596 {
597 // does the effect actively provide any knotholder entities of its own?
598 if (kh_entity_vector.size() > 0)
599 return true;
601 // otherwise: are there any PointParams?
602 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
603 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
604 return true;
605 }
606 }
608 return false;
609 }
611 } /* namespace LivePathEffect */
613 } /* namespace Inkscape */
615 /*
616 Local Variables:
617 mode:c++
618 c-file-style:"stroustrup"
619 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
620 indent-tabs-mode:nil
621 fill-column:99
622 End:
623 */
624 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :