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