Code

- try to use more forward declarations for less dependencies on display/curve.h
[inkscape.git] / src / live_effects / effect.cpp
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;
134 Effect::Effect(LivePathEffectObject *lpeobject)
135     : oncanvasedit_it(0),
136       lpeobj(lpeobject),
137       concatenate_before_pwd2(false)
141 Effect::~Effect()
145 Glib::ustring
146 Effect::getName()
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") );
154 void
155 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
157     //Do nothing for simple effects
161 /*
162  *  Here be the doEffect function chain:
163  */
164 void
165 Effect::doEffect (SPCurve * curve)
167     NArtBpath *new_bpath = doEffect_nartbpath(curve->get_bpath());
169     curve->set_bpath(new_bpath);
172 NArtBpath *
173 Effect::doEffect_nartbpath (NArtBpath const * path_in)
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     }
203 std::vector<Geom::Path>
204 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
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;
232 Geom::Piecewise<Geom::D2<Geom::SBasis> >
233 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
235     g_warning("Effect has no doEffect implementation");
236     return pwd2_in;
239 void
240 Effect::readallParameters(Inkscape::XML::Node * repr)
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     }
261 /* This function does not and SHOULD NOT write to XML */
262 void
263 Effect::setParameter(const gchar * key, const gchar * new_value)
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     }
279 void
280 Effect::registerParameter(Parameter * param)
282     param_vector.push_back(param);
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)
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);
315 Inkscape::XML::Node *
316 Effect::getRepr()
318     return SP_OBJECT_REPR(lpeobj);
321 SPDocument *
322 Effect::getSPDoc()
324     if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
325     return SP_OBJECT_DOCUMENT(lpeobj);
328 Parameter *
329 Effect::getParameter(const char * key)
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;
346 Parameter *
347 Effect::getNextOncanvasEditableParam()
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;
373 void
374 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
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     }
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*/)
396     // do nothing for simple effects
399 void
400 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
402     np->helperpath_rgba = 0xff0000ff;
403     np->helperpath_width = 1.0;
406 void
407 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
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     }
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 :