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 ¢er) {
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