906955575599d13fac395f6764148d2b7d9cc9c8
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 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
148 lpeobj(lpeobject),
149 concatenate_before_pwd2(false)
150 {
151 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
152 }
154 Effect::~Effect()
155 {
156 }
158 Glib::ustring
159 Effect::getName()
160 {
161 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
162 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
163 else
164 return Glib::ustring( _("No effect") );
165 }
167 EffectType
168 Effect::effectType() {
169 return lpeobj->effecttype;
170 }
172 void
173 Effect::doOnApply (SPLPEItem */*lpeitem*/)
174 {
175 // This is performed once when the effect is freshly applied to a path
176 }
178 void
179 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
180 {
181 //Do nothing for simple effects
182 }
185 /*
186 * Here be the doEffect function chain:
187 */
188 void
189 Effect::doEffect (SPCurve * curve)
190 {
191 NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
193 curve->set_bpath(new_bpath);
194 }
196 NArtBpath *
197 Effect::doEffect_nartbpath (NArtBpath const * path_in)
198 {
199 try {
200 std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
202 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
204 NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
206 return new_bpath;
207 }
208 catch (std::exception & e) {
209 g_warning("Exception during LPE %s execution. \n %s", getName().c_str(), e.what());
210 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
211 _("An exception occurred during execution of the Path Effect.") );
213 NArtBpath *path_out;
215 unsigned ret = 0;
216 while ( path_in[ret].code != NR_END ) {
217 ++ret;
218 }
219 unsigned len = ++ret;
221 path_out = g_new(NArtBpath, len);
222 memcpy(path_out, path_in, len * sizeof(NArtBpath));
223 return path_out;
224 }
225 }
227 std::vector<Geom::Path>
228 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
229 {
230 std::vector<Geom::Path> path_out;
232 if ( !concatenate_before_pwd2 ) {
233 // default behavior
234 for (unsigned int i=0; i < path_in.size(); i++) {
235 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
236 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
237 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
238 // add the output path vector to the already accumulated vector:
239 for (unsigned int j=0; j < path.size(); j++) {
240 path_out.push_back(path[j]);
241 }
242 }
243 } else {
244 // concatenate the path into possibly discontinuous pwd2
245 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
246 for (unsigned int i=0; i < path_in.size(); i++) {
247 pwd2_in.concat( path_in[i].toPwSb() );
248 }
249 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
250 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
251 }
253 return path_out;
254 }
256 Geom::Piecewise<Geom::D2<Geom::SBasis> >
257 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
258 {
259 g_warning("Effect has no doEffect implementation");
260 return pwd2_in;
261 }
263 void
264 Effect::readallParameters(Inkscape::XML::Node * repr)
265 {
266 std::vector<Parameter *>::iterator it = param_vector.begin();
267 while (it != param_vector.end()) {
268 Parameter * param = *it;
269 const gchar * key = param->param_key.c_str();
270 const gchar * value = repr->attribute(key);
271 if (value) {
272 bool accepted = param->param_readSVGValue(value);
273 if (!accepted) {
274 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
275 }
276 } else {
277 // set default value
278 param->param_set_default();
279 }
281 it++;
282 }
283 }
285 /* This function does not and SHOULD NOT write to XML */
286 void
287 Effect::setParameter(const gchar * key, const gchar * new_value)
288 {
289 Parameter * param = getParameter(key);
290 if (param) {
291 if (new_value) {
292 bool accepted = param->param_readSVGValue(new_value);
293 if (!accepted) {
294 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
295 }
296 } else {
297 // set default value
298 param->param_set_default();
299 }
300 }
301 }
303 void
304 Effect::registerParameter(Parameter * param)
305 {
306 param_vector.push_back(param);
307 }
309 void
310 Effect::registerKnotHolderHandle(SPKnotHolderSetFunc set_func, SPKnotHolderGetFunc get_func)
311 {
312 knotholder_func_vector.push_back(std::make_pair(set_func, get_func));
313 }
315 // TODO: allow for adding click_functions and description strings, too
316 void
317 Effect::addHandles(SPKnotHolder *knotholder) {
318 std::vector<std::pair<SPKnotHolderSetFunc, SPKnotHolderGetFunc> >::iterator i;
319 for (i = knotholder_func_vector.begin(); i != knotholder_func_vector.end(); ++i) {
320 sp_knot_holder_add(knotholder, i->first, i->second, NULL, (""));
321 }
322 }
324 /**
325 * This *creates* a new widget, management of deletion should be done by the caller
326 */
327 Gtk::Widget *
328 Effect::newWidget(Gtk::Tooltips * tooltips)
329 {
330 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
331 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
333 vbox->set_border_width(5);
335 std::vector<Parameter *>::iterator it = param_vector.begin();
336 while (it != param_vector.end()) {
337 Parameter * param = *it;
338 Gtk::Widget * widg = param->param_newWidget(tooltips);
339 Glib::ustring * tip = param->param_getTooltip();
340 if (widg) {
341 vbox->pack_start(*widg, true, true, 2);
342 if (tip != NULL) {
343 tooltips->set_tip(*widg, *tip);
344 }
345 }
347 it++;
348 }
350 return dynamic_cast<Gtk::Widget *>(vbox);
351 }
354 Inkscape::XML::Node *
355 Effect::getRepr()
356 {
357 return SP_OBJECT_REPR(lpeobj);
358 }
360 SPDocument *
361 Effect::getSPDoc()
362 {
363 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
364 return SP_OBJECT_DOCUMENT(lpeobj);
365 }
367 Parameter *
368 Effect::getParameter(const char * key)
369 {
370 Glib::ustring stringkey(key);
372 std::vector<Parameter *>::iterator it = param_vector.begin();
373 while (it != param_vector.end()) {
374 Parameter * param = *it;
375 if ( param->param_key == key) {
376 return param;
377 }
379 it++;
380 }
382 return NULL;
383 }
385 Parameter *
386 Effect::getNextOncanvasEditableParam()
387 {
388 if (param_vector.size() == 0) // no parameters
389 return NULL;
391 oncanvasedit_it++;
392 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
393 oncanvasedit_it = 0;
394 }
395 int old_it = oncanvasedit_it;
397 do {
398 Parameter * param = param_vector[oncanvasedit_it];
399 if(param && param->oncanvas_editable) {
400 return param;
401 } else {
402 oncanvasedit_it++;
403 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
404 oncanvasedit_it = 0;
405 }
406 }
407 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
409 return NULL;
410 }
412 void
413 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
414 {
415 if (!desktop) return;
417 Parameter * param = getNextOncanvasEditableParam();
418 if (param) {
419 param->param_editOncanvas(item, desktop);
420 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
421 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
422 g_free(message);
423 } else {
424 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
425 _("None of the applied path effect's parameters can be edited on-canvas.") );
426 }
427 }
429 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
430 * 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!
431 */
432 void
433 Effect::resetDefaults(SPItem * /*item*/)
434 {
435 // do nothing for simple effects
436 }
438 void
439 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
440 {
441 np->helperpath_rgba = 0xff0000ff;
442 np->helperpath_width = 1.0;
443 }
445 void
446 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
447 {
448 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
449 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
450 Parameter * param = *it;
451 param->param_transform_multiply(postmul, set);
452 }
453 }
455 } /* namespace LivePathEffect */
457 } /* namespace Inkscape */
459 /*
460 Local Variables:
461 mode:c++
462 c-file-style:"stroustrup"
463 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
464 indent-tabs-mode:nil
465 fill-column:99
466 End:
467 */
468 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :