1 
2 
3 #include "controlpointselection.h"
4 #include "tvectorimage.h"
5 #include "tmathutil.h"
6 
7 #include "tools/toolhandle.h"
8 #include "tools/toolutils.h"
9 
10 #include "toonz/tobjecthandle.h"
11 #include "toonz/txshlevelhandle.h"
12 #include "toonz/tstageobject.h"
13 
14 using namespace ToolUtils;
15 
16 namespace {
17 
18 //-----------------------------------------------------------------------------
19 
isLinearPoint(const TPointD & p0,const TPointD & p1,const TPointD & p2)20 inline bool isLinearPoint(const TPointD &p0, const TPointD &p1,
21                           const TPointD &p2) {
22   return (tdistance(p0, p1) < 0.02) && (tdistance(p1, p2) < 0.02);
23 }
24 
25 //-----------------------------------------------------------------------------
26 
27 //! Ritorna \b true se il punto \b p1 e' una cuspide.
isCuspPoint(const TPointD & p0,const TPointD & p1,const TPointD & p2)28 bool isCuspPoint(const TPointD &p0, const TPointD &p1, const TPointD &p2) {
29   TPointD p0_p1(p0 - p1), p2_p1(p2 - p1);
30   double n1 = norm(p0_p1), n2 = norm(p2_p1);
31 
32   // Partial linear points are ALWAYS cusps (since directions from them are
33   // determined by neighbours, not by the points themselves)
34   if ((n1 < 0.02) || (n2 < 0.02)) return true;
35 
36   p0_p1 = p0_p1 * (1.0 / n1);
37   p2_p1 = p2_p1 * (1.0 / n2);
38 
39   return (p0_p1 * p2_p1 > 0) ||
40          (fabs(cross(p0_p1, p2_p1)) > 0.09);  // more than 5° is yes
41 
42   // Distance-based check. Unscalable...
43   // return
44   // !areAlmostEqual(tdistance(p0,p2),tdistance(p0,p1)+tdistance(p1,p2),2);
45 }
46 
47 //-----------------------------------------------------------------------------
48 
computeLinearPoint(const TThickPoint & p1,const TThickPoint & p2,double factor,bool isIn)49 TThickPoint computeLinearPoint(const TThickPoint &p1, const TThickPoint &p2,
50                                double factor, bool isIn) {
51   TThickPoint p = p2 - p1;
52   TThickPoint v = p * (1 / norm(p));
53   if (isIn) return p2 - factor * v;
54   return p1 + factor * v;
55 }
56 
57 //-----------------------------------------------------------------------------
58 /*! Insert a point in the most long chunk between chunk \b indexA and chunk \b
59  * indexB. */
insertPoint(TStroke * stroke,int indexA,int indexB)60 void insertPoint(TStroke *stroke, int indexA, int indexB) {
61   assert(stroke);
62   int j          = 0;
63   int chunkCount = indexB - indexA;
64   if (chunkCount % 2 == 0) return;
65   double length = 0;
66   double firstW, lastW;
67   for (j = indexA; j < indexB; j++) {
68     // cerco il chunk piu' lungo
69     double w0 = stroke->getW(stroke->getChunk(j)->getP0());
70     double w1;
71     if (j == stroke->getChunkCount() - 1)
72       w1 = 1;
73     else
74       w1           = stroke->getW(stroke->getChunk(j)->getP2());
75     double length0 = stroke->getLength(w0);
76     double length1 = stroke->getLength(w1);
77     if (length < length1 - length0) {
78       firstW = w0;
79       lastW  = w1;
80       length = length1 - length0;
81     }
82   }
83   stroke->insertControlPoints((firstW + lastW) * 0.5);
84 }
85 
86 }  // namespace
87 
88 //=============================================================================
89 // ControlPointEditorStroke
90 //-----------------------------------------------------------------------------
91 
clone() const92 ControlPointEditorStroke *ControlPointEditorStroke::clone() const {
93   ControlPointEditorStroke *controlPointEditorStroke =
94       new ControlPointEditorStroke();
95   controlPointEditorStroke->setStroke(m_vi->clone(), m_strokeIndex);
96   return controlPointEditorStroke;
97 }
98 
99 //-----------------------------------------------------------------------------
100 
nextIndex(int index) const101 int ControlPointEditorStroke::nextIndex(int index) const {
102   int cpCount = m_controlPoints.size();
103   if (++index < cpCount) return index;
104 
105   if (isSelfLoop()) {
106     index %= cpCount;
107     return (index < 0) ? index + cpCount : index;
108   }
109 
110   return -1;
111 }
112 
113 //-----------------------------------------------------------------------------
114 
prevIndex(int index) const115 int ControlPointEditorStroke::prevIndex(int index) const {
116   int cpCount = m_controlPoints.size();
117   if (--index >= 0) return index;
118 
119   if (isSelfLoop()) {
120     index %= cpCount;
121     return (index < 0) ? index + cpCount : index;
122   }
123 
124   return -1;
125 }
126 
127 //-----------------------------------------------------------------------------
128 
adjustChunkParity()129 void ControlPointEditorStroke::adjustChunkParity() {
130   TStroke *stroke = getStroke();
131   if (!stroke) return;
132   int firstChunk;
133   int secondChunk = stroke->getChunkCount();
134   int i;
135   for (i = stroke->getChunkCount() - 1; i > 0; i--) {
136     if (tdistance(stroke->getChunk(i - 1)->getP0(),
137                   stroke->getChunk(i)->getP2()) < 0.5)
138       continue;
139     TPointD p0 = stroke->getChunk(i - 1)->getP1();
140     TPointD p1 = stroke->getChunk(i - 1)->getP2();
141     TPointD p2 = stroke->getChunk(i)->getP1();
142     if (isCuspPoint(p0, p1, p2) || isLinearPoint(p0, p1, p2)) {
143       firstChunk = i;
144       insertPoint(stroke, firstChunk, secondChunk);
145       secondChunk = firstChunk;
146     }
147   }
148   insertPoint(stroke, 0, secondChunk);
149 }
150 
151 //-----------------------------------------------------------------------------
152 
resetControlPoints()153 void ControlPointEditorStroke::resetControlPoints() {
154   TStroke *stroke = getStroke();
155   if (!stroke) return;
156   m_controlPoints.clear();
157   int i;
158   int cpCount = stroke->getControlPointCount();
159   if (cpCount == 3) {
160     const TThickQuadratic *chunk = stroke->getChunk(0);
161     if (chunk->getP0() == chunk->getP1() &&
162         chunk->getP0() == chunk->getP2())  // E' un punto
163     {
164       m_controlPoints.push_back(
165           ControlPoint(0, TPointD(0.0, 0.0), TPointD(0.0, 0.0), true));
166       return;
167     }
168   }
169   for (i = 0; i < cpCount; i = i + 4) {
170     TThickPoint speedIn, speedOut;
171     bool isPickOut    = false;
172     TThickPoint p     = stroke->getControlPoint(i);
173     TThickPoint precP = stroke->getControlPoint(i - 1);
174     TThickPoint nextP = stroke->getControlPoint(i + 1);
175     if (0 < i && i < cpCount - 1)  // calcola speedIn e speedOut
176     {
177       speedIn  = p - precP;
178       speedOut = nextP - p;
179     }
180     if (i == 0)  // calcola solo lo speedOut
181     {
182       speedOut                  = nextP - p;
183       if (isSelfLoop()) speedIn = p - stroke->getControlPoint(cpCount - 2);
184     }
185     if (i == cpCount - 1)  // calcola solo lo speedIn
186       speedIn = p - precP;
187     if (i == cpCount - 1 && isSelfLoop())
188       break;  // Se lo stroke e' selfLoop inserisco solo il primo dei due punti
189               // coincidenti
190 
191     bool isCusp = ((i != 0 && i != cpCount - 1) || (isSelfLoop() && i == 0))
192                       ? isCuspPoint(precP, p, nextP)
193                       : true;
194     m_controlPoints.push_back(ControlPoint(i, speedIn, speedOut, isCusp));
195   }
196 }
197 
198 //-----------------------------------------------------------------------------
199 
getPureDependentPoint(int index) const200 TThickPoint ControlPointEditorStroke::getPureDependentPoint(int index) const {
201   TStroke *stroke = getStroke();
202   if (!stroke) return TThickPoint();
203   bool selfLoop = isSelfLoop();
204   int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
205   int nextIndex  = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
206   int pointIndex = m_controlPoints[index].m_pointIndex;
207 
208   TThickPoint oldP(stroke->getControlPoint(pointIndex + 2));
209   TPointD oldSpeedOutP = stroke->getControlPoint(pointIndex + 1);
210   TPointD oldSpeedInP  = stroke->getControlPoint(pointIndex + 3);
211 
212   double dist = tdistance(oldSpeedOutP, oldSpeedInP);
213   double t = (dist > 1e-4) ? tdistance(oldSpeedInP, convert(oldP)) / dist : 0.5;
214 
215   TPointD speedOutPoint(getSpeedOutPoint(index));
216   TPointD nextSpeedInPoint(getSpeedInPoint(nextIndex));
217 
218   return TThickPoint((1 - t) * nextSpeedInPoint + t * speedOutPoint,
219                      oldP.thick);
220 
221   // return TThickPoint(0.5 * (speedOutPoint + nextSpeedInPoint), oldThick);
222 }
223 
224 //-----------------------------------------------------------------------------
225 
getDependentPoints(int index,std::vector<std::pair<int,TThickPoint>> & points) const226 void ControlPointEditorStroke::getDependentPoints(
227     int index, std::vector<std::pair<int, TThickPoint>> &points) const {
228   TStroke *stroke = getStroke();
229   if (!stroke) return;
230 
231   int cpCount = m_controlPoints.size();
232   if (index == cpCount && isSelfLoop())  // strange, but was treated...
233     index = 0;
234 
235   if (index == 0 && cpCount == 1) {
236     // Single point case
237     TStroke *stroke = getStroke();
238     TThickPoint pos(stroke->getControlPoint(m_controlPoints[0].m_pointIndex));
239 
240     points.push_back(std::make_pair(1, pos));
241     points.push_back(std::make_pair(2, pos));
242     return;
243   }
244 
245   int prev = prevIndex(index);
246   if (prev >= 0) {
247     int prevPointIndex = m_controlPoints[prev].m_pointIndex;
248 
249     if (isSpeedOutLinear(prev))
250       points.push_back(
251           std::make_pair(prevPointIndex + 1, getSpeedOutPoint(prev)));
252     points.push_back(
253         std::make_pair(prevPointIndex + 2, getPureDependentPoint(prev)));
254     points.push_back(
255         std::make_pair(prevPointIndex + 3, getSpeedInPoint(index)));
256   }
257 
258   int next = nextIndex(index);
259   if (next >= 0) {
260     int pointIndex = m_controlPoints[index].m_pointIndex;
261 
262     points.push_back(std::make_pair(pointIndex + 1, getSpeedOutPoint(index)));
263     points.push_back(
264         std::make_pair(pointIndex + 2, getPureDependentPoint(index)));
265     if (isSpeedInLinear(next))
266       points.push_back(std::make_pair(pointIndex + 3, getSpeedInPoint(next)));
267   }
268 }
269 
270 //-----------------------------------------------------------------------------
271 
updatePoints()272 void ControlPointEditorStroke::updatePoints() {
273   TStroke *stroke = getStroke();
274   if (!stroke) return;
275   bool selfLoop = isSelfLoop();
276   // Se e' rimasto un unico punto non ha senso che la stroke sia selfloop
277   if (selfLoop && m_controlPoints.size() == 1) {
278     stroke->setSelfLoop(false);
279     selfLoop = false;
280   }
281 
282   // Se e' self loop  devo aggiungere un punto in piu' al cpCount
283   std::vector<TThickPoint> points;
284 
285   int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
286   if (cpCount == 1)
287     // Single point case
288     points.resize(3, getControlPoint(0));
289   else {
290     std::vector<std::pair<int, TThickPoint>> dependentPoints;
291 
292     points.push_back(getControlPoint(0));
293     points.push_back(getSpeedOutPoint(0));
294 
295     int i, pointIndex, currPointIndex = m_controlPoints[0].m_pointIndex + 1;
296     for (i = 1; i < cpCount; ++i) {
297       bool isLastSelfLoopPoint = (selfLoop && i == cpCount - 1);
298       int index                = isLastSelfLoopPoint ? 0 : i;
299 
300       TThickPoint p = getControlPoint(index);
301       pointIndex    = isLastSelfLoopPoint ? getStroke()->getControlPointCount()
302                                        : m_controlPoints[index].m_pointIndex;
303 
304       dependentPoints.clear();
305       getDependentPoints(index, dependentPoints);
306 
307       int j;
308       for (j = 0; j < (int)dependentPoints.size() &&
309                   dependentPoints[j].first < pointIndex;
310            j++) {
311         if (currPointIndex < dependentPoints[j].first) {
312           currPointIndex = dependentPoints[j].first;
313           points.push_back(dependentPoints[j].second);
314         }
315       }
316 
317       points.push_back(p);
318 
319       for (; j < (int)dependentPoints.size(); j++) {
320         if (currPointIndex < dependentPoints[j].first) {
321           currPointIndex = dependentPoints[j].first;
322           points.push_back(dependentPoints[j].second);
323         }
324       }
325     }
326   }
327 
328   stroke->reshape(&points[0], points.size());
329   m_vi->notifyChangedStrokes(m_strokeIndex);
330 }
331 
332 //-----------------------------------------------------------------------------
333 
updateDependentPoint(int index)334 void ControlPointEditorStroke::updateDependentPoint(int index) {
335   TStroke *stroke = getStroke();
336   if (!stroke) return;
337 
338   std::vector<std::pair<int, TThickPoint>> points;
339   getDependentPoints(index, points);
340 
341   int i;
342   for (i = 0; i < (int)points.size(); i++)
343     stroke->setControlPoint(points[i].first, points[i].second);
344 
345   m_vi->notifyChangedStrokes(m_strokeIndex);
346 }
347 
348 //-----------------------------------------------------------------------------
349 
moveSpeedOut(int index,const TPointD & delta,double minDistance)350 void ControlPointEditorStroke::moveSpeedOut(int index, const TPointD &delta,
351                                             double minDistance) {
352   TStroke *stroke = getStroke();
353   if (!stroke) return;
354 
355   // If the next cp has linear speed in, it must be recomputed
356   bool selfLoop = isSelfLoop();
357   int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
358   int nextIndex = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
359   if (m_controlPoints[nextIndex].m_isCusp && isSpeedInLinear(nextIndex))
360     setLinearSpeedIn(nextIndex, true, false);
361 
362   // Update the speedOut
363   m_controlPoints[index].m_speedOut += delta;
364   TPointD newP = m_controlPoints[index].m_speedOut;
365   if (areAlmostEqual(newP.x, 0, minDistance) &&
366       areAlmostEqual(newP.y, 0, minDistance))  // Setto a linear
367   {
368     setLinearSpeedOut(index);
369     return;
370   }
371   if (!m_controlPoints[index].m_isCusp && !isSpeedInLinear(index)) {
372     // Devo ricalcolare lo SpeedIn
373     TPointD v(m_controlPoints[index].m_speedOut *
374               (1.0 / norm(m_controlPoints[index].m_speedOut)));
375     m_controlPoints[index].m_speedIn =
376         TThickPoint(v * norm(m_controlPoints[index].m_speedIn),
377                     m_controlPoints[index].m_speedIn.thick);
378   }
379 }
380 
381 //-----------------------------------------------------------------------------
382 
moveSpeedIn(int index,const TPointD & delta,double minDistance)383 void ControlPointEditorStroke::moveSpeedIn(int index, const TPointD &delta,
384                                            double minDistance) {
385   TStroke *stroke = getStroke();
386   if (!stroke) return;
387 
388   // If the prev cp has linear speed out, it must be recomputed
389   bool selfLoop = isSelfLoop();
390   int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
391   int prevIndex = (selfLoop && index == 0) ? cpCount - 2 : index - 1;
392   if (m_controlPoints[prevIndex].m_isCusp && isSpeedOutLinear(prevIndex))
393     setLinearSpeedOut(prevIndex, true, false);
394 
395   // Update the speedOut
396   m_controlPoints[index].m_speedIn -= delta;
397   TPointD newP = m_controlPoints[index].m_speedIn;
398   if (areAlmostEqual(newP.x, 0, minDistance) &&
399       areAlmostEqual(newP.y, 0, minDistance))  // Setto a linear
400   {
401     setLinearSpeedIn(index);
402     return;
403   }
404   if (!m_controlPoints[index].m_isCusp && !isSpeedOutLinear(index)) {
405     // Devo ricalcolare lo SpeedOut
406     TPointD v(m_controlPoints[index].m_speedIn *
407               (1.0 / norm(m_controlPoints[index].m_speedIn)));
408     m_controlPoints[index].m_speedOut =
409         TThickPoint(v * norm(m_controlPoints[index].m_speedOut),
410                     m_controlPoints[index].m_speedOut.thick);
411   }
412 }
413 
414 //-----------------------------------------------------------------------------
415 
moveSingleControlPoint(int index,const TPointD & delta)416 void ControlPointEditorStroke::moveSingleControlPoint(int index,
417                                                       const TPointD &delta) {
418   TStroke *stroke = getStroke();
419   if (!stroke) return;
420 
421   int pointIndex = m_controlPoints[index].m_pointIndex;
422   assert(stroke && 0 <= pointIndex &&
423          pointIndex < stroke->getControlPointCount());
424 
425   bool selfLoop = isSelfLoop();
426   int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
427 
428   TThickPoint p = stroke->getControlPoint(pointIndex);
429   p             = TThickPoint(p + delta, p.thick);
430   stroke->setControlPoint(pointIndex, p);
431   if (pointIndex == 0 && selfLoop)
432     stroke->setControlPoint(stroke->getControlPointCount() - 1, p);
433 
434   // Directions must be recalculated in the linear cases
435   if ((selfLoop || index > 0) && isSpeedInLinear(index)) {
436     setLinearSpeedIn(index, true, false);
437 
438     // Furthermore, if the NEIGHBOUR point is linear, it has to be
439     // recalculated too
440     int prevIndex = (selfLoop && index == 0) ? cpCount - 2 : index - 1;
441     if (m_controlPoints[prevIndex].m_isCusp && isSpeedOutLinear(prevIndex))
442       setLinearSpeedOut(prevIndex, true, false);
443   }
444   if ((selfLoop || index < cpCount - 1) && isSpeedOutLinear(index)) {
445     setLinearSpeedOut(index, true, false);
446 
447     int nextIndex = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
448     if (m_controlPoints[nextIndex].m_isCusp && isSpeedInLinear(nextIndex))
449       setLinearSpeedIn(nextIndex, true, false);
450   }
451 }
452 
453 //-----------------------------------------------------------------------------
454 
setStroke(const TVectorImageP & vi,int strokeIndex)455 void ControlPointEditorStroke::setStroke(const TVectorImageP &vi,
456                                          int strokeIndex) {
457   m_strokeIndex = strokeIndex;
458   m_vi          = vi;
459   if (!vi || strokeIndex == -1) {
460     m_controlPoints.clear();
461     return;
462   }
463   TStroke *stroke              = getStroke();
464   const TThickQuadratic *chunk = stroke->getChunk(0);
465   if (stroke->getControlPointCount() == 3 && chunk->getP0() == chunk->getP1() &&
466       chunk->getP0() == chunk->getP2()) {
467     resetControlPoints();
468     return;
469   }
470   adjustChunkParity();
471   resetControlPoints();
472 }
473 
474 //-----------------------------------------------------------------------------
475 
getControlPoint(int index) const476 TThickPoint ControlPointEditorStroke::getControlPoint(int index) const {
477   TStroke *stroke = getStroke();
478   assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
479   return stroke->getControlPoint(m_controlPoints[index].m_pointIndex);
480 }
481 
482 //-----------------------------------------------------------------------------
483 
getIndexPointInStroke(int index) const484 int ControlPointEditorStroke::getIndexPointInStroke(int index) const {
485   return m_controlPoints[index].m_pointIndex;
486 }
487 
488 //-----------------------------------------------------------------------------
489 
getSpeedInPoint(int index) const490 TThickPoint ControlPointEditorStroke::getSpeedInPoint(int index) const {
491   TStroke *stroke = getStroke();
492   assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
493 
494   ControlPoint cp = m_controlPoints[index];
495   return stroke->getControlPoint(cp.m_pointIndex) - cp.m_speedIn;
496 }
497 
498 //-----------------------------------------------------------------------------
499 
getSpeedOutPoint(int index) const500 TThickPoint ControlPointEditorStroke::getSpeedOutPoint(int index) const {
501   TStroke *stroke = getStroke();
502   assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
503 
504   ControlPoint cp = m_controlPoints[index];
505   return stroke->getControlPoint(cp.m_pointIndex) + cp.m_speedOut;
506 }
507 
508 //-----------------------------------------------------------------------------
509 
isCusp(int index) const510 bool ControlPointEditorStroke::isCusp(int index) const {
511   TStroke *stroke = getStroke();
512   assert(stroke && 0 <= index && index < (int)getControlPointCount());
513   return m_controlPoints[index].m_isCusp;
514 }
515 
516 //-----------------------------------------------------------------------------
517 
setCusp(int index,bool isCusp,bool setSpeedIn)518 void ControlPointEditorStroke::setCusp(int index, bool isCusp,
519                                        bool setSpeedIn) {
520   m_controlPoints[index].m_isCusp = isCusp;
521   if (isCusp == true) return;
522   moveSpeed(index, TPointD(0.0, 0.0), setSpeedIn, 0.0);
523 }
524 
525 //-----------------------------------------------------------------------------
526 
isSpeedInLinear(int index) const527 bool ControlPointEditorStroke::isSpeedInLinear(int index) const {
528   assert(index < (int)m_controlPoints.size());
529   return (fabs(m_controlPoints[index].m_speedIn.x) <= 0.02) &&
530          (fabs(m_controlPoints[index].m_speedIn.y) <= 0.02);
531 }
532 
533 //-----------------------------------------------------------------------------
534 
isSpeedOutLinear(int index) const535 bool ControlPointEditorStroke::isSpeedOutLinear(int index) const {
536   assert(index < (int)m_controlPoints.size());
537   return (fabs(m_controlPoints[index].m_speedOut.x) <= 0.02) &&
538          (fabs(m_controlPoints[index].m_speedOut.y) <= 0.02);
539 }
540 
541 //-----------------------------------------------------------------------------
542 
setLinearSpeedIn(int index,bool linear,bool updatePoints)543 void ControlPointEditorStroke::setLinearSpeedIn(int index, bool linear,
544                                                 bool updatePoints) {
545   TStroke *stroke = getStroke();
546   if (!stroke || m_controlPoints.size() == 1) return;
547   int pointIndex = m_controlPoints[index].m_pointIndex;
548   if (pointIndex == 0) {
549     if (isSelfLoop())
550       pointIndex = stroke->getControlPointCount() - 1;
551     else
552       return;
553   }
554   int precIndex =
555       (index == 0 && isSelfLoop()) ? m_controlPoints.size() - 1 : index - 1;
556 
557   TThickPoint point     = stroke->getControlPoint(pointIndex);
558   TThickPoint precPoint = (pointIndex > 2)
559                               ? stroke->getControlPoint(pointIndex - 3)
560                               : TThickPoint();
561 
562   if (linear) {
563     TThickPoint p(point - precPoint);
564     double n = norm(p);
565     TThickPoint speedIn =
566         (n != 0.0) ? (0.01 / n) * p : TThickPoint(0.001, 0.001, 0.0);
567     m_controlPoints[index].m_speedIn = speedIn;
568   } else {
569     TThickPoint newPrec2             = (precPoint + point) * 0.5;
570     TThickPoint speedIn              = (point - newPrec2) * 0.5;
571     m_controlPoints[index].m_speedIn = speedIn;
572   }
573   if (updatePoints) updateDependentPoint(index);
574 }
575 
576 //-----------------------------------------------------------------------------
577 
setLinearSpeedOut(int index,bool linear,bool updatePoints)578 void ControlPointEditorStroke::setLinearSpeedOut(int index, bool linear,
579                                                  bool updatePoints) {
580   TStroke *stroke = getStroke();
581   if (!stroke || m_controlPoints.size() == 1) return;
582   int cpCount    = stroke->getControlPointCount();
583   int pointIndex = m_controlPoints[index].m_pointIndex;
584   if (pointIndex == cpCount - 1) {
585     if (isSelfLoop())
586       pointIndex = 0;
587     else
588       return;
589   }
590   int nextIndex =
591       (index == m_controlPoints.size() - 1 && isSelfLoop()) ? 0 : index + 1;
592 
593   TThickPoint point     = stroke->getControlPoint(pointIndex);
594   TThickPoint nextPoint = (pointIndex < cpCount - 3)
595                               ? stroke->getControlPoint(pointIndex + 3)
596                               : TThickPoint();
597 
598   if (linear) {
599     TThickPoint p(nextPoint - point);
600     double n = norm(p);
601     TThickPoint speedOut =
602         (n != 0.0) ? (0.01 / n) * p : TThickPoint(0.001, 0.001, 0.0);
603     m_controlPoints[index].m_speedOut = speedOut;
604   } else {
605     TThickPoint newNext2              = (nextPoint + point) * 0.5;
606     TThickPoint speedOut              = (newNext2 - point) * 0.5;
607     m_controlPoints[index].m_speedOut = speedOut;
608   }
609   if (updatePoints) updateDependentPoint(index);
610 }
611 
612 //-----------------------------------------------------------------------------
613 
setLinear(int index,bool isLinear,bool updatePoints)614 bool ControlPointEditorStroke::setLinear(int index, bool isLinear,
615                                          bool updatePoints) {
616   bool movePrec = (!isSelfLoop()) ? index > 0 : true;
617   bool moveNext = (!isSelfLoop()) ? (index < getControlPointCount() - 1) : true;
618   if (isLinear != isSpeedInLinear(index))
619     setLinearSpeedIn(index, isLinear, updatePoints);
620   else
621     movePrec = false;
622   if (isLinear != isSpeedOutLinear(index))
623     setLinearSpeedOut(index, isLinear, updatePoints);
624   else
625     moveNext                               = false;
626   bool ret                                 = moveNext || movePrec;
627   if (ret) m_controlPoints[index].m_isCusp = true;
628   return ret;
629 }
630 
631 //-----------------------------------------------------------------------------
632 
setControlPointsLinear(std::set<int> points,bool isLinear)633 bool ControlPointEditorStroke::setControlPointsLinear(std::set<int> points,
634                                                       bool isLinear) {
635   std::set<int>::iterator it;
636   bool isChanged = false;
637   for (it = points.begin(); it != points.end(); it++)
638     isChanged = setLinear(*it, isLinear, false) || isChanged;
639   for (it = points.begin(); it != points.end(); it++) updateDependentPoint(*it);
640   return isChanged;
641 }
642 
643 //-----------------------------------------------------------------------------
644 
moveControlPoint(int index,const TPointD & delta)645 void ControlPointEditorStroke::moveControlPoint(int index,
646                                                 const TPointD &delta) {
647   TStroke *stroke = getStroke();
648   if (!stroke) return;
649   assert(stroke && 0 <= index && index < (int)getControlPointCount());
650 
651   moveSingleControlPoint(index, delta);
652   updateDependentPoint(index);
653 }
654 
655 //-----------------------------------------------------------------------------
656 
addControlPoint(const TPointD & pos)657 int ControlPointEditorStroke::addControlPoint(const TPointD &pos) {
658   TStroke *stroke = getStroke();
659   if (!stroke) return -1;
660   double d = 0.01;
661   int indexAtPos;
662   int cpCount = stroke->getControlPointCount();
663   if (cpCount <= 3)  // e' un unico chunk e in questo caso rappresenta un punto
664   {
665     getPointTypeAt(pos, d, indexAtPos);
666     return indexAtPos;
667   }
668 
669   double w       = stroke->getW(pos);
670   int pointIndex = stroke->getControlPointIndexAfterParameter(w);
671 
672   int i, index;
673   for (i = 0; i < getControlPointCount(); i++) {
674     // Cerco il ControlPoint corrispondente all'indice pointIndex. OSS.:
675     // Effettuo il
676     // controllo da zero a getControlPointCount()-1 per gestire il caso del
677     // selfLoop
678     if (pointIndex == m_controlPoints[i].m_pointIndex + 1 ||
679         pointIndex == m_controlPoints[i].m_pointIndex + 2 ||
680         pointIndex == m_controlPoints[i].m_pointIndex + 3 ||
681         pointIndex == m_controlPoints[i].m_pointIndex + 4)
682       index = i;
683   }
684 
685   ControlPoint precCp = m_controlPoints[index];
686   assert(precCp.m_pointIndex >= 0);
687   std::vector<TThickPoint> points;
688 
689   for (i = 0; i < cpCount; i++) {
690     if (i != precCp.m_pointIndex + 1 && i != precCp.m_pointIndex + 2 &&
691         i != precCp.m_pointIndex + 3)
692       points.push_back(stroke->getControlPoint(i));
693     if (i == precCp.m_pointIndex + 2) {
694       bool isBeforePointLinear = isSpeedOutLinear(index);
695       int nextIndex =
696           (isSelfLoop() && index == m_controlPoints.size() - 1) ? 0 : index + 1;
697       bool isNextPointLinear =
698           nextIndex < (int)m_controlPoints.size() && isSpeedInLinear(nextIndex);
699 
700       TThickPoint a0 = stroke->getControlPoint(precCp.m_pointIndex);
701       TThickPoint a1 = stroke->getControlPoint(precCp.m_pointIndex + 1);
702       TThickPoint a2 = stroke->getControlPoint(precCp.m_pointIndex + 2);
703       TThickPoint a3 = stroke->getControlPoint(precCp.m_pointIndex + 3);
704       TThickPoint a4 = stroke->getControlPoint(precCp.m_pointIndex + 4);
705       double dist2   = tdistance2(pos, TPointD(a2));
706       TThickPoint d0, d1, d2, d3, d4, d5, d6;
707 
708       if (isBeforePointLinear && isNextPointLinear) {
709         // Se sono entrambi i punti lineari  inserisco un punto lineare
710         d0 = a1;
711         d3 = stroke->getThickPoint(w);
712         d6 = a3;
713         d2 = computeLinearPoint(d0, d3, 0.01, true);   // SpeedIn
714         d4 = computeLinearPoint(d3, d6, 0.01, false);  // SpeedOut
715         d1 = 0.5 * (d0 + d2);
716         d5 = 0.5 * (d4 + d6);
717       } else if (dist2 < 32) {
718         // Sono molto vicino al punto che non viene visualizzato
719         TThickPoint b0 = 0.5 * (a0 + a1);
720         TThickPoint b1 = 0.5 * (a2 + a1);
721         TThickPoint c0 = 0.5 * (b0 + b1);
722 
723         TThickPoint b2 = 0.5 * (a2 + a3);
724         TThickPoint b3 = 0.5 * (a3 + a4);
725 
726         TThickPoint c1 = 0.5 * (b2 + b3);
727         d0             = b0;
728         d1             = c0;
729         d2             = b1;
730         d3             = a2;
731         d4             = b2;
732         d5             = c1;
733         d6             = b3;
734       } else {
735         bool isInFirstChunk = true;
736         if (pointIndex > precCp.m_pointIndex + 2) {
737           // nel caso in cui sono nel secondo chunk scambio i punti
738           a0 = a4;
739           std::swap(a1, a3);
740           isInFirstChunk = false;
741         }
742 
743         double w0 = (isSelfLoop() && precCp.m_pointIndex + 4 == cpCount - 1 &&
744                      !isInFirstChunk)
745                         ? 1
746                         : stroke->getW(a0);
747         double w1 = stroke->getW(a2);
748         double t  = (w - w0) / (w1 - w0);
749 
750         TThickPoint p  = stroke->getThickPoint(w);
751         TThickPoint b0 = TThickPoint((1 - t) * a0 + t * a1,
752                                      (1 - t) * a0.thick + t * a1.thick);
753         TThickPoint b1 = TThickPoint((1 - t) * a1 + t * a2,
754                                      (1 - t) * a1.thick + t * a2.thick);
755         TThickPoint c0 =
756             TThickPoint(0.5 * a0 + 0.5 * b0, (1 - t) * a0.thick + t * b0.thick);
757         TThickPoint c1 =
758             TThickPoint(0.5 * b0 + 0.5 * p, (1 - t) * b0.thick + t * p.thick);
759         TThickPoint c2 =
760             TThickPoint(0.5 * c0 + 0.5 * c1, (1 - t) * c0.thick + t * c1.thick);
761 
762         d0 = (isInFirstChunk) ? c0 : a3;
763         d1 = (isInFirstChunk) ? c2 : a2;
764         d2 = (isInFirstChunk) ? c1 : b1;
765         d3 = p;
766         d4 = (isInFirstChunk) ? b1 : c1;
767         d5 = (isInFirstChunk) ? a2 : c2;
768         d6 = (isInFirstChunk) ? a3 : c0;
769       }
770       if (isBeforePointLinear && !isNextPointLinear)
771         d1 = computeLinearPoint(d0, d2, 0.01, false);
772       else if (isNextPointLinear && !isBeforePointLinear)
773         d5 = computeLinearPoint(d4, d6, 0.01, true);
774       points.push_back(d0);
775       points.push_back(d1);
776       points.push_back(d2);
777       points.push_back(d3);
778       points.push_back(d4);
779       points.push_back(d5);
780       points.push_back(d6);
781     }
782   }
783 
784   stroke->reshape(&points[0], points.size());
785   resetControlPoints();
786 
787   getPointTypeAt(pos, d, indexAtPos);
788   return indexAtPos;
789 }
790 
791 //-----------------------------------------------------------------------------
792 
deleteControlPoint(int index)793 void ControlPointEditorStroke::deleteControlPoint(int index) {
794   TStroke *stroke = getStroke();
795   if (!stroke) return;
796 
797   assert(stroke && 0 <= index && index < (int)getControlPointCount());
798   // E' un unico chunk e in questo caso rappresenta un punto
799   if (stroke->getControlPointCount() <= 3 ||
800       (isSelfLoop() && stroke->getControlPointCount() <= 5)) {
801     m_controlPoints.clear();
802     m_vi->deleteStroke(m_strokeIndex);
803     return;
804   }
805 
806   QList<int> newPointsIndex;
807   int i;
808   for (i = 0; i < (int)getControlPointCount() - 1; i++)
809     newPointsIndex.push_back(m_controlPoints[i].m_pointIndex);
810 
811   m_controlPoints.removeAt(index);
812   updatePoints();
813 
814   // Aggiorno gli indici dei punti nella stroke
815   assert((int)newPointsIndex.size() == (int)getControlPointCount());
816   for (i                            = 0; i < (int)getControlPointCount(); i++)
817     m_controlPoints[i].m_pointIndex = newPointsIndex.at(i);
818 
819   int prev = prevIndex(index);
820   if (prev >= 0 && isSpeedOutLinear(prev)) {
821     setLinearSpeedOut(prev);
822     updateDependentPoint(prev);
823   }
824   if (index < (int)m_controlPoints.size() && isSpeedInLinear(index)) {
825     setLinearSpeedIn(index);
826     updateDependentPoint(index);
827   }
828 }
829 
830 //-----------------------------------------------------------------------------
831 
moveSpeed(int index,const TPointD & delta,bool isIn,double minDistance)832 void ControlPointEditorStroke::moveSpeed(int index, const TPointD &delta,
833                                          bool isIn, double minDistance) {
834   if (!isIn)
835     moveSpeedOut(index, delta, minDistance);
836   else
837     moveSpeedIn(index, delta, minDistance);
838 
839   updateDependentPoint(index);
840 }
841 
842 //-----------------------------------------------------------------------------
843 
moveSegment(int beforeIndex,int nextIndex,const TPointD & delta,const TPointD & pos)844 void ControlPointEditorStroke::moveSegment(int beforeIndex, int nextIndex,
845                                            const TPointD &delta,
846                                            const TPointD &pos) {
847   TStroke *stroke = getStroke();
848   if (!stroke) return;
849 
850   int cpCount = getControlPointCount();
851   // Verifiche per il caso in cui lo stroke e' selfLoop
852   if (isSelfLoop() && beforeIndex == 0 && nextIndex == cpCount - 1)
853     std::swap(beforeIndex, nextIndex);
854 
855   int beforePointIndex = m_controlPoints[beforeIndex].m_pointIndex;
856   int nextPointIndex   = (isSelfLoop() && nextIndex == 0)
857                            ? stroke->getControlPointCount() - 1
858                            : m_controlPoints[nextIndex].m_pointIndex;
859   double w  = stroke->getW(pos);
860   double w0 = stroke->getParameterAtControlPoint(beforePointIndex);
861   double w4 = stroke->getParameterAtControlPoint(nextPointIndex);
862   if (w0 > w) return;
863   assert(w0 <= w && w <= w4);
864 
865   double t = 1;
866   double s = 1;
867 
868   if (isSpeedOutLinear(beforeIndex)) {
869     m_controlPoints[beforeIndex].m_speedOut =
870         (stroke->getControlPoint(nextPointIndex) -
871          stroke->getControlPoint(beforePointIndex)) *
872         0.3;
873     if (!isSpeedInLinear(beforeIndex))
874       m_controlPoints[beforeIndex].m_isCusp = true;
875   } else if (!isSpeedOutLinear(beforeIndex) && !isSpeedInLinear(beforeIndex) &&
876              !isCusp(beforeIndex)) {
877     t = 1 - fabs(w - w0) / fabs(w4 - w0);
878     moveSingleControlPoint(beforeIndex, t * delta);
879     t = 1 - t;
880   }
881 
882   if (isSpeedInLinear(nextIndex)) {
883     m_controlPoints[nextIndex].m_speedIn =
884         (stroke->getControlPoint(nextPointIndex) -
885          stroke->getControlPoint(beforePointIndex)) *
886         0.3;
887     if (!isSpeedOutLinear(nextIndex))
888       m_controlPoints[nextIndex].m_isCusp = true;
889   } else if (!isSpeedInLinear(nextIndex) && !isSpeedOutLinear(nextIndex) &&
890              !isCusp(nextIndex)) {
891     s = 1 - fabs(w4 - w) / fabs(w4 - w0);
892     moveSingleControlPoint(nextIndex, s * delta);
893     s = 1 - s;
894   }
895 
896   moveSpeedOut(beforeIndex, delta * s, 0);
897   // updateDependentPoint(beforeIndex);
898   moveSpeedIn(nextIndex, delta * t, 0);
899   // updateDependentPoint(nextIndex);
900 
901   updatePoints();
902 }
903 
904 //-----------------------------------------------------------------------------
905 
getPointTypeAt(const TPointD & pos,double & distance2,int & index) const906 ControlPointEditorStroke::PointType ControlPointEditorStroke::getPointTypeAt(
907     const TPointD &pos, double &distance2, int &index) const {
908   TStroke *stroke = getStroke();
909   if (!stroke) return NONE;
910   double w              = stroke->getW(pos);
911   TPointD p             = stroke->getPoint(w);
912   double strokeDistance = tdistance2(p, pos);
913 
914   int precPointIndex     = -1;
915   double minPrecDistance = 0;
916   double minDistance2    = distance2;
917   index                  = -1;
918   PointType type         = NONE;
919   int cpCount            = m_controlPoints.size();
920   int i;
921   for (i = 0; i < cpCount; i++) {
922     ControlPoint cPoint = m_controlPoints[i];
923     TPointD point       = stroke->getControlPoint(cPoint.m_pointIndex);
924     double cpDistance2  = tdistance2(pos, point);
925     double distanceIn2  = !isSpeedInLinear(i)
926                              ? tdistance2(pos, point - cPoint.m_speedIn)
927                              : cpDistance2 + 1;
928     double distanceOut2 = !isSpeedOutLinear(i)
929                               ? tdistance2(pos, point + cPoint.m_speedOut)
930                               : cpDistance2 + 1;
931     if (i == 0 && !isSelfLoop())
932       distanceIn2 = std::max(cpDistance2, distanceOut2) + 1;
933     if (i == cpCount - 1 && !isSelfLoop())
934       distanceOut2 = std::max(cpDistance2, distanceIn2) + 1;
935 
936     if (cpDistance2 < distanceIn2 && cpDistance2 < distanceOut2 &&
937         (cpDistance2 < minDistance2 || index < 0)) {
938       minDistance2 = cpDistance2;
939       index        = i;
940       type         = CONTROL_POINT;
941     } else if (distanceIn2 < cpDistance2 && distanceIn2 < distanceOut2 &&
942                (distanceIn2 < minDistance2 || index < 0)) {
943       minDistance2 = distanceIn2;
944       index        = i;
945       type         = SPEED_IN;
946     } else if (distanceOut2 < cpDistance2 && distanceOut2 < distanceIn2 &&
947                (distanceOut2 < minDistance2 || index < 0)) {
948       minDistance2 = distanceOut2;
949       index        = i;
950       type         = SPEED_OUT;
951     }
952 
953     double cpw =
954         stroke->getParameterAtControlPoint(m_controlPoints[i].m_pointIndex);
955     if (w <= cpw) continue;
956     double precDistance = w - cpw;
957     if (precPointIndex < 0 || precDistance < minPrecDistance) {
958       precPointIndex  = i;
959       minPrecDistance = precDistance;
960     }
961   }
962 
963   if (minDistance2 < distance2)
964     distance2 = minDistance2;
965   else if (strokeDistance > distance2) {
966     distance2 = strokeDistance;
967     index     = -1;
968     type      = NONE;
969   } else {
970     distance2 = minPrecDistance;
971     index     = precPointIndex;
972     type      = SEGMENT;
973   }
974 
975   return type;
976 }
977 
978 //=============================================================================
979 // ControlPointSelection
980 //-----------------------------------------------------------------------------
981 
isSelected(int index) const982 bool ControlPointSelection::isSelected(int index) const {
983   return m_selectedPoints.find(index) != m_selectedPoints.end();
984 }
985 
986 //-----------------------------------------------------------------------------
987 
select(int index)988 void ControlPointSelection::select(int index) {
989   m_selectedPoints.insert(index);
990 }
991 
992 //-----------------------------------------------------------------------------
993 
unselect(int index)994 void ControlPointSelection::unselect(int index) {
995   m_selectedPoints.erase(index);
996 }
997 
998 //-----------------------------------------------------------------------------
999 
addMenuItems(QMenu * menu)1000 void ControlPointSelection::addMenuItems(QMenu *menu) {
1001   int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1002   if (isEmpty() || currentStrokeIndex == -1 ||
1003       (m_controlPointEditorStroke &&
1004        m_controlPointEditorStroke->getControlPointCount() <= 1))
1005     return;
1006   QAction *linear   = menu->addAction(tr("Set Linear Control Point"));
1007   QAction *unlinear = menu->addAction(tr("Set Nonlinear Control Point"));
1008   menu->addSeparator();
1009   bool ret = connect(linear, SIGNAL(triggered()), this, SLOT(setLinear()));
1010   ret =
1011       ret && connect(unlinear, SIGNAL(triggered()), this, SLOT(setUnlinear()));
1012   assert(ret);
1013 }
1014 
1015 //-----------------------------------------------------------------------------
1016 
setLinear()1017 void ControlPointSelection::setLinear() {
1018   TTool *tool            = TTool::getApplication()->getCurrentTool()->getTool();
1019   int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1020   TVectorImageP vi(tool->getImage(false));
1021   if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1022   TUndo *undo;
1023   if (tool->getApplication()->getCurrentObject()->isSpline())
1024     undo = new UndoPath(
1025         tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1026   else {
1027     TXshSimpleLevel *level =
1028         tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1029     UndoControlPointEditor *cpEditorUndo =
1030         new UndoControlPointEditor(level, tool->getCurrentFid());
1031     cpEditorUndo->addOldStroke(currentStrokeIndex,
1032                                vi->getVIStroke(currentStrokeIndex));
1033     undo = cpEditorUndo;
1034   }
1035   if (m_controlPointEditorStroke->getControlPointCount() == 0) return;
1036 
1037   bool isChanged = m_controlPointEditorStroke->setControlPointsLinear(
1038       m_selectedPoints, true);
1039 
1040   if (!isChanged) return;
1041   TUndoManager::manager()->add(undo);
1042   tool->notifyImageChanged();
1043 }
1044 
1045 //-----------------------------------------------------------------------------
1046 
setUnlinear()1047 void ControlPointSelection::setUnlinear() {
1048   TTool *tool            = TTool::getApplication()->getCurrentTool()->getTool();
1049   int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1050   TVectorImageP vi(tool->getImage(false));
1051   if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1052 
1053   TUndo *undo;
1054   if (tool->getApplication()->getCurrentObject()->isSpline())
1055     undo = new UndoPath(
1056         tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1057   else {
1058     TXshSimpleLevel *level =
1059         tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1060     UndoControlPointEditor *cpEditorUndo =
1061         new UndoControlPointEditor(level, tool->getCurrentFid());
1062     cpEditorUndo->addOldStroke(currentStrokeIndex,
1063                                vi->getVIStroke(currentStrokeIndex));
1064     undo = cpEditorUndo;
1065   }
1066   if (m_controlPointEditorStroke->getControlPointCount() == 0) return;
1067 
1068   bool isChanged = m_controlPointEditorStroke->setControlPointsLinear(
1069       m_selectedPoints, false);
1070 
1071   if (!isChanged) return;
1072   TUndoManager::manager()->add(undo);
1073   tool->notifyImageChanged();
1074 }
1075 
1076 //-----------------------------------------------------------------------------
1077 
deleteControlPoints()1078 void ControlPointSelection::deleteControlPoints() {
1079   TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
1080   TVectorImageP vi(tool->getImage(false));
1081   int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1082   if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1083 
1084   // Inizializzo l'UNDO
1085   TUndo *undo;
1086   bool isCurrentObjectSpline =
1087       tool->getApplication()->getCurrentObject()->isSpline();
1088   if (isCurrentObjectSpline)
1089     undo = new UndoPath(
1090         tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1091   else {
1092     TXshSimpleLevel *level =
1093         tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1094     UndoControlPointEditor *cpEditorUndo =
1095         new UndoControlPointEditor(level, tool->getCurrentFid());
1096     cpEditorUndo->addOldStroke(currentStrokeIndex,
1097                                vi->getVIStroke(currentStrokeIndex));
1098     undo = cpEditorUndo;
1099   }
1100 
1101   int i;
1102   for (i = m_controlPointEditorStroke->getControlPointCount() - 1; i >= 0; i--)
1103     if (isSelected(i)) m_controlPointEditorStroke->deleteControlPoint(i);
1104 
1105   if (m_controlPointEditorStroke->getControlPointCount() == 0) {
1106     m_controlPointEditorStroke->setStroke((TVectorImage *)0, -1);
1107     if (!isCurrentObjectSpline) {
1108       UndoControlPointEditor *cpEditorUndo =
1109           dynamic_cast<UndoControlPointEditor *>(undo);
1110       if (cpEditorUndo) cpEditorUndo->isStrokeDelete(true);
1111     }
1112   }
1113 
1114   // La spline non puo' essere cancellata completamente!!!
1115   if (vi->getStrokeCount() == 0) {
1116     if (TTool::getApplication()->getCurrentObject()->isSpline()) {
1117       std::vector<TPointD> points;
1118       double d = 10;
1119       points.push_back(TPointD(-d, 0));
1120       points.push_back(TPointD(0, 0));
1121       points.push_back(TPointD(d, 0));
1122       TStroke *stroke = new TStroke(points);
1123       vi->addStroke(stroke, false);
1124       m_controlPointEditorStroke->setStrokeIndex(0);
1125     }
1126   }
1127   tool->notifyImageChanged();
1128   selectNone();
1129   // Registro l'UNDO
1130   TUndoManager::manager()->add(undo);
1131 }
1132 
1133 //-----------------------------------------------------------------------------
1134 
enableCommands()1135 void ControlPointSelection::enableCommands() {
1136   enableCommand(this, "MI_Clear", &ControlPointSelection::deleteControlPoints);
1137 }
1138