1 
2 
3 #include "toonzqt/functionpanel.h"
4 
5 // TnzQt includes
6 #include "toonzqt/functionselection.h"
7 #include "toonzqt/functionsegmentviewer.h"
8 #include "toonzqt/imageutils.h"
9 #include "functionpaneltools.h"
10 #include "toonzqt/gutil.h"
11 
12 // TnzLib includes
13 #include "toonz/tframehandle.h"
14 #include "toonz/doubleparamcmd.h"
15 #include "toonz/toonzfolders.h"
16 #include "toonz/preferences.h"
17 // TnzBase includes
18 #include "tdoubleparam.h"
19 #include "tdoublekeyframe.h"
20 #include "tunit.h"
21 
22 // TnzCore includes
23 #include "tcommon.h"
24 
25 // Qt includes
26 #include <QPainter>
27 #include <QPainterPath>
28 #include <QMouseEvent>
29 #include <QWheelEvent>
30 #include <QMenu>
31 #include <QSettings>
32 
33 #include <cmath>
34 
35 namespace {
36 
drawCircle(QPainter & painter,double x,double y,double r)37 void drawCircle(QPainter &painter, double x, double y, double r) {
38   painter.drawEllipse(x - r, y - r, 2 * r, 2 * r);
39 }
drawCircle(QPainter & painter,const QPointF & p,double r)40 void drawCircle(QPainter &painter, const QPointF &p, double r) {
41   painter.drawEllipse(p.x() - r, p.y() - r, 2 * r, 2 * r);
42 }
drawSquare(QPainter & painter,double x,double y,double r)43 void drawSquare(QPainter &painter, double x, double y, double r) {
44   painter.drawRect(x - r, y - r, 2 * r, 2 * r);
45 }
drawSquare(QPainter & painter,const QPointF & p,double r)46 void drawSquare(QPainter &painter, const QPointF &p, double r) {
47   drawSquare(painter, p.x(), p.y(), r);
48 }
49 
drawRoundedSquare(QPainter & painter,const QPointF & p,double r)50 void drawRoundedSquare(QPainter &painter, const QPointF &p, double r) {
51   painter.drawRoundRect(p.x() - r, p.y() - r, 2 * r, 2 * r, 99, 99);
52 }
53 
norm2(const QPointF & p)54 double norm2(const QPointF &p) { return p.x() * p.x() + p.y() * p.y(); }
55 
56 class FunctionPanelZoomer final : public ImageUtils::ShortcutZoomer {
57   FunctionPanel *m_panel;
58 
59 public:
FunctionPanelZoomer(FunctionPanel * panel)60   FunctionPanelZoomer(FunctionPanel *panel)
61       : ShortcutZoomer(panel), m_panel(panel) {}
62 
zoom(bool zoomin,bool resetZoom)63   bool zoom(bool zoomin, bool resetZoom) override {
64     if (resetZoom)
65       m_panel->fitGraphToWindow();
66     else {
67       double f  = 1.25;
68       double sc = zoomin ? f : 1.0 / f;
69       QPoint center(m_panel->width() / 2, m_panel->height() / 2);
70       m_panel->zoom(sc, sc, center);
71     }
72 
73     return true;
74   }
75 };
76 
77 }  // namespace
78 
79 //=============================================================================
80 //
81 // Ruler
82 //
83 //-----------------------------------------------------------------------------
84 
85 class Ruler {
86   double m_minValue, m_step;
87   int m_labelPeriod, m_labelOffset, m_tickCount;
88 
89   double m_unit, m_pan, m_vOrigin;
90   int m_x0, m_x1;
91   int m_minLabelDistance, m_minDistance;
92   double m_minStep;
93 
94 public:
95   Ruler();
96 
97   // unit,pan define the world to viewport transformation: pixel = value * unit
98   // + pan
99   // note: unit can be <0 (e.g. in the vertical rulers)
100   // use vOrigin!=0 when there is a offset between values and labels
101   // (e.g. frame=0 is visualized as 1)
setTransform(double unit,double pan,double vOrigin=0)102   void setTransform(double unit, double pan, double vOrigin = 0) {
103     m_unit    = unit;
104     m_pan     = pan;
105     m_vOrigin = vOrigin;
106   }
107 
setRange(int x0,int x1)108   void setRange(int x0, int x1) {
109     m_x0 = x0;
110     m_x1 = x1;
111   }  // [x0,x1] is the pixel range
112 
113   // set minimum distance (pixel) between two consecutive labels
setMinLabelDistance(int distance)114   void setMinLabelDistance(int distance) { m_minLabelDistance = distance; }
115   // set minimum distance (pixel) between two consecutive ticks
setMinDistance(int distance)116   void setMinDistance(int distance) { m_minDistance = distance; }
117 
118   // use setMinStep to define a minimum tick (e.g. for integer rulers as 'frame'
119   // call setMinStep(1);)
setMinStep(double step)120   void setMinStep(double step) { m_minStep = step; }
121 
122   void compute();  // call compute() once, before calling the following methods
123 
getTickCount() const124   int getTickCount() const { return m_tickCount; }
getTick(int index) const125   double getTick(int index) const { return m_minValue + index * m_step; }
isLabel(int index) const126   bool isLabel(int index) const {
127     return ((m_labelOffset + index) % m_labelPeriod) == 0;
128   }
129 };
130 
131 //-----------------------------------------------------------------------------
132 
Ruler()133 Ruler::Ruler()
134     : m_minValue(0)
135     , m_step(1)
136     , m_labelPeriod(2)
137     , m_labelOffset(0)
138     , m_tickCount(0)
139     , m_unit(1)
140     , m_pan(0)
141     , m_x0(0)
142     , m_x1(100)
143     , m_minLabelDistance(20)
144     , m_minDistance(5)
145     , m_minStep(0) {}
146 
147 //-----------------------------------------------------------------------------
148 
compute()149 void Ruler::compute() {
150   assert(m_x0 < m_x1);
151   assert(m_unit != 0.0);
152   assert(m_minLabelDistance > 0);
153   assert(m_minDistance >= 0);
154   // compute m_step (world distance between two adjacent ticks)
155   // and m_labelPeriod (number of ticks between two adjacent labels)
156 
157   // the distance (world units) between two labels must be >=
158   // minLabelWorldDistance
159   const double absUnit               = std::abs(m_unit);
160   const double minLabelWorldDistance = m_minLabelDistance / absUnit;
161   const double minWorldDistance      = m_minDistance / absUnit;
162 
163   // we want the minimum step with:
164   //    step*labelPeriod (i.e. label distance) >=  minLabelWorldDistance
165   //    step (i.e. tick distance) >= minWorldDistance
166   // Note: labelPeriod alternates between 5 and 2 => labelPeriod' =
167   // 7-labelPeriod
168   m_step        = 1;
169   m_labelPeriod = 5;
170   if (m_step * m_labelPeriod >= minLabelWorldDistance &&
171       m_step >= minWorldDistance) {
172     while (m_step >= minLabelWorldDistance &&
173            m_step / (7 - m_labelPeriod) >= minWorldDistance) {
174       m_labelPeriod = 7 - m_labelPeriod;
175       m_step /= m_labelPeriod;
176     }
177   } else {
178     do {
179       m_step *= m_labelPeriod;
180       m_labelPeriod =
181           7 - m_labelPeriod;  // m_labelPeriod alternates between 5 and 2
182     } while (m_step * m_labelPeriod < minLabelWorldDistance ||
183              m_step < minWorldDistance);
184   }
185 
186   if (m_step >= minLabelWorldDistance) {
187     m_labelPeriod = 1;
188   }
189 
190   if (m_step * m_labelPeriod < m_minStep) {
191     m_step        = m_minStep;
192     m_labelPeriod = 1;
193   } else if (m_step < m_minStep) {
194     m_step *= m_labelPeriod;
195     m_labelPeriod = 1;
196   }
197 
198   // compute range
199   double v0 = (m_x0 - m_pan) / m_unit;  // left margin (world units)
200   double v1 = (m_x1 - m_pan) / m_unit;  // right margin (world units)
201   if (m_unit < 0) std::swap(v0, v1);
202   int i0 =
203       tfloor((v0 - m_vOrigin) / m_step);  // largest tick <=v0 is i0 * m_step
204   int i1 =
205       tceil((v1 - m_vOrigin) / m_step);  // smallest tick >=v1 is i1 * m_step
206   m_minValue  = i0 * m_step + m_vOrigin;
207   m_tickCount = i1 - i0 + 1;
208 
209   m_labelOffset = i0 >= 0 ? (i0 % m_labelPeriod)
210                           : (m_labelPeriod - ((-i0) % m_labelPeriod));
211 }
212 
213 //=============================================================================
214 
215 //=============================================================================
216 //
217 // FunctionPanel::Gadget
218 //
219 //-----------------------------------------------------------------------------
220 
Gadget(FunctionPanel::Handle handle,int kIndex,const QPointF & p,int rx,int ry,const QPointF & pointPos)221 FunctionPanel::Gadget::Gadget(FunctionPanel::Handle handle, int kIndex,
222                               const QPointF &p, int rx, int ry,
223                               const QPointF &pointPos)
224     : m_handle(handle)
225     , m_kIndex(kIndex)
226     , m_hitRegion(QRect((int)p.x() - rx, (int)p.y() - ry, 2 * rx, 2 * ry))
227     , m_pos(p)
228     , m_pointPos(pointPos)
229     , m_channel(0)
230     , m_keyframePosition(0) {}
231 
232 //=============================================================================
233 //
234 // FunctionPanel
235 //
236 //-----------------------------------------------------------------------------
237 
FunctionPanel(QWidget * parent,bool isFloating)238 FunctionPanel::FunctionPanel(QWidget *parent, bool isFloating)
239     : QDialog(parent)
240     , m_functionTreeModel(0)
241     , m_viewTransform()
242     , m_valueAxisX(50)
243     , m_frameAxisY(50)
244     , m_graphViewportY(50)
245     , m_frameHandle(0)
246     , m_dragTool(0)
247     , m_currentFrameStatus(0)
248     , m_selection(0)
249     , m_curveShape(SMOOTH)
250     , m_isFloating(isFloating) {
251   setWindowTitle(tr("Function Curves"));
252 
253   m_viewTransform.translate(50, 200);
254   m_viewTransform.scale(5, -1);
255 
256   setFocusPolicy(Qt::ClickFocus);
257   setMouseTracking(true);
258   m_highlighted.handle = None;
259   m_highlighted.gIndex = -1;
260   m_cursor.visible     = false;
261   m_cursor.frame = m_cursor.value = 0;
262   m_curveLabel.text               = "";
263   m_curveLabel.curve              = 0;
264 
265   if (m_isFloating) {
266     // load the dialog size
267     TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath("popups.ini"));
268     QSettings settings(toQString(fp), QSettings::IniFormat);
269 
270     setGeometry(
271         settings.value("FunctionCurves", QRect(500, 500, 400, 300)).toRect());
272   }
273 }
274 
275 //-----------------------------------------------------------------------------
276 
~FunctionPanel()277 FunctionPanel::~FunctionPanel() {
278   if (m_isFloating) {
279     // save the dialog size
280     TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath("popups.ini"));
281     QSettings settings(toQString(fp), QSettings::IniFormat);
282 
283     settings.setValue("FunctionCurves", geometry());
284   }
285 
286   delete m_dragTool;
287 }
288 
289 //-----------------------------------------------------------------------------
290 
getPixelRatio(TDoubleParam * curve) const291 double FunctionPanel::getPixelRatio(TDoubleParam *curve) const {
292   double framePixelSize = xToFrame(1) - xToFrame(0);
293   assert(framePixelSize > 0);
294   double valuePixelSize = fabs(yToValue(curve, 1) - yToValue(curve, 0));
295   assert(valuePixelSize > 0);
296   return framePixelSize / valuePixelSize;
297 }
298 
299 //-----------------------------------------------------------------------------
300 
frameToX(double f) const301 double FunctionPanel::frameToX(double f) const {
302   return m_viewTransform.m11() * f + m_viewTransform.dx();
303 }
304 
305 //-----------------------------------------------------------------------------
306 
xToFrame(double x) const307 double FunctionPanel::xToFrame(double x) const {
308   return (x - m_viewTransform.dx()) / m_viewTransform.m11();
309 }
310 
311 //-----------------------------------------------------------------------------
312 
valueToY(TDoubleParam * curve,double v) const313 double FunctionPanel::valueToY(TDoubleParam *curve, double v) const {
314   const double bigNumber = 1.0e9;
315   TMeasure *m            = curve->getMeasure();
316   if (m) {
317     const TUnit *unit = m->getCurrentUnit();
318     v                 = unit->convertTo(v);
319   }
320   return tcrop(m_viewTransform.m22() * v + m_viewTransform.dy(), -bigNumber,
321                bigNumber);
322 }
323 
324 //-----------------------------------------------------------------------------
325 
yToValue(TDoubleParam * curve,double y) const326 double FunctionPanel::yToValue(TDoubleParam *curve, double y) const {
327   double v    = (y - m_viewTransform.dy()) / m_viewTransform.m22();
328   TMeasure *m = curve->getMeasure();
329   if (m) {
330     const TUnit *unit = m->getCurrentUnit();
331     v                 = unit->convertFrom(v);
332   }
333   return v;
334 }
335 
336 //-----------------------------------------------------------------------------
337 
pan(int dx,int dy)338 void FunctionPanel::pan(int dx, int dy) {
339   QTransform m;
340   m.translate(dx, dy);
341   m_viewTransform *= m;
342   update();
343 }
344 
345 //-----------------------------------------------------------------------------
346 
zoom(double sx,double sy,const QPoint & center)347 void FunctionPanel::zoom(double sx, double sy, const QPoint &center) {
348   QTransform m;
349   m.translate(center.x(), center.y());
350   m.scale(sx, sy);
351   m.translate(-center.x(), -center.y());
352   m_viewTransform *= m;
353   update();
354 }
355 
356 //-----------------------------------------------------------------------------
357 
getWinPos(TDoubleParam * curve,double frame,double value) const358 QPointF FunctionPanel::getWinPos(TDoubleParam *curve, double frame,
359                                  double value) const {
360   return QPointF(frameToX(frame), valueToY(curve, value));
361 }
362 
363 //-----------------------------------------------------------------------------
364 
getWinPos(TDoubleParam * curve,double frame) const365 QPointF FunctionPanel::getWinPos(TDoubleParam *curve, double frame) const {
366   return getWinPos(curve, frame, curve->getValue(frame));
367 }
368 
369 //-----------------------------------------------------------------------------
370 
getWinPos(TDoubleParam * curve,const TDoubleKeyframe & kf) const371 QPointF FunctionPanel::getWinPos(TDoubleParam *curve,
372                                  const TDoubleKeyframe &kf) const {
373   return getWinPos(curve, kf.m_frame, kf.m_value);
374 }
375 
376 //-----------------------------------------------------------------------------
377 
getCurveDistance(TDoubleParam * curve,const QPoint & winPos)378 int FunctionPanel::getCurveDistance(TDoubleParam *curve, const QPoint &winPos) {
379   double frame  = xToFrame(winPos.x());
380   double value  = curve->getValue(frame);
381   double curveY = valueToY(curve, value);
382   return std::abs(curveY - winPos.y());
383 }
384 
385 //-----------------------------------------------------------------------------
386 
findClosestChannel(const QPoint & winPos,int maxWinDistance)387 FunctionTreeModel::Channel *FunctionPanel::findClosestChannel(
388     const QPoint &winPos, int maxWinDistance) {
389   FunctionTreeModel::Channel *closestChannel = 0;
390   int minDistance                            = maxWinDistance;
391   int i;
392   for (i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) {
393     FunctionTreeModel::Channel *channel =
394         m_functionTreeModel->getActiveChannel(i);
395     TDoubleParam *curve = channel->getParam();
396     int distance        = getCurveDistance(curve, winPos);
397     if (distance < minDistance) {
398       closestChannel = channel;
399       minDistance    = distance;
400     }
401   }
402   return closestChannel;
403 }
404 
405 //-----------------------------------------------------------------------------
406 
findClosestCurve(const QPoint & winPos,int maxWinDistance)407 TDoubleParam *FunctionPanel::findClosestCurve(const QPoint &winPos,
408                                               int maxWinDistance) {
409   FunctionTreeModel::Channel *closestChannel =
410       findClosestChannel(winPos, maxWinDistance);
411   return closestChannel ? closestChannel->getParam() : 0;
412 }
413 
414 //-----------------------------------------------------------------------------
415 
416 // return the gadget index (-1 if no gadget is close enough)
findClosestGadget(const QPoint & winPos,Handle & handle,int maxDistance)417 int FunctionPanel::findClosestGadget(const QPoint &winPos, Handle &handle,
418                                      int maxDistance) {
419   // search only handles close enough (i.e. distance<maxDistance)
420   int minDistance = maxDistance;
421   int k           = -1;
422   for (int i = 0; i < m_gadgets.size(); i++) {
423     if (m_gadgets[i].m_hitRegion.contains(winPos)) {
424       QPoint p;
425       double d = (m_gadgets[i].m_hitRegion.center() - winPos).manhattanLength();
426       if (d < minDistance) {
427         k           = i;
428         minDistance = d;
429       }
430     }
431   }
432   if (k >= 0) {
433     handle = m_gadgets[k].m_handle;
434     return k;  // m_gadgets[k].m_kIndex;
435   } else {
436     handle = None;
437     return -1;
438   }
439 }
440 
441 //-----------------------------------------------------------------------------
442 
getCurrentCurve() const443 TDoubleParam *FunctionPanel::getCurrentCurve() const {
444   FunctionTreeModel::Channel *currentChannel =
445       m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0;
446   if (!currentChannel)
447     return 0;
448   else
449     return currentChannel->getParam();
450 }
451 
452 //-----------------------------------------------------------------------------
453 
getSegmentPainterPath(TDoubleParam * curve,int segmentIndex,int x0,int x1)454 QPainterPath FunctionPanel::getSegmentPainterPath(TDoubleParam *curve,
455                                                   int segmentIndex, int x0,
456                                                   int x1) {
457   double frame0 = xToFrame(x0), frame1 = xToFrame(x1);
458   int kCount = curve->getKeyframeCount();
459   int step   = 1;
460   if (kCount > 0) {
461     if (segmentIndex < 0)
462       frame1 = std::min(
463           frame1, curve->keyframeIndexToFrame(0));  // before first keyframe
464     else if (segmentIndex >= kCount - 1)
465       frame0 = std::max(frame0, curve->keyframeIndexToFrame(
466                                     kCount - 1));  // after last keyframe
467     else {
468       // between keyframes
469       TDoubleKeyframe kf = curve->getKeyframe(segmentIndex);
470       frame0             = std::max(frame0, kf.m_frame);
471       double f           = curve->keyframeIndexToFrame(segmentIndex + 1);
472       frame1             = std::min(frame1, f);
473       step               = kf.m_step;
474     }
475   }
476   if (frame0 >= frame1) return QPainterPath();
477   double frame;
478   double df = xToFrame(3) - xToFrame(0);
479 
480   if (m_curveShape == SMOOTH) {
481     frame = frame0;
482   } else  // FRAME_BASED
483   {
484     frame = (double)tfloor(frame0);
485     df    = std::max(df, 1.0);
486   }
487 
488   QPainterPath path;
489   if (0 <= segmentIndex && segmentIndex < kCount && step > 1) {
490     // step>1
491     path.moveTo(getWinPos(curve, frame));
492 
493     int f0        = curve->keyframeIndexToFrame(segmentIndex);
494     int vFrame    = f0 + tfloor(tfloor(frame - f0), step);
495     double vValue = curve->getValue(vFrame);
496     assert(vFrame <= frame);
497     assert(vFrame + step > frame);
498     while (vFrame + step < frame1) {
499       vValue = curve->getValue(vFrame);
500       path.lineTo(getWinPos(curve, vFrame, vValue));
501       vFrame += step;
502       path.lineTo(getWinPos(curve, vFrame, vValue));
503       vValue = curve->getValue(vFrame);
504       path.lineTo(getWinPos(curve, vFrame, vValue));
505     }
506     path.lineTo(getWinPos(curve, frame1, vValue));
507     path.lineTo(getWinPos(curve, frame1, curve->getValue(frame1, true)));
508   } else {
509     // step = 1
510     path.moveTo(getWinPos(curve, frame));
511     while (frame + df < frame1) {
512       frame += df;
513       path.lineTo(getWinPos(curve, frame));
514     }
515     path.lineTo(getWinPos(curve, frame1, curve->getValue(frame1, true)));
516   }
517   return path;
518 }
519 
520 //-----------------------------------------------------------------------------
521 
drawCurrentFrame(QPainter & painter)522 void FunctionPanel::drawCurrentFrame(QPainter &painter) {
523   int currentframe                = 0;
524   if (m_frameHandle) currentframe = m_frameHandle->getFrame();
525   int x                           = frameToX(currentframe);
526   if (m_currentFrameStatus == 0)
527     painter.setPen(Qt::magenta);
528   else if (m_currentFrameStatus == 1)
529     painter.setPen(Qt::white);
530   else
531     painter.setPen(m_selectedColor);
532   int y = m_graphViewportY + 1;
533   painter.drawLine(x - 1, y, x - 1, height());
534   painter.drawLine(x + 1, y, x + 1, height());
535 }
536 
537 //-----------------------------------------------------------------------------
538 
drawFrameGrid(QPainter & painter)539 void FunctionPanel::drawFrameGrid(QPainter &painter) {
540   QFontMetrics fm(painter.font());
541 
542   // ruler background
543   painter.setPen(Qt::NoPen);
544   painter.setBrush(getRulerBackground());
545   painter.drawRect(0, 0, width(), m_frameAxisY);
546 
547   // draw ticks and labels
548   Ruler ruler;
549   ruler.setTransform(m_viewTransform.m11(), m_viewTransform.dx(), -1);
550   ruler.setRange(m_valueAxisX, width());
551   ruler.setMinLabelDistance(fm.width("-8888") + 2);
552   ruler.setMinDistance(5);
553   ruler.setMinStep(1);
554   ruler.compute();
555   for (int i = 0; i < ruler.getTickCount(); i++) {
556     double f     = ruler.getTick(i);
557     bool isLabel = ruler.isLabel(i);
558     int x        = frameToX(f);
559     painter.setPen(m_textColor);
560     int y = m_frameAxisY;
561     painter.drawLine(x, y - (isLabel ? 4 : 2), x, y);
562     painter.setPen(getFrameLineColor());
563     painter.drawLine(x, m_graphViewportY, x, height());
564     if (isLabel) {
565       painter.setPen(m_textColor);
566       QString labelText = QString::number(f + 1);
567       painter.drawText(x - fm.width(labelText) / 2, y - 6, labelText);
568     }
569   }
570 }
571 
572 //-----------------------------------------------------------------------------
573 
drawValueGrid(QPainter & painter)574 void FunctionPanel::drawValueGrid(QPainter &painter) {
575   TDoubleParam *curve = getCurrentCurve();
576   if (!curve) return;
577 
578   QFontMetrics fm(painter.font());
579 
580   // ruler background
581   painter.setPen(Qt::NoPen);
582   painter.setBrush(getRulerBackground());
583   painter.drawRect(0, 0, m_valueAxisX, height());
584 
585   Ruler ruler;
586   ruler.setTransform(m_viewTransform.m22(), m_viewTransform.dy());
587   ruler.setRange(m_graphViewportY, height());
588   ruler.setMinLabelDistance(fm.height() + 2);
589   ruler.setMinDistance(5);
590   ruler.compute();
591 
592   painter.setBrush(Qt::NoBrush);
593   for (int i = 0; i < ruler.getTickCount(); i++) {
594     double v     = ruler.getTick(i);
595     bool isLabel = ruler.isLabel(i);
596     int y        = tround(m_viewTransform.m22() * v +
597                    m_viewTransform.dy());  // valueToY(curve, v);
598     painter.setPen(m_textColor);
599     int x = m_valueAxisX;
600     painter.drawLine(x - (isLabel ? 5 : 2), y, x, y);
601 
602     painter.setPen(getValueLineColor());
603     painter.drawLine(x, y, width(), y);
604 
605     if (isLabel) {
606       painter.setPen(m_textColor);
607       QString labelText = QString::number(v);
608       painter.drawText(std::max(0, x - 5 - fm.width(labelText)),
609                        y + fm.height() / 2, labelText);
610     }
611   }
612   if (false && ruler.getTickCount() > 10) {
613     double value = ruler.getTick(9);
614     double dv    = fabs(ruler.getTick(10));
615     double frame = 10;
616     double df    = dv * getPixelRatio(curve);
617     QPointF p0   = getWinPos(curve, frame, value);
618     QPointF p1   = getWinPos(curve, frame + df, value + dv);
619 
620     painter.setPen(Qt::magenta);
621     painter.drawRect(p0.x(), p0.y(), (p1 - p0).x(), (p1 - p0).y());
622   }
623 }
624 
625 //-----------------------------------------------------------------------------
626 
drawOtherCurves(QPainter & painter)627 void FunctionPanel::drawOtherCurves(QPainter &painter) {
628   painter.setRenderHint(QPainter::Antialiasing, false);
629   painter.setBrush(Qt::NoBrush);
630   int x0 = m_valueAxisX;
631   int x1 = width();
632 
633   QPen solidPen;
634   QPen dashedPen;
635   QVector<qreal> dashes;
636   dashes << 4 << 4;
637   dashedPen.setDashPattern(dashes);
638 
639   for (int i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) {
640     FunctionTreeModel::Channel *channel =
641         m_functionTreeModel->getActiveChannel(i);
642     if (channel->isCurrent()) continue;
643     TDoubleParam *curve = channel->getParam();
644     QColor color =
645         curve == m_curveLabel.curve ? m_selectedColor : getOtherCurvesColor();
646     solidPen.setColor(color);
647     dashedPen.setColor(color);
648     painter.setBrush(Qt::NoBrush);
649 
650     int kCount = curve->getKeyframeCount();
651     if (kCount == 0) {
652       // no control points
653       painter.setPen(dashedPen);
654       painter.drawPath(getSegmentPainterPath(curve, 0, x0, x1));
655     }
656     // draw control points and handles
657     else {
658       for (int k = -1; k < kCount; k++) {
659         painter.setPen((k < 0 || k >= kCount - 1) ? dashedPen : solidPen);
660         painter.drawPath(getSegmentPainterPath(curve, k, x0, x1));
661       }
662       painter.setPen(m_textColor);
663       painter.setBrush(m_subColor);
664       for (int k = 0; k < kCount; k++) {
665         double frame = curve->keyframeIndexToFrame(k);
666         QPointF p    = getWinPos(curve, frame, curve->getValue(frame));
667         painter.drawRect(p.x() - 1, p.y() - 1, 3, 3);
668         QPointF p2 = getWinPos(curve, frame, curve->getValue(frame, true));
669         if (p2.y() != p.y()) {
670           painter.drawRect(p2.x() - 1, p2.y() - 1, 3, 3);
671           painter.setPen(solidPen);
672           painter.drawLine(p, p2);
673           painter.setPen(m_textColor);
674         }
675       }
676     }
677   }
678   painter.setBrush(Qt::NoBrush);
679   painter.setPen(m_textColor);
680   painter.setRenderHint(QPainter::Antialiasing, false);
681 }
682 
683 //-----------------------------------------------------------------------------
684 
updateGadgets(TDoubleParam * curve)685 void FunctionPanel::updateGadgets(TDoubleParam *curve) {
686   m_gadgets.clear();
687 
688   TDoubleKeyframe oldKf;
689   oldKf.m_type = TDoubleKeyframe::None;
690 
691   int keyframeCount = curve->getKeyframeCount();
692 
693   for (int i = 0; i != keyframeCount; ++i) {
694     const int pointHitRadius = 10, handleHitRadius = 6;
695 
696     TDoubleKeyframe kf = curve->getKeyframe(i);
697     kf.m_value         = curve->getValue(
698         kf.m_frame);  // Some keyframe values do NOT correspond to the
699                       // actual displayed curve value (eg with expressions)
700     // Build keyframe positions
701     QPointF p     = getWinPos(curve, kf.m_frame);
702     QPointF pLeft = p;
703 
704     if (i == keyframeCount - 1 &&
705         curve
706             ->isCycleEnabled())  // This is probably OBSOLETE. I don't think the
707       p = getWinPos(curve, kf.m_frame,
708                     curve->getValue(
709                         kf.m_frame,
710                         true));  // GUI allows cycling single curves nowadays...
711                                  // However, is the assignment correct?
712     // Add keyframe gadget(s)
713     m_gadgets.push_back(Gadget(Point, i, p, pointHitRadius, pointHitRadius));
714 
715     TPointD currentPointRight(kf.m_frame, kf.m_value);
716     TPointD currentPointLeft(currentPointRight);
717 
718     // If the previous segment or the current segment are not keyframe based,
719     // the curve can have two different values in kf.m_frame
720     if (i > 0 &&
721         (!TDoubleKeyframe::isKeyframeBased(
722              kf.m_type) ||  // Keyframe-based are the above mentioned curves
723          !TDoubleKeyframe::isKeyframeBased(
724              curve->getKeyframe(i - 1)
725                  .m_type)))  // where values stored in keyframes are not used
726     {                        // to calculate the actual curve values.
727       currentPointLeft.y = curve->getValue(kf.m_frame, true);
728       pLeft              = getWinPos(curve, currentPointLeft);
729       m_gadgets.push_back(
730           Gadget(Point, i, pLeft, pointHitRadius, pointHitRadius));
731     }
732 
733     // Add handle gadgets (eg the speed or ease handles)
734     if (getSelection()->isSelected(curve, i)) {
735       // Left handle
736       switch (oldKf.m_type) {
737       case TDoubleKeyframe::SpeedInOut: {
738         TPointD speedIn = curve->getSpeedIn(i);
739         if (norm2(speedIn) > 0) {
740           QPointF q = getWinPos(curve, currentPointLeft + speedIn);
741           m_gadgets.push_back(
742               Gadget(SpeedIn, i, q, handleHitRadius, handleHitRadius, pLeft));
743         }
744         break;
745       }
746 
747       case TDoubleKeyframe::EaseInOut: {
748         QPointF q = getWinPos(curve, kf.m_frame + kf.m_speedIn.x);
749         m_gadgets.push_back(Gadget(EaseIn, i, q, 6, 15));
750         break;
751       }
752 
753       case TDoubleKeyframe::EaseInOutPercentage: {
754         double easeIn = kf.m_speedIn.x * (kf.m_frame - oldKf.m_frame) * 0.01;
755         QPointF q     = getWinPos(curve, kf.m_frame + easeIn);
756         m_gadgets.push_back(Gadget(EaseInPercentage, i, q, 6, 15));
757         break;
758       }
759       default:
760         break;
761       }
762 
763       // Right handle
764       if (i != keyframeCount - 1) {
765         switch (kf.m_type) {
766         case TDoubleKeyframe::SpeedInOut: {
767           TPointD speedOut = curve->getSpeedOut(i);
768           if (norm2(speedOut) > 0) {
769             QPointF q = getWinPos(curve, currentPointRight + speedOut);
770             m_gadgets.push_back(
771                 Gadget(SpeedOut, i, q, handleHitRadius, handleHitRadius, p));
772           }
773           break;
774         }
775 
776         case TDoubleKeyframe::EaseInOut: {
777           QPointF q = getWinPos(curve, kf.m_frame + kf.m_speedOut.x);
778           m_gadgets.push_back(Gadget(EaseOut, i, q, 6, 15));
779           break;
780         }
781 
782         case TDoubleKeyframe::EaseInOutPercentage: {
783           double segmentWidth = curve->keyframeIndexToFrame(i + 1) - kf.m_frame;
784           double easeOut      = segmentWidth * kf.m_speedOut.x * 0.01;
785 
786           QPointF q = getWinPos(curve, kf.m_frame + easeOut);
787           m_gadgets.push_back(Gadget(EaseOutPercentage, i, q, 6, 15));
788           break;
789         }
790         default:
791           break;
792         }
793       }
794     }
795 
796     oldKf = kf;
797   }
798 
799   // Add group gadgets (ie those that can be added when multiple channels share
800   // the same keyframe data)
801   int channelCount = m_functionTreeModel->getActiveChannelCount();
802 
803   // Using a map of vectors. Yes, really. The *ideal* way would be that of
804   // copying the first keyframes
805   // vector, and then comparing it with the others from each channel - keeping
806   // the common data only...
807 
808   typedef std::map<double, std::vector<TDoubleKeyframe>>
809       KeyframeTable;  // frame -> { keyframes }
810   KeyframeTable keyframes;
811 
812   for (int i = 0; i != channelCount; ++i) {
813     FunctionTreeModel::Channel *channel =
814         m_functionTreeModel->getActiveChannel(i);
815     if (!channel) continue;
816 
817     TDoubleParam *curve = channel->getParam();
818     for (int j = 0; j != curve->getKeyframeCount(); ++j) {
819       TDoubleKeyframe kf = curve->getKeyframe(
820           j);  // Well... this stuff gets called upon *painting*  o_o'
821       keyframes[kf.m_frame].push_back(
822           kf);  // It's bound to be slow. Do we really need it?
823     }
824   }
825 
826   int groupHandleY = m_graphViewportY - 6;
827 
828   KeyframeTable::iterator it, iEnd(keyframes.end()),
829       iLast(keyframes.empty() ? iEnd : --iEnd);
830   for (KeyframeTable::iterator it = keyframes.begin(); it != keyframes.end();
831        ++it) {
832     assert(!it->second.empty());
833 
834     double frame = it->first;  // redundant, already in the key... oh well
835     QPointF p(frameToX(frame), groupHandleY);
836 
837     Gadget gadget((FunctionPanel::Handle)100, -1, p, 6,
838                   6);  // No idea what the '100' type value mean...
839     gadget.m_keyframePosition = frame;
840 
841     m_gadgets.push_back(gadget);
842 
843     TDoubleKeyframe kf = it->second[0];
844 
845     if ((int)it->second.size() < channelCount) continue;
846 
847     // All channels had this keyframe - so, add further gadgets about stuff...
848 
849     for (int i = 1; i < channelCount; ++i) {
850       // Find out if keyframes data differs
851       const TDoubleKeyframe &kf2 = it->second[i];
852 
853       if (kf.m_type != kf2.m_type || kf.m_speedOut.x != kf2.m_speedOut.x)
854         kf.m_type = TDoubleKeyframe::None;
855       if (kf.m_prevType != kf2.m_prevType || kf.m_speedIn.x != kf2.m_speedIn.x)
856         kf.m_prevType = TDoubleKeyframe::None;
857     }
858 
859     // NOTE: EaseInOutPercentage are currently NOT SUPPORTED - they would be
860     // harder to code and
861     //       controversial, since the handle position depends on the *segment
862     //       size* too.
863     //       So, keyframe data could be shared, but adjacent segment lengths
864     //       could not...
865 
866     if (it != iLast && (kf.m_type == TDoubleKeyframe::SpeedInOut ||
867                         kf.m_type == TDoubleKeyframe::EaseInOut) &&
868         kf.m_speedOut.x != 0) {
869       QPointF p(frameToX(frame + kf.m_speedOut.x), groupHandleY);
870       Gadget gadget((FunctionPanel::Handle)101, -1, p, 6, 15);  // type value...
871       gadget.m_keyframePosition = frame;
872       m_gadgets.push_back(gadget);
873     }
874 
875     if ((kf.m_prevType == TDoubleKeyframe::SpeedInOut ||
876          kf.m_prevType == TDoubleKeyframe::EaseInOut) &&
877         kf.m_speedIn.x != 0) {
878       QPointF p(frameToX(frame + kf.m_speedIn.x), groupHandleY);
879       Gadget gadget((FunctionPanel::Handle)102, -1, p, 6, 15);  // type value...
880       gadget.m_keyframePosition = frame;
881       m_gadgets.push_back(gadget);
882     }
883   }
884 }
885 
886 //-----------------------------------------------------------------------------
887 
drawCurrentCurve(QPainter & painter)888 void FunctionPanel::drawCurrentCurve(QPainter &painter) {
889   FunctionTreeModel::Channel *channel =
890       m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0;
891   if (!channel) return;
892   TDoubleParam *curve = channel->getParam();
893 
894   painter.setRenderHint(QPainter::Antialiasing, true);
895   QColor color = Qt::red;
896   QPen solidPen(color);
897   QPen dashedPen(color);
898   QVector<qreal> dashes;
899   dashes << 4 << 4;
900   dashedPen.setDashPattern(dashes);
901   painter.setBrush(Qt::NoBrush);
902 
903   int x0 = m_valueAxisX;
904   int x1 = width();
905 
906   // draw curve
907   int kCount = curve->getKeyframeCount();
908   if (kCount == 0) {
909     // no control points
910     painter.setPen(dashedPen);
911     painter.drawPath(getSegmentPainterPath(curve, 0, x0, x1));
912   } else {
913     for (int k = -1; k < kCount; k++) {
914       if (k < 0 || k >= kCount - 1) {
915         painter.setPen(dashedPen);
916         painter.drawPath(getSegmentPainterPath(curve, k, x0, x1));
917       } else {
918         TDoubleKeyframe::Type segmentType = curve->getKeyframe(k).m_type;
919         QColor color                      = Qt::red;
920         if (segmentType == TDoubleKeyframe::Expression ||
921             segmentType == TDoubleKeyframe::SimilarShape ||
922             segmentType == TDoubleKeyframe::File)
923           color = QColor(185, 0, 0);
924         if (getSelection()->isSegmentSelected(curve, k))
925           solidPen.setWidth(2);
926         else
927           solidPen.setWidth(0);
928         solidPen.setColor(color);
929         painter.setPen(solidPen);
930         painter.drawPath(getSegmentPainterPath(curve, k, x0, x1));
931       }
932     }
933   }
934   painter.setPen(QPen(m_textColor, 0));
935 
936   // draw control points
937   updateGadgets(curve);
938   painter.setPen(m_textColor);
939   for (int j = 0; j < (int)m_gadgets.size(); j++) {
940     const Gadget &g = m_gadgets[j];
941     if (g.m_handle == SpeedIn || g.m_handle == SpeedOut)
942       painter.drawLine(g.m_pointPos, g.m_pos);
943   }
944   solidPen.setWidth(0);
945   solidPen.setColor(Qt::red);
946   painter.setPen(solidPen);
947   for (int j = 0; j < (int)m_gadgets.size() - 1; j++)
948     if (m_gadgets[j].m_handle == Point && m_gadgets[j + 1].m_handle &&
949         m_gadgets[j + 1].m_handle != 100 &&
950         m_gadgets[j].m_pos.x() == m_gadgets[j + 1].m_pos.x())
951       painter.drawLine(m_gadgets[j].m_pos, m_gadgets[j + 1].m_pos);
952 
953   painter.setRenderHint(QPainter::Antialiasing, false);
954   for (int j = 0; j < (int)m_gadgets.size(); j++) {
955     const Gadget &g = m_gadgets[j];
956     int i           = g.m_kIndex;
957     int r           = 1;
958     QPointF p       = g.m_pos;
959     double easeDx = 0, easeHeight = 15, easeTick = 2;
960     bool isSelected = getSelection()->isSelected(curve, i);
961     bool isHighlighted =
962         m_highlighted.handle == g.m_handle && m_highlighted.gIndex == j;
963     switch (g.m_handle) {
964     case Point:
965       painter.setBrush(isSelected ? QColor(255, 126, 0) : m_subColor);
966       painter.setPen(m_textColor);
967       r = isHighlighted ? 3 : 2;
968       drawSquare(painter, p, r);
969       break;
970 
971     case SpeedIn:
972     case SpeedOut:
973       painter.setBrush(m_subColor);
974       painter.setPen(m_textColor);
975       r = isHighlighted ? 3 : 2;
976       drawRoundedSquare(painter, p, r);
977       break;
978 
979     case EaseIn:
980     case EaseOut:
981     case EaseInPercentage:
982     case EaseOutPercentage:
983       painter.setBrush(Qt::NoBrush);
984       painter.setPen(isHighlighted ? QColor(255, 126, 0) : m_textColor);
985       painter.drawLine(p.x(), p.y() - easeHeight, p.x(), p.y() + easeHeight);
986       if (g.m_handle == EaseIn || g.m_handle == EaseInPercentage)
987         easeDx = easeTick;
988       else
989         easeDx = -easeTick;
990       painter.drawLine(p.x(), p.y() - easeHeight, p.x() + easeDx,
991                        p.y() - easeHeight - easeTick);
992       painter.drawLine(p.x(), p.y() + easeHeight, p.x() + easeDx,
993                        p.y() + easeHeight + easeTick);
994       break;
995 
996       painter.setBrush(
997           Qt::NoBrush);  // isSelected ? QColor(255,126,0) : Qt::white);
998       painter.setPen(isHighlighted ? QColor(255, 126, 0) : m_selectedColor);
999       painter.drawLine(p.x(), p.y() - 15, p.x(), p.y() + 15);
1000       break;
1001     default:
1002       break;
1003     }
1004   }
1005 
1006   painter.setRenderHint(QPainter::Antialiasing, false);
1007 }
1008 
1009 //-----------------------------------------------------------------------------
1010 
drawGroupKeyframes(QPainter & painter)1011 void FunctionPanel::drawGroupKeyframes(QPainter &painter) {
1012   FunctionTreeModel::Channel *channel =
1013       m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0;
1014   if (!channel) return;
1015   QColor color = Qt::red;
1016   QPen solidPen(color);
1017   QPen dashedPen(color);
1018   QVector<qreal> dashes;
1019   dashes << 4 << 4;
1020   dashedPen.setDashPattern(dashes);
1021   painter.setBrush(Qt::NoBrush);
1022 
1023   int x0 = m_valueAxisX;
1024   int x1 = width();
1025 
1026   solidPen.setWidth(0);
1027   solidPen.setColor(Qt::red);
1028   painter.setPen(solidPen);
1029 
1030   std::vector<double> keyframes;
1031   int y = 0;
1032   for (int j = 0; j < (int)m_gadgets.size(); j++) {
1033     const Gadget &g = m_gadgets[j];
1034     int i           = g.m_kIndex;
1035     int r           = 1;
1036     QPointF p       = g.m_pos;
1037     double easeDx = 0, easeHeight = 15, easeTick = 2;
1038     bool isSelected = false;  // getSelection()->isSelected(curve, i);
1039     bool isHighlighted =
1040         m_highlighted.handle == g.m_handle && m_highlighted.gIndex == j;
1041     painter.setBrush(isSelected ? QColor(255, 126, 0) : m_subColor);
1042     painter.setPen(m_textColor);
1043     r = isHighlighted ? 3 : 2;
1044     QPainterPath pp;
1045     int d = 2;
1046     int h = 4;
1047     switch (g.m_handle) {
1048     case 100:
1049       drawSquare(painter, p, r);
1050       y = p.y();
1051       keyframes.push_back(p.x());
1052       break;
1053     case 101:
1054       d = -d;
1055     // Note: NO break!
1056     case 102:
1057       painter.setBrush(Qt::NoBrush);
1058       painter.setPen(isHighlighted ? QColor(255, 126, 0) : m_textColor);
1059       pp.moveTo(p + QPointF(d, -h));
1060       pp.lineTo(p + QPointF(0, -h));
1061       pp.lineTo(p + QPointF(0, h));
1062       pp.lineTo(p + QPointF(d, h));
1063       painter.drawPath(pp);
1064       break;
1065     default:
1066       break;
1067     }
1068   }
1069   painter.setPen(m_textColor);
1070   for (int i = 0; i + 1 < (int)keyframes.size(); i++) {
1071     painter.drawLine(keyframes[i] + 3, y, keyframes[i + 1] - 3, y);
1072   }
1073 }
1074 
1075 //-----------------------------------------------------------------------------
1076 
paintEvent(QPaintEvent * e)1077 void FunctionPanel::paintEvent(QPaintEvent *e) {
1078   m_gadgets.clear();
1079 
1080   QString fontName = Preferences::instance()->getInterfaceFont();
1081   if (fontName == "") {
1082 #ifdef _WIN32
1083     fontName = "Arial";
1084 #else
1085     fontName = "Helvetica";
1086 #endif
1087   }
1088 
1089   QPainter painter(this);
1090   QFont font(fontName, 8);
1091   painter.setFont(font);
1092   QFontMetrics fm(font);
1093 
1094   // define ruler sizes
1095   m_valueAxisX     = fm.width("-888.88") + 2;
1096   m_frameAxisY     = fm.height() + 2;
1097   m_graphViewportY = m_frameAxisY + 12;
1098   int ox           = m_valueAxisX;
1099   int oy0          = m_graphViewportY;
1100   int oy1          = m_frameAxisY;
1101 
1102   // QRect bounds(0,0,width(),height());
1103 
1104   // draw functions background
1105   painter.setBrush(getBGColor());
1106   painter.setPen(Qt::NoPen);
1107   painter.drawRect(ox, oy0, width() - ox, height() - oy0);
1108 
1109   painter.setClipRect(ox, 0, width() - ox, height());
1110   drawCurrentFrame(painter);
1111   drawFrameGrid(painter);
1112 
1113   painter.setClipRect(0, oy0, width(), height() - oy0);
1114   drawValueGrid(painter);
1115 
1116   // draw axes
1117   painter.setClipping(false);
1118   painter.setPen(m_textColor);
1119   painter.drawLine(0, oy0, width(), oy0);
1120   painter.drawLine(ox, oy1, width(), oy1);
1121   painter.drawLine(ox, 0, ox, height());
1122 
1123   // draw curves
1124   painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1);
1125   drawOtherCurves(painter);
1126   drawCurrentCurve(painter);
1127 
1128   painter.setClipping(false);
1129   painter.setClipRect(ox + 1, oy1 + 1, width() - ox - 1, oy0 - oy1 - 1);
1130   drawGroupKeyframes(painter);
1131   painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1);
1132 
1133   // tool
1134   if (m_dragTool) m_dragTool->draw(painter);
1135 
1136   // cursor
1137   if (m_cursor.visible) {
1138     painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1);
1139     painter.setPen(getOtherCurvesColor());
1140     int x = frameToX(m_cursor.frame);
1141     painter.drawLine(x, oy0 + 1, x, oy0 + 10);
1142     QString text = QString::number(tround(m_cursor.frame) + 1);
1143     painter.drawText(x - fm.width(text) / 2, oy0 + 10 + fm.height(), text);
1144 
1145     TDoubleParam *currentCurve = getCurrentCurve();
1146     if (currentCurve) {
1147       const TUnit *unit = 0;
1148       if (currentCurve->getMeasure())
1149         unit                 = currentCurve->getMeasure()->getCurrentUnit();
1150       double displayValue    = m_cursor.value;
1151       if (unit) displayValue = unit->convertTo(displayValue);
1152       // painter.setClipRect(0,oy0,height(),height()-oy0);
1153       int y = valueToY(currentCurve, m_cursor.value);
1154       painter.drawLine(ox, y, ox + 10, y);
1155       painter.drawText(m_origin.x() + 10, y + 4, QString::number(displayValue));
1156     }
1157   }
1158 
1159   // curve name
1160   if (m_curveLabel.text != "") {
1161     painter.setClipRect(ox, oy0, width() - ox, height() - oy0);
1162     painter.setPen(m_selectedColor);
1163     painter.drawLine(m_curveLabel.curvePos, m_curveLabel.labelPos);
1164     painter.drawText(m_curveLabel.labelPos,
1165                      QString::fromStdString(m_curveLabel.text));
1166   }
1167 
1168   // painter.setPen(Qt::black);
1169   // painter.drawText(QPointF(70,70),
1170   //  "f0=" + QString::number(xToFrame(ox)) +
1171   //  " f1=" + QString::number(xToFrame(width())));
1172 
1173   // painter.setPen(Qt::black);
1174   // painter.setBrush(Qt::NoBrush);
1175   // painter.drawRect(ox+10,oy+10,width()-ox-20,height()-oy-20);
1176 }
1177 
1178 //-----------------------------------------------------------------------------
1179 
mousePressEvent(QMouseEvent * e)1180 void FunctionPanel::mousePressEvent(QMouseEvent *e) {
1181   m_cursor.visible = false;
1182 
1183   // m_dragTool should be 0. just in case...
1184   assert(m_dragTool == 0);
1185   m_dragTool = 0;
1186 
1187   if (e->button() == Qt::MidButton) {
1188     // mid mouse click => panning
1189     bool xLocked = e->pos().x() <= m_valueAxisX;
1190     bool yLocked = e->pos().y() <= m_valueAxisX;
1191     m_dragTool   = new PanDragTool(this, xLocked, yLocked);
1192     m_dragTool->click(e);
1193     return;
1194   } else if (e->button() == Qt::RightButton) {
1195     // right mouse click => open context menu
1196     openContextMenu(e);
1197     return;
1198   }
1199 
1200   QPoint winPos         = e->pos();
1201   Handle handle         = None;
1202   const int maxDistance = 20;
1203   int closestGadgetId   = findClosestGadget(e->pos(), handle, maxDistance);
1204 
1205   if (e->pos().x() > m_valueAxisX && e->pos().y() < m_frameAxisY &&
1206       closestGadgetId < 0 && (e->modifiers() & Qt::ControlModifier) == 0) {
1207     // click on topbar => frame zoom
1208     m_dragTool = new ZoomDragTool(this, ZoomDragTool::FrameZoom);
1209   } else if (e->pos().x() < m_valueAxisX && e->pos().y() > m_graphViewportY) {
1210     // click on topbar => value zoom
1211     m_dragTool = new ZoomDragTool(this, ZoomDragTool::ValueZoom);
1212   } else if (m_currentFrameStatus == 1 && m_frameHandle != 0 &&
1213              closestGadgetId < 0) {
1214     // click on current frame => move frame
1215     m_currentFrameStatus = 2;
1216     m_dragTool           = new MoveFrameDragTool(this, m_frameHandle);
1217   }
1218 
1219   if (0 <= closestGadgetId && closestGadgetId < (int)m_gadgets.size()) {
1220     if (handle == 100)  // group move gadget
1221     {
1222       MovePointDragTool *dragTool = new MovePointDragTool(this, 0);
1223       dragTool->selectKeyframes(m_gadgets[closestGadgetId].m_keyframePosition);
1224       m_dragTool = dragTool;
1225     } else if (handle == 101 || handle == 102) {
1226       m_dragTool = new MoveGroupHandleDragTool(
1227           this, m_gadgets[closestGadgetId].m_keyframePosition, handle);
1228     }
1229   }
1230 
1231   if (m_dragTool) {
1232     m_dragTool->click(e);
1233     return;
1234   }
1235 
1236   FunctionTreeModel::Channel *currentChannel =
1237       m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0;
1238   if (!currentChannel ||
1239       (getCurveDistance(currentChannel->getParam(), winPos) > maxDistance &&
1240           closestGadgetId < 0)) {
1241     // if current channel is undefined or its curve is too far from the clicked
1242     // point
1243     // the user is possibly trying to select a different curve
1244     FunctionTreeModel::Channel *channel =
1245         findClosestChannel(winPos, maxDistance);
1246     if (channel) {
1247       channel->setIsCurrent(true);
1248       // Open folder
1249       FunctionTreeModel::ChannelGroup *channelGroup =
1250           channel->getChannelGroup();
1251       if (!channelGroup->isOpen())
1252         channelGroup->getModel()->setExpandedItem(channelGroup->createIndex(),
1253                                                   true);
1254       currentChannel = channel;
1255       getSelection()->selectNone();
1256     }
1257   }
1258 
1259   if (currentChannel) {
1260     TDoubleParam *currentCurve = currentChannel->getParam();
1261     if (currentCurve) {
1262       int kIndex =
1263           closestGadgetId >= 0 ? m_gadgets[closestGadgetId].m_kIndex : -1;
1264       if (kIndex >= 0) {
1265         // keyframe clicked
1266         if (handle == FunctionPanel::Point) {
1267           // select point (if needed)
1268           if (!getSelection()->isSelected(currentCurve, kIndex)) {
1269             // shift-click => add to selection
1270             if (0 == (e->modifiers() & Qt::ShiftModifier))
1271               getSelection()->deselectAllKeyframes();
1272             getSelection()->select(currentCurve, kIndex);
1273           }
1274           // move selected point(s)
1275           MovePointDragTool *dragTool =
1276               new MovePointDragTool(this, currentCurve);
1277           if (getSelection()->getSelectedSegment().first != 0) {
1278             // if a segment is selected then move only the clicked point
1279             dragTool->addKeyframe2(kIndex);
1280           } else {
1281             dragTool->setSelection(getSelection());
1282           }
1283           m_dragTool = dragTool;
1284         } else {
1285           m_dragTool =
1286               new MoveHandleDragTool(this, currentCurve, kIndex, handle);
1287         }
1288       } else {
1289         // no keyframe clicked
1290         int curveDistance =
1291             getCurveDistance(currentChannel->getParam(), winPos);
1292         bool isKeyframeable = true;
1293         bool isGroup        = abs(winPos.y() - (m_graphViewportY - 5)) < 5;
1294         if (0 != (e->modifiers() & Qt::ControlModifier) &&
1295             (curveDistance < maxDistance || isGroup) && isKeyframeable) {
1296           // ctrl-clicked near curve => create a new keyframe
1297           double frame = tround(xToFrame(winPos.x()));
1298           MovePointDragTool *dragTool =
1299               new MovePointDragTool(this, isGroup ? 0 : currentCurve);
1300           //          if(curveDistance>=maxDistance)
1301           //            dragTool->m_channelGroup =
1302           //            currentChannel->getChannelGroup();
1303           dragTool->createKeyframe(frame);
1304           dragTool->selectKeyframes(frame);
1305           m_dragTool = dragTool;
1306 
1307           /*
1308 int kIndex = dragTool->createKeyframe(frame);
1309           if(kIndex!=-1)
1310           {
1311                   getSelection()->deselectAllKeyframes();
1312                   getSelection()->select(currentCurve, kIndex);
1313                   m_dragTool = dragTool;
1314           }
1315 */
1316           // assert(0);
1317         } else if (curveDistance < maxDistance) {
1318           // clicked near curve (but far from keyframes)
1319           getSelection()->deselectAllKeyframes();
1320           double frame = xToFrame(winPos.x());
1321           int k0       = currentCurve->getPrevKeyframe(frame);
1322           int k1       = currentCurve->getNextKeyframe(frame);
1323           if (k0 >= 0 && k1 == k0 + 1) {
1324             // select and move the segment
1325             getSelection()->selectSegment(currentCurve, k0);
1326             MovePointDragTool *dragTool =
1327                 new MovePointDragTool(this, currentCurve);
1328             dragTool->addKeyframe2(k0);
1329             dragTool->addKeyframe2(k1);
1330             m_dragTool = dragTool;
1331           } else {
1332             // start a rectangular selection
1333             m_dragTool = new RectSelectTool(this, currentCurve);
1334           }
1335         } else {
1336           // nothing clicked: start a rectangular selection
1337           getSelection()->deselectAllKeyframes();
1338           m_dragTool = new RectSelectTool(this, currentCurve);
1339         }
1340       }
1341     }
1342   }
1343 
1344   if (m_dragTool) m_dragTool->click(e);
1345   update();
1346 }
1347 
1348 //-----------------------------------------------------------------------------
1349 
mouseReleaseEvent(QMouseEvent * e)1350 void FunctionPanel::mouseReleaseEvent(QMouseEvent *e) {
1351   if (m_dragTool) m_dragTool->release(e);
1352   delete m_dragTool;
1353   m_dragTool           = 0;
1354   m_cursor.visible     = true;
1355   m_currentFrameStatus = 0;
1356   update();
1357 }
1358 
1359 //-----------------------------------------------------------------------------
1360 
mouseMoveEvent(QMouseEvent * e)1361 void FunctionPanel::mouseMoveEvent(QMouseEvent *e) {
1362   if (e->buttons()) {
1363     if (m_dragTool) m_dragTool->drag(e);
1364   } else {
1365     m_cursor.frame   = xToFrame(e->pos().x());
1366     m_cursor.value   = 0;
1367     m_cursor.visible = true;
1368 
1369     TDoubleParam *currentCurve = getCurrentCurve();
1370     if (currentCurve) {
1371       Handle handle = None;
1372       int gIndex    = findClosestGadget(e->pos(), handle, 20);
1373       if (m_highlighted.handle != handle || m_highlighted.gIndex != gIndex) {
1374         m_highlighted.handle = handle;
1375         m_highlighted.gIndex = gIndex;
1376       }
1377       m_cursor.value = yToValue(currentCurve, e->pos().y());
1378     }
1379 
1380     double currentFrame = m_frameHandle ? m_frameHandle->getFrame() : 0;
1381     if (m_highlighted.handle == None &&
1382         std::abs(e->pos().x() - frameToX(currentFrame)) < 5)
1383       m_currentFrameStatus = 1;
1384     else
1385       m_currentFrameStatus = 0;
1386 
1387     FunctionTreeModel::Channel *closestChannel =
1388         findClosestChannel(e->pos(), 20);
1389     if (closestChannel && m_highlighted.handle == None) {
1390       TDoubleParam *curve = closestChannel->getParam();
1391       if (m_functionTreeModel->getActiveChannelCount() <= 1)
1392         //|| closestChannel == m_functionTreeModel->getCurrentChannel())
1393         curve = 0;
1394       if (curve && m_curveLabel.curve != curve) {
1395         m_curveLabel.curve  = curve;
1396         QString channelName = closestChannel->data(Qt::DisplayRole).toString();
1397         QString parentChannelName =
1398             closestChannel->getChannelGroup()->data(Qt::DisplayRole).toString();
1399         QString name      = parentChannelName + QString(", ") + channelName;
1400         m_curveLabel.text = name.toStdString();
1401 
1402         // in order to avoid run off the right-end of visible area
1403         int textWidth = fontMetrics().width(name) + 30;
1404         double frame  = xToFrame(width() - textWidth);
1405 
1406         m_curveLabel.curvePos = getWinPos(curve, frame).toPoint();
1407         m_curveLabel.labelPos = m_curveLabel.curvePos + QPoint(20, -10);
1408       }
1409     } else {
1410       m_curveLabel.text  = "";
1411       m_curveLabel.curve = 0;
1412     }
1413 
1414     update();
1415   }
1416 }
1417 
1418 //-----------------------------------------------------------------------------
1419 
keyPressEvent(QKeyEvent * e)1420 void FunctionPanel::keyPressEvent(QKeyEvent *e) {
1421   FunctionPanelZoomer(this).exec(e);
1422 }
1423 
1424 //-----------------------------------------------------------------------------
1425 
enterEvent(QEvent *)1426 void FunctionPanel::enterEvent(QEvent *) {
1427   m_cursor.visible = true;
1428   update();
1429 }
1430 
1431 //-----------------------------------------------------------------------------
1432 
leaveEvent(QEvent *)1433 void FunctionPanel::leaveEvent(QEvent *) {
1434   m_cursor.visible = false;
1435   update();
1436 }
1437 
1438 //-----------------------------------------------------------------------------
1439 
wheelEvent(QWheelEvent * e)1440 void FunctionPanel::wheelEvent(QWheelEvent *e) {
1441   double factor = exp(0.002 * (double)e->delta());
1442   zoom(factor, factor, e->pos());
1443 }
1444 
1445 //-----------------------------------------------------------------------------
1446 
fitGraphToWindow(bool currentCurveOnly)1447 void FunctionPanel::fitGraphToWindow(bool currentCurveOnly) {
1448   double f0 = 0, f1 = -1;
1449   double v0 = 0, v1 = -1;
1450 
1451   for (int i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) {
1452     FunctionTreeModel::Channel *channel =
1453         m_functionTreeModel->getActiveChannel(i);
1454     TDoubleParam *curve = channel->getParam();
1455     if (currentCurveOnly && curve != getCurrentCurve()) continue;
1456 
1457     const TUnit *unit             = 0;
1458     if (curve->getMeasure()) unit = curve->getMeasure()->getCurrentUnit();
1459     int n                         = curve->getKeyframeCount();
1460     if (n == 0) {
1461       double v    = curve->getDefaultValue();
1462       if (unit) v = unit->convertTo(v);
1463       if (v0 > v1)
1464         v0 = v1 = v;
1465       else if (v > v1)
1466         v1 = v;
1467       else if (v < v0)
1468         v0 = v;
1469     } else {
1470       TDoubleKeyframe k = curve->getKeyframe(0);
1471       double fa         = k.m_frame;
1472       k                 = curve->getKeyframe(n - 1);
1473       double fb         = k.m_frame;
1474       if (f0 > f1) {
1475         f0 = fa;
1476         f1 = fb;
1477       } else {
1478         f0 = std::min(f0, fa);
1479         f1 = std::max(f1, fb);
1480       }
1481       double v        = curve->getValue(fa);
1482       if (unit) v     = unit->convertTo(v);
1483       if (v0 > v1) v0 = v1 = v;
1484       int m                = 50;
1485       for (int j = 0; j < m; j++) {
1486         double t    = (double)j / (double)(m - 1);
1487         double v    = curve->getValue((1 - t) * fa + t * fb);
1488         if (unit) v = unit->convertTo(v);
1489         v0          = std::min(v0, v);
1490         v1          = std::max(v1, v);
1491       }
1492     }
1493   }
1494   if (f0 >= f1 || v0 >= v1) {
1495     m_viewTransform = QTransform();
1496     m_viewTransform.translate(m_valueAxisX, 200);
1497     m_viewTransform.scale(5, -1);
1498   } else {
1499     double mx       = (width() - m_valueAxisX - 20) / (f1 - f0);
1500     double my       = -(height() - m_graphViewportY - 20) / (v1 - v0);
1501     double dx       = m_valueAxisX + 10 - f0 * mx;
1502     double dy       = m_graphViewportY + 10 - v1 * my;
1503     m_viewTransform = QTransform(mx, 0, 0, my, dx, dy);
1504   }
1505   update();
1506 }
1507 
1508 //-----------------------------------------------------------------------------
1509 
fitSelectedPoints()1510 void FunctionPanel::fitSelectedPoints() { fitGraphToWindow(true); }
1511 
1512 //-----------------------------------------------------------------------------
1513 
fitCurve()1514 void FunctionPanel::fitCurve() { fitGraphToWindow(); }
1515 
1516 //-----------------------------------------------------------------------------
1517 
fitRegion(double f0,double v0,double f1,double v1)1518 void FunctionPanel::fitRegion(double f0, double v0, double f1, double v1) {}
1519 
1520 //-----------------------------------------------------------------------------
1521 
setSegmentType(FunctionSelection * selection,TDoubleParam * curve,int segmentIndex,TDoubleKeyframe::Type type)1522 static void setSegmentType(FunctionSelection *selection, TDoubleParam *curve,
1523                            int segmentIndex, TDoubleKeyframe::Type type) {
1524   selection->selectSegment(curve, segmentIndex);
1525   KeyframeSetter setter(curve, segmentIndex);
1526   setter.setType(type);
1527 }
1528 
1529 //-----------------------------------------------------------------------------
1530 
openContextMenu(QMouseEvent * e)1531 void FunctionPanel::openContextMenu(QMouseEvent *e) {
1532   QAction linkHandlesAction(tr("Link Handles"), 0);
1533   QAction unlinkHandlesAction(tr("Unlink Handles"), 0);
1534   QAction resetHandlesAction(tr("Reset Handles"), 0);
1535   QAction deleteKeyframeAction(tr("Delete"), 0);
1536   QAction insertKeyframeAction(tr("Set Key"), 0);
1537   QAction activateCycleAction(tr("Activate Cycle"), 0);
1538   QAction deactivateCycleAction(tr("Deactivate Cycle"), 0);
1539   QAction setLinearAction(tr("Linear Interpolation"), 0);
1540   QAction setSpeedInOutAction(tr("Speed In / Speed Out Interpolation"), 0);
1541   QAction setEaseInOutAction(tr("Ease In / Ease Out Interpolation"), 0);
1542   QAction setEaseInOut2Action(tr("Ease In / Ease Out (%) Interpolation"), 0);
1543   QAction setExponentialAction(tr("Exponential Interpolation"), 0);
1544   QAction setExpressionAction(tr("Expression Interpolation"), 0);
1545   QAction setFileAction(tr("File Interpolation"), 0);
1546   QAction setConstantAction(tr("Constant Interpolation"), 0);
1547   QAction setSimilarShapeAction(tr("Similar Shape Interpolation"), 0);
1548   QAction fitSelectedAction(tr("Fit Selection"), 0);
1549   QAction fitAllAction(tr("Fit"), 0);
1550   QAction setStep1Action(tr("Step 1"), 0);
1551   QAction setStep2Action(tr("Step 2"), 0);
1552   QAction setStep3Action(tr("Step 3"), 0);
1553   QAction setStep4Action(tr("Step 4"), 0);
1554 
1555   TDoubleParam *curve = getCurrentCurve();
1556   int segmentIndex    = -1;
1557   if (!curve) return;
1558   TDoubleKeyframe kf;
1559   double frame = xToFrame(e->pos().x());
1560 
1561   // build menu
1562   QMenu menu(0);
1563   if (m_highlighted.handle == Point && m_highlighted.gIndex >= 0 &&
1564       m_gadgets[m_highlighted.gIndex].m_handle != 100) {
1565     kf = curve->getKeyframe(m_gadgets[m_highlighted.gIndex].m_kIndex);
1566     if (kf.m_linkedHandles)
1567       menu.addAction(&unlinkHandlesAction);
1568     else
1569       menu.addAction(&linkHandlesAction);
1570     menu.addAction(&resetHandlesAction);
1571     menu.addAction(&deleteKeyframeAction);
1572   } else {
1573     int k0 = curve->getPrevKeyframe(frame);
1574     int k1 = curve->getNextKeyframe(frame);
1575     if (k0 == curve->getKeyframeCount() - 1)  // after last keyframe
1576     {
1577       if (curve->isCycleEnabled())
1578         menu.addAction(&deactivateCycleAction);
1579       else
1580         menu.addAction(&activateCycleAction);
1581     }
1582     menu.addAction(&insertKeyframeAction);
1583     if (k0 >= 0 && k1 >= 0) {
1584       menu.addSeparator();
1585       segmentIndex = k0;
1586       kf           = curve->getKeyframe(k0);
1587       menu.addAction(&setLinearAction);
1588       if (kf.m_type == TDoubleKeyframe::Linear)
1589         setLinearAction.setEnabled(false);
1590       menu.addAction(&setSpeedInOutAction);
1591       if (kf.m_type == TDoubleKeyframe::SpeedInOut)
1592         setSpeedInOutAction.setEnabled(false);
1593       menu.addAction(&setEaseInOutAction);
1594       if (kf.m_type == TDoubleKeyframe::EaseInOut)
1595         setEaseInOutAction.setEnabled(false);
1596       menu.addAction(&setEaseInOut2Action);
1597       if (kf.m_type == TDoubleKeyframe::EaseInOutPercentage)
1598         setEaseInOut2Action.setEnabled(false);
1599       menu.addAction(&setExponentialAction);
1600       if (kf.m_type == TDoubleKeyframe::Exponential)
1601         setExponentialAction.setEnabled(false);
1602       menu.addAction(&setExpressionAction);
1603       if (kf.m_type == TDoubleKeyframe::Expression)
1604         setExpressionAction.setEnabled(false);
1605       menu.addAction(&setSimilarShapeAction);
1606       if (kf.m_type == TDoubleKeyframe::SimilarShape)
1607         setSimilarShapeAction.setEnabled(false);
1608       menu.addAction(&setFileAction);
1609       if (kf.m_type == TDoubleKeyframe::File) setFileAction.setEnabled(false);
1610       menu.addAction(&setConstantAction);
1611       if (kf.m_type == TDoubleKeyframe::Constant)
1612         setConstantAction.setEnabled(false);
1613       menu.addSeparator();
1614       if (kf.m_step != 1) menu.addAction(&setStep1Action);
1615       if (kf.m_step != 2) menu.addAction(&setStep2Action);
1616       if (kf.m_step != 3) menu.addAction(&setStep3Action);
1617       if (kf.m_step != 4) menu.addAction(&setStep4Action);
1618       menu.addSeparator();
1619     }
1620   }
1621   if (!getSelection()->isEmpty()) menu.addAction(&fitSelectedAction);
1622   menu.addAction(&fitAllAction);
1623 
1624   // curve shape type
1625   QAction curveShapeSmoothAction(tr("Smooth"), 0);
1626   QAction curveShapeFrameBasedAction(tr("Frame Based"), 0);
1627   QMenu curveShapeSubmenu(tr("Curve Shape"), 0);
1628   menu.addSeparator();
1629   curveShapeSubmenu.addAction(&curveShapeSmoothAction);
1630   curveShapeSubmenu.addAction(&curveShapeFrameBasedAction);
1631   menu.addMenu(&curveShapeSubmenu);
1632 
1633   curveShapeSmoothAction.setCheckable(true);
1634   curveShapeSmoothAction.setChecked(m_curveShape == SMOOTH);
1635   curveShapeFrameBasedAction.setCheckable(true);
1636   curveShapeFrameBasedAction.setChecked(m_curveShape == FRAME_BASED);
1637 
1638   // Store m_highlighted due to the following exec()
1639   Highlighted highlighted(m_highlighted);
1640 
1641   // execute menu
1642   QAction *action = menu.exec(e->globalPos());  // Will process events, possibly
1643                                                 // altering m_highlighted
1644                                                 // (MAC-verified)
1645   if (action == &linkHandlesAction)  // Let's just *hope* that doesn't happen to
1646                                      // m_gadgets though...  :/
1647   {
1648     if (m_gadgets[highlighted.gIndex].m_handle != 100)
1649       KeyframeSetter(curve, m_gadgets[highlighted.gIndex].m_kIndex)
1650           .linkHandles();
1651   } else if (action == &unlinkHandlesAction) {
1652     if (m_gadgets[highlighted.gIndex].m_handle != 100)
1653       KeyframeSetter(curve, m_gadgets[highlighted.gIndex].m_kIndex)
1654           .unlinkHandles();
1655   } else if (action == &resetHandlesAction) {
1656     kf.m_speedIn  = TPointD(-5, 0);
1657     kf.m_speedOut = -kf.m_speedIn;
1658     curve->setKeyframe(kf);
1659   } else if (action == &deleteKeyframeAction) {
1660     KeyframeSetter::removeKeyframeAt(curve, kf.m_frame);
1661   } else if (action == &insertKeyframeAction) {
1662     KeyframeSetter(curve).createKeyframe(tround(frame));
1663   } else if (action == &activateCycleAction) {
1664     KeyframeSetter::enableCycle(curve, true);
1665   } else if (action == &deactivateCycleAction) {
1666     KeyframeSetter::enableCycle(curve, false);
1667   } else if (action == &setLinearAction)
1668     setSegmentType(getSelection(), curve, segmentIndex,
1669                    TDoubleKeyframe::Linear);
1670   else if (action == &setSpeedInOutAction)
1671     setSegmentType(getSelection(), curve, segmentIndex,
1672                    TDoubleKeyframe::SpeedInOut);
1673   else if (action == &setEaseInOutAction)
1674     setSegmentType(getSelection(), curve, segmentIndex,
1675                    TDoubleKeyframe::EaseInOut);
1676   else if (action == &setEaseInOut2Action)
1677     setSegmentType(getSelection(), curve, segmentIndex,
1678                    TDoubleKeyframe::EaseInOutPercentage);
1679   else if (action == &setExponentialAction)
1680     setSegmentType(getSelection(), curve, segmentIndex,
1681                    TDoubleKeyframe::Exponential);
1682   else if (action == &setExpressionAction)
1683     setSegmentType(getSelection(), curve, segmentIndex,
1684                    TDoubleKeyframe::Expression);
1685   else if (action == &setSimilarShapeAction)
1686     setSegmentType(getSelection(), curve, segmentIndex,
1687                    TDoubleKeyframe::SimilarShape);
1688   else if (action == &setFileAction)
1689     setSegmentType(getSelection(), curve, segmentIndex, TDoubleKeyframe::File);
1690   else if (action == &setConstantAction)
1691     setSegmentType(getSelection(), curve, segmentIndex,
1692                    TDoubleKeyframe::Constant);
1693   else if (action == &fitSelectedAction)
1694     fitSelectedPoints();
1695   else if (action == &fitAllAction)
1696     fitCurve();
1697   else if (action == &setStep1Action)
1698     KeyframeSetter(curve, segmentIndex).setStep(1);
1699   else if (action == &setStep2Action)
1700     KeyframeSetter(curve, segmentIndex).setStep(2);
1701   else if (action == &setStep3Action)
1702     KeyframeSetter(curve, segmentIndex).setStep(3);
1703   else if (action == &setStep4Action)
1704     KeyframeSetter(curve, segmentIndex).setStep(4);
1705 
1706   else if (action == &curveShapeSmoothAction)
1707     m_curveShape = SMOOTH;
1708   else if (action == &curveShapeFrameBasedAction)
1709     m_curveShape = FRAME_BASED;
1710 
1711   update();
1712 }
1713 
1714 //-----------------------------------------------------------------------------
1715 
setFrameHandle(TFrameHandle * frameHandle)1716 void FunctionPanel::setFrameHandle(TFrameHandle *frameHandle) {
1717   if (m_frameHandle == frameHandle) return;
1718   if (m_frameHandle) m_frameHandle->disconnect(this);
1719   m_frameHandle = frameHandle;
1720   if (isVisible() && m_frameHandle) {
1721     connect(m_frameHandle, SIGNAL(frameSwitched()), this,
1722             SLOT(onFrameSwitched()));
1723     update();
1724   }
1725   assert(m_selection);
1726   m_selection->setFrameHandle(frameHandle);
1727 }
1728 
1729 //-----------------------------------------------------------------------------
1730 
showEvent(QShowEvent *)1731 void FunctionPanel::showEvent(QShowEvent *) {
1732   if (m_frameHandle)
1733     connect(m_frameHandle, SIGNAL(frameSwitched()), this,
1734             SLOT(onFrameSwitched()));
1735 }
1736 
1737 //-----------------------------------------------------------------------------
1738 
hideEvent(QHideEvent *)1739 void FunctionPanel::hideEvent(QHideEvent *) {
1740   if (m_frameHandle) m_frameHandle->disconnect(this);
1741 }
1742 
1743 //-----------------------------------------------------------------------------
1744 
onFrameSwitched()1745 void FunctionPanel::onFrameSwitched() { update(); }
1746