1 /** @file
2 * @brief LPE knot effect implementation
3 */
4 /* Authors:
5 * Jean-Francois Barraud <jf.barraud@gmail.com>
6 * Abhishek Sharma
7 *
8 * Copyright (C) 2007 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include "sp-shape.h"
14 #include "sp-path.h"
15 #include "display/curve.h"
16 #include "live_effects/lpe-knot.h"
17 #include "svg/svg.h"
18 #include "style.h"
19 #include "knot-holder-entity.h"
21 #include <2geom/sbasis-to-bezier.h>
22 #include <2geom/sbasis.h>
23 #include <2geom/d2.h>
24 #include <2geom/d2-sbasis.h>
25 #include <2geom/path.h>
26 //#include <2geom/crossing.h>
27 #include <2geom/bezier-to-sbasis.h>
28 #include <2geom/basic-intersection.h>
29 #include <2geom/exception.h>
31 // for change crossing undo
32 #include "verbs.h"
33 #include "document.h"
35 #include <exception>
37 namespace Inkscape {
38 namespace LivePathEffect {
40 class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity
41 {
42 public:
43 virtual ~KnotHolderEntityCrossingSwitcher() {}
45 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
46 virtual Geom::Point knot_get();
47 virtual void knot_click(guint state);
48 };
51 Geom::Path::size_type size_nondegenerate(Geom::Path const &path) {
52 Geom::Path::size_type retval = path.size_open();
54 // if path is closed and closing segment is not degenerate
55 if (path.closed() && !path.back_closed().isDegenerate()) {
56 retval = path.size_closed();
57 }
59 return retval;
60 }
62 //---------------------------------------------------------------------------
63 //LPEKnot specific Interval manipulation.
64 //---------------------------------------------------------------------------
66 //remove an interval from an union of intervals.
67 //TODO: is it worth moving it to 2Geom?
68 static
69 std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
70 std::vector<Geom::Interval> ret;
71 if (!domain.empty()) {
72 double min = domain.front().min();
73 double max = domain.back().max();
74 Geom::Interval I1 = Geom::Interval(min,I.min());
75 Geom::Interval I2 = Geom::Interval(I.max(),max);
77 for (unsigned i = 0; i<domain.size(); i++){
78 boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
79 if (I1i && !I1i->isSingular()) ret.push_back(I1i.get());
80 boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
81 if (I2i && !I2i->isSingular()) ret.push_back(I2i.get());
82 }
83 }
84 return ret;
85 }
87 //find the time interval during which patha is hidden by pathb near a given crossing.
88 // Warning: not accurate!
89 static
90 Geom::Interval
91 findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
92 double const ta, double const width){
93 using namespace Geom;
94 Point T = unit_vector(pt_and_dir[1]);
95 Point N = T.cw();
96 Point A = pt_and_dir[0]-3*width*T, B = A+6*width*T;
98 Matrix mat = from_basis( T, N, pt_and_dir[0] );
99 mat = mat.inverse();
100 Path p = patha * mat;
102 std::vector<double> times;
104 //TODO: explore the path fwd/backward from ta (worth?)
105 for (unsigned i = 0; i < size_nondegenerate(patha); i++){
106 D2<SBasis> f = p[i].toSBasis();
107 std::vector<double> times_i, temptimes;
108 temptimes = roots(f[Y]-width);
109 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
110 temptimes = roots(f[Y]+width);
111 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
112 temptimes = roots(f[X]-3*width);
113 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
114 temptimes = roots(f[X]+3*width);
115 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
116 for (unsigned k=0; k<times_i.size(); k++){
117 times_i[k]+=i;
118 }
119 times.insert(times.end(), times_i.begin(), times_i.end() );
120 }
121 std::sort( times.begin(), times.end() );
122 std::vector<double>::iterator new_end = std::unique( times.begin(), times.end() );
123 times.resize( new_end - times.begin() );
125 double tmin = 0, tmax = size_nondegenerate(patha);
126 double period = size_nondegenerate(patha);
127 if (times.size()>0){
128 unsigned rk = upper_bound( times.begin(), times.end(), ta ) - times.begin();
129 if ( rk < times.size() )
130 tmax = times[rk];
131 else if ( patha.closed() )
132 tmax = times[0]+period;
134 if ( rk > 0 )
135 tmin = times[rk-1];
136 else if ( patha.closed() )
137 tmin = times.back()-period;
138 }
139 return Interval(tmin,tmax);
140 }
142 //---------------------------------------------------------------------------
143 //LPEKnot specific Crossing Data manipulation.
144 //---------------------------------------------------------------------------
146 //Yet another crossing data representation.
147 // an CrossingPoint stores
148 // -an intersection point
149 // -the involved path components
150 // -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
152 namespace LPEKnotNS {//just in case...
153 CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){
154 // std::cout<<"\nCrossingPoints creation from path vector\n";
155 for( unsigned i=0; i<paths.size(); i++){
156 for( unsigned ii=0; ii < size_nondegenerate(paths[i]); ii++){
157 for( unsigned j=i; j<paths.size(); j++){
158 for( unsigned jj=(i==j?ii:0); jj < size_nondegenerate(paths[j]); jj++){
159 std::vector<std::pair<double,double> > times;
160 if ( i==j && ii==jj){
162 // std::cout<<"--(self int)\n";
163 // std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
164 // std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
166 find_self_intersections( times, paths[i][ii].toSBasis() );
167 }else{
168 // std::cout<<"--(pair int)\n";
169 // std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
170 // std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
171 // std::cout<<"with\n";
172 // std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
173 // std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
175 find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
176 }
177 for (unsigned k=0; k<times.size(); k++){
178 //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
179 if (times[k].first == times[k].first && times[k].second == times[k].second ){//is this the way to test NaN?
180 double zero = 1e-4;
181 if ( i==j && fabs(times[k].first+ii - times[k].second-jj)<=zero ){//this is just end=start of successive curves in a path.
182 continue;
183 }
184 if ( i==j && ii == 0 && jj == size_nondegenerate(paths[i])-1 &&
185 paths[i].closed() &&
186 fabs(times[k].first) <= zero &&
187 fabs(times[k].second - 1) <= zero ){//this is just end=start of a closed path.
188 continue;
189 }
190 CrossingPoint cp;
191 cp.pt = paths[i][ii].pointAt(times[k].first);
192 cp.sign = 1;
193 cp.i = i;
194 cp.j = j;
195 cp.ni = 0; cp.nj=0;//not set yet
196 cp.ti = times[k].first + ii;
197 cp.tj = times[k].second + jj;
198 push_back(cp);
199 }else{
200 std::cout<<"ooops: find_(self)_intersections returned NaN:";
201 //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
202 }
203 }
204 }
205 }
206 }
207 }
208 for( unsigned i=0; i<paths.size(); i++){
209 std::map < double, unsigned > cuts;
210 for( unsigned k=0; k<size(); k++){
211 CrossingPoint cp = (*this)[k];
212 if (cp.i == i) cuts[cp.ti] = k;
213 if (cp.j == i) cuts[cp.tj] = k;
214 }
215 unsigned count = 0;
216 for ( std::map < double, unsigned >::iterator m=cuts.begin(); m!=cuts.end(); m++ ){
217 if ( (*this)[m->second].i == i && (*this)[m->second].ti == m->first ){
218 (*this)[m->second].ni = count;
219 }else{
220 (*this)[m->second].nj = count;
221 }
222 count++;
223 }
224 }
225 }
227 CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
228 {
229 if (input.size()>0 && input.size()%9 ==0){
230 using namespace Geom;
231 for( unsigned n=0; n<input.size(); ){
232 CrossingPoint cp;
233 cp.pt[X] = input[n++];
234 cp.pt[Y] = input[n++];
235 cp.i = input[n++];
236 cp.j = input[n++];
237 cp.ni = input[n++];
238 cp.nj = input[n++];
239 cp.ti = input[n++];
240 cp.tj = input[n++];
241 cp.sign = input[n++];
242 push_back(cp);
243 }
244 }
245 }
247 std::vector<double>
248 CrossingPoints::to_vector()
249 {
250 using namespace Geom;
251 std::vector<double> result;
252 for( unsigned n=0; n<size(); n++){
253 CrossingPoint cp = (*this)[n];
254 result.push_back(cp.pt[X]);
255 result.push_back(cp.pt[Y]);
256 result.push_back(double(cp.i));
257 result.push_back(double(cp.j));
258 result.push_back(double(cp.ni));
259 result.push_back(double(cp.nj));
260 result.push_back(double(cp.ti));
261 result.push_back(double(cp.tj));
262 result.push_back(double(cp.sign));
263 }
264 return result;
265 }
267 //FIXME: rewrite to check success: return bool, put result in arg.
268 CrossingPoint
269 CrossingPoints::get(unsigned const i, unsigned const ni)
270 {
271 for (unsigned k=0; k<size(); k++){
272 if (
273 ((*this)[k].i==i && (*this)[k].ni==ni) ||
274 ((*this)[k].j==i && (*this)[k].nj==ni)
275 ) return (*this)[k];
276 }
277 g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
278 assert(false);//debug purpose...
279 return CrossingPoint();
280 }
282 unsigned
283 idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
284 {
285 double dist=-1;
286 unsigned result = cpts.size();
287 for (unsigned k=0; k<cpts.size(); k++){
288 double dist_k = Geom::L2(p-cpts[k].pt);
289 if (dist<0 || dist>dist_k){
290 result = k;
291 dist = dist_k;
292 }
293 }
294 return result;
295 }
297 //TODO: Find a way to warn the user when the topology changes.
298 //TODO: be smarter at guessing the signs when the topology changed?
299 void
300 CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
301 {
302 bool topo_changed = false;
303 for (unsigned n=0; n<size(); n++){
304 if ( n<other.size() &&
305 other[n].i == (*this)[n].i &&
306 other[n].j == (*this)[n].j &&
307 other[n].ni == (*this)[n].ni &&
308 other[n].nj == (*this)[n].nj )
309 {
310 (*this)[n].sign = other[n].sign;
311 }else{
312 topo_changed = true;
313 break;
314 }
315 }
316 if (topo_changed){
317 //TODO: Find a way to warn the user!!
318 // std::cout<<"knot topolgy changed!\n";
319 for (unsigned n=0; n<size(); n++){
320 Geom::Point p = (*this)[n].pt;
321 unsigned idx = idx_of_nearest(other,p);
322 if (idx<other.size()){
323 (*this)[n].sign = other[idx].sign;
324 }else{
325 (*this)[n].sign = default_value;
326 }
327 }
328 }
329 }
331 }
333 //---------------------------------------------------------------------------
334 //---------------------------------------------------------------------------
335 //LPEKnot effect.
336 //---------------------------------------------------------------------------
337 //---------------------------------------------------------------------------
340 LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
341 Effect(lpeobject),
342 // initialise your parameters here:
343 interruption_width(_("Fixed width:"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3),
344 prop_to_stroke_width(_("In units of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width"), "prop_to_stroke_width", &wr, this, true),
345 add_stroke_width(_("Stroke width"), _("Add the stroke width to the interruption size"), "add_stroke_width", &wr, this, true),
346 add_other_stroke_width(_("Crossing path stroke width"), _("Add crossed stroke width to the interruption size"), "add_other_stroke_width", &wr, this, true),
347 switcher_size(_("Switcher size:"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15),
348 crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this),
349 gpaths(),gstroke_widths()
350 {
351 // register all your parameters here, so Inkscape knows which parameters this effect has:
352 registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
353 registerParameter( dynamic_cast<Parameter *>(&prop_to_stroke_width) );
354 registerParameter( dynamic_cast<Parameter *>(&add_stroke_width) );
355 registerParameter( dynamic_cast<Parameter *>(&add_other_stroke_width) );
356 registerParameter( dynamic_cast<Parameter *>(&switcher_size) );
357 registerParameter( dynamic_cast<Parameter *>(&crossing_points_vector) );
359 registerKnotHolderHandle(new KnotHolderEntityCrossingSwitcher(), _("Drag to select a crossing, click to flip it"));
360 crossing_points = LPEKnotNS::CrossingPoints();
361 selectedCrossing = 0;
362 switcher = Geom::Point(0,0);
363 }
365 LPEKnot::~LPEKnot()
366 {
368 }
370 void
371 LPEKnot::updateSwitcher(){
372 if (selectedCrossing < crossing_points.size()){
373 switcher = crossing_points[selectedCrossing].pt;
374 //std::cout<<"placing switcher at "<<switcher<<" \n";
375 }else if (crossing_points.size()>0){
376 selectedCrossing = 0;
377 switcher = crossing_points[selectedCrossing].pt;
378 //std::cout<<"placing switcher at "<<switcher<<" \n";
379 }else{
380 //std::cout<<"hiding switcher!\n";
381 //TODO: is there a way to properly hide the helper.
382 //switcher = Geom::Point(Geom::infinity(),Geom::infinity());
383 switcher = Geom::Point(1e10,1e10);
384 }
385 }
387 std::vector<Geom::Path>
388 LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in)
389 {
390 using namespace Geom;
391 std::vector<Geom::Path> path_out;
393 if (gpaths.size()==0){
394 return path_in;
395 }
397 for (unsigned comp=0; comp<path_in.size(); comp++){
399 //find the relevant path component in gpaths (required to allow groups!)
400 //Q: do we always recieve the group members in the same order? can we rest on that?
401 unsigned i0 = 0;
402 for (i0=0; i0<gpaths.size(); i0++){
403 if (path_in[comp]==gpaths[i0]) break;
404 }
405 if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
407 std::vector<Interval> dom;
408 dom.push_back(Interval(0., size_nondegenerate(gpaths[i0])));
409 for (unsigned p = 0; p < crossing_points.size(); p++){
410 if (crossing_points[p].i == i0 || crossing_points[p].j == i0){
411 unsigned i = crossing_points[p].i;
412 unsigned j = crossing_points[p].j;
413 double ti = crossing_points[p].ti;
414 double tj = crossing_points[p].tj;
416 double curveidx, t;
418 t = modf(ti, &curveidx);
419 if(curveidx == size_nondegenerate(gpaths[i]) ) { curveidx--; t = 1.;}
420 assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[i]));
421 std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
423 t = modf(tj, &curveidx);
424 if(curveidx == size_nondegenerate(gpaths[j]) ) { curveidx--; t = 1.;}
425 assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[j]));
426 std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
429 int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1);
431 bool i0_is_under = false;
432 if ( crossing_points[p].sign * geom_sign > 0 ){
433 i0_is_under = ( i == i0 );
434 }else if ( crossing_points[p].sign * geom_sign < 0 ){
435 if (j == i0){
436 std::swap( i, j);
437 std::swap(ti, tj);
438 std::swap(flag_i,flag_j);
439 i0_is_under = true;
440 }
441 }
442 if (i0_is_under){
443 double width = interruption_width;
444 if ( prop_to_stroke_width.get_value() ) {
445 width *= gstroke_widths[i];
446 }
447 if ( add_stroke_width.get_value() ) {
448 width += gstroke_widths[i];
449 }
450 if ( add_other_stroke_width.get_value() ) {
451 width += gstroke_widths[j];
452 }
453 Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
454 double period = size_nondegenerate(gpaths[i0]);
455 if (hidden.max() > period ) hidden -= period;
456 if (hidden.min()<0){
457 dom = complementOf( Interval(0,hidden.max()) ,dom);
458 dom = complementOf( Interval(hidden.min()+period, period) ,dom);
459 }else{
460 dom = complementOf(hidden,dom);
461 }
462 }
463 }
464 }
466 //If the all component is hidden, continue.
467 if ( dom.size() == 0){
468 continue;
469 }
471 //If the current path is closed and the last/first point is still there, glue first and last piece.
472 unsigned beg_comp = 0, end_comp = dom.size();
473 if ( gpaths[i0].closed() && dom.front().min() == 0 && dom.back().max() == size_nondegenerate(gpaths[i0]) ){
474 if ( dom.size() == 1){
475 path_out.push_back(gpaths[i0]);
476 continue;
477 }else{
478 // std::cout<<"fusing first and last component\n";
479 beg_comp++;
480 end_comp--;
481 Path first = gpaths[i0].portion(dom.back());
482 //FIXME: STITCH_DISCONTINUOUS should not be necessary (?!?)
483 first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS);
484 path_out.push_back(first);
485 }
486 }
487 for (unsigned comp = beg_comp; comp < end_comp; comp++){
488 assert(dom.at(comp).min() >=0 and dom.at(comp).max() <= size_nondegenerate(gpaths.at(i0)));
489 path_out.push_back(gpaths[i0].portion(dom.at(comp)));
490 }
491 }
492 return path_out;
493 }
497 //recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
498 void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){
499 if (SP_IS_GROUP(lpeitem)) {
500 GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
501 for ( GSList const *iter = item_list; iter; iter = iter->next ) {
502 SPObject *subitem = static_cast<SPObject *>(iter->data);
503 if (SP_IS_LPE_ITEM(subitem)) {
504 collectPathsAndWidths(SP_LPE_ITEM(subitem), paths, stroke_widths);
505 }
506 }
507 }
508 else if (SP_IS_SHAPE(lpeitem)) {
509 SPCurve * c = NULL;
510 if (SP_IS_PATH(lpeitem)) {
511 c = sp_path_get_curve_for_edit(SP_PATH(lpeitem));
512 } else {
513 c = SP_SHAPE(lpeitem)->getCurve();
514 }
515 if (c) {
516 Geom::PathVector subpaths = c->get_pathvector();
517 for (unsigned i=0; i<subpaths.size(); i++){
518 paths.push_back(subpaths[i]);
519 //FIXME: do we have to be more carefull when trying to access stroke width?
520 stroke_widths.push_back(SP_ITEM(lpeitem)->style->stroke_width.computed);
521 }
522 }
523 }
524 }
527 void
528 LPEKnot::doBeforeEffect (SPLPEItem *lpeitem)
529 {
530 using namespace Geom;
531 original_bbox(lpeitem);
533 gpaths = std::vector<Geom::Path>();
534 gstroke_widths = std::vector<double>();
535 collectPathsAndWidths(lpeitem, gpaths, gstroke_widths);
537 // std::cout<<"\nPaths on input:\n";
538 // for (unsigned i=0; i<gpaths.size(); i++){
539 // for (unsigned ii=0; ii<gpaths[i].size(); ii++){
540 // std::cout << gpaths[i][ii].toSBasis()[Geom::X] <<"\n";
541 // std::cout << gpaths[i][ii].toSBasis()[Geom::Y] <<"\n";
542 // std::cout<<"--\n";
543 // }
544 // }
546 //std::cout<<"crossing_pts_vect: "<<crossing_points_vector.param_getSVGValue()<<".\n";
547 //std::cout<<"prop_to_stroke_width: "<<prop_to_stroke_width.param_getSVGValue()<<".\n";
549 LPEKnotNS::CrossingPoints old_crdata(crossing_points_vector.data());
551 // std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
553 // std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
554 // for (unsigned toto=0; toto<old_crdata.size(); toto++){
555 // std::cout<<"(";
556 // std::cout<<old_crdata[toto].i<<",";
557 // std::cout<<old_crdata[toto].j<<",";
558 // std::cout<<old_crdata[toto].ni<<",";
559 // std::cout<<old_crdata[toto].nj<<",";
560 // std::cout<<old_crdata[toto].ti<<",";
561 // std::cout<<old_crdata[toto].tj<<",";
562 // std::cout<<old_crdata[toto].sign<<"),";
563 // }
565 //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
566 //else std::cout<<"old data is empty!!\n";
567 crossing_points = LPEKnotNS::CrossingPoints(gpaths);
568 // std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
569 // for (unsigned toto=0; toto<crossing_points.size(); toto++){
570 // std::cout<<"(";
571 // std::cout<<crossing_points[toto].i<<",";
572 // std::cout<<crossing_points[toto].j<<",";
573 // std::cout<<crossing_points[toto].ni<<",";
574 // std::cout<<crossing_points[toto].nj<<",";
575 // std::cout<<crossing_points[toto].ti<<",";
576 // std::cout<<crossing_points[toto].tj<<",";
577 // std::cout<<crossing_points[toto].sign<<"),";
578 // }
579 crossing_points.inherit_signs(old_crdata);
580 crossing_points_vector.param_set_and_write_new_value(crossing_points.to_vector());
581 updateSwitcher();
582 }
585 static LPEKnot *
586 get_effect(SPItem *item)
587 {
588 Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
589 if (effect->effectType() != KNOT) {
590 g_print ("Warning: Effect is not of type LPEKnot!\n");
591 return NULL;
592 }
593 return static_cast<LPEKnot *>(effect);
594 }
596 void
597 LPEKnot::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
598 {
599 using namespace Geom;
600 double r = switcher_size*.1;
601 char const * svgd;
602 //TODO: use a nice path!
603 if (selectedCrossing >= crossing_points.size()||crossing_points[selectedCrossing].sign > 0){
604 //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l 5,-1 -1,2";
605 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";
606 }else if (crossing_points[selectedCrossing].sign < 0){
607 //svgd = "M 10,0 A 10 10 0 1 1 0,-10 l -5,-1 1,2";
608 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";
609 }else{
610 //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
611 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";
612 }
613 PathVector pathv = sp_svg_read_pathv(svgd);
614 pathv *= Matrix(r,0,0,r,0,0);
615 pathv+=switcher;
616 hp_vec.push_back(pathv);
617 }
619 void
620 KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
621 {
622 LPEKnot* lpe = get_effect(item);
624 lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
625 lpe->updateSwitcher();
626 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
627 sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
628 }
630 Geom::Point
631 KnotHolderEntityCrossingSwitcher::knot_get()
632 {
633 LPEKnot* lpe = get_effect(item);
634 return snap_knot_position(lpe->switcher);
635 }
637 void
638 KnotHolderEntityCrossingSwitcher::knot_click(guint state)
639 {
640 LPEKnot* lpe = get_effect(item);
641 unsigned s = lpe->selectedCrossing;
642 if (s < lpe->crossing_points.size()){
643 if (state & GDK_SHIFT_MASK){
644 lpe->crossing_points[s].sign = 1;
645 }else{
646 int sign = lpe->crossing_points[s].sign;
647 lpe->crossing_points[s].sign = ((sign+2)%3)-1;
648 //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
649 }
650 lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
651 DocumentUndo::done(lpe->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, /// @todo Is this the right verb?
652 _("Change knot crossing"));
654 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
655 // sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
656 }
657 }
660 /* ######################## */
662 } // namespace LivePathEffect
663 } // namespace Inkscape
665 /*
666 Local Variables:
667 mode:c++
668 c-file-style:"stroustrup"
669 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
670 indent-tabs-mode:nil
671 fill-column:99
672 End:
673 */
674 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :