Code

knot lpe: enable groups + cleanups/simplifications
[inkscape.git] / src / live_effects / lpe-knot.cpp
1 /** @file
2  * @brief LPE knot effect implementation
3  */
4 /* Authors:
5  *   Jean-Francois Barraud <jf.barraud@gmail.com>
6  *
7  * Copyright (C) 2007 Authors
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
12 #include "sp-shape.h"
13 #include "display/curve.h"
14 #include "live_effects/lpe-knot.h"
15 #include "svg/svg.h"
16 #include "style.h"
18 #include <2geom/sbasis-to-bezier.h>
19 #include <2geom/sbasis.h>
20 #include <2geom/d2.h>
21 #include <2geom/d2-sbasis.h>
22 #include <2geom/path.h>
23 #include <2geom/crossing.h>
24 #include <2geom/bezier-to-sbasis.h>
25 #include <2geom/basic-intersection.h>
26 #include <2geom/exception.h>
27 //#include "2geom/recursive-bezier-intersection.cpp"
29 #include <exception>
31 namespace Inkscape {
32 namespace LivePathEffect {
34 class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity
35 {
36 public:
37     virtual ~KnotHolderEntityCrossingSwitcher() {}
39     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
40     virtual Geom::Point knot_get();
41     virtual void knot_click(guint state);
42 };
45 //---------------------------------------------------------------------------
46 //LPEKnot specific Interval manipulation.
47 //---------------------------------------------------------------------------
49 //remove an interval from an union of intervals.
50 //TODO: is it worth moving it to 2Geom?
51 static
52 std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
53     std::vector<Geom::Interval> ret;
54     double min = domain.front().min();
55     double max = domain.back().max();
56     Geom::Interval I1 = Geom::Interval(min,I.min());
57     Geom::Interval I2 = Geom::Interval(I.max(),max);
59     for (unsigned i = 0; i<domain.size(); i++){
60         boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
61         if (I1i && !I1i->isSingular()) ret.push_back(I1i.get());
62         boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
63         if (I2i && !I2i->isSingular()) ret.push_back(I2i.get());
64     }    
65     return ret;
66 }
68 //find the time interval during which patha is hidden by pathb near a given crossing.
69 // Warning: not accurate!
70 static
71 Geom::Interval
72 findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
73                  double const ta, double const width){
74     using namespace Geom;
75     Point T = unit_vector(pt_and_dir[1]);
76     Point N = T.cw();
77     Point A = pt_and_dir[0]-3*width*T, B = A+6*width*T;
79     Matrix mat = from_basis( T, N, pt_and_dir[0] );
80     mat = mat.inverse();
81     Path p = patha * mat;
82     std::vector<double> times;
83     for (unsigned i = 0; i<patha.size(); i++){
84         D2<SBasis> f = p[i].toSBasis();
85         std::vector<double> times_i, temptimes;
86         //TODO: explore the path fwd/backward from ta to avoid all those useless computations.
87         temptimes = roots(f[Y]-width);
88         times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
89         temptimes = roots(f[Y]+width);
90         times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
91         temptimes = roots(f[X]-3*width);
92         times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
93         temptimes = roots(f[X]+3*width);
94         times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
95         for (unsigned k=0; k<times_i.size(); k++){
96             times_i[k]+=i;
97         }
98         times.insert(times.end(), times_i.begin(), times_i.end() );
99     }
100     std::sort( times.begin(),  times.end() );
101     std::unique( times.begin(),  times.end() );
102     double tmin = 0, tmax = patha.size();
103     double period = patha.size();//hm... Should this be patha.size()+1? 
104     if (times.size()>0){
105         unsigned rk = upper_bound( times.begin(),  times.end(), ta ) - times.begin();
106         if ( rk < times.size() ) 
107             tmax = times[rk];
108         else if ( patha.closed() ) 
109             tmax = times[0]+period;
111         if ( rk > 0 ) 
112             tmin = times[rk-1];
113         else if ( patha.closed() ) 
114             tmin = times.back()-period;
115     }
116     return Interval(tmin,tmax);
119 //---------------------------------------------------------------------------
120 //LPEKnot specific Crossing Data manipulation.
121 //---------------------------------------------------------------------------
123 //Yet another crossing data representation.
124 // an CrossingPoint stores
125 //    -an intersection point
126 //    -the involved path components
127 //    -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
129 namespace LPEKnotNS {//just in case...
130 CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){
131     //std::cout<<"\nCrossingPoints creation from path vector\n";
132     for( unsigned i=0; i<paths.size(); i++){
133         for( unsigned ii=0; ii<paths[i].size(); ii++){
134             for( unsigned j=i; j<paths.size(); j++){
135                 for( unsigned jj=(i==j?ii:0); jj<paths[j].size(); jj++){
136                     std::vector<std::pair<double,double> > times;
137                     if ( i==j && ii==jj){
139 //                         std::cout<<"--(self int)\n";
140 //                         std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
141 //                         std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
143                         find_self_intersections( times, paths[i][ii].toSBasis() );
144                     }else{
145 //                         std::cout<<"--(pair int)\n";
146 //                         std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
147 //                         std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
148 //                         std::cout<<"with\n";
149 //                         std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
150 //                         std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
152                         find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
153                     }
154                     for (unsigned k=0; k<times.size(); k++){
155                         //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
156                         if (times[k].first == times[k].first && times[k].second == times[k].second ){//is this the way to test NaN?
157                             double zero = 1e-4;
158                             if ( i==j && fabs(times[k].first+ii - times[k].second-jj)<=zero ){//this is just end=start of successive curves in a path.
159                                 continue;
160                             }
161                             if ( i==j && ii == 0 && jj==paths[i].size()-1 &&
162                                  paths[i].closed() &&
163                                  fabs(times[k].first) <= zero && 
164                                  fabs(times[k].second - 1) <= zero ){//this is just end=start of a closed path.
165                                 continue;
166                             }
167                             CrossingPoint cp;
168                             cp.pt = paths[i][ii].pointAt(times[k].first);
169                             cp.sign = 1;
170                             cp.i = i;
171                             cp.j = j;
172                             cp.ni = 0; cp.nj=0;//not set yet
173                             cp.ti = times[k].first + ii;
174                             cp.tj = times[k].second + jj;
175                             push_back(cp);
176                         }else{
177                             std::cout<<"ooops: find_(self)_intersections returned NaN:";
178                             //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
179                         }
180                     }
181                 }
182             }
183         }
184     }
185     for( unsigned i=0; i<paths.size(); i++){
186         std::map < double, unsigned > cuts;
187         for( unsigned k=0; k<size(); k++){
188             CrossingPoint cp = (*this)[k];
189             if (cp.i == i) cuts[cp.ti] = k;
190             if (cp.j == i) cuts[cp.tj] = k;
191         }
192         unsigned count = 0;
193         for ( std::map < double, unsigned >::iterator m=cuts.begin(); m!=cuts.end(); m++ ){
194             if ( (*this)[m->second].i == i && (*this)[m->second].ti == m->first ){
195                 (*this)[m->second].ni = count;
196             }else{
197                 (*this)[m->second].nj = count;
198             }
199             count++;
200         }
201     }
204 CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
206     if (input.size()>0 && input.size()%9 ==0){
207         using namespace Geom;
208         for( unsigned n=0; n<input.size();  ){
209             CrossingPoint cp;
210             cp.pt[X] = input[n++];
211             cp.pt[Y] = input[n++];
212             cp.i = input[n++];
213             cp.j = input[n++];
214             cp.ni = input[n++];
215             cp.nj = input[n++];
216             cp.ti = input[n++];
217             cp.tj = input[n++];
218             cp.sign = input[n++];
219             push_back(cp);
220         }
221     }
224 std::vector<double>
225 CrossingPoints::to_vector()
227     using namespace Geom;
228     std::vector<double> result;
229     for( unsigned n=0; n<size(); n++){
230         CrossingPoint cp = (*this)[n];
231         result.push_back(cp.pt[X]);
232         result.push_back(cp.pt[Y]);
233         result.push_back(double(cp.i));
234         result.push_back(double(cp.j));
235         result.push_back(double(cp.ni));
236         result.push_back(double(cp.nj));
237         result.push_back(double(cp.ti));
238         result.push_back(double(cp.tj));
239         result.push_back(double(cp.sign));
240     }
241     return result;
244 //FIXME: rewrite to check success: return bool, put result in arg.
245 CrossingPoint
246 CrossingPoints::get(unsigned const i, unsigned const ni)
248     for (unsigned k=0; k<size(); k++){
249         if (
250             ((*this)[k].i==i && (*this)[k].ni==ni) ||
251             ((*this)[k].j==i && (*this)[k].nj==ni)
252             ) return (*this)[k];
253     }
254     g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
255     assert(false);//debug purpose...
256     return CrossingPoint();
259 unsigned
260 idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
262     double dist=-1;
263     unsigned result = cpts.size();
264     for (unsigned k=0; k<cpts.size(); k++){
265         double dist_k = Geom::L2(p-cpts[k].pt);
266         if (dist<0 || dist>dist_k){
267             result = k;
268             dist = dist_k;
269         }
270     }
271     return result;
274 //TODO: Find a way to warn the user when the topology changes.
275 //TODO: be smarter at guessing the signs when the topology changed?
276 void
277 CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
279     bool topo_changed = false;
280     for (unsigned n=0; n<size(); n++){
281         if ( n<other.size() &&
282              other[n].i  == (*this)[n].i  &&
283              other[n].j  == (*this)[n].j  &&
284              other[n].ni == (*this)[n].ni &&
285              other[n].nj == (*this)[n].nj    )
286         {
287             (*this)[n].sign = other[n].sign;
288         }else{
289             topo_changed = true;
290             break;
291         }
292     }
293     if (topo_changed){
294         //TODO: Find a way to warn the user!!
295         std::cout<<"knot topolgy changed!\n";
296         for (unsigned n=0; n<size(); n++){
297             Geom::Point p = (*this)[n].pt;
298             unsigned idx = idx_of_nearest(other,p);
299             if (idx<other.size()){
300                 (*this)[n].sign = other[idx].sign;
301             }else{
302                 (*this)[n].sign = default_value;
303             }
304         }
305     }
310 //---------------------------------------------------------------------------
311 //---------------------------------------------------------------------------
312 //LPEKnot effect.
313 //---------------------------------------------------------------------------
314 //---------------------------------------------------------------------------
317 LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
318     Effect(lpeobject),
319     // initialise your parameters here:
320     interruption_width(_("Interruption width"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3),
321     prop_to_stroke_width(_("unit of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width."), "prop_to_stroke_width", &wr, this, true),
322     add_stroke_width(_("add stroke width to interruption size"), _("Add the stroke width to the interruption size."), "add_stroke_width", &wr, this, true),
323     add_other_stroke_width(_("add other's stroke width to interruption size"), _("Add crossed stroke width to the interruption size."), "add_other_stroke_width", &wr, this, true),
324     switcher_size(_("Switcher size"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15),
325     crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this),
326     gpaths(),gstroke_widths()
328     // register all your parameters here, so Inkscape knows which parameters this effect has:
329     registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
330     registerParameter( dynamic_cast<Parameter *>(&prop_to_stroke_width) );
331     registerParameter( dynamic_cast<Parameter *>(&add_stroke_width) );
332     registerParameter( dynamic_cast<Parameter *>(&add_other_stroke_width) );
333     registerParameter( dynamic_cast<Parameter *>(&switcher_size) );
334     registerParameter( dynamic_cast<Parameter *>(&crossing_points_vector) );
336     registerKnotHolderHandle(new KnotHolderEntityCrossingSwitcher(), _("Drag to select a crossing, click to flip it"));
337     crossing_points = LPEKnotNS::CrossingPoints();
338     selectedCrossing = 0;
339     switcher = Geom::Point(0,0);
342 LPEKnot::~LPEKnot()
347 void
348 LPEKnot::updateSwitcher(){
349     if (selectedCrossing < crossing_points.size()){
350         switcher = crossing_points[selectedCrossing].pt;
351         //std::cout<<"placing switcher at "<<switcher<<" \n";
352     }else if (crossing_points.size()>0){
353         selectedCrossing = 0;
354         switcher = crossing_points[selectedCrossing].pt;
355         //std::cout<<"placing switcher at "<<switcher<<" \n";
356     }else{
357         std::cout<<"hiding switcher!\n";
358         //TODO: is there a way to properly hide the helper.
359         //switcher = Geom::Point(Geom::infinity(),Geom::infinity());
360         switcher = Geom::Point(1e10,1e10);
361     }
364 std::vector<Geom::Path>
365 LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in)
367     using namespace Geom;
368     std::vector<Geom::Path> path_out;
370     if (gpaths.size()==0){
371         return path_in;
372     }
374     for (unsigned comp=0; comp<path_in.size(); comp++){
376         //find the relevant path component in gpaths (required to allow groups!)
377         //Q: do we always recieve the group members in the same order? can we rest on that?
378         unsigned i0 = 0;
379         for (i0=0; i0<gpaths.size(); i0++){
380             if (path_in[comp]==gpaths[i0]) break;
381         }
382         if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
384         std::vector<Interval> dom;
385         dom.push_back(Interval(0.,gpaths[i0].size()));
386         for (unsigned p = 0; p < crossing_points.size(); p++){
387             if (crossing_points[p].i == i0 || crossing_points[p].j == i0){
388                 unsigned i = crossing_points[p].i;
389                 unsigned j = crossing_points[p].j;
390                 double ti = crossing_points[p].ti;
391                 double tj = crossing_points[p].tj;
392                 
393                 double curveidx, t;
394                 
395                 t = modf(ti, &curveidx);
396                 if(curveidx == gpaths[i].size() ) { curveidx--; t = 1.;}
397                 assert(curveidx >= 0 && curveidx < gpaths[i].size());
398                 std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
400                 t = modf(tj, &curveidx);
401                 if(curveidx == gpaths[j].size() ) { curveidx--; t = 1.;}
402                 assert(curveidx >= 0 && curveidx < gpaths[j].size());
403                 std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
406                 int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1);
408                 bool i0_is_under = false;
409                 if ( crossing_points[p].sign * geom_sign > 0 ){
410                     i0_is_under = ( i == i0 );
411                 }else if ( crossing_points[p].sign * geom_sign < 0 ){
412                     if (j == i0){
413                         std::swap( i, j);
414                         std::swap(ti, tj);
415                         std::swap(flag_i,flag_j);
416                         i0_is_under = true;
417                     }
418                 }
419                 if (i0_is_under){
420                     double width = interruption_width;
421                     if ( prop_to_stroke_width.get_value() ) {
422                         width *= gstroke_widths[i];
423                     }
424                     if ( add_stroke_width.get_value() ) {
425                         width += gstroke_widths[i];
426                     }
427                     if ( add_other_stroke_width.get_value() ) {
428                         width += gstroke_widths[j];
429                     }
430                     Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
431                     double period  = gpaths[i0].size();//hm... Should this be gpaths[i0].size()+1?
432                     if (hidden.max() > period ) hidden -= period;
433                     if (hidden.min()<0){
434                         dom = complementOf( Interval(0,hidden.max()) ,dom);
435                         dom = complementOf( Interval(hidden.min()+period, period) ,dom);
436                     }else{
437                         dom = complementOf(hidden,dom);
438                     }
439                 }
440             }
441         }
442         //If the current path is closed and the last/first point is still there, glue first and last piece.
443         unsigned beg_comp = 0, end_comp = dom.size();
444         if ( gpaths[i0].closed() && dom.size() > 1 && dom.front().min() == 0 && dom.back().max() ==  gpaths[i0].size() ){
445             beg_comp++;
446             end_comp--;
447             Path first = gpaths[i0].portion(dom.back());
448             first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS);//FIXME: STITCH_DISCONTINUOUS should not be necessary.
449             path_out.push_back(first);
450         }
451         for (unsigned comp = beg_comp; comp < end_comp; comp++){
452             assert(dom.at(comp).min() >=0 and dom.at(comp).max() <= gpaths.at(i0).size());
453             path_out.push_back(gpaths[i0].portion(dom.at(comp)));
454         }
455     }
456     return path_out;
461 //recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
462 void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){
463     if (SP_IS_GROUP(lpeitem)) {
464         GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
465         for ( GSList const *iter = item_list; iter; iter = iter->next ) {
466             SPObject *subitem = static_cast<SPObject *>(iter->data);
467             if (SP_IS_LPE_ITEM(subitem)) {
468                 collectPathsAndWidths(SP_LPE_ITEM(subitem), paths, stroke_widths);
469             }
470         }
471     }
472     else if (SP_IS_SHAPE(lpeitem)) {
473         SPCurve * c = sp_shape_get_curve(SP_SHAPE(lpeitem));
474         if (c) {
475             Geom::PathVector subpaths = c->get_pathvector();
476             for (unsigned i=0; i<subpaths.size(); i++){
477                 paths.push_back(subpaths[i]);
478                 //FIXME: do we have to be more carefull when trying to access stroke width?
479                 stroke_widths.push_back(SP_ITEM(lpeitem)->style->stroke_width.computed);
480             }
481         }
482     }
486 void
487 LPEKnot::doBeforeEffect (SPLPEItem *lpeitem)
489     using namespace Geom;
490     original_bbox(lpeitem);
492     gpaths = std::vector<Geom::Path>();
493     gstroke_widths = std::vector<double>();
494     collectPathsAndWidths(lpeitem, gpaths, gstroke_widths);
496     LPEKnotNS::CrossingPoints old_crdata(crossing_points_vector.data());
498     std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
500     std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
501     for (unsigned toto=0; toto<old_crdata.size(); toto++){
502         std::cout<<"(";
503         std::cout<<old_crdata[toto].i<<",";
504         std::cout<<old_crdata[toto].j<<",";
505         std::cout<<old_crdata[toto].ni<<",";
506         std::cout<<old_crdata[toto].nj<<",";
507         std::cout<<old_crdata[toto].ti<<",";
508         std::cout<<old_crdata[toto].tj<<",";
509         std::cout<<old_crdata[toto].sign<<"),";
510     }
511     std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
512     for (unsigned toto=0; toto<crossing_points.size(); toto++){
513         std::cout<<"(";
514         std::cout<<crossing_points[toto].i<<",";
515         std::cout<<crossing_points[toto].j<<",";
516         std::cout<<crossing_points[toto].ni<<",";
517         std::cout<<crossing_points[toto].nj<<",";
518         std::cout<<crossing_points[toto].ti<<",";
519         std::cout<<crossing_points[toto].tj<<",";
520         std::cout<<crossing_points[toto].sign<<"),";
521     }
523     //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
524     //else std::cout<<"old data is empty!!\n";
525     crossing_points = LPEKnotNS::CrossingPoints(gpaths);
526     crossing_points.inherit_signs(old_crdata);
527     crossing_points_vector.param_set_and_write_new_value(crossing_points.to_vector());
528     updateSwitcher();
532 static LPEKnot *
533 get_effect(SPItem *item)
535     Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
536     if (effect->effectType() != KNOT) {
537         g_print ("Warning: Effect is not of type LPEKnot!\n");
538         return NULL;
539     }
540     return static_cast<LPEKnot *>(effect);
543 void
544 LPEKnot::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
546     using namespace Geom;
547     double r = switcher_size*.1;
548     char const * svgd;
549     //TODO: use a nice path!
550     if (selectedCrossing >= crossing_points.size()||crossing_points[selectedCrossing].sign > 0){
551         //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l  5,-1 -1,2";
552         svgd = "m -7.07,7.07 c 3.9,3.91 10.24,3.91 14.14,0 3.91,-3.9 3.91,-10.24 0,-14.14 -3.9,-3.91 -10.24,-3.91 -14.14,0 l 2.83,-4.24 0.7,2.12";
553     }else if (crossing_points[selectedCrossing].sign < 0){
554         //svgd = "M  10,0 A 10 10 0 1 1 0,-10 l -5,-1  1,2";
555         svgd = "m 7.07,7.07 c -3.9,3.91 -10.24,3.91 -14.14,0 -3.91,-3.9 -3.91,-10.24 0,-14.14 3.9,-3.91 10.24,-3.91 14.14,0 l -2.83,-4.24 -0.7,2.12";
556     }else{
557         //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
558         svgd = "M 10,0 C 10,5.52 5.52,10 0,10 -5.52,10 -10,5.52 -10,0 c 0,-5.52 4.48,-10 10,-10 5.52,0 10,4.48 10,10 z";
559     }
560     PathVector pathv = sp_svg_read_pathv(svgd);
561     pathv *= Matrix(r,0,0,r,0,0);
562     pathv+=switcher;
563     hp_vec.push_back(pathv);
566 void
567 KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
569     LPEKnot* lpe = get_effect(item);
571     lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
572     lpe->updateSwitcher();
573     // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
574     sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
577 Geom::Point
578 KnotHolderEntityCrossingSwitcher::knot_get()
580     LPEKnot* lpe = get_effect(item);
581     return snap_knot_position(lpe->switcher);
584 void
585 KnotHolderEntityCrossingSwitcher::knot_click(guint state)
587     LPEKnot* lpe = get_effect(item);
588     unsigned s = lpe->selectedCrossing;
589     if (s < lpe->crossing_points.size()){
590         if (state & GDK_SHIFT_MASK){
591             lpe->crossing_points[s].sign = 1;
592         }else{
593             int sign = lpe->crossing_points[s].sign;
594             lpe->crossing_points[s].sign = ((sign+2)%3)-1;
595             //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
596         }
597         lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
599         // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
600         sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
601     }
605 /* ######################## */
607 } // namespace LivePathEffect
608 } // namespace Inkscape
610 /*
611   Local Variables:
612   mode:c++
613   c-file-style:"stroustrup"
614   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
615   indent-tabs-mode:nil
616   fill-column:99
617   End:
618 */
619 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :