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