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 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)
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_pathv(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 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 }
424 }
426 void
427 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
428 {
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);
442 }
444 void
445 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
446 {
447 // if this method is overloaded in derived classes, provides_own_flash_paths will be true
448 provides_own_flash_paths = false;
449 }
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)
456 {
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);
478 }
481 Inkscape::XML::Node *
482 Effect::getRepr()
483 {
484 return SP_OBJECT_REPR(lpeobj);
485 }
487 SPDocument *
488 Effect::getSPDoc()
489 {
490 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
491 return SP_OBJECT_DOCUMENT(lpeobj);
492 }
494 Parameter *
495 Effect::getParameter(const char * key)
496 {
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;
510 }
512 Parameter *
513 Effect::getNextOncanvasEditableParam()
514 {
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;
537 }
539 void
540 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
541 {
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 }
554 }
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*/)
561 {
562 // do nothing for simple effects
563 }
565 void
566 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
567 {
568 np->helperpath_rgba = 0xff0000ff;
569 np->helperpath_width = 1.0;
570 }
572 void
573 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
574 {
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 }
580 }
582 bool
583 Effect::providesKnotholder()
584 {
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;
597 }
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 :