db395c81b51c6b7684f49d965cffa99d21f13396
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 /*
219 * Here be the doEffect function chain:
220 */
221 void
222 Effect::doEffect (SPCurve * curve)
223 {
224 NArtBpath const *bpath_in = curve->get_bpath();
226 std::vector<Geom::Path> result_pathv;
228 try {
229 std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(bpath_in);
231 result_pathv = doEffect_path(orig_pathv);
232 }
233 catch (std::exception & e) {
234 g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
235 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
236 _("An exception occurred during execution of the Path Effect.") );
237 }
239 curve->set_pathv(result_pathv);
240 }
242 std::vector<Geom::Path>
243 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
244 {
245 std::vector<Geom::Path> path_out;
247 if ( !concatenate_before_pwd2 ) {
248 // default behavior
249 for (unsigned int i=0; i < path_in.size(); i++) {
250 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
251 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
252 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
253 // add the output path vector to the already accumulated vector:
254 for (unsigned int j=0; j < path.size(); j++) {
255 path_out.push_back(path[j]);
256 }
257 }
258 } else {
259 // concatenate the path into possibly discontinuous pwd2
260 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
261 for (unsigned int i=0; i < path_in.size(); i++) {
262 pwd2_in.concat( path_in[i].toPwSb() );
263 }
264 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
265 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
266 }
268 return path_out;
269 }
271 Geom::Piecewise<Geom::D2<Geom::SBasis> >
272 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
273 {
274 g_warning("Effect has no doEffect implementation");
275 return pwd2_in;
276 }
278 void
279 Effect::readallParameters(Inkscape::XML::Node * repr)
280 {
281 std::vector<Parameter *>::iterator it = param_vector.begin();
282 while (it != param_vector.end()) {
283 Parameter * param = *it;
284 const gchar * key = param->param_key.c_str();
285 const gchar * value = repr->attribute(key);
286 if (value) {
287 bool accepted = param->param_readSVGValue(value);
288 if (!accepted) {
289 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
290 }
291 } else {
292 // set default value
293 param->param_set_default();
294 }
296 it++;
297 }
298 }
300 /* This function does not and SHOULD NOT write to XML */
301 void
302 Effect::setParameter(const gchar * key, const gchar * new_value)
303 {
304 Parameter * param = getParameter(key);
305 if (param) {
306 if (new_value) {
307 bool accepted = param->param_readSVGValue(new_value);
308 if (!accepted) {
309 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
310 }
311 } else {
312 // set default value
313 param->param_set_default();
314 }
315 }
316 }
318 void
319 Effect::registerParameter(Parameter * param)
320 {
321 param_vector.push_back(param);
322 }
324 // TODO: Does it still make sense to use this? E.g., should we keep a list of knotholder entities
325 // in the effect itself and add them here in a semi-automatic manner (similarly to how it is
326 // done with parameters) instead of adding each one separately in the overloaded function
327 // addKnotHolderHandles()?
328 void
329 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
330 {
331 //knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
332 }
334 void
335 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
336 using namespace std;
337 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
338 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
339 g_print ("Parameter is of type PointParam\n");
340 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
341 e->create(desktop, item, knotholder);
342 knotholder->add(e);
343 } else {
344 g_print ("Parameter is *not* of type PointParam\n");
345 }
346 }
347 }
349 /**
350 * Virtual method to add knotholder handles for LPE items
351 */
352 void
353 Effect::addKnotHolderHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item)
354 {
355 }
357 /**
358 * This *creates* a new widget, management of deletion should be done by the caller
359 */
360 Gtk::Widget *
361 Effect::newWidget(Gtk::Tooltips * tooltips)
362 {
363 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
364 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
366 vbox->set_border_width(5);
368 std::vector<Parameter *>::iterator it = param_vector.begin();
369 while (it != param_vector.end()) {
370 Parameter * param = *it;
371 Gtk::Widget * widg = param->param_newWidget(tooltips);
372 Glib::ustring * tip = param->param_getTooltip();
373 if (widg) {
374 vbox->pack_start(*widg, true, true, 2);
375 if (tip != NULL) {
376 tooltips->set_tip(*widg, *tip);
377 }
378 }
380 it++;
381 }
383 return dynamic_cast<Gtk::Widget *>(vbox);
384 }
387 Inkscape::XML::Node *
388 Effect::getRepr()
389 {
390 return SP_OBJECT_REPR(lpeobj);
391 }
393 SPDocument *
394 Effect::getSPDoc()
395 {
396 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
397 return SP_OBJECT_DOCUMENT(lpeobj);
398 }
400 Parameter *
401 Effect::getParameter(const char * key)
402 {
403 Glib::ustring stringkey(key);
405 std::vector<Parameter *>::iterator it = param_vector.begin();
406 while (it != param_vector.end()) {
407 Parameter * param = *it;
408 if ( param->param_key == key) {
409 return param;
410 }
412 it++;
413 }
415 return NULL;
416 }
418 Parameter *
419 Effect::getNextOncanvasEditableParam()
420 {
421 if (param_vector.size() == 0) // no parameters
422 return NULL;
424 oncanvasedit_it++;
425 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
426 oncanvasedit_it = 0;
427 }
428 int old_it = oncanvasedit_it;
430 do {
431 Parameter * param = param_vector[oncanvasedit_it];
432 if(param && param->oncanvas_editable) {
433 return param;
434 } else {
435 oncanvasedit_it++;
436 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
437 oncanvasedit_it = 0;
438 }
439 }
440 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
442 return NULL;
443 }
445 void
446 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
447 {
448 if (!desktop) return;
450 Parameter * param = getNextOncanvasEditableParam();
451 if (param) {
452 param->param_editOncanvas(item, desktop);
453 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
454 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
455 g_free(message);
456 } else {
457 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
458 _("None of the applied path effect's parameters can be edited on-canvas.") );
459 }
460 }
462 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
463 * 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!
464 */
465 void
466 Effect::resetDefaults(SPItem * /*item*/)
467 {
468 // do nothing for simple effects
469 }
471 void
472 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
473 {
474 np->helperpath_rgba = 0xff0000ff;
475 np->helperpath_width = 1.0;
476 }
478 void
479 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
480 {
481 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
482 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
483 Parameter * param = *it;
484 param->param_transform_multiply(postmul, set);
485 }
486 }
488 } /* namespace LivePathEffect */
490 } /* namespace Inkscape */
492 /*
493 Local Variables:
494 mode:c++
495 c-file-style:"stroustrup"
496 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
497 indent-tabs-mode:nil
498 fill-column:99
499 End:
500 */
501 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :