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