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"
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>
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_reflect.h"
58 // end of includes
60 #include "nodepath.h"
62 namespace Inkscape {
64 namespace LivePathEffect {
66 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
67 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
68 {BEND_PATH, N_("Bend"), "bend_path"},
69 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
70 {SKETCH, N_("Sketch"), "sketch"},
71 {VONKOCH, N_("VonKoch"), "vonkoch"},
72 {KNOT, N_("Knot"), "knot"},
73 #ifdef LPE_ENABLE_TEST_EFFECTS
74 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
75 #endif
76 {GEARS, N_("Gears"), "gears"},
77 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
78 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
79 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
80 {SPIRO, N_("Spiro spline"), "spiro"},
81 {LATTICE, N_("Lattice Deformation"), "lattice"},
82 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
83 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
84 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
85 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
86 {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
87 };
88 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
90 Effect*
91 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
92 {
93 Effect* neweffect = NULL;
94 switch (lpenr) {
95 case PATTERN_ALONG_PATH:
96 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
97 break;
98 case BEND_PATH:
99 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
100 break;
101 case SKETCH:
102 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
103 break;
104 case VONKOCH:
105 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
106 break;
107 case KNOT:
108 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
109 break;
110 #ifdef LPE_ENABLE_TEST_EFFECTS
111 case DOEFFECTSTACK_TEST:
112 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
113 break;
114 #endif
115 case GEARS:
116 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
117 break;
118 case CURVE_STITCH:
119 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
120 break;
121 case LATTICE:
122 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
123 break;
124 case ENVELOPE:
125 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
126 break;
127 case CIRCLE_WITH_RADIUS:
128 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
129 break;
130 case PERSPECTIVE_PATH:
131 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
132 break;
133 case SPIRO:
134 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
135 break;
136 case CONSTRUCT_GRID:
137 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
138 break;
139 case PERP_BISECTOR:
140 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
141 break;
142 case TANGENT_TO_CURVE:
143 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
144 break;
145 case MIRROR_REFLECT:
146 neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
147 break;
148 default:
149 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
150 neweffect = NULL;
151 break;
152 }
154 if (neweffect) {
155 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
156 }
158 return neweffect;
159 }
161 void
162 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
163 {
164 // Path effect definition
165 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
166 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
167 repr->setAttribute("effect", name);
169 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
170 const gchar * repr_id = repr->attribute("id");
171 Inkscape::GC::release(repr);
173 gchar *href = g_strdup_printf("#%s", repr_id);
174 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
175 g_free(href);
177 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
178 _("Create and apply path effect"));
179 }
181 void
182 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
183 {
184 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
185 }
187 Effect::Effect(LivePathEffectObject *lpeobject)
188 : oncanvasedit_it(0),
189 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
190 done_pathparam_set(false),
191 lpeobj(lpeobject),
192 concatenate_before_pwd2(false)
193 {
194 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
195 }
197 Effect::~Effect()
198 {
199 }
201 Glib::ustring
202 Effect::getName()
203 {
204 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
205 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
206 else
207 return Glib::ustring( _("No effect") );
208 }
210 EffectType
211 Effect::effectType() {
212 return lpeobj->effecttype;
213 }
215 /**
216 * Is performed a single time when the effect is freshly applied to a path
217 */
218 void
219 Effect::doOnApply (SPLPEItem */*lpeitem*/)
220 {
221 }
223 /**
224 * Is performed each time before the effect is updated.
225 */
226 void
227 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
228 {
229 //Do nothing for simple effects
230 }
232 /**
233 * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
234 * clicks. This method activates the pen context, which waits for the specified number of clicks.
235 * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
236 */
237 void
238 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
239 {
240 // switch to pen context
241 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
242 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
243 tools_switch(desktop, TOOLS_FREEHAND_PEN);
244 }
246 SPEventContext *ec = desktop->event_context;
247 SPPenContext *pc = SP_PEN_CONTEXT(ec);
248 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
249 pc->waiting_LPE = this;
250 pc->waiting_item = lpeitem;
251 pc->polylines_only = true;
253 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
254 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
255 getName().c_str(), acceptsNumParams()));
256 }
258 void
259 Effect::writeParamsToSVG() {
260 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
261 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
262 (*p)->write_to_SVG();
263 }
264 }
266 /**
267 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
268 * applied, this is the method that processes the resulting path. Override it to customize it for
269 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
270 */
271 void
272 Effect::acceptParamPath (SPPath *param_path) {
273 done_pathparam_set = true;
274 }
276 /*
277 * Here be the doEffect function chain:
278 */
279 void
280 Effect::doEffect (SPCurve * curve)
281 {
282 NArtBpath const *bpath_in = curve->get_bpath();
284 std::vector<Geom::Path> result_pathv;
286 try {
287 std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
289 result_pathv = doEffect_path(orig_pathv);
290 }
291 catch (std::exception & e) {
292 g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
293 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
294 _("An exception occurred during execution of the Path Effect.") );
295 }
297 curve->set_pathv(result_pathv);
298 }
300 std::vector<Geom::Path>
301 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
302 {
303 std::vector<Geom::Path> path_out;
305 if ( !concatenate_before_pwd2 ) {
306 // default behavior
307 for (unsigned int i=0; i < path_in.size(); i++) {
308 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
309 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
310 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
311 // add the output path vector to the already accumulated vector:
312 for (unsigned int j=0; j < path.size(); j++) {
313 path_out.push_back(path[j]);
314 }
315 }
316 } else {
317 // concatenate the path into possibly discontinuous pwd2
318 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
319 for (unsigned int i=0; i < path_in.size(); i++) {
320 pwd2_in.concat( path_in[i].toPwSb() );
321 }
322 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
323 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
324 }
326 return path_out;
327 }
329 Geom::Piecewise<Geom::D2<Geom::SBasis> >
330 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
331 {
332 g_warning("Effect has no doEffect implementation");
333 return pwd2_in;
334 }
336 void
337 Effect::readallParameters(Inkscape::XML::Node * repr)
338 {
339 std::vector<Parameter *>::iterator it = param_vector.begin();
340 while (it != param_vector.end()) {
341 Parameter * param = *it;
342 const gchar * key = param->param_key.c_str();
343 const gchar * value = repr->attribute(key);
344 if (value) {
345 bool accepted = param->param_readSVGValue(value);
346 if (!accepted) {
347 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
348 }
349 } else {
350 // set default value
351 param->param_set_default();
352 }
354 it++;
355 }
356 }
358 /* This function does not and SHOULD NOT write to XML */
359 void
360 Effect::setParameter(const gchar * key, const gchar * new_value)
361 {
362 Parameter * param = getParameter(key);
363 if (param) {
364 if (new_value) {
365 bool accepted = param->param_readSVGValue(new_value);
366 if (!accepted) {
367 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
368 }
369 } else {
370 // set default value
371 param->param_set_default();
372 }
373 }
374 }
376 void
377 Effect::registerParameter(Parameter * param)
378 {
379 param_vector.push_back(param);
380 }
382 // TODO: should we provide a way to alter the handle's appearance?
383 void
384 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
385 {
386 kh_entity_vector.push_back(std::make_pair(entity, descr));
387 }
389 /**
390 * Add all registered LPE knotholder handles to the knotholder
391 */
392 void
393 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
394 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
395 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
396 KnotHolderEntity *entity = i->first;
397 const char *descr = i->second;
399 entity->create(desktop, item, knotholder, descr);
400 knotholder->add(entity);
401 }
402 }
404 void
405 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
406 using namespace std;
407 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
408 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
409 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
410 e->create(desktop, item, knotholder);
411 knotholder->add(e);
412 }
413 }
414 }
416 /**
417 * This *creates* a new widget, management of deletion should be done by the caller
418 */
419 Gtk::Widget *
420 Effect::newWidget(Gtk::Tooltips * tooltips)
421 {
422 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
423 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
425 vbox->set_border_width(5);
427 std::vector<Parameter *>::iterator it = param_vector.begin();
428 while (it != param_vector.end()) {
429 Parameter * param = *it;
430 Gtk::Widget * widg = param->param_newWidget(tooltips);
431 Glib::ustring * tip = param->param_getTooltip();
432 if (widg) {
433 vbox->pack_start(*widg, true, true, 2);
434 if (tip != NULL) {
435 tooltips->set_tip(*widg, *tip);
436 }
437 }
439 it++;
440 }
442 return dynamic_cast<Gtk::Widget *>(vbox);
443 }
446 Inkscape::XML::Node *
447 Effect::getRepr()
448 {
449 return SP_OBJECT_REPR(lpeobj);
450 }
452 SPDocument *
453 Effect::getSPDoc()
454 {
455 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
456 return SP_OBJECT_DOCUMENT(lpeobj);
457 }
459 Parameter *
460 Effect::getParameter(const char * key)
461 {
462 Glib::ustring stringkey(key);
464 std::vector<Parameter *>::iterator it = param_vector.begin();
465 while (it != param_vector.end()) {
466 Parameter * param = *it;
467 if ( param->param_key == key) {
468 return param;
469 }
471 it++;
472 }
474 return NULL;
475 }
477 Parameter *
478 Effect::getNextOncanvasEditableParam()
479 {
480 if (param_vector.size() == 0) // no parameters
481 return NULL;
483 oncanvasedit_it++;
484 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
485 oncanvasedit_it = 0;
486 }
487 int old_it = oncanvasedit_it;
489 do {
490 Parameter * param = param_vector[oncanvasedit_it];
491 if(param && param->oncanvas_editable) {
492 return param;
493 } else {
494 oncanvasedit_it++;
495 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
496 oncanvasedit_it = 0;
497 }
498 }
499 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
501 return NULL;
502 }
504 void
505 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
506 {
507 if (!desktop) return;
509 Parameter * param = getNextOncanvasEditableParam();
510 if (param) {
511 param->param_editOncanvas(item, desktop);
512 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
513 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
514 g_free(message);
515 } else {
516 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
517 _("None of the applied path effect's parameters can be edited on-canvas.") );
518 }
519 }
521 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
522 * 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!
523 */
524 void
525 Effect::resetDefaults(SPItem * /*item*/)
526 {
527 // do nothing for simple effects
528 }
530 void
531 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
532 {
533 np->helperpath_rgba = 0xff0000ff;
534 np->helperpath_width = 1.0;
535 }
537 void
538 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
539 {
540 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
541 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
542 Parameter * param = *it;
543 param->param_transform_multiply(postmul, set);
544 }
545 }
547 } /* namespace LivePathEffect */
549 } /* namespace Inkscape */
551 /*
552 Local Variables:
553 mode:c++
554 c-file-style:"stroustrup"
555 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
556 indent-tabs-mode:nil
557 fill-column:99
558 End:
559 */
560 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :