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