551b23a1db1fc84931587195f6d8b7b344cecebe
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 "document-private.h"
20 #include "xml/document.h"
21 #include <glibmm/i18n.h>
22 #include "pen-context.h"
23 #include "tools-switch.h"
25 #include "live_effects/lpeobject.h"
26 #include "live_effects/parameter/parameter.h"
27 #include <glibmm/ustring.h>
28 #include "libnr/n-art-bpath-2geom.h"
29 #include "display/curve.h"
30 #include <gtkmm.h>
32 #include <exception>
34 #include <2geom/sbasis-to-bezier.h>
35 #include <2geom/matrix.h>
38 // include effects:
39 #include "live_effects/lpe-patternalongpath.h"
40 #include "live_effects/lpe-bendpath.h"
41 #include "live_effects/lpe-sketch.h"
42 #include "live_effects/lpe-vonkoch.h"
43 #include "live_effects/lpe-knot.h"
44 #include "live_effects/lpe-test-doEffect-stack.h"
45 #include "live_effects/lpe-gears.h"
46 #include "live_effects/lpe-curvestitch.h"
47 #include "live_effects/lpe-circle_with_radius.h"
48 #include "live_effects/lpe-perspective_path.h"
49 #include "live_effects/lpe-spiro.h"
50 #include "live_effects/lpe-lattice.h"
51 #include "live_effects/lpe-envelope.h"
52 #include "live_effects/lpe-constructgrid.h"
53 #include "live_effects/lpe-perp_bisector.h"
54 #include "live_effects/lpe-tangent_to_curve.h"
55 #include "live_effects/lpe-mirror_reflect.h"
56 // end of includes
58 #include "nodepath.h"
60 namespace Inkscape {
62 namespace LivePathEffect {
64 const Util::EnumData<EffectType> LPETypeData[INVALID_LPE] = {
65 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
66 {BEND_PATH, N_("Bend"), "bend_path"},
67 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
68 {SKETCH, N_("Sketch"), "sketch"},
69 {VONKOCH, N_("VonKoch"), "vonkoch"},
70 {KNOT, N_("Knot"), "knot"},
71 #ifdef LPE_ENABLE_TEST_EFFECTS
72 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
73 #endif
74 {GEARS, N_("Gears"), "gears"},
75 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
76 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
77 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
78 {SPIRO, N_("Spiro spline"), "spiro"},
79 {LATTICE, N_("Lattice Deformation"), "lattice"},
80 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
81 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
82 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
83 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
84 {MIRROR_REFLECT, N_("Mirror reflection"), "mirror_reflect"},
85 };
86 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, INVALID_LPE);
88 Effect*
89 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
90 {
91 Effect* neweffect = NULL;
92 switch (lpenr) {
93 case PATTERN_ALONG_PATH:
94 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
95 break;
96 case BEND_PATH:
97 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
98 break;
99 case SKETCH:
100 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
101 break;
102 case VONKOCH:
103 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
104 break;
105 case KNOT:
106 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
107 break;
108 #ifdef LPE_ENABLE_TEST_EFFECTS
109 case DOEFFECTSTACK_TEST:
110 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
111 break;
112 #endif
113 case GEARS:
114 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
115 break;
116 case CURVE_STITCH:
117 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
118 break;
119 case LATTICE:
120 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
121 break;
122 case ENVELOPE:
123 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
124 break;
125 case CIRCLE_WITH_RADIUS:
126 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
127 break;
128 case PERSPECTIVE_PATH:
129 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
130 break;
131 case SPIRO:
132 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
133 break;
134 case CONSTRUCT_GRID:
135 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
136 break;
137 case PERP_BISECTOR:
138 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
139 break;
140 case TANGENT_TO_CURVE:
141 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
142 break;
143 case MIRROR_REFLECT:
144 neweffect = static_cast<Effect*> ( new LPEMirrorReflect(lpeobj) );
145 break;
146 default:
147 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
148 neweffect = NULL;
149 break;
150 }
152 if (neweffect) {
153 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
154 }
156 return neweffect;
157 }
159 void
160 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
161 {
162 // Path effect definition
163 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
164 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
165 repr->setAttribute("effect", name);
167 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
168 const gchar * repr_id = repr->attribute("id");
169 Inkscape::GC::release(repr);
171 gchar *href = g_strdup_printf("#%s", repr_id);
172 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
173 g_free(href);
175 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
176 _("Create and apply path effect"));
177 }
179 void
180 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
181 {
182 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
183 }
185 Effect::Effect(LivePathEffectObject *lpeobject)
186 : oncanvasedit_it(0),
187 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
188 done_pathparam_set(false),
189 lpeobj(lpeobject),
190 concatenate_before_pwd2(false)
191 {
192 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
193 }
195 Effect::~Effect()
196 {
197 }
199 Glib::ustring
200 Effect::getName()
201 {
202 if (lpeobj->effecttype_set && lpeobj->effecttype < INVALID_LPE)
203 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
204 else
205 return Glib::ustring( _("No effect") );
206 }
208 EffectType
209 Effect::effectType() {
210 return lpeobj->effecttype;
211 }
213 /**
214 * Is performed a single time when the effect is freshly applied to a path
215 */
216 void
217 Effect::doOnApply (SPLPEItem */*lpeitem*/)
218 {
219 }
221 /**
222 * Is performed each time before the effect is updated.
223 */
224 void
225 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
226 {
227 //Do nothing for simple effects
228 }
230 /**
231 * Effects have a parameter path set before they are applied by accepting a nonzero number of mouse
232 * clicks. This method activates the pen context, which waits for the specified number of clicks.
233 * Override Effect::acceptsNumParams() to set the number of expected mouse clicks.
234 */
235 void
236 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
237 {
238 // switch to pen context
239 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
240 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
241 tools_switch(desktop, TOOLS_FREEHAND_PEN);
242 }
244 SPEventContext *ec = desktop->event_context;
245 SPPenContext *pc = SP_PEN_CONTEXT(ec);
246 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
247 pc->waiting_LPE = this;
248 pc->waiting_item = lpeitem;
249 pc->polylines_only = true;
251 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
252 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
253 getName().c_str(), acceptsNumParams()));
254 }
256 void
257 Effect::writeParamsToSVG() {
258 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
259 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
260 (*p)->write_to_SVG();
261 }
262 }
264 /**
265 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
266 * applied, this is the method that processes the resulting path. Override it to customize it for
267 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
268 */
269 void
270 Effect::acceptParamPath (SPPath *param_path) {
271 done_pathparam_set = true;
272 }
274 /*
275 * Here be the doEffect function chain:
276 */
277 void
278 Effect::doEffect (SPCurve * curve)
279 {
280 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
282 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
284 curve->set_pathv(result_pathv);
285 }
287 std::vector<Geom::Path>
288 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
289 {
290 std::vector<Geom::Path> path_out;
292 if ( !concatenate_before_pwd2 ) {
293 // default behavior
294 for (unsigned int i=0; i < path_in.size(); i++) {
295 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
296 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
297 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
298 // add the output path vector to the already accumulated vector:
299 for (unsigned int j=0; j < path.size(); j++) {
300 path_out.push_back(path[j]);
301 }
302 }
303 } else {
304 // concatenate the path into possibly discontinuous pwd2
305 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
306 for (unsigned int i=0; i < path_in.size(); i++) {
307 pwd2_in.concat( path_in[i].toPwSb() );
308 }
309 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
310 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
311 }
313 return path_out;
314 }
316 Geom::Piecewise<Geom::D2<Geom::SBasis> >
317 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
318 {
319 g_warning("Effect has no doEffect implementation");
320 return pwd2_in;
321 }
323 void
324 Effect::readallParameters(Inkscape::XML::Node * repr)
325 {
326 std::vector<Parameter *>::iterator it = param_vector.begin();
327 while (it != param_vector.end()) {
328 Parameter * param = *it;
329 const gchar * key = param->param_key.c_str();
330 const gchar * value = repr->attribute(key);
331 if (value) {
332 bool accepted = param->param_readSVGValue(value);
333 if (!accepted) {
334 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
335 }
336 } else {
337 // set default value
338 param->param_set_default();
339 }
341 it++;
342 }
343 }
345 /* This function does not and SHOULD NOT write to XML */
346 void
347 Effect::setParameter(const gchar * key, const gchar * new_value)
348 {
349 Parameter * param = getParameter(key);
350 if (param) {
351 if (new_value) {
352 bool accepted = param->param_readSVGValue(new_value);
353 if (!accepted) {
354 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
355 }
356 } else {
357 // set default value
358 param->param_set_default();
359 }
360 }
361 }
363 void
364 Effect::registerParameter(Parameter * param)
365 {
366 param_vector.push_back(param);
367 }
369 // TODO: should we provide a way to alter the handle's appearance?
370 void
371 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
372 {
373 kh_entity_vector.push_back(std::make_pair(entity, descr));
374 }
376 /**
377 * Add all registered LPE knotholder handles to the knotholder
378 */
379 void
380 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
381 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
382 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
383 KnotHolderEntity *entity = i->first;
384 const char *descr = i->second;
386 entity->create(desktop, item, knotholder, descr);
387 knotholder->add(entity);
388 }
389 }
391 void
392 Effect::addPointParamHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
393 using namespace std;
394 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
395 if ((*p)->paramType() == Inkscape::LivePathEffect::POINT_PARAM) {
396 KnotHolderEntity *e = dynamic_cast<KnotHolderEntity *>(*p);
397 e->create(desktop, item, knotholder);
398 knotholder->add(e);
399 }
400 }
401 }
403 /**
404 * This *creates* a new widget, management of deletion should be done by the caller
405 */
406 Gtk::Widget *
407 Effect::newWidget(Gtk::Tooltips * tooltips)
408 {
409 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
410 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
412 vbox->set_border_width(5);
414 std::vector<Parameter *>::iterator it = param_vector.begin();
415 while (it != param_vector.end()) {
416 Parameter * param = *it;
417 Gtk::Widget * widg = param->param_newWidget(tooltips);
418 Glib::ustring * tip = param->param_getTooltip();
419 if (widg) {
420 vbox->pack_start(*widg, true, true, 2);
421 if (tip != NULL) {
422 tooltips->set_tip(*widg, *tip);
423 }
424 }
426 it++;
427 }
429 return dynamic_cast<Gtk::Widget *>(vbox);
430 }
433 Inkscape::XML::Node *
434 Effect::getRepr()
435 {
436 return SP_OBJECT_REPR(lpeobj);
437 }
439 SPDocument *
440 Effect::getSPDoc()
441 {
442 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
443 return SP_OBJECT_DOCUMENT(lpeobj);
444 }
446 Parameter *
447 Effect::getParameter(const char * key)
448 {
449 Glib::ustring stringkey(key);
451 std::vector<Parameter *>::iterator it = param_vector.begin();
452 while (it != param_vector.end()) {
453 Parameter * param = *it;
454 if ( param->param_key == key) {
455 return param;
456 }
458 it++;
459 }
461 return NULL;
462 }
464 Parameter *
465 Effect::getNextOncanvasEditableParam()
466 {
467 if (param_vector.size() == 0) // no parameters
468 return NULL;
470 oncanvasedit_it++;
471 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
472 oncanvasedit_it = 0;
473 }
474 int old_it = oncanvasedit_it;
476 do {
477 Parameter * param = param_vector[oncanvasedit_it];
478 if(param && param->oncanvas_editable) {
479 return param;
480 } else {
481 oncanvasedit_it++;
482 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
483 oncanvasedit_it = 0;
484 }
485 }
486 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
488 return NULL;
489 }
491 void
492 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
493 {
494 if (!desktop) return;
496 Parameter * param = getNextOncanvasEditableParam();
497 if (param) {
498 param->param_editOncanvas(item, desktop);
499 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
500 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
501 g_free(message);
502 } else {
503 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
504 _("None of the applied path effect's parameters can be edited on-canvas.") );
505 }
506 }
508 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
509 * 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!
510 */
511 void
512 Effect::resetDefaults(SPItem * /*item*/)
513 {
514 // do nothing for simple effects
515 }
517 void
518 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
519 {
520 np->helperpath_rgba = 0xff0000ff;
521 np->helperpath_width = 1.0;
522 }
524 void
525 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
526 {
527 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
528 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
529 Parameter * param = *it;
530 param->param_transform_multiply(postmul, set);
531 }
532 }
534 } /* namespace LivePathEffect */
536 } /* namespace Inkscape */
538 /*
539 Local Variables:
540 mode:c++
541 c-file-style:"stroustrup"
542 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
543 indent-tabs-mode:nil
544 fill-column:99
545 End:
546 */
547 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :