1 
2 
3 #include "shifttracetool.h"
4 #include "toonz/onionskinmask.h"
5 #include "toonz/tonionskinmaskhandle.h"
6 #include "tools/cursors.h"
7 #include "timage.h"
8 #include "trasterimage.h"
9 #include "ttoonzimage.h"
10 #include "tvectorimage.h"
11 #include "toonz/txsheet.h"
12 #include "toonz/txshcell.h"
13 #include "toonz/txsheethandle.h"
14 #include "toonz/tframehandle.h"
15 #include "toonz/tcolumnhandle.h"
16 #include "toonz/txshlevelhandle.h"
17 #include "tools/toolhandle.h"
18 #include "toonz/txshsimplelevel.h"
19 #include "toonz/dpiscale.h"
20 #include "toonz/stage.h"
21 #include "tpixel.h"
22 #include "toonzqt/menubarcommand.h"
23 
24 #include "toonz/preferences.h"
25 #include "toonzqt/gutil.h"
26 
27 #include "tgl.h"
28 #include <math.h>
29 #include <QKeyEvent>
30 
31 //=============================================================================
32 
circumCenter(TPointD & out,const TPointD & a,const TPointD & b,const TPointD & c)33 static bool circumCenter(TPointD &out, const TPointD &a, const TPointD &b,
34                          const TPointD &c) {
35   double d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
36   if (fabs(d) < 0.0001) {
37     out = TPointD();
38     return false;
39   }
40   out.x = ((a.y * a.y + a.x * a.x) * (b.y - c.y) +
41            (b.y * b.y + b.x * b.x) * (c.y - a.y) +
42            (c.y * c.y + c.x * c.x) * (a.y - b.y)) /
43           d;
44   out.y = ((a.y * a.y + a.x * a.x) * (c.x - b.x) +
45            (b.y * b.y + b.x * b.x) * (a.x - c.x) +
46            (c.y * c.y + c.x * c.x) * (b.x - a.x)) /
47           d;
48   return true;
49 }
50 
51 //=============================================================================
52 
ShiftTraceTool()53 ShiftTraceTool::ShiftTraceTool()
54     : TTool("T_ShiftTrace")
55     , m_ghostIndex(0)
56     , m_curveStatus(NoCurve)
57     , m_gadget(NoGadget)
58     , m_highlightedGadget(NoGadget) {
59   bind(TTool::AllTargets);  // Deals with tool deactivation internally
60 }
61 
clearData()62 void ShiftTraceTool::clearData() {
63   m_ghostIndex        = 0;
64   m_curveStatus       = NoCurve;
65   m_gadget            = NoGadget;
66   m_highlightedGadget = NoGadget;
67 
68   m_box = TRectD();
69   for (int i = 0; i < 2; i++) {
70     m_row[i]    = -1;
71     m_aff[i]    = TAffine();
72     m_center[i] = TPointD();
73   }
74 }
75 
updateBox()76 void ShiftTraceTool::updateBox() {
77   if (m_ghostIndex < 0 || 2 <= m_ghostIndex || m_row[m_ghostIndex] < 0) return;
78 
79   TImageP img;
80 
81   TApplication *app = TTool::getApplication();
82   if (app->getCurrentFrame()->isEditingScene()) {
83     int col      = app->getCurrentColumn()->getColumnIndex();
84     int row      = m_row[m_ghostIndex];
85     TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
86 
87     TXshCell cell       = xsh->getCell(row, col);
88     TXshSimpleLevel *sl = cell.getSimpleLevel();
89     if (sl) {
90       m_dpiAff = getDpiAffine(sl, cell.m_frameId);
91       img      = cell.getImage(false);
92     }
93   }
94   // on editing level
95   else {
96     TXshLevel *level = app->getCurrentLevel()->getLevel();
97     if (!level) return;
98     TXshSimpleLevel *sl = level->getSimpleLevel();
99     if (!sl) return;
100 
101     const TFrameId &ghostFid = sl->index2fid(m_row[m_ghostIndex]);
102     m_dpiAff                 = getDpiAffine(sl, ghostFid);
103     img                      = sl->getFrame(ghostFid, false);
104   }
105 
106   if (img) {
107     if (TRasterImageP ri = img) {
108       TRasterP ras = ri->getRaster();
109       m_box        = (convert(ras->getBounds()) - ras->getCenterD()) *
110               ri->getSubsampling();
111     } else if (TToonzImageP ti = img) {
112       TRasterP ras = ti->getRaster();
113       m_box        = (convert(ras->getBounds()) - ras->getCenterD()) *
114               ti->getSubsampling();
115     } else if (TVectorImageP vi = img) {
116       m_box = vi->getBBox();
117     }
118   }
119 }
120 
updateData()121 void ShiftTraceTool::updateData() {
122   m_box = TRectD();
123   for (int i = 0; i < 2; i++) m_row[i] = -1;
124   m_dpiAff          = TAffine();
125   TApplication *app = TTool::getApplication();
126 
127   OnionSkinMask osm  = app->getCurrentOnionSkin()->getOnionSkinMask();
128   int previousOffset = osm.getShiftTraceGhostFrameOffset(0);
129   int forwardOffset  = osm.getShiftTraceGhostFrameOffset(1);
130   // we must find the prev (m_row[0]) and next (m_row[1]) reference images
131   // (either might not exist)
132   // see also stage.cpp, StageBuilder::addCellWithOnionSkin
133   if (app->getCurrentFrame()->isEditingScene()) {
134     TXsheet *xsh  = app->getCurrentXsheet()->getXsheet();
135     int row       = app->getCurrentFrame()->getFrame();
136     int col       = app->getCurrentColumn()->getColumnIndex();
137     TXshCell cell = xsh->getCell(row, col);
138     int r;
139     r = row + previousOffset;
140     if (r >= 0 && xsh->getCell(r, col) != cell &&
141         (cell.getSimpleLevel() == 0 ||
142          xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
143       m_row[0] = r;
144     }
145 
146     r = row + forwardOffset;
147     if (r >= 0 && xsh->getCell(r, col) != cell &&
148         (cell.getSimpleLevel() == 0 ||
149          xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
150       m_row[1] = r;
151     }
152   }
153   // on editing level
154   else {
155     TXshLevel *level = app->getCurrentLevel()->getLevel();
156     if (level) {
157       TXshSimpleLevel *sl = level->getSimpleLevel();
158       if (sl) {
159         TFrameId fid = app->getCurrentFrame()->getFid();
160         int row      = sl->guessIndex(fid);
161         m_row[0]     = row + previousOffset;
162         m_row[1]     = row + forwardOffset;
163       }
164     }
165   }
166   updateBox();
167 }
168 
169 //
170 // Compute m_aff[0] and m_aff[1] according to the current curve
171 //
updateCurveAffs()172 void ShiftTraceTool::updateCurveAffs() {
173   if (m_curveStatus != ThreePointsCurve) {
174     m_aff[0] = m_aff[1] = TAffine();
175   } else {
176     double phi0 = 0, phi1 = 0;
177     TPointD center;
178     if (circumCenter(center, m_p0, m_p1, m_p2)) {
179       TPointD v0 = normalize(m_p0 - center);
180       TPointD v1 = normalize(m_p1 - center);
181       TPointD v2 = normalize(m_p2 - center);
182       TPointD u0(-v0.y, v0.x);
183       TPointD u1(-v1.y, v1.x);
184       phi0 = atan2((v2 * u0), (v2 * v0)) * 180.0 / 3.1415;
185       phi1 = atan2((v2 * u1), (v2 * v1)) * 180.0 / 3.1415;
186     }
187     m_aff[0] = TTranslation(m_p2 - m_p0) * TRotation(m_p0, phi0);
188     m_aff[1] = TTranslation(m_p2 - m_p1) * TRotation(m_p1, phi1);
189   }
190 }
191 
updateGhost()192 void ShiftTraceTool::updateGhost() {
193   OnionSkinMask osm =
194       TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask();
195   osm.setShiftTraceGhostAff(0, m_aff[0]);
196   osm.setShiftTraceGhostAff(1, m_aff[1]);
197   osm.setShiftTraceGhostCenter(0, m_center[0]);
198   osm.setShiftTraceGhostCenter(1, m_center[1]);
199   TTool::getApplication()->getCurrentOnionSkin()->setOnionSkinMask(osm);
200 }
201 
reset()202 void ShiftTraceTool::reset() {
203   int ghostIndex = m_ghostIndex;
204   onActivate();
205   invalidate();
206   m_ghostIndex = ghostIndex;
207 
208   TTool::getApplication()
209       ->getCurrentTool()
210       ->notifyToolChanged();  // Refreshes toolbar values
211 }
212 
getGhostAff()213 TAffine ShiftTraceTool::getGhostAff() {
214   if (0 <= m_ghostIndex && m_ghostIndex < 2)
215     return m_aff[m_ghostIndex] * m_dpiAff;
216   else
217     return TAffine();
218 }
219 
drawDot(const TPointD & center,double r,const TPixel32 & color)220 void ShiftTraceTool::drawDot(const TPointD &center, double r,
221                              const TPixel32 &color) {
222   tglColor(color);
223   tglDrawDisk(center, r);
224   glColor3d(0.2, 0.2, 0.2);
225   tglDrawCircle(center, r);
226 }
227 
drawControlRect()228 void ShiftTraceTool::drawControlRect() {  // TODO
229   if (m_ghostIndex < 0 || m_ghostIndex > 1) return;
230   int row = m_row[m_ghostIndex];
231   if (row < 0) return;
232 
233   TRectD box = m_box;
234   if (box.isEmpty()) return;
235   glPushMatrix();
236   tglMultMatrix(getGhostAff());
237 
238   TPixel32 color;
239 
240   // draw onion-colored rectangle to indicate which ghost is grabbed
241   {
242     TPixel32 frontOniColor, backOniColor;
243     bool inksOnly;
244     Preferences::instance()->getOnionData(frontOniColor, backOniColor,
245                                           inksOnly);
246     color       = (m_ghostIndex == 0) ? backOniColor : frontOniColor;
247     double unit = sqrt(tglGetPixelSize2());
248     unit *= getDevPixRatio();
249     TRectD coloredBox = box.enlarge(3.0 * unit);
250     tglColor(color);
251     glBegin(GL_LINE_STRIP);
252     glVertex2d(coloredBox.x0, coloredBox.y0);
253     glVertex2d(coloredBox.x1, coloredBox.y0);
254     glVertex2d(coloredBox.x1, coloredBox.y1);
255     glVertex2d(coloredBox.x0, coloredBox.y1);
256     glVertex2d(coloredBox.x0, coloredBox.y0);
257     glEnd();
258   }
259 
260   color = m_highlightedGadget == TranslateGadget
261               ? TPixel32(200, 100, 100)
262               : m_highlightedGadget == RotateGadget ? TPixel32(100, 200, 100)
263                                                     : TPixel32(120, 120, 120);
264   tglColor(color);
265   glBegin(GL_LINE_STRIP);
266   glVertex2d(box.x0, box.y0);
267   glVertex2d(box.x1, box.y0);
268   glVertex2d(box.x1, box.y1);
269   glVertex2d(box.x0, box.y1);
270   glVertex2d(box.x0, box.y0);
271   glEnd();
272   color = m_highlightedGadget == ScaleGadget ? TPixel32(200, 100, 100)
273                                              : TPixel32::White;
274   double r = 4 * sqrt(tglGetPixelSize2());
275   drawDot(box.getP00(), r, color);
276   drawDot(box.getP01(), r, color);
277   drawDot(box.getP10(), r, color);
278   drawDot(box.getP11(), r, color);
279   if (m_curveStatus == NoCurve) {
280     color = m_highlightedGadget == MoveCenterGadget ? TPixel32(200, 100, 100)
281                                                     : TPixel32::White;
282     TPointD c = m_center[m_ghostIndex];
283     drawDot(c, r, color);
284   }
285   glPopMatrix();
286 }
287 
drawCurve()288 void ShiftTraceTool::drawCurve() {
289   if (m_curveStatus == NoCurve) return;
290   double r = 4 * sqrt(tglGetPixelSize2());
291   double u = getPixelSize();
292   if (m_curveStatus == TwoPointsCurve) {
293     TPixel32 color = m_highlightedGadget == CurveP0Gadget
294                          ? TPixel32(200, 100, 100)
295                          : TPixel32::White;
296     drawDot(m_p0, r, color);
297     glColor3d(0.2, 0.2, 0.2);
298     tglDrawSegment(m_p0, m_p1);
299     drawDot(m_p1, r, TPixel32::Red);
300   } else if (m_curveStatus == ThreePointsCurve) {
301     TPixel32 color = m_highlightedGadget == CurveP0Gadget
302                          ? TPixel32(200, 100, 100)
303                          : TPixel32::White;
304     drawDot(m_p0, r, color);
305     color = m_highlightedGadget == CurveP1Gadget ? TPixel32(200, 100, 100)
306                                                  : TPixel32::White;
307     drawDot(m_p1, r, color);
308 
309     glColor3d(0.2, 0.2, 0.2);
310 
311     TPointD center;
312     if (circumCenter(center, m_p0, m_p1, m_p2)) {
313       double radius = norm(center - m_p1);
314       glBegin(GL_LINE_STRIP);
315       int n = 100;
316       for (int i = 0; i < n; i++) {
317         double t  = (double)i / n;
318         TPointD p = (1 - t) * m_p0 + t * m_p2;
319         p         = center + radius * normalize(p - center);
320         tglVertex(p);
321       }
322       for (int i = 0; i < n; i++) {
323         double t  = (double)i / n;
324         TPointD p = (1 - t) * m_p2 + t * m_p1;
325         p         = center + radius * normalize(p - center);
326         tglVertex(p);
327       }
328       glEnd();
329     } else {
330       tglDrawSegment(m_p0, m_p1);
331     }
332     color = m_highlightedGadget == CurvePmGadget ? TPixel32(200, 100, 100)
333                                                  : TPixel32::White;
334     drawDot(m_p2, r, color);
335   }
336 }
337 
onActivate()338 void ShiftTraceTool::onActivate() {
339   m_ghostIndex  = 0;
340   m_curveStatus = NoCurve;
341   clearData();
342   OnionSkinMask osm =
343       TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask();
344   m_aff[0]    = osm.getShiftTraceGhostAff(0);
345   m_aff[1]    = osm.getShiftTraceGhostAff(1);
346   m_center[0] = osm.getShiftTraceGhostCenter(0);
347   m_center[1] = osm.getShiftTraceGhostCenter(1);
348 }
349 
onDeactivate()350 void ShiftTraceTool::onDeactivate() {
351   QAction *action = CommandManager::instance()->getAction("MI_EditShift");
352   action->setChecked(false);
353 }
354 
getGadget(const TPointD & p)355 ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) {
356   std::vector<std::pair<TPointD, GadgetId>> gadgets;
357   gadgets.push_back(std::make_pair(m_p0, CurveP0Gadget));
358   gadgets.push_back(std::make_pair(m_p1, CurveP1Gadget));
359   gadgets.push_back(std::make_pair(m_p2, CurvePmGadget));
360   TAffine aff      = getGhostAff();
361   double pixelSize = getPixelSize();
362   double d         = 15 * pixelSize;  // offset for rotation handle
363   if (0 <= m_ghostIndex && m_ghostIndex < 2) {
364     gadgets.push_back(std::make_pair(aff * m_box.getP00(), ScaleGadget));
365     gadgets.push_back(std::make_pair(aff * m_box.getP01(), ScaleGadget));
366     gadgets.push_back(std::make_pair(aff * m_box.getP10(), ScaleGadget));
367     gadgets.push_back(std::make_pair(aff * m_box.getP11(), ScaleGadget));
368     gadgets.push_back(
369         std::make_pair(aff * m_center[m_ghostIndex], MoveCenterGadget));
370   }
371   int k           = -1;
372   double minDist2 = pow(10 * pixelSize, 2);
373   for (int i = 0; i < (int)gadgets.size(); i++) {
374     double d2 = norm2(gadgets[i].first - p);
375     if (d2 < minDist2) {
376       minDist2 = d2;
377       k        = i;
378     }
379   }
380   if (k >= 0) return gadgets[k].second;
381 
382   // rect-point
383   if (0 <= m_ghostIndex && m_ghostIndex < 2) {
384     TPointD q  = aff.inv() * p;
385     double big = 1.0e6;
386     double d = big, x = 0, y = 0;
387     if (m_box.x0 < q.x && q.x < m_box.x1) {
388       x         = q.x;
389       double d0 = fabs(m_box.y0 - q.y);
390       double d1 = fabs(m_box.y1 - q.y);
391       if (d0 < d1) {
392         d = d0;
393         y = m_box.y0;
394       } else {
395         d = d1;
396         y = m_box.y1;
397       }
398     }
399     if (m_box.y0 < q.y && q.y < m_box.y1) {
400       double d0 = fabs(m_box.x0 - q.x);
401       double d1 = fabs(m_box.x1 - q.x);
402       if (d0 < d) {
403         d = d0;
404         y = q.y;
405         x = m_box.x0;
406       }
407       if (d1 < d) {
408         d = d1;
409         y = q.y;
410         x = m_box.x1;
411       }
412     }
413     if (d < big) {
414       TPointD pp = aff * TPointD(x, y);
415       double d   = norm(p - pp);
416       if (d < 10 * getPixelSize()) {
417         if (m_box.contains(q))
418           return TranslateGadget;
419         else
420           return RotateGadget;
421       }
422     }
423     if (m_box.contains(q))
424       return NoGadget_InBox;
425     else
426       return NoGadget;
427   }
428   return NoGadget;
429 }
430 
mouseMove(const TPointD & pos,const TMouseEvent & e)431 void ShiftTraceTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
432   GadgetId highlightedGadget = getGadget(pos);
433   if (highlightedGadget != m_highlightedGadget) {
434     m_highlightedGadget = highlightedGadget;
435     invalidate();
436   }
437 }
438 
leftButtonDown(const TPointD & pos,const TMouseEvent & e)439 void ShiftTraceTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
440   m_gadget = m_highlightedGadget;
441   m_oldPos = m_startPos = pos;
442 
443   bool notify = false;
444 
445   if (!e.isCtrlPressed() &&
446       (m_gadget == NoGadget || m_gadget == NoGadget_InBox)) {
447     if (m_gadget == NoGadget_InBox) {
448       m_gadget = TranslateGadget;
449     } else {
450       m_gadget = RotateGadget;
451     }
452 
453     int row = getViewer()->posToRow(e.m_pos, 5.0, false, true);
454     if (row >= 0) {
455       int index         = -1;
456       TApplication *app = TTool::getApplication();
457       if (app->getCurrentFrame()->isEditingScene()) {
458         int currentRow = getFrame();
459         if (m_row[0] >= 0 && row < currentRow)
460           index = 0;
461         else if (m_row[1] >= 0 && row > currentRow)
462           index = 1;
463       } else {
464         if (m_row[0] == row)
465           index = 0;
466         else if (m_row[1] == row)
467           index = 1;
468       }
469 
470       if (index >= 0) {
471         m_ghostIndex = index;
472         updateBox();
473         m_gadget            = TranslateGadget;
474         m_highlightedGadget = TranslateGadget;
475         notify              = true;
476       }
477     }
478   } else if (e.isCtrlPressed()) {
479     m_gadget = NoGadget_InBox;
480   }
481 
482   m_oldAff = m_aff[m_ghostIndex];
483   invalidate();
484 
485   if (notify) {
486     TTool::getApplication()
487         ->getCurrentTool()
488         ->notifyToolChanged();  // Refreshes toolbar values
489   }
490 }
491 
leftButtonDrag(const TPointD & pos,const TMouseEvent & e)492 void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
493   if (m_gadget == NoGadget || m_gadget == NoGadget_InBox) {
494     if (norm(pos - m_oldPos) > 10 * getPixelSize()) {
495       m_curveStatus = TwoPointsCurve;
496       m_p0          = m_oldPos;
497       m_gadget      = CurveP1Gadget;
498     }
499   }
500 
501   if (isCurveGadget(m_gadget)) {
502     if (m_gadget == CurveP0Gadget)
503       m_p0 = pos;
504     else if (m_gadget == CurveP1Gadget)
505       m_p1 = pos;
506     else
507       m_p2 = pos;
508     updateCurveAffs();
509   } else if (m_gadget == RotateGadget) {
510     TAffine aff = getGhostAff();
511     TPointD c   = aff * m_center[m_ghostIndex];
512     TPointD a   = m_oldPos - c;
513     TPointD b   = pos - c;
514     m_oldPos    = pos;
515     TPointD u   = normalize(a);
516     double phi =
517         atan2(-u.y * b.x + u.x * b.y, u.x * b.x + u.y * b.y) * 180.0 / 3.14153;
518 
519     TPointD imgC = aff * m_center[m_ghostIndex];
520 
521     m_aff[m_ghostIndex] = TRotation(imgC, phi) * m_aff[m_ghostIndex];
522   } else if (m_gadget == MoveCenterGadget) {
523     TAffine aff   = getGhostAff().inv();
524     TPointD delta = aff * pos - aff * m_oldPos;
525     m_oldPos      = pos;
526     m_center[m_ghostIndex] += delta;
527   } else if (m_gadget == TranslateGadget) {
528     TPointD delta       = pos - m_oldPos;
529     m_oldPos            = pos;
530     m_aff[m_ghostIndex] = TTranslation(delta) * m_aff[m_ghostIndex];
531   } else if (m_gadget == ScaleGadget) {
532     TAffine aff  = getGhostAff();
533     TPointD c    = m_center[m_ghostIndex];
534     TPointD a    = aff.inv() * m_oldPos - c;
535     TPointD b    = aff.inv() * pos - c;
536     TPointD imgC = aff * m_center[m_ghostIndex];
537 
538     if (e.isShiftPressed())
539       m_aff[m_ghostIndex] = TScale(imgC, b.x / a.x, b.y / a.y) * m_oldAff;
540     else {
541       double scale        = std::max(b.x / a.x, b.y / a.y);
542       m_aff[m_ghostIndex] = TScale(imgC, scale) * m_oldAff;
543     }
544   }
545 
546   updateGhost();
547   invalidate();
548 }
549 
leftButtonUp(const TPointD & pos,const TMouseEvent &)550 void ShiftTraceTool::leftButtonUp(const TPointD &pos, const TMouseEvent &) {
551   if (CurveP0Gadget <= m_gadget && m_gadget <= CurvePmGadget) {
552     if (m_curveStatus == TwoPointsCurve) {
553       m_p2          = (m_p0 + m_p1) * 0.5;
554       m_curveStatus = ThreePointsCurve;
555       updateCurveAffs();
556       updateGhost();
557 
558       m_center[0] = (m_aff[0] * m_dpiAff).inv() * m_p2;
559       m_center[1] = (m_aff[1] * m_dpiAff).inv() * m_p2;
560     }
561   }
562   m_gadget = NoGadget;
563   invalidate();
564 
565   TTool::getApplication()
566       ->getCurrentTool()
567       ->notifyToolChanged();  // Refreshes toolbar values
568 }
569 
draw()570 void ShiftTraceTool::draw() {
571   updateData();
572   drawControlRect();
573   drawCurve();
574 }
575 
getCursorId() const576 int ShiftTraceTool::getCursorId() const {
577   if (m_highlightedGadget == RotateGadget || m_highlightedGadget == NoGadget)
578     return ToolCursor::RotateCursor;
579   else if (m_highlightedGadget == ScaleGadget)
580     return ToolCursor::ScaleCursor;
581   else if (isCurveGadget(m_highlightedGadget))
582     return ToolCursor::PinchCursor;
583   else  // Curve Points, TranslateGadget, NoGadget_InBox
584     return ToolCursor::MoveCursor;
585 }
586 
isEventAcceptable(QEvent * e)587 bool ShiftTraceTool::isEventAcceptable(QEvent *e) {
588   // F1, F2 and F3 keys are used for flipping
589   QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
590   int key             = keyEvent->key();
591   return (Qt::Key_F1 <= key && key <= Qt::Key_F3);
592 }
593 
onLeave()594 void ShiftTraceTool::onLeave() {
595   OnionSkinMask osm =
596       TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask();
597   osm.clearGhostFlipKey();
598   TTool::getApplication()->getCurrentOnionSkin()->setOnionSkinMask(osm);
599 }
600 
setCurrentGhostIndex(int index)601 void ShiftTraceTool::setCurrentGhostIndex(int index) {
602   m_ghostIndex = index;
603   updateBox();
604   invalidate();
605 }
606 
607 ShiftTraceTool shiftTraceTool;
608