1 /*******************************************************************************
2 **
3 ** Photivo
4 **
5 ** Copyright (C) 2008-2009 Jos De Laender <jos.de_laender@telenet.be>
6 ** Copyright (C) 2009-2012 Michael Munzert <mail@mm-log.com>
7 ** Copyright (C) 2012-2013 Bernd Schoeler <brjohn@brother-john.net>
8 **
9 ** This file is part of Photivo.
10 **
11 ** Photivo is free software: you can redistribute it and/or modify
12 ** it under the terms of the GNU General Public License version 3
13 ** as published by the Free Software Foundation.
14 **
15 ** Photivo is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ** GNU General Public License for more details.
19 **
20 ** You should have received a copy of the GNU General Public License
21 ** along with Photivo.  If not, see <http://www.gnu.org/licenses/>.
22 **
23 *******************************************************************************/
24 #include "ptCurveWindow.h"
25 #include "ptTheme.h"
26 #include "ptInfo.h"
27 #include "ptSettings.h"
28 #include "filters/ptCfgItem.h"
29 #include <QPainter>
30 #include <QActionGroup>
31 #include <QMenu>
32 #include <QTimer>
33 #include <QMouseEvent>
34 #include <QWheelEvent>
35 #include <QLabel>
36 #include <QFileDialog>
37 #include <vector>
38 
39 
40 //------------------------------------------------------------------------------
41 extern QString CurveFilePattern;
42 
43 // How many pixels will be considered as 'bingo' for having the anchor ?
44 const int CSnapDelta = 6;
45 // Percentage to be close to a curve to get a new Anchor
46 const double CCurveDelta = 0.12;
47 // Distance to the next anchor
48 const float CAnchorDelta = 0.005f;
49 // Delays in ms before certain actions are triggered
50 const int CPipeDelay   = 300;
51 
52 //------------------------------------------------------------------------------
53 // NOTE: ptCurveWindow would be a good place to use C++11’s ctor delegation.
54 // Unfortunately it’s only available in GCC 4.7.
ptCurveWindow(QWidget * AParent)55 ptCurveWindow::ptCurveWindow(QWidget *AParent)
56 : ptWidget(AParent)
57 {}
58 
59 //------------------------------------------------------------------------------
ptCurveWindow(const ptCfgItem & ACfgItem,QWidget * AParent)60 ptCurveWindow::ptCurveWindow(const ptCfgItem &ACfgItem, QWidget *AParent)
61 : ptWidget(AParent)
62 {
63   this->init(ACfgItem);
64 }
65 
66 //------------------------------------------------------------------------------
~ptCurveWindow()67 ptCurveWindow::~ptCurveWindow() {
68 /*  Resources managed by Qt parent or external objects. Do not delete manually.
69       all QAction and QActionGroup
70       FCaptionLabel
71       FWheelTimer
72 */
73 }
74 
75 //------------------------------------------------------------------------------
init(const ptCfgItem & ACfgItem)76 void ptCurveWindow::init(const ptCfgItem &ACfgItem) {
77   FCaptionLabel     = nullptr;
78   FWheelTimer       = new QTimer(this);
79   FMouseAction      = NoAction;
80   FMovingAnchor     = -1;
81   FLinearIpolAction = nullptr;
82   FSplineIpolAction = nullptr;
83   FCosineIpolAction = nullptr;
84   FIpolGroup        = nullptr;
85   FByLumaAction     = nullptr;
86   FByChromaAction   = nullptr;
87   FMaskGroup        = nullptr;
88   FOpenCurveAction  = nullptr;
89 
90   // Timer for the wheel interaction
91   FWheelTimer->setSingleShot(1);
92   FWheelTimer->setInterval(CPipeDelay);
93   connect(FWheelTimer, SIGNAL(timeout()), this, SLOT(wheelTimerExpired()));
94 
95   QSizePolicy hPolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
96   hPolicy.setHeightForWidth(true);
97   this->setSizePolicy(hPolicy);
98 
99   this->setObjectName(ACfgItem.Id);  // Do not touch! Filter commonDispatch relies on this.
100   FCurve = ACfgItem.Curve;
101 
102   // set up caption in topleft corner
103   this->setCaption(ACfgItem.Caption);
104 }
105 
106 //------------------------------------------------------------------------------
setValue(const QVariant & AValue)107 void ptCurveWindow::setValue(const QVariant &AValue) {
108   GInfo->Assert(AValue.type() == QVariant::Map,
109                 QString("%1: Value must be of type QVariant::Map (8), but is (%2).")
110                     .arg(this->objectName()).arg(AValue.type()), AT);
111 
112   auto hTempMap = AValue.toMap();
113   FCurve->loadConfig(hTempMap, "");
114   updateView();
115   requestPipeRun();
116 }
117 
118 //------------------------------------------------------------------------------
setCaption(const QString & ACaption)119 void ptCurveWindow::setCaption(const QString &ACaption) {
120   if (ACaption.isEmpty()) {
121     DelAndNull(FCaptionLabel);
122 
123   } else {
124     if (!FCaptionLabel) {
125       FCaptionLabel = new QLabel(this);
126       FCaptionLabel->move(5,5);
127       FCaptionLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
128       FCaptionLabel->setAttribute(Qt::WA_NoSystemBackground, true);
129       FCaptionLabel->setAttribute(Qt::WA_OpaquePaintEvent, false);
130     }
131     FCaptionLabel->setText("<b style='color:#ffffff'>" + ACaption + "</b>");
132   }
133 }
134 
135 //------------------------------------------------------------------------------
setMaskType()136 void ptCurveWindow::setMaskType() {
137   auto hOldMask = FCurve->mask();
138 
139   if (FByLumaAction->isChecked())
140     FCurve->setMask(ptCurve::LumaMask);
141   else if (FByChromaAction->isChecked())
142     FCurve->setMask(ptCurve::ChromaMask);
143 
144   if (hOldMask == FCurve->mask()) return;
145 
146   if (isCyclicCurve()) {
147     // wrap-around/cyclic curve, i.e. left edge = right edge
148     FCurve->anchors()->back().second = FCurve->anchors()->front().second;
149     FCurve->calcCurve();
150   }
151 
152   updateView();
153   requestPipeRun();
154 }
155 
156 //------------------------------------------------------------------------------
setInterpolationType()157 void ptCurveWindow::setInterpolationType() {
158   auto hOldIPol = FCurve->interpolType();
159 
160   if (FLinearIpolAction->isChecked())
161     FCurve->setInterpolType(ptCurve::LinearInterpol);
162   else if (FSplineIpolAction->isChecked())
163     FCurve->setInterpolType(ptCurve::SplineInterpol);
164   else if (FCosineIpolAction->isChecked())
165     FCurve->setInterpolType(ptCurve::CosineInterpol);
166 
167   if (hOldIPol == FCurve->interpolType()) return;
168 
169   updateView();
170   requestPipeRun();
171 }
172 
173 //------------------------------------------------------------------------------
openCurveFile()174 void ptCurveWindow::openCurveFile() {
175   QString CurveFileName = QFileDialog::getOpenFileName(nullptr,
176                                                        QObject::tr("Open Curve"),
177                                                        Settings->GetString("CurvesDirectory"),
178                                                        CurveFilePattern);
179 
180   if (CurveFileName.size() == 0) {
181     return;
182   } else {
183     if (FCurve->readCurveFile(CurveFileName, true) == 0) {
184       updateView();
185       requestPipeRun();
186     }
187   }
188 }
189 
190 //------------------------------------------------------------------------------
setBWGradient(ptImage8 * AImage)191 void ptCurveWindow::setBWGradient(ptImage8* AImage) {
192   int Width  = width();
193   int Height = height();
194 
195   for (uint16_t i=0;i<Width;i++) {
196     int Value = (int)(i/(float)Width*255);
197     for (uint16_t Row = Height-Height/20;
198          Row <= Height-2;
199          Row++) {
200       AImage->image()[Row*Width+i][0] = Value;
201       AImage->image()[Row*Width+i][1] = Value;
202       AImage->image()[Row*Width+i][2] = Value;
203     }
204   }
205 }
206 
207 //------------------------------------------------------------------------------
setBWGammaGradient(ptImage8 * AImage)208 void ptCurveWindow::setBWGammaGradient(ptImage8* AImage) {
209   int Width  = width();
210   int Height = height();
211 
212   for (uint16_t i=0;i<Width;i++) {
213     int Value = (int)(powf(i/(float)Width,0.45f)*255);
214     for (uint16_t Row = Height-Height/20;
215          Row <= Height-2;
216          Row++) {
217       AImage->image()[Row*Width+i][0] = Value;
218       AImage->image()[Row*Width+i][1] = Value;
219       AImage->image()[Row*Width+i][2] = Value;
220     }
221   }
222 }
223 
224 //------------------------------------------------------------------------------
setColorGradient(ptImage8 * AImage)225 void ptCurveWindow::setColorGradient(ptImage8* AImage) {
226   int Width  = width();
227   int Height = height();
228 
229   for (uint16_t i=0;i<Width;i++) {
230     int ValueR = 0;
231     int ValueG = 0;
232     int ValueB = 0;
233     if (i < Width/4) {
234       ValueR = 255;
235       ValueG = (int) (255*i/(Width/4));
236     } else if (i < Width/2) {
237       ValueR = 255-(int) (255*(i-Width/4)/(Width/4));
238       ValueG = 255;
239     } else if (i < 3*Width/4) {
240       ValueG = 255-(int) (255*(i-Width/2)/(Width/4));
241       ValueB = (int) (255*(i-Width/2)/(Width/4));
242     } else if (i < Width) {
243       ValueR = (int) (255*(i-3*Width/4)/(Width/4));
244       ValueB = 255-(int) (255*(i-3*Width/4)/(Width/4));
245     }
246     for (uint16_t Row = Height-Height/20;
247          Row <= Height-2;
248          Row++) {
249       AImage->image()[Row*Width+i][0] = ValueB;
250       AImage->image()[Row*Width+i][1] = ValueG;
251       AImage->image()[Row*Width+i][2] = ValueR;
252     }
253   }
254 }
255 
256 //------------------------------------------------------------------------------
setColorBlocks(const QColor & ATopLeftColor,const QColor & ABottomRightColor)257 void ptCurveWindow::setColorBlocks(const QColor &ATopLeftColor, const QColor &ABottomRightColor) {
258   int hWidth  = this->width();
259   int hHeight = this->height();
260 
261   // topleft color block
262   for (uint16_t i=1;  i < 3*(hHeight-1)/10;  ++i) {
263     for (uint16_t Row = 1;  Row < 2*(hWidth-1)/10+1;  ++Row) {
264       int hImgIdx = Row*hWidth+i;
265       FCanvas.image()[hImgIdx][0] = ATopLeftColor.blue();
266       FCanvas.image()[hImgIdx][1] = ATopLeftColor.green();
267       FCanvas.image()[hImgIdx][2] = ATopLeftColor.red();
268     }
269   }
270 
271   // bottom right color block
272   for (uint16_t i = 7*(hHeight-1)/10+1;  i < hWidth; ++i) {
273     for (uint16_t Row = 8*(hWidth-1)/10+1;  Row < hHeight;  ++Row) {
274       int hImgIdx = Row*hWidth+i;
275       FCanvas.image()[hImgIdx][0] = ABottomRightColor.blue();
276       FCanvas.image()[hImgIdx][1] = ABottomRightColor.green();
277       FCanvas.image()[hImgIdx][2] = ABottomRightColor.red();
278     }
279   }
280 }
281 
282 //------------------------------------------------------------------------------
283 // Calculate the GUI representation of the curve as a ptImage8.
calcCurveImage()284 void ptCurveWindow::calcCurveImage() {
285   // Viewport dimensions are needed very often -> local variables for quicker access.
286   int hWidth  = this->width();
287   int hHeight = this->height();
288 
289   if (hHeight == 0 || hWidth == 0)
290     return;
291 
292   FCanvas.setSize(hWidth, hHeight, 3);
293   FCanvas.fillColor(0, 0, 0, 0);
294 
295   QColor hCurveColor = QColor(200,200,200);
296   QColor hGridColor  = QColor(53,53,53);
297 
298   // paint visual aids: gradient bar, colour blocks
299   switch (FCurve->mask()) {
300     case ptCurve::LumaMask:     setBWGradient(&FCanvas); break;
301     case ptCurve::ChromaMask:   setColorGradient(&FCanvas); break;
302     case ptCurve::GammaMask:    setBWGammaGradient(&FCanvas); break;
303     case ptCurve::AChannelMask: setColorBlocks(QColor(200,50,100), QColor(50,150,50)); break;
304     case ptCurve::BChannelMask: setColorBlocks(QColor(255,255,75), QColor(50,100,200)); break;
305     case ptCurve::NoMask:       // fall through
306     default:                    break; // nothing to do
307   }
308 
309   // paint grid lines producing a 10×10 grid
310   for (uint16_t Count = 0, Row = hHeight-1;
311        Count <= 10;
312        Count++, Row = hHeight-1-Count*(hHeight-1)/10)
313   {
314     uint32_t Temp = Row*hWidth;
315     for (uint16_t i=0;i<hWidth;i++) {
316       FCanvas.image()[Temp][0] = hGridColor.blue();
317       FCanvas.image()[Temp][1] = hGridColor.green();
318       FCanvas.image()[Temp][2] = hGridColor.red();
319       ++Temp;
320     }
321   }
322   for (uint16_t Count = 0, Column = 0;
323        Count <= 10;
324        Count++, Column = Count*(hWidth-1)/10)
325   {
326     uint32_t Temp = Column;
327     for (uint16_t i=0;i<hHeight;i++) {
328       FCanvas.image()[Temp][0] = hGridColor.blue();
329       FCanvas.image()[Temp][1] = hGridColor.green();
330       FCanvas.image()[Temp][2] = hGridColor.red();
331       Temp += hWidth;
332     }
333   }
334 
335   if (!FCurve.get()) return;
336 
337   // Compute curve points. The vector stores the position of the display curve (y value)
338   // for each display x value. Note that coordinates origin is topleft.
339   std::vector<uint16_t> hLocalCurve(hWidth);
340   for (uint16_t LocalX = 0; LocalX < hWidth; ++LocalX) {
341     uint16_t CurveX = (uint16_t)(0.5f + (float)LocalX / (hWidth-1) * 0xffff);
342     hLocalCurve[LocalX] = hHeight-1 - (uint16_t)(0.5f + (float) FCurve->Curve[CurveX]/0xffff * (hHeight-1));
343   }
344 
345   // paint the curve itself
346   for (uint16_t i=0; i<hWidth; i++) {
347     int32_t  Row      = hLocalCurve[i];
348     int32_t  NextRow  = hLocalCurve[(i<(hWidth-1))?i+1:i];
349     uint16_t kStart   = ptMin(Row,NextRow);
350     uint16_t kEnd     = ptMax(Row,NextRow);
351     uint32_t Temp     = i+kStart*hWidth;
352 
353     for(uint16_t k=kStart;k<=kEnd;k++) {
354       FCanvas.image()[Temp][0] = hCurveColor.blue();
355       FCanvas.image()[Temp][1] = hCurveColor.green();
356       FCanvas.image()[Temp][2] = hCurveColor.red();
357       Temp += hWidth;
358     }
359   }
360 
361   // paint anchors if we have an anchored curve
362   FDisplayAnchors.clear();
363   if (FCurve->curveType() == ptCurve::AnchorType) {
364     for (auto hAnchor: *FCurve->anchors()) {
365       int XSpot = 0.5 + hAnchor.first*(hWidth-1);
366       int YSpot = 0.5 + hHeight-1 - hAnchor.second*(hHeight-1);
367       FDisplayAnchors.push_back(TScreenAnchor(XSpot, YSpot));  // Remember anchors for fast UI access later
368 
369       for (int32_t Row = YSpot-3; Row < YSpot+4; ++Row) {
370         if (Row >= hHeight) continue;
371         if (Row <  0)       continue;
372         for (int32_t Column = XSpot-3; Column < XSpot+4; ++Column) {
373            if (Column >= hWidth) continue;
374            if (Column <  0)      continue;
375            int hImgIdx = Row*hWidth+Column;
376            FCanvas.image()[hImgIdx][0] = hCurveColor.blue();
377            FCanvas.image()[hImgIdx][1] = hCurveColor.green();
378            FCanvas.image()[hImgIdx][2] = hCurveColor.red();
379         }
380       }
381     }
382   }
383 }
384 
385 //------------------------------------------------------------------------------
386 /*! This is an overloaded function. Assigns a new \c ptCurve object to the curve window,
387     then recalcs the curve window image and repaints the viewport. Does *not* trigger a pipe run.
388  */
updateView(const std::shared_ptr<ptCurve> ANewCurve)389 void ptCurveWindow::updateView(const std::shared_ptr<ptCurve> ANewCurve) {
390   FCurve = ANewCurve;
391   updateView();
392 }
393 
394 //------------------------------------------------------------------------------
395 /*! Recalcs the curve window image and repaints the viewport. Does *not* trigger a pipe run. */
updateView()396 void ptCurveWindow::updateView() {
397   calcCurveImage();
398 
399   // grey out when curve windows is disabled
400   if (!this->isEnabled()) {
401     for (uint16_t j = 0; j < FCanvas.width(); j++) {
402       for (uint16_t i = 0; i < FCanvas.height(); i++) {
403         int hImgIdx = i*FCanvas.width()+j;
404         FCanvas.image()[hImgIdx][0] >>= 1;
405         FCanvas.image()[hImgIdx][1] >>= 1;
406         FCanvas.image()[hImgIdx][2] >>= 1;
407       }
408     }
409   }
410 
411   // Prepare the curve image for display. We take the detour QImage=>QPixmap instead of drawing
412   // the QImage directly because QPixmap has HW accelerated drawing. Unfortunately the internal
413   // layout of ptImage8 is not suited to be loaded into a QPixmap directly.
414   FDisplayImage = QPixmap::fromImage(QImage((const uchar*) FCanvas.image().data(),
415                                             FCanvas.width(),
416                                             FCanvas.height(),
417                                             QImage::Format_RGB32));
418   repaint();
419 }
420 
421 //------------------------------------------------------------------------------
resizeEvent(QResizeEvent *)422 void ptCurveWindow::resizeEvent(QResizeEvent *) {
423   updateView();
424 }
425 
426 //------------------------------------------------------------------------------
changeEvent(QEvent * Event)427 void ptCurveWindow::changeEvent(QEvent* Event) {
428   // react on enable/disable
429   if (Event->type() == QEvent::EnabledChange)
430     updateView();   // No pipe request!
431 }
432 
433 //------------------------------------------------------------------------------
paintEvent(QPaintEvent *)434 void ptCurveWindow::paintEvent(QPaintEvent*) {
435   QPainter Painter(this);
436   Painter.drawPixmap(0, 0, FDisplayImage);
437 }
438 
439 //------------------------------------------------------------------------------
mousePressEvent(QMouseEvent * AEvent)440 void ptCurveWindow::mousePressEvent(QMouseEvent *AEvent) {
441   if (!FCurve) return;
442 
443   if (FMouseAction != NoAction) return;
444 
445   FMouseAction  = NoAction;
446   FMovingAnchor = -1;
447 
448   // only handle curves with minimum 2 anchors.
449   if ((FCurve->anchorCount() < 2) || (FCurve->curveType() == ptCurve::FullPrecalcType))
450     return;
451 
452   int hCaughtIdx = hasCaughtAnchor(AEvent->pos());
453   if (hCaughtIdx > -1) {
454     // mouse caught one of the anchors
455     if (AEvent->buttons() == Qt::LeftButton) {
456       // Initialize anchor dragging
457       FMouseAction  = DragAction;
458       FMovingAnchor = hCaughtIdx;
459       return;
460     }
461 
462     if ((AEvent->buttons() == Qt::RightButton) && (FCurve->anchorCount() > 2)) {
463       // Delete if still more than 2 anchors
464       FMouseAction = DeleteAction;
465       // std::vector can only erase element via an iterator :(
466       FCurve->anchors()->erase(FCurve->anchors()->begin() + hCaughtIdx);
467 
468       // handle wrap-around curves
469       if (isCyclicCurve()) {
470         if (hCaughtIdx == 0) {
471           FCurve->anchors()->front().second = FCurve->anchors()->back().second;
472         } else if (hCaughtIdx == FCurve->anchorCount()) {
473           FCurve->anchors()->back().second = FCurve->anchors()->front().second;
474         }
475       }
476 
477       FCurve->calcCurve();
478       return;
479     }
480   }
481 
482   if ((fabs(((double)FCurve->Curve[(uint16_t)((double)AEvent->x()/(double)width()*0xffff)]
483             / (double)0xffff) - (((double)height()-(double)AEvent->y())/(double)height()))
484        < CCurveDelta) && (AEvent->button() == Qt::RightButton))
485   {
486     // Insert a new anchor (Right mouse not on an anchor but close to the curve).
487     FMouseAction   = InsertAction;
488     int hInsertIdx = FDisplayAnchors.size();
489     for (size_t i = 0; i < FDisplayAnchors.size(); ++i) {
490       if (AEvent->x() < FDisplayAnchors.at(i).first) {
491         hInsertIdx = i;
492         break;
493       }
494     }
495 
496     double hNewX = (double)AEvent->x() / (double)this->width();
497     double hNewY = (double)FCurve->Curve[(uint16_t)(hNewX*0xffff)] / (double)0xffff;
498     FCurve->anchors()->insert(FCurve->anchors()->begin()+hInsertIdx, TAnchor(hNewX, hNewY));
499     FCurve->calcCurve();
500 
501   } else if (AEvent->button() == Qt::RightButton) {
502     // right click outside curve/anchor => context menu
503     execContextMenu(AEvent->globalPos());
504   }
505 }
506 
507 //------------------------------------------------------------------------------
mouseReleaseEvent(QMouseEvent *)508 void ptCurveWindow::mouseReleaseEvent(QMouseEvent*) {
509   if (!FCurve) return;
510   if (FMouseAction == NoAction) return;
511 
512   if (FMouseAction != DragAction)  // for drag updating is done in move event
513     updateView();
514 
515   FMouseAction  = NoAction;
516   FMovingAnchor = -1;
517 
518   requestPipeRun();
519 }
520 
521 //------------------------------------------------------------------------------
clampMovingAnchor(const TAnchor & APoint,const QPoint & AMousePos)522 TAnchor ptCurveWindow::clampMovingAnchor(const TAnchor &APoint,
523                                          const QPoint &AMousePos)
524 {
525   auto   hMaxX = (double)(this->width()-1);
526   double hNewX = APoint.first;
527 
528   if (FMovingAnchor == 0) {
529     if (AMousePos.x() >= FDisplayAnchors[1].first) {
530       hNewX = (FDisplayAnchors[1].first / hMaxX) - CAnchorDelta;
531     }
532 
533   } else if (FMovingAnchor == FCurve->anchorCount()-1)  {
534     if (AMousePos.x() <= FDisplayAnchors[FCurve->anchorCount()-2].first) {
535       hNewX = (FDisplayAnchors[FCurve->anchorCount()-1].first / hMaxX) + CAnchorDelta;
536     }
537 
538   } else if (AMousePos.x() >= FDisplayAnchors[FMovingAnchor+1].first) {
539     hNewX = (FDisplayAnchors[FMovingAnchor+1].first / hMaxX) - CAnchorDelta;
540 
541   } else if (AMousePos.x() <= FDisplayAnchors[FMovingAnchor-1].first) {
542     hNewX = (FDisplayAnchors[FMovingAnchor-1].first / hMaxX) + CAnchorDelta;
543   }
544 
545   return TAnchor(ptBound(0.0, hNewX,         1.0),
546                  ptBound(0.0, APoint.second, 1.0));
547 }
548 
549 //------------------------------------------------------------------------------
mouseMoveEvent(QMouseEvent * AEvent)550 void ptCurveWindow::mouseMoveEvent(QMouseEvent *AEvent) {
551   if (!FCurve) return;
552   if (FMouseAction == DragAction && FMovingAnchor > -1) {
553     // mouse position normalised and clamped to (0.0-1.0) and inverted y axis = curve coordinates
554     TAnchor hNormPos(AEvent->x()/(double)(this->width()-1),
555                               1.0 - AEvent->y()/(double)(this->height()-1) );
556 
557     // Handle mouse out of range X and Y coordinates
558     hNormPos = clampMovingAnchor(hNormPos, AEvent->pos());
559 
560     (*FCurve->anchors())[FMovingAnchor] = hNormPos;
561 
562     // handle wrap-around curves
563     if (isCyclicCurve()) {
564       if (FMovingAnchor == 0)
565         FCurve->setAnchorY(FCurve->anchorCount()-1, hNormPos.second);
566       else if (FMovingAnchor == FCurve->anchorCount()-1)
567         FCurve->setAnchorY(0, hNormPos.second);
568     }
569 
570     FCurve->calcCurve();
571     updateView();
572   }
573 }
574 
575 //------------------------------------------------------------------------------
wheelTimerExpired()576 void ptCurveWindow::wheelTimerExpired() {
577   FMouseAction  = NoAction;
578   FMovingAnchor = -1;
579   requestPipeRun();
580 }
581 
582 //------------------------------------------------------------------------------
hasCaughtAnchor(const QPoint APos)583 int ptCurveWindow::hasCaughtAnchor(const QPoint APos) {
584   int hResult = -1;
585 
586   int i = 0;
587   for (TScreenAnchor hAnchor: FDisplayAnchors) {
588     if ((abs(hAnchor.first  - APos.x()) < CSnapDelta) &&   // snap on x axis
589         (abs(hAnchor.second - APos.y()) < CSnapDelta))     // snap on y axis
590     {
591       hResult = i;
592       break;
593     }
594     ++i;
595   }
596 
597   return hResult;
598 }
599 
600 //------------------------------------------------------------------------------
isCyclicCurve()601 bool ptCurveWindow::isCyclicCurve() {
602   return FCurve->mask() == ptCurve::ChromaMask;
603 }
604 
605 //------------------------------------------------------------------------------
requestPipeRun()606 void ptCurveWindow::requestPipeRun() {
607   emit valueChanged(this->objectName(), FCurve->storeConfig(""));
608 }
609 
610 //------------------------------------------------------------------------------
execContextMenu(const QPoint APos)611 void ptCurveWindow::execContextMenu(const QPoint APos) {
612   createMenuActions();
613 
614   // build the menu entries
615   QMenu hMenu(nullptr);
616   hMenu.setPalette(Theme->menuPalette());
617   hMenu.setStyle(Theme->style());
618 
619   hMenu.addActions(FIpolGroup->actions());
620   switch (FCurve->interpolType()) {
621     case ptCurve::LinearInterpol: FLinearIpolAction->setChecked(true); break;
622     case ptCurve::SplineInterpol: FSplineIpolAction->setChecked(true); break;
623     case ptCurve::CosineInterpol: FCosineIpolAction->setChecked(true); break;
624   default:
625     GInfo->Raise(QString("Unhandled curve interpolation type: ") + QString::number((int)FCurve->interpolType()), AT);
626   }
627 
628   if (FCurve->supportedMasks() == (ptCurve::LumaMask | ptCurve::ChromaMask)) {
629     hMenu.addSeparator();
630     hMenu.addActions(FMaskGroup->actions());
631     switch (FCurve->mask()) {
632       case ptCurve::LumaMask: FByLumaAction->setChecked(true); break;
633       case ptCurve::ChromaMask: FByChromaAction->setChecked(true); break;
634     default:
635       GInfo->Raise(QString("Unhandled curve mask type: ") + QString::number((int)FCurve->mask()), AT);
636     }
637   }
638 
639   hMenu.addSeparator();
640   hMenu.addAction(FOpenCurveAction);
641 
642   hMenu.exec(APos);
643 }
644 
645 //------------------------------------------------------------------------------
createMenuActions()646 void ptCurveWindow::createMenuActions() {
647   if (FByLumaAction) return;  // actions already created
648 
649   // Mask type group
650   FByLumaAction = new QAction(tr("L&uminance mask"), this);
651   FByLumaAction->setCheckable(true);
652   connect(FByLumaAction, SIGNAL(triggered()), this, SLOT(setMaskType()));
653 
654   FByChromaAction = new QAction(tr("C&olor mask"), this);
655   FByChromaAction->setCheckable(true);
656   connect(FByChromaAction, SIGNAL(triggered()), this, SLOT(setMaskType()));
657 
658   FMaskGroup = new QActionGroup(this);
659   FMaskGroup->addAction(FByLumaAction);
660   FMaskGroup->addAction(FByChromaAction);
661 
662   // Interpolation type group
663   FLinearIpolAction = new QAction(tr("&Linear"), this);
664   FLinearIpolAction->setStatusTip(tr("Linear interpolation"));
665   FLinearIpolAction->setCheckable(true);
666   connect(FLinearIpolAction, SIGNAL(triggered()), this, SLOT(setInterpolationType()));
667 
668   FSplineIpolAction = new QAction(tr("&Spline"), this);
669   FSplineIpolAction->setStatusTip(tr("Spline interpolation"));
670   FSplineIpolAction->setCheckable(true);
671 
672   connect(FSplineIpolAction, SIGNAL(triggered()), this, SLOT(setInterpolationType()));
673   FCosineIpolAction = new QAction(tr("&Cosine"), this);
674   FCosineIpolAction->setStatusTip(tr("Cosine interpolation"));
675   FCosineIpolAction->setCheckable(true);
676   connect(FCosineIpolAction, SIGNAL(triggered()), this, SLOT(setInterpolationType()));
677 
678   FIpolGroup = new QActionGroup(this);
679   FIpolGroup->addAction(FLinearIpolAction);
680   FIpolGroup->addAction(FSplineIpolAction);
681   FIpolGroup->addAction(FCosineIpolAction);
682 
683   FOpenCurveAction = new QAction(tr("Open &file"), this);
684   FOpenCurveAction->setStatusTip(tr("Open anchor curve file"));
685   connect(FOpenCurveAction, SIGNAL(triggered()), this, SLOT(openCurveFile()));
686 }
687 
688