4f4aaff3fc88775d4ea648aad75511e99effd814
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 // end of includes
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 {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
88 };
89 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
91 Effect*
92 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
93 {
94 Effect* neweffect = NULL;
95 switch (lpenr) {
96 case PATTERN_ALONG_PATH:
97 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
98 break;
99 case BEND_PATH:
100 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
101 break;
102 case SKETCH:
103 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
104 break;
105 case VONKOCH:
106 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
107 break;
108 case KNOT:
109 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
110 break;
111 #ifdef LPE_ENABLE_TEST_EFFECTS
112 case DOEFFECTSTACK_TEST:
113 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
114 break;
115 #endif
116 case GEARS:
117 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
118 break;
119 case CURVE_STITCH:
120 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
121 break;
122 case LATTICE:
123 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
124 break;
125 case ENVELOPE:
126 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
127 break;
128 case CIRCLE_WITH_RADIUS:
129 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
130 break;
131 case PERSPECTIVE_PATH:
132 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
133 break;
134 case SPIRO:
135 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
136 break;
137 case CONSTRUCT_GRID:
138 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
139 break;
140 case PERP_BISECTOR:
141 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
142 break;
143 case TANGENT_TO_CURVE:
144 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
145 break;
146 case MIRROR_REFLECT:
147 neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
148 break;
149 case CIRCLE_3PTS:
150 neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
151 break;
152 default:
153 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
154 neweffect = NULL;
155 break;
156 }
158 if (neweffect) {
159 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
160 }
162 return neweffect;
163 }
165 void
166 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
167 {
168 // Path effect definition
169 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
170 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
171 repr->setAttribute("effect", name);
173 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
174 const gchar * repr_id = repr->attribute("id");
175 Inkscape::GC::release(repr);
177 gchar *href = g_strdup_printf("#%s", repr_id);
178 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
179 g_free(href);
181 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
182 _("Create and apply path effect"));
183 }
185 void
186 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
187 {
188 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
189 }
191 Effect::Effect(LivePathEffectObject *lpeobject)
192 : oncanvasedit_it(0),
193 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
194 done_pathparam_set(false),
195 provides_own_flash_paths(true), // is automatically set to false if providesOwnFlashPaths() is not overridden
196 show_orig_path(false),
197 lpeobj(lpeobject),
198 concatenate_before_pwd2(false)
199 {
200 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
201 }
203 Effect::~Effect()
204 {
205 }
207 Glib::ustring
208 Effect::getName()
209 {
210 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
211 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
212 else
213 return Glib::ustring( _("No effect") );
214 }
216 EffectType
217 Effect::effectType() {
218 return lpeobj->effecttype;
219 }
221 /**
222 * Is performed a single time when the effect is freshly applied to a path
223 */
224 void
225 Effect::doOnApply (SPLPEItem */*lpeitem*/)
226 {
227 }
229 /**
230 * Is performed each time before the effect is updated.
231 */
232 void
233 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
234 {
235 //Do nothing for simple effects
236 }
238 /**
239 * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
240 * clicks. This method activates the pen context, which waits for the specified number of clicks.
241 * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
242 */
243 void
244 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
245 {
246 // switch to pen context
247 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
248 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
249 tools_switch(desktop, TOOLS_FREEHAND_PEN);
250 }
252 SPEventContext *ec = desktop->event_context;
253 SPPenContext *pc = SP_PEN_CONTEXT(ec);
254 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
255 pc->waiting_LPE = this;
256 pc->waiting_item = lpeitem;
257 pc->polylines_only = true;
259 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
260 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
261 getName().c_str(), acceptsNumParams()));
262 }
264 void
265 Effect::writeParamsToSVG() {
266 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
267 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
268 (*p)->write_to_SVG();
269 }
270 }
272 /**
273 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
274 * applied, this is the method that processes the resulting path. Override it to customize it for
275 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
276 */
277 void
278 Effect::acceptParamPath (SPPath *param_path) {
279 done_pathparam_set = true;
280 }
282 /*
283 * Here be the doEffect function chain:
284 */
285 void
286 Effect::doEffect (SPCurve * curve)
287 {
288 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
290 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
292 curve->set_pathv(result_pathv);
293 }
295 std::vector<Geom::Path>
296 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
297 {
298 std::vector<Geom::Path> path_out;
300 if ( !concatenate_before_pwd2 ) {
301 // default behavior
302 for (unsigned int i=0; i < path_in.size(); i++) {
303 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
304 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
305 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
306 // add the output path vector to the already accumulated vector:
307 for (unsigned int j=0; j < path.size(); j++) {
308 path_out.push_back(path[j]);
309 }
310 }
311 } else {
312 // concatenate the path into possibly discontinuous pwd2
313 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
314 for (unsigned int i=0; i < path_in.size(); i++) {
315 pwd2_in.concat( path_in[i].toPwSb() );
316 }
317 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
318 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
319 }
321 return path_out;
322 }
324 Geom::Piecewise<Geom::D2<Geom::SBasis> >
325 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
326 {
327 g_warning("Effect has no doEffect implementation");
328 return pwd2_in;
329 }
331 void
332 Effect::readallParameters(Inkscape::XML::Node * repr)
333 {
334 std::vector<Parameter *>::iterator it = param_vector.begin();
335 while (it != param_vector.end()) {
336 Parameter * param = *it;
337 const gchar * key = param->param_key.c_str();
338 const gchar * value = repr->attribute(key);
339 if (value) {
340 bool accepted = param->param_readSVGValue(value);
341 if (!accepted) {
342 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
343 }
344 } else {
345 // set default value
346 param->param_set_default();
347 }
349 it++;
350 }
351 }
353 /* This function does not and SHOULD NOT write to XML */
354 void
355 Effect::setParameter(const gchar * key, const gchar * new_value)
356 {
357 Parameter * param = getParameter(key);
358 if (param) {
359 if (new_value) {
360 bool accepted = param->param_readSVGValue(new_value);
361 if (!accepted) {
362 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
363 }
364 } else {
365 // set default value
366 param->param_set_default();
367 }
368 }
369 }
371 void
372 Effect::registerParameter(Parameter * param)
373 {
374 param_vector.push_back(param);
375 }
377 // TODO: should we provide a way to alter the handle's appearance?
378 void
379 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
380 {
381 kh_entity_vector.push_back(std::make_pair(entity, descr));
382 }
384 /**
385 * Add all registered LPE knotholder handles to the knotholder
386 */
387 void
388 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
389 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
390 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
391 KnotHolderEntity *entity = i->first;
392 const char *descr = i->second;
394 entity->create(desktop, item, knotholder, descr);
395 knotholder->add(entity);
396 }
397 }
399 void
400 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
401 using namespace std;
402 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
403 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
404 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
405 e->create(desktop, item, knotholder);
406 knotholder->add(e);
407 }
408 }
409 }
411 void
412 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
413 {
414 g_return_if_fail(SP_IS_PATH(lpeitem));
416 if (providesKnotholder() && showOrigPath()) {
417 // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
418 // must create the helper curve for the original path manually; when we allow nodepaths and
419 // knotholders alongside each other, this needs to be rethought!
420 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
421 // TODO: Make sure the tempitem doesn't get destroyed when the mouse leaves the item
422 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
423 lpeitem->lpe_helperpaths.push_back(tmpitem);
424 }
426 addHelperPathsImpl(lpeitem, desktop);
427 }
429 void
430 Effect::addHelperPathsImpl(SPLPEItem *lpeitem, SPDesktop *desktop)
431 {
432 // if this method is overloaded in derived classes, provides_own_flash_paths will be true
433 provides_own_flash_paths = false;
434 }
436 /**
437 * This *creates* a new widget, management of deletion should be done by the caller
438 */
439 Gtk::Widget *
440 Effect::newWidget(Gtk::Tooltips * tooltips)
441 {
442 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
443 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
445 vbox->set_border_width(5);
447 std::vector<Parameter *>::iterator it = param_vector.begin();
448 while (it != param_vector.end()) {
449 Parameter * param = *it;
450 Gtk::Widget * widg = param->param_newWidget(tooltips);
451 Glib::ustring * tip = param->param_getTooltip();
452 if (widg) {
453 vbox->pack_start(*widg, true, true, 2);
454 if (tip != NULL) {
455 tooltips->set_tip(*widg, *tip);
456 }
457 }
459 it++;
460 }
462 return dynamic_cast<Gtk::Widget *>(vbox);
463 }
466 Inkscape::XML::Node *
467 Effect::getRepr()
468 {
469 return SP_OBJECT_REPR(lpeobj);
470 }
472 SPDocument *
473 Effect::getSPDoc()
474 {
475 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
476 return SP_OBJECT_DOCUMENT(lpeobj);
477 }
479 Parameter *
480 Effect::getParameter(const char * key)
481 {
482 Glib::ustring stringkey(key);
484 std::vector<Parameter *>::iterator it = param_vector.begin();
485 while (it != param_vector.end()) {
486 Parameter * param = *it;
487 if ( param->param_key == key) {
488 return param;
489 }
491 it++;
492 }
494 return NULL;
495 }
497 Parameter *
498 Effect::getNextOncanvasEditableParam()
499 {
500 if (param_vector.size() == 0) // no parameters
501 return NULL;
503 oncanvasedit_it++;
504 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
505 oncanvasedit_it = 0;
506 }
507 int old_it = oncanvasedit_it;
509 do {
510 Parameter * param = param_vector[oncanvasedit_it];
511 if(param && param->oncanvas_editable) {
512 return param;
513 } else {
514 oncanvasedit_it++;
515 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
516 oncanvasedit_it = 0;
517 }
518 }
519 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
521 return NULL;
522 }
524 void
525 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
526 {
527 if (!desktop) return;
529 Parameter * param = getNextOncanvasEditableParam();
530 if (param) {
531 param->param_editOncanvas(item, desktop);
532 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
533 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
534 g_free(message);
535 } else {
536 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
537 _("None of the applied path effect's parameters can be edited on-canvas.") );
538 }
539 }
541 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
542 * 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!
543 */
544 void
545 Effect::resetDefaults(SPItem * /*item*/)
546 {
547 // do nothing for simple effects
548 }
550 void
551 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
552 {
553 np->helperpath_rgba = 0xff0000ff;
554 np->helperpath_width = 1.0;
555 }
557 void
558 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
559 {
560 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
561 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
562 Parameter * param = *it;
563 param->param_transform_multiply(postmul, set);
564 }
565 }
567 } /* namespace LivePathEffect */
569 } /* namespace Inkscape */
571 /*
572 Local Variables:
573 mode:c++
574 c-file-style:"stroustrup"
575 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
576 indent-tabs-mode:nil
577 fill-column:99
578 End:
579 */
580 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :