164447387a2a80c3989419dc4eb961b089a3b3e1
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 // end of includes
63 namespace Inkscape {
65 namespace LivePathEffect {
67 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
68 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
69 {BEND_PATH, N_("Bend"), "bend_path"},
70 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
71 {SKETCH, N_("Sketch"), "sketch"},
72 {VONKOCH, N_("VonKoch"), "vonkoch"},
73 {KNOT, N_("Knot"), "knot"},
74 #ifdef LPE_ENABLE_TEST_EFFECTS
75 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
76 #endif
77 {GEARS, N_("Gears"), "gears"},
78 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
79 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
80 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
81 {SPIRO, N_("Spiro spline"), "spiro"},
82 {LATTICE, N_("Lattice Deformation"), "lattice"},
83 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
84 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
85 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
86 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
87 {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
88 {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
89 {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
90 };
91 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
93 Effect*
94 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
95 {
96 Effect* neweffect = NULL;
97 switch (lpenr) {
98 case PATTERN_ALONG_PATH:
99 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
100 break;
101 case BEND_PATH:
102 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
103 break;
104 case SKETCH:
105 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
106 break;
107 case VONKOCH:
108 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
109 break;
110 case KNOT:
111 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
112 break;
113 #ifdef LPE_ENABLE_TEST_EFFECTS
114 case DOEFFECTSTACK_TEST:
115 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
116 break;
117 #endif
118 case GEARS:
119 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
120 break;
121 case CURVE_STITCH:
122 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
123 break;
124 case LATTICE:
125 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
126 break;
127 case ENVELOPE:
128 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
129 break;
130 case CIRCLE_WITH_RADIUS:
131 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
132 break;
133 case PERSPECTIVE_PATH:
134 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
135 break;
136 case SPIRO:
137 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
138 break;
139 case CONSTRUCT_GRID:
140 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
141 break;
142 case PERP_BISECTOR:
143 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
144 break;
145 case TANGENT_TO_CURVE:
146 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
147 break;
148 case MIRROR_REFLECT:
149 neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
150 break;
151 case CIRCLE_3PTS:
152 neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
153 break;
154 case ANGLE_BISECTOR:
155 neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
156 break;
157 default:
158 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
159 neweffect = NULL;
160 break;
161 }
163 if (neweffect) {
164 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
165 }
167 return neweffect;
168 }
170 void
171 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
172 {
173 // Path effect definition
174 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
175 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
176 repr->setAttribute("effect", name);
178 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
179 const gchar * repr_id = repr->attribute("id");
180 Inkscape::GC::release(repr);
182 gchar *href = g_strdup_printf("#%s", repr_id);
183 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
184 g_free(href);
186 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
187 _("Create and apply path effect"));
188 }
190 void
191 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
192 {
193 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
194 }
196 Effect::Effect(LivePathEffectObject *lpeobject)
197 : oncanvasedit_it(0),
198 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
199 done_pathparam_set(false),
200 show_orig_path(false),
201 lpeobj(lpeobject),
202 concatenate_before_pwd2(false),
203 provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
204 {
205 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
206 }
208 Effect::~Effect()
209 {
210 }
212 Glib::ustring
213 Effect::getName()
214 {
215 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
216 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
217 else
218 return Glib::ustring( _("No effect") );
219 }
221 EffectType
222 Effect::effectType() {
223 return lpeobj->effecttype;
224 }
226 /**
227 * Is performed a single time when the effect is freshly applied to a path
228 */
229 void
230 Effect::doOnApply (SPLPEItem */*lpeitem*/)
231 {
232 }
234 /**
235 * Is performed each time before the effect is updated.
236 */
237 void
238 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
239 {
240 //Do nothing for simple effects
241 }
243 /**
244 * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
245 * clicks. This method activates the pen context, which waits for the specified number of clicks.
246 * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
247 */
248 void
249 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
250 {
251 // switch to pen context
252 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
253 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
254 tools_switch(desktop, TOOLS_FREEHAND_PEN);
255 }
257 SPEventContext *ec = desktop->event_context;
258 SPPenContext *pc = SP_PEN_CONTEXT(ec);
259 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
260 pc->waiting_LPE = this;
261 pc->waiting_item = lpeitem;
262 pc->polylines_only = true;
264 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
265 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
266 getName().c_str(), acceptsNumParams()));
267 }
269 void
270 Effect::writeParamsToSVG() {
271 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
272 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
273 (*p)->write_to_SVG();
274 }
275 }
277 /**
278 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
279 * applied, this is the method that processes the resulting path. Override it to customize it for
280 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
281 */
282 void
283 Effect::acceptParamPath (SPPath *param_path) {
284 done_pathparam_set = true;
285 }
287 /*
288 * Here be the doEffect function chain:
289 */
290 void
291 Effect::doEffect (SPCurve * curve)
292 {
293 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
295 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
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 void
417 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
418 {
419 g_return_if_fail(SP_IS_PATH(lpeitem));
421 if (providesKnotholder() && showOrigPath()) {
422 // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
423 // must create the helper curve for the original path manually; when we allow nodepaths and
424 // knotholders alongside each other, this needs to be rethought!
425 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
426 // TODO: Make sure the tempitem doesn't get destroyed when the mouse leaves the item
427 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
428 lpeitem->lpe_helperpaths.push_back(tmpitem);
429 }
431 addHelperPathsImpl(lpeitem, desktop);
432 }
434 void
435 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
436 {
437 // if this method is overloaded in derived classes, provides_own_flash_paths will be true
438 provides_own_flash_paths = false;
439 }
441 /**
442 * This *creates* a new widget, management of deletion should be done by the caller
443 */
444 Gtk::Widget *
445 Effect::newWidget(Gtk::Tooltips * tooltips)
446 {
447 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
448 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
450 vbox->set_border_width(5);
452 std::vector<Parameter *>::iterator it = param_vector.begin();
453 while (it != param_vector.end()) {
454 Parameter * param = *it;
455 Gtk::Widget * widg = param->param_newWidget(tooltips);
456 Glib::ustring * tip = param->param_getTooltip();
457 if (widg) {
458 vbox->pack_start(*widg, true, true, 2);
459 if (tip != NULL) {
460 tooltips->set_tip(*widg, *tip);
461 }
462 }
464 it++;
465 }
467 return dynamic_cast<Gtk::Widget *>(vbox);
468 }
471 Inkscape::XML::Node *
472 Effect::getRepr()
473 {
474 return SP_OBJECT_REPR(lpeobj);
475 }
477 SPDocument *
478 Effect::getSPDoc()
479 {
480 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
481 return SP_OBJECT_DOCUMENT(lpeobj);
482 }
484 Parameter *
485 Effect::getParameter(const char * key)
486 {
487 Glib::ustring stringkey(key);
489 std::vector<Parameter *>::iterator it = param_vector.begin();
490 while (it != param_vector.end()) {
491 Parameter * param = *it;
492 if ( param->param_key == key) {
493 return param;
494 }
496 it++;
497 }
499 return NULL;
500 }
502 Parameter *
503 Effect::getNextOncanvasEditableParam()
504 {
505 if (param_vector.size() == 0) // no parameters
506 return NULL;
508 oncanvasedit_it++;
509 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
510 oncanvasedit_it = 0;
511 }
512 int old_it = oncanvasedit_it;
514 do {
515 Parameter * param = param_vector[oncanvasedit_it];
516 if(param && param->oncanvas_editable) {
517 return param;
518 } else {
519 oncanvasedit_it++;
520 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
521 oncanvasedit_it = 0;
522 }
523 }
524 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
526 return NULL;
527 }
529 void
530 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
531 {
532 if (!desktop) return;
534 Parameter * param = getNextOncanvasEditableParam();
535 if (param) {
536 param->param_editOncanvas(item, desktop);
537 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
538 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
539 g_free(message);
540 } else {
541 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
542 _("None of the applied path effect's parameters can be edited on-canvas.") );
543 }
544 }
546 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
547 * 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!
548 */
549 void
550 Effect::resetDefaults(SPItem * /*item*/)
551 {
552 // do nothing for simple effects
553 }
555 void
556 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
557 {
558 np->helperpath_rgba = 0xff0000ff;
559 np->helperpath_width = 1.0;
560 }
562 void
563 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
564 {
565 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
566 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
567 Parameter * param = *it;
568 param->param_transform_multiply(postmul, set);
569 }
570 }
572 } /* namespace LivePathEffect */
574 } /* namespace Inkscape */
576 /*
577 Local Variables:
578 mode:c++
579 c-file-style:"stroustrup"
580 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
581 indent-tabs-mode:nil
582 fill-column:99
583 End:
584 */
585 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :