c46a9dd2397dc864fc46f382a596866e4fcaa63a
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>
23 #include "live_effects/lpeobject.h"
24 #include "live_effects/parameter/parameter.h"
25 #include <glibmm/ustring.h>
26 #include "libnr/n-art-bpath-2geom.h"
27 #include "display/curve.h"
28 #include <gtkmm.h>
30 #include <exception>
32 #include <2geom/sbasis-to-bezier.h>
33 #include <2geom/matrix.h>
36 // include effects:
37 #include "live_effects/lpe-patternalongpath.h"
38 #include "live_effects/lpe-bendpath.h"
39 #include "live_effects/lpe-sketch.h"
40 #include "live_effects/lpe-vonkoch.h"
41 #include "live_effects/lpe-knot.h"
42 #include "live_effects/lpe-test-doEffect-stack.h"
43 #include "live_effects/lpe-gears.h"
44 #include "live_effects/lpe-curvestitch.h"
45 #include "live_effects/lpe-circle_with_radius.h"
46 #include "live_effects/lpe-perspective_path.h"
47 #include "live_effects/lpe-spiro.h"
48 #include "live_effects/lpe-lattice.h"
49 #include "live_effects/lpe-envelope.h"
50 #include "live_effects/lpe-constructgrid.h"
51 #include "live_effects/lpe-perp_bisector.h"
52 #include "live_effects/lpe-tangent_to_curve.h"
53 // end of includes
55 #include "nodepath.h"
57 namespace Inkscape {
59 namespace LivePathEffect {
61 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
62 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
63 {BEND_PATH, N_("Bend"), "bend_path"},
64 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
65 {SKETCH, N_("Sketch"), "sketch"},
66 {VONKOCH, N_("VonKoch"), "vonkoch"},
67 {KNOT, N_("Knot"), "knot"},
68 #ifdef LPE_ENABLE_TEST_EFFECTS
69 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
70 #endif
71 {GEARS, N_("Gears"), "gears"},
72 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
73 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
74 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
75 {SPIRO, N_("Spiro spline"), "spiro"},
76 {LATTICE, N_("Lattice Deformation"), "lattice"},
77 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
78 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
79 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
80 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}
81 };
82 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
84 Effect*
85 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
86 {
87 Effect* neweffect = NULL;
88 switch (lpenr) {
89 case PATTERN_ALONG_PATH:
90 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
91 break;
92 case BEND_PATH:
93 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
94 break;
95 case SKETCH:
96 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
97 break;
98 case VONKOCH:
99 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
100 break;
101 case KNOT:
102 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
103 break;
104 #ifdef LPE_ENABLE_TEST_EFFECTS
105 case DOEFFECTSTACK_TEST:
106 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
107 break;
108 #endif
109 case GEARS:
110 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
111 break;
112 case CURVE_STITCH:
113 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
114 break;
115 case LATTICE:
116 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
117 break;
118 case ENVELOPE:
119 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
120 break;
121 case CIRCLE_WITH_RADIUS:
122 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
123 break;
124 case PERSPECTIVE_PATH:
125 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
126 break;
127 case SPIRO:
128 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
129 break;
130 case CONSTRUCT_GRID:
131 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
132 break;
133 case PERP_BISECTOR:
134 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
135 break;
136 case TANGENT_TO_CURVE:
137 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
138 break;
139 default:
140 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
141 neweffect = NULL;
142 break;
143 }
145 if (neweffect) {
146 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
147 }
149 return neweffect;
150 }
152 void
153 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
154 {
155 // Path effect definition
156 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
157 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
158 repr->setAttribute("effect", name);
160 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
161 const gchar * repr_id = repr->attribute("id");
162 Inkscape::GC::release(repr);
164 gchar *href = g_strdup_printf("#%s", repr_id);
165 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
166 g_free(href);
168 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
169 _("Create and apply path effect"));
170 }
172 void
173 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
174 {
175 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
176 }
178 Effect::Effect(LivePathEffectObject *lpeobject)
179 : oncanvasedit_it(0),
180 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
181 lpeobj(lpeobject),
182 concatenate_before_pwd2(false)
183 {
184 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
185 }
187 Effect::~Effect()
188 {
189 }
191 Glib::ustring
192 Effect::getName()
193 {
194 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
195 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
196 else
197 return Glib::ustring( _("No effect") );
198 }
200 EffectType
201 Effect::effectType() {
202 return lpeobj->effecttype;
203 }
205 void
206 Effect::doOnApply (SPLPEItem */*lpeitem*/)
207 {
208 // This is performed once when the effect is freshly applied to a path
209 }
211 void
212 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
213 {
214 //Do nothing for simple effects
215 }
218 void
219 Effect::writeParamsToSVG() {
220 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
221 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
222 (*p)->write_to_SVG();
223 }
224 }
226 /*
227 * Here be the doEffect function chain:
228 */
229 void
230 Effect::doEffect (SPCurve * curve)
231 {
232 NArtBpath const *bpath_in = curve->get_bpath();
234 std::vector<Geom::Path> result_pathv;
236 try {
237 std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
239 result_pathv = doEffect_path(orig_pathv);
240 }
241 catch (std::exception & e) {
242 g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
243 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
244 _("An exception occurred during execution of the Path Effect.") );
245 }
247 curve->set_pathv(result_pathv);
248 }
250 std::vector<Geom::Path>
251 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
252 {
253 std::vector<Geom::Path> path_out;
255 if ( !concatenate_before_pwd2 ) {
256 // default behavior
257 for (unsigned int i=0; i < path_in.size(); i++) {
258 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
259 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
260 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
261 // add the output path vector to the already accumulated vector:
262 for (unsigned int j=0; j < path.size(); j++) {
263 path_out.push_back(path[j]);
264 }
265 }
266 } else {
267 // concatenate the path into possibly discontinuous pwd2
268 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
269 for (unsigned int i=0; i < path_in.size(); i++) {
270 pwd2_in.concat( path_in[i].toPwSb() );
271 }
272 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
273 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
274 }
276 return path_out;
277 }
279 Geom::Piecewise<Geom::D2<Geom::SBasis> >
280 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
281 {
282 g_warning("Effect has no doEffect implementation");
283 return pwd2_in;
284 }
286 void
287 Effect::readallParameters(Inkscape::XML::Node * repr)
288 {
289 std::vector<Parameter *>::iterator it = param_vector.begin();
290 while (it != param_vector.end()) {
291 Parameter * param = *it;
292 const gchar * key = param->param_key.c_str();
293 const gchar * value = repr->attribute(key);
294 if (value) {
295 bool accepted = param->param_readSVGValue(value);
296 if (!accepted) {
297 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
298 }
299 } else {
300 // set default value
301 param->param_set_default();
302 }
304 it++;
305 }
306 }
308 /* This function does not and SHOULD NOT write to XML */
309 void
310 Effect::setParameter(const gchar * key, const gchar * new_value)
311 {
312 Parameter * param = getParameter(key);
313 if (param) {
314 if (new_value) {
315 bool accepted = param->param_readSVGValue(new_value);
316 if (!accepted) {
317 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
318 }
319 } else {
320 // set default value
321 param->param_set_default();
322 }
323 }
324 }
326 void
327 Effect::registerParameter(Parameter * param)
328 {
329 param_vector.push_back(param);
330 }
332 // TODO: should we provide a way to alter the handle's appearance?
333 void
334 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
335 {
336 kh_entity_vector.push_back(std::make_pair(entity, descr));
337 }
339 /**
340 * Add all registered LPE knotholder handles to the knotholder
341 */
342 void
343 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
344 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
345 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
346 KnotHolderEntity *entity = i->first;
347 const char *descr = i->second;
349 entity->create(desktop, item, knotholder, descr);
350 knotholder->add(entity);
351 }
352 }
354 void
355 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
356 using namespace std;
357 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
358 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
359 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
360 e->create(desktop, item, knotholder);
361 knotholder->add(e);
362 }
363 }
364 }
366 /**
367 * This *creates* a new widget, management of deletion should be done by the caller
368 */
369 Gtk::Widget *
370 Effect::newWidget(Gtk::Tooltips * tooltips)
371 {
372 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
373 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
375 vbox->set_border_width(5);
377 std::vector<Parameter *>::iterator it = param_vector.begin();
378 while (it != param_vector.end()) {
379 Parameter * param = *it;
380 Gtk::Widget * widg = param->param_newWidget(tooltips);
381 Glib::ustring * tip = param->param_getTooltip();
382 if (widg) {
383 vbox->pack_start(*widg, true, true, 2);
384 if (tip != NULL) {
385 tooltips->set_tip(*widg, *tip);
386 }
387 }
389 it++;
390 }
392 return dynamic_cast<Gtk::Widget *>(vbox);
393 }
396 Inkscape::XML::Node *
397 Effect::getRepr()
398 {
399 return SP_OBJECT_REPR(lpeobj);
400 }
402 SPDocument *
403 Effect::getSPDoc()
404 {
405 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
406 return SP_OBJECT_DOCUMENT(lpeobj);
407 }
409 Parameter *
410 Effect::getParameter(const char * key)
411 {
412 Glib::ustring stringkey(key);
414 std::vector<Parameter *>::iterator it = param_vector.begin();
415 while (it != param_vector.end()) {
416 Parameter * param = *it;
417 if ( param->param_key == key) {
418 return param;
419 }
421 it++;
422 }
424 return NULL;
425 }
427 Parameter *
428 Effect::getNextOncanvasEditableParam()
429 {
430 if (param_vector.size() == 0) // no parameters
431 return NULL;
433 oncanvasedit_it++;
434 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
435 oncanvasedit_it = 0;
436 }
437 int old_it = oncanvasedit_it;
439 do {
440 Parameter * param = param_vector[oncanvasedit_it];
441 if(param && param->oncanvas_editable) {
442 return param;
443 } else {
444 oncanvasedit_it++;
445 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
446 oncanvasedit_it = 0;
447 }
448 }
449 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
451 return NULL;
452 }
454 void
455 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
456 {
457 if (!desktop) return;
459 Parameter * param = getNextOncanvasEditableParam();
460 if (param) {
461 param->param_editOncanvas(item, desktop);
462 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
463 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
464 g_free(message);
465 } else {
466 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
467 _("None of the applied path effect's parameters can be edited on-canvas.") );
468 }
469 }
471 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
472 * 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!
473 */
474 void
475 Effect::resetDefaults(SPItem * /*item*/)
476 {
477 // do nothing for simple effects
478 }
480 void
481 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
482 {
483 np->helperpath_rgba = 0xff0000ff;
484 np->helperpath_width = 1.0;
485 }
487 void
488 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
489 {
490 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
491 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
492 Parameter * param = *it;
493 param->param_transform_multiply(postmul, set);
494 }
495 }
497 } /* namespace LivePathEffect */
499 } /* namespace Inkscape */
501 /*
502 Local Variables:
503 mode:c++
504 c-file-style:"stroustrup"
505 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
506 indent-tabs-mode:nil
507 fill-column:99
508 End:
509 */
510 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :