639ad93a48ee34ecf60eeb1e1d8028e14f2b7365
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 <glibmm/i18n.h>
21 #include "live_effects/lpeobject.h"
22 #include "live_effects/parameter/parameter.h"
23 #include <glibmm/ustring.h>
24 #include "libnr/n-art-bpath-2geom.h"
25 #include "display/curve.h"
26 #include <gtkmm.h>
28 #include <exception>
30 #include <2geom/sbasis-to-bezier.h>
31 #include <2geom/matrix.h>
34 // include effects:
35 #include "live_effects/lpe-patternalongpath.h"
36 #include "live_effects/lpe-bendpath.h"
37 #include "live_effects/lpe-sketch.h"
38 #include "live_effects/lpe-vonkoch.h"
39 #include "live_effects/lpe-knot.h"
40 #include "live_effects/lpe-test-doEffect-stack.h"
41 #include "live_effects/lpe-gears.h"
42 #include "live_effects/lpe-curvestitch.h"
43 #include "live_effects/lpe-circle_with_radius.h"
44 #include "live_effects/lpe-perspective_path.h"
45 #include "live_effects/lpe-spiro.h"
46 #include "live_effects/lpe-lattice.h"
47 #include "live_effects/lpe-envelope.h"
48 #include "live_effects/lpe-constructgrid.h"
49 #include "live_effects/lpe-perp_bisector.h"
50 #include "live_effects/lpe-tangent_to_curve.h"
51 // end of includes
53 #include "nodepath.h"
55 namespace Inkscape {
57 namespace LivePathEffect {
59 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
60 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
61 {BEND_PATH, N_("Bend"), "bend_path"},
62 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
63 {SKETCH, N_("Sketch"), "sketch"},
64 {VONKOCH, N_("VonKoch"), "vonkoch"},
65 {KNOT, N_("Knot"), "knot"},
66 #ifdef LPE_ENABLE_TEST_EFFECTS
67 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
68 #endif
69 {GEARS, N_("Gears"), "gears"},
70 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
71 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
72 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
73 {SPIRO, N_("Spiro spline"), "spiro"},
74 {LATTICE, N_("Lattice Deformation"), "lattice"},
75 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
76 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
77 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
78 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"}
79 };
80 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
82 Effect*
83 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
84 {
85 Effect* neweffect = NULL;
86 switch (lpenr) {
87 case PATTERN_ALONG_PATH:
88 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
89 break;
90 case BEND_PATH:
91 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
92 break;
93 case SKETCH:
94 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
95 break;
96 case VONKOCH:
97 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
98 break;
99 case KNOT:
100 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
101 break;
102 #ifdef LPE_ENABLE_TEST_EFFECTS
103 case DOEFFECTSTACK_TEST:
104 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
105 break;
106 #endif
107 case GEARS:
108 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
109 break;
110 case CURVE_STITCH:
111 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
112 break;
113 case LATTICE:
114 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
115 break;
116 case ENVELOPE:
117 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
118 break;
119 case CIRCLE_WITH_RADIUS:
120 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
121 break;
122 case PERSPECTIVE_PATH:
123 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
124 break;
125 case SPIRO:
126 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
127 break;
128 case CONSTRUCT_GRID:
129 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
130 break;
131 case PERP_BISECTOR:
132 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
133 break;
134 case TANGENT_TO_CURVE:
135 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
136 break;
137 default:
138 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
139 neweffect = NULL;
140 break;
141 }
143 if (neweffect) {
144 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
145 }
147 return neweffect;
148 }
150 Effect::Effect(LivePathEffectObject *lpeobject)
151 : oncanvasedit_it(0),
152 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
153 lpeobj(lpeobject),
154 concatenate_before_pwd2(false)
155 {
156 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
157 }
159 Effect::~Effect()
160 {
161 }
163 Glib::ustring
164 Effect::getName()
165 {
166 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
167 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
168 else
169 return Glib::ustring( _("No effect") );
170 }
172 EffectType
173 Effect::effectType() {
174 return lpeobj->effecttype;
175 }
177 void
178 Effect::doOnApply (SPLPEItem */*lpeitem*/)
179 {
180 // This is performed once when the effect is freshly applied to a path
181 }
183 void
184 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
185 {
186 //Do nothing for simple effects
187 }
190 /*
191 * Here be the doEffect function chain:
192 */
193 void
194 Effect::doEffect (SPCurve * curve)
195 {
196 NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
198 curve->set_bpath(new_bpath);
199 }
201 NArtBpath *
202 Effect::doEffect_nartbpath (NArtBpath const * path_in)
203 {
204 try {
205 std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
207 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
209 NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
211 return new_bpath;
212 }
213 catch (std::exception & e) {
214 g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
215 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
216 _("An exception occurred during execution of the Path Effect.") );
218 NArtBpath *path_out;
220 unsigned ret = 0;
221 while ( path_in[ret].code != NR_END ) {
222 ++ret;
223 }
224 unsigned len = ++ret;
226 path_out = g_new(NArtBpath, len);
227 memcpy(path_out, path_in, len * sizeof(NArtBpath));
228 return path_out;
229 }
230 }
232 std::vector<Geom::Path>
233 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
234 {
235 std::vector<Geom::Path> path_out;
237 if ( !concatenate_before_pwd2 ) {
238 // default behavior
239 for (unsigned int i=0; i < path_in.size(); i++) {
240 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
241 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
242 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
243 // add the output path vector to the already accumulated vector:
244 for (unsigned int j=0; j < path.size(); j++) {
245 path_out.push_back(path[j]);
246 }
247 }
248 } else {
249 // concatenate the path into possibly discontinuous pwd2
250 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
251 for (unsigned int i=0; i < path_in.size(); i++) {
252 pwd2_in.concat( path_in[i].toPwSb() );
253 }
254 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
255 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
256 }
258 return path_out;
259 }
261 Geom::Piecewise<Geom::D2<Geom::SBasis> >
262 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
263 {
264 g_warning("Effect has no doEffect implementation");
265 return pwd2_in;
266 }
268 void
269 Effect::readallParameters(Inkscape::XML::Node * repr)
270 {
271 std::vector<Parameter *>::iterator it = param_vector.begin();
272 while (it != param_vector.end()) {
273 Parameter * param = *it;
274 const gchar * key = param->param_key.c_str();
275 const gchar * value = repr->attribute(key);
276 if (value) {
277 bool accepted = param->param_readSVGValue(value);
278 if (!accepted) {
279 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
280 }
281 } else {
282 // set default value
283 param->param_set_default();
284 }
286 it++;
287 }
288 }
290 /* This function does not and SHOULD NOT write to XML */
291 void
292 Effect::setParameter(const gchar * key, const gchar * new_value)
293 {
294 Parameter * param = getParameter(key);
295 if (param) {
296 if (new_value) {
297 bool accepted = param->param_readSVGValue(new_value);
298 if (!accepted) {
299 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
300 }
301 } else {
302 // set default value
303 param->param_set_default();
304 }
305 }
306 }
308 void
309 Effect::registerParameter(Parameter * param)
310 {
311 param_vector.push_back(param);
312 }
314 void
315 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
316 {
317 knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
318 }
320 // TODO: allow for adding click_functions and description strings, too
321 void
322 Effect::addHandles(KnotHolder *knotholder) {
323 std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
324 for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
325 //knotholder->add(i->first, i->second, NULL, (""));
326 }
327 }
329 /**
330 * This *creates* a new widget, management of deletion should be done by the caller
331 */
332 Gtk::Widget *
333 Effect::newWidget(Gtk::Tooltips * tooltips)
334 {
335 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
336 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
338 vbox->set_border_width(5);
340 std::vector<Parameter *>::iterator it = param_vector.begin();
341 while (it != param_vector.end()) {
342 Parameter * param = *it;
343 Gtk::Widget * widg = param->param_newWidget(tooltips);
344 Glib::ustring * tip = param->param_getTooltip();
345 if (widg) {
346 vbox->pack_start(*widg, true, true, 2);
347 if (tip != NULL) {
348 tooltips->set_tip(*widg, *tip);
349 }
350 }
352 it++;
353 }
355 return dynamic_cast<Gtk::Widget *>(vbox);
356 }
359 Inkscape::XML::Node *
360 Effect::getRepr()
361 {
362 return SP_OBJECT_REPR(lpeobj);
363 }
365 SPDocument *
366 Effect::getSPDoc()
367 {
368 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
369 return SP_OBJECT_DOCUMENT(lpeobj);
370 }
372 Parameter *
373 Effect::getParameter(const char * key)
374 {
375 Glib::ustring stringkey(key);
377 std::vector<Parameter *>::iterator it = param_vector.begin();
378 while (it != param_vector.end()) {
379 Parameter * param = *it;
380 if ( param->param_key == key) {
381 return param;
382 }
384 it++;
385 }
387 return NULL;
388 }
390 Parameter *
391 Effect::getNextOncanvasEditableParam()
392 {
393 if (param_vector.size() == 0) // no parameters
394 return NULL;
396 oncanvasedit_it++;
397 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
398 oncanvasedit_it = 0;
399 }
400 int old_it = oncanvasedit_it;
402 do {
403 Parameter * param = param_vector[oncanvasedit_it];
404 if(param && param->oncanvas_editable) {
405 return param;
406 } else {
407 oncanvasedit_it++;
408 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
409 oncanvasedit_it = 0;
410 }
411 }
412 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
414 return NULL;
415 }
417 void
418 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
419 {
420 if (!desktop) return;
422 Parameter * param = getNextOncanvasEditableParam();
423 if (param) {
424 param->param_editOncanvas(item, desktop);
425 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
426 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
427 g_free(message);
428 } else {
429 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
430 _("None of the applied path effect's parameters can be edited on-canvas.") );
431 }
432 }
434 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
435 * 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!
436 */
437 void
438 Effect::resetDefaults(SPItem * /*item*/)
439 {
440 // do nothing for simple effects
441 }
443 void
444 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
445 {
446 np->helperpath_rgba = 0xff0000ff;
447 np->helperpath_width = 1.0;
448 }
450 void
451 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
452 {
453 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
454 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
455 Parameter * param = *it;
456 param->param_transform_multiply(postmul, set);
457 }
458 }
460 } /* namespace LivePathEffect */
462 } /* namespace Inkscape */
464 /*
465 Local Variables:
466 mode:c++
467 c-file-style:"stroustrup"
468 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
469 indent-tabs-mode:nil
470 fill-column:99
471 End:
472 */
473 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :