1 /*******************************************************************************
2 **
3 ** Photivo
4 **
5 ** Copyright (C) 2009-2011 Michael Munzert <mail@mm-log.com>
6 ** Copyright (C) 2011 Bernd Schoeler <brjohn@brother-john.net>
7 ** Copyright (C) 2013 Alexander Tzyganenko <tz@fast-report.com>
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 
25 #include "ptDefines.h"
26 #include "ptSettings.h"
27 #include "ptTheme.h"
28 #include "ptViewWindow.h"
29 #include "ptConstants.h"
30 #include "ptImage.h"
31 
32 #include <QScrollBar>
33 
34 #include <cassert>
35 
36 extern ptTheme* Theme;
37 
38 //==============================================================================
39 
ptViewWindow(QWidget * Parent,ptMainWindow * mainWin)40 ptViewWindow::ptViewWindow(QWidget* Parent, ptMainWindow* mainWin)
41 : QGraphicsView(Parent),
42   // constants
43   MinZoom(0.05), MaxZoom(4.0),
44   // member variables
45   FCtrlIsPressed(0),
46   FInteractionHasMousePress(false),
47   FCurrentInteraction(nullptr),
48   FDrawLine(nullptr),
49   FSpotTuning(nullptr),
50   FSelectRect(nullptr),
51   FCrop(nullptr),
52   FInteraction(iaNone),
53   FLeftMousePressed(0),
54   FZoomIsSaved(0),
55   FZoomFactor(1.0),
56   FZoomFactorSav(0.0),
57   FZoomModeSav(ptZoomMode_Fit),
58   FPixelReading(prNone),
59   // always keep this at the end of the initializer list
60   FMainWindow(mainWin)
61 {
62   assert(FMainWindow != NULL);    // Main window must exist before view window
63   assert(Theme != NULL);
64   assert(Settings != NULL);
65 
66   ZoomFactors << MinZoom << 0.08 << 0.10 << 0.15 << 0.20 << 0.25 << 0.33 << 0.50 << 0.66 << 1.00
67               << 1.50 << 2.00 << 3.00 << MaxZoom;
68 
69   FDragDelta       = new QLine();
70   FStatusOverlay   = new ptReportOverlay(this, "", QColor(),           QColor(),
71                                          0,    Qt::AlignLeft,  20);
72   FZoomSizeOverlay = new ptReportOverlay(this, "", QColor(75,150,255), QColor(190,220,255),
73                                          1000, Qt::AlignRight, 20);
74 
75   setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
76   setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
77   setMouseTracking(true);   // Move events without pressed button. Needed for crop cursor change.
78 
79   // Layout to always fill the complete image pane with ViewWindow
80   QGridLayout* Layout = new QGridLayout(Parent);
81   Layout->setContentsMargins(0,0,0,0);
82   Layout->setSpacing(0);
83   Layout->addWidget(this);
84   this->setStyleSheet("QGraphicsView { border: none; }");
85 
86   // Init the scene
87   FImageScene = new QGraphicsScene(0, 0, 0, 0, Parent);
88   F8bitImageItem = FImageScene->addPixmap(QPixmap());
89   F8bitImageItem->setPos(0, 0);
90   this->setScene(FImageScene);
91 
92   FGrid = new ptGridInteraction(this);  // scene must already be alive
93   FPixelReader = NULL;
94   ConstructContextMenu();
95 
96   FPReadTimer = new QTimer();
97   FPReadTimer->setSingleShot(true);
98 }
99 
100 //==============================================================================
101 
~ptViewWindow()102 ptViewWindow::~ptViewWindow() {
103   DelAndNull(FDragDelta);
104   DelAndNull(FStatusOverlay);
105   DelAndNull(FZoomSizeOverlay);
106   DelAndNull(FDrawLine);
107   DelAndNull(FSelectRect);
108   DelAndNull(FGrid);
109 
110   DelAndNull(ac_PRead_None);
111   DelAndNull(ac_PRead_Linear);
112   DelAndNull(ac_PRead_Preview);
113   DelAndNull(ac_PReadGroup);
114 
115   DelAndNull(FPReadTimer);
116 }
117 
118 //==============================================================================
119 
120 // Convert a 16bit ptImage to an 8bit QPixmap. Mind R<->B. Also update the
121 // graphics scene and the viewport.
UpdateImage(const ptImage * relatedImage)122 void ptViewWindow::UpdateImage(const ptImage* relatedImage) {
123   if (relatedImage) {
124     this->blockSignals(1);
125     QImage* Img8bit = new QImage(relatedImage->m_Width, relatedImage->m_Height, QImage::Format_RGB32);
126     uint32_t Size = relatedImage->m_Height * relatedImage->m_Width;
127     uint32_t* ImagePtr = (QRgb*) Img8bit->scanLine(0);
128 
129 #pragma omp parallel for schedule(static)
130     for (uint32_t i = 0; i < Size; i++) {
131       uint8_t* Pixel = (uint8_t*) ImagePtr + (i<<2);
132       for (short c=0; c<3; c++) {
133         // Mind the R<->B swap !
134         Pixel[2-c] = relatedImage->m_Image[i][c]>>8;
135       }
136       Pixel[3] = 0xff;
137     }
138 
139     F8bitImageItem->setPixmap(QPixmap::fromImage(*Img8bit, Qt::ColorOnly));
140     DelAndNull(Img8bit);
141     FImageScene->setSceneRect(0, 0,
142                               F8bitImageItem->pixmap().width(),
143                               F8bitImageItem->pixmap().height());
144 
145     if (Settings->GetInt("ZoomMode") == ptZoomMode_Fit) {
146       ZoomToFit(0);
147     }
148 
149     this->blockSignals(0);
150   }
151 }
152 
153 //==============================================================================
154 
155 // ZoomTo() is also called by wheelEvent() for mouse wheel zoom.
ZoomTo(float factor)156 void ptViewWindow::ZoomTo(float factor) {
157   Settings->SetValue("ZoomMode",ptZoomMode_NonFit);
158   factor = qBound(MinZoom, factor, MaxZoom);
159 
160   if(((uint)(factor * 10000) % 10000) < 1) {
161     // nearest neighbour resize for 200%, 300%, 400% zoom
162     F8bitImageItem->setTransformationMode(Qt::FastTransformation);
163   } else {
164     // bilinear resize for all others
165     F8bitImageItem->setTransformationMode(Qt::SmoothTransformation);
166   }
167   setTransform(QTransform(factor, 0, 0, factor, 0, 0));
168 
169   FZoomFactor = transform().m11();
170   int z = qRound(FZoomFactor * 100);
171   Settings->SetValue("Zoom", z);
172   FZoomSizeOverlay->exec(QString::number(z) + "%");
173 }
174 
175 //==============================================================================
176 
ZoomToFit(const short withMsg)177 int ptViewWindow::ZoomToFit(const short withMsg /*= 1*/) {
178   Settings->SetValue("ZoomMode",ptZoomMode_Fit);
179 
180   if (!F8bitImageItem->pixmap().isNull()) {
181     // Always smooth scaling because we don't know the zoom factor in advance.
182     F8bitImageItem->setTransformationMode(Qt::SmoothTransformation);
183     fitInView(F8bitImageItem, Qt::KeepAspectRatio);
184     FZoomFactor = transform().m11();
185 
186     if (withMsg) {
187       FZoomSizeOverlay->exec(tr("Fit"));
188     }
189   }
190 
191   Settings->SetValue("Zoom",qRound(FZoomFactor * 100));
192   return FZoomFactor;
193 }
194 
195 //==============================================================================
196 
ZoomStep(int direction)197 void ptViewWindow::ZoomStep(int direction) {
198   int ZoomIdx = -1;
199 
200   // zoom larger
201   if (direction > 0) {
202     for (int i = 0; i < ZoomFactors.size(); i++) {
203       if (ZoomFactors[i] > FZoomFactor) {
204         ZoomIdx = i;
205         break;
206       }
207     }
208 
209   // zoom smaller
210   } else if (direction < 0) {
211     for (int i = ZoomFactors.size() - 1; i >= 0; i--) {
212       if (ZoomFactors[i] < FZoomFactor) {
213         ZoomIdx = i;
214         break;
215       }
216     }
217   }
218 
219   if (ZoomIdx != -1) {
220     ZoomTo(ZoomFactors[ZoomIdx]);
221   }
222 }
223 
224 //==============================================================================
225 
RestoreZoom()226 void ptViewWindow::RestoreZoom() {
227   if (FZoomIsSaved) {
228     if (FZoomModeSav == ptZoomMode_Fit) {
229       ZoomToFit();
230     } else {
231       ZoomTo(FZoomFactorSav);
232     }
233 
234     FZoomIsSaved = 0;
235   }
236 }
237 
238 //==============================================================================
239 
SaveZoom()240 void ptViewWindow::SaveZoom() {
241   FZoomFactorSav = FZoomFactor;
242   FZoomModeSav = Settings->GetValue("ZoomMode").toInt();
243   FZoomIsSaved = 1;
244 }
245 
246 //==============================================================================
247 
setGrid(const short enabled,const uint linesX,const uint linesY)248 void ptViewWindow::setGrid(const short enabled, const uint linesX, const uint linesY) {
249   if (enabled) {
250     FGrid->show(linesX, linesY);
251   } else {
252     FGrid->hide();
253   }
254 }
255 
256 //==============================================================================
257 
paintEvent(QPaintEvent * event)258 void ptViewWindow::paintEvent(QPaintEvent* event) {
259   // Fill viewport with background colour
260   QPainter Painter(viewport());
261   Painter.fillRect(0, 0, viewport()->width(), viewport()->height(), palette().color(QPalette::Window));
262   Painter.end();
263 
264   // takes care of updating the scene
265   QGraphicsView::paintEvent(event);
266 }
267 
268 //==============================================================================
269 
mousePressEvent(QMouseEvent * event)270 void ptViewWindow::mousePressEvent(QMouseEvent* event) {
271   if (event->button() == Qt::LeftButton) {
272     event->accept();
273     FLeftMousePressed = true;
274     FDragDelta->setPoints(event->pos(), event->pos());
275   }
276 
277   // Broadcast event to possible interaction handlers
278   if (FInteraction != iaNone) {
279     emit mouseChanged(event);
280   }
281 }
282 
283 //==============================================================================
284 
mouseReleaseEvent(QMouseEvent * event)285 void ptViewWindow::mouseReleaseEvent(QMouseEvent* event) {
286   if (event->button() == Qt::LeftButton && FLeftMousePressed) {
287     FLeftMousePressed = false;
288     event->accept();
289   } else {
290     event->ignore();
291   }
292 
293   // Broadcast event to possible interaction handlers
294   if (FInteraction != iaNone) {
295     emit mouseChanged(event);
296   }
297 }
298 
299 //==============================================================================
300 
mouseDoubleClickEvent(QMouseEvent * event)301 void ptViewWindow::mouseDoubleClickEvent(QMouseEvent* event) {
302   // Broadcast event to possible interaction handlers
303   if (FInteraction != iaNone) {
304     emit mouseChanged(event);
305   } else {
306     event->ignore();
307   }
308 }
309 
310 //==============================================================================
311 
mouseMoveEvent(QMouseEvent * event)312 void ptViewWindow::mouseMoveEvent(QMouseEvent* event) {
313   // We broadcast the pixel location
314   if (FPixelReading != prNone && !FPReadTimer->isActive()) {
315     FPReadTimer->start(10);
316     QPointF Point = mapToScene(event->pos());
317     if (Point.x() >= 0 && Point.x() < F8bitImageItem->pixmap().width() &&
318         Point.y() >= 0 && Point.y() < F8bitImageItem->pixmap().height()) {
319       if (FPixelReader) FPixelReader(Point, FPixelReading);
320     } else {
321       if (FPixelReader) FPixelReader(Point, prNone);
322     }
323   }
324 
325   if (this->isImgDragging()) {
326     // drag move visible image area
327     FDragDelta->setP2(event->pos());
328 
329     horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
330                                     FDragDelta->x2() +
331                                     FDragDelta->x1());
332     verticalScrollBar()->setValue(verticalScrollBar()->value() -
333                                   FDragDelta->y2() +
334                                   FDragDelta->y1());
335     FDragDelta->setP1(event->pos());
336     event->accept();
337 
338   } else {
339     event->ignore();
340     // Broadcast event to possible interaction handlers
341     if (FInteraction != iaNone) {
342       emit mouseChanged(event);
343     }
344   }
345 }
346 
347 //==============================================================================
348 
wheelEvent(QWheelEvent * event)349 void ptViewWindow::wheelEvent(QWheelEvent* event) {
350   ZoomStep(event->delta());
351 }
352 
353 //==============================================================================
354 
leaveEvent(QEvent *)355 void ptViewWindow::leaveEvent(QEvent*) {
356   if (FPixelReader)
357     FPixelReader(QPoint(), prNone);
358 
359   // When mouse cursor leaves the viewwindow ctrl key should not be handled anymore.
360   // Reset related stuff to avoid problems when re-entering viewwindow. Qt mouse grabbing
361   // makes sure this event does not fire accidentally when the cursor leaves the window
362   // while dragging.
363   if (FCtrlIsPressed > 0){
364     FCtrlIsPressed = 0;
365     this->setCursor(Qt::ArrowCursor);
366   }
367 }
368 
369 //==============================================================================
370 
isImgDragging()371 bool ptViewWindow::isImgDragging() {
372   bool hResult = FLeftMousePressed;
373 
374   // Handle special cases first
375   if (FInteraction != iaNone) {
376     if (FCurrentInteraction->mouseActions().testFlag(maDragDrop)) {
377       // Interaction handles d&d. Test if Ctrl key is available
378       if (FCurrentInteraction->modifiers().testFlag(Qt::ControlModifier)) {
379         // Interaction handles d&d and Ctrl key. Dragging image not possible.
380         return false;
381       } else {
382         // image dragging by Ctrl+Drag
383         hResult = FLeftMousePressed && FCtrlIsPressed;
384         if (hResult) FCurrentInteraction->abortMouseAction(maClick);
385       }
386 
387     } else {
388       if (hResult) FCurrentInteraction->abortMouseAction(maClick);
389     }
390   }
391 
392   // usual case: no interaction or interaction doesn’t handle d&d
393   return hResult;
394 }
395 
396 //==============================================================================
397 
keyPressEvent(QKeyEvent * event)398 void ptViewWindow::keyPressEvent(QKeyEvent* event) {
399   // FCtrlIsPressed is not a simple bool flag to account for keyboards with
400   // multiple ctrl keys. There's a lot of those. ;-) For each ctrl press the
401   // variable is increased, for each release it's decreased. This is necessary
402   // because in cases like press left ctrl - press right ctrl - release left ctrl
403   // Photivo should still recognise the ctrl key as being held down.
404   if (event->key() == Qt::Key_Control) {
405     FCtrlIsPressed++;
406     if (FInteraction == iaNone || FInteraction == iaCrop || FInteraction == iaSpotRepair) {
407       setCursor(Qt::ClosedHandCursor);
408     }
409   } else {
410     event->ignore();  // necessary to forward unhandled keys to main window
411   }
412 
413   if (FInteraction != iaNone) {
414     emit keyChanged(event);
415   }
416 }
417 
418 //==============================================================================
419 
keyReleaseEvent(QKeyEvent * event)420 void ptViewWindow::keyReleaseEvent(QKeyEvent* event) {
421   if (event->key() == Qt::Key_Control) {
422     if (FCtrlIsPressed > 0)
423       --FCtrlIsPressed;
424 
425     if (FCtrlIsPressed == 0 &&
426       (FInteraction == iaNone || FInteraction == iaCrop || FInteraction == iaSpotRepair))
427     {
428       this->setCursor(Qt::ArrowCursor);
429     }
430   } else {
431     event->ignore();  // necessary to forward unhandled keys to main window
432   }
433 
434   if (FInteraction != iaNone) {
435     emit keyChanged(event);
436   }
437 }
438 
439 //==============================================================================
440 // The two functions are necessary to enable d&d over the view window.
441 // The actual d&d action is handled by the resp. main window events.
442 
dragEnterEvent(QDragEnterEvent * event)443 void ptViewWindow::dragEnterEvent(QDragEnterEvent* event) {
444   event->ignore();
445 }
446 
dropEvent(QDropEvent * event)447 void ptViewWindow::dropEvent(QDropEvent* event) {
448   event->ignore();
449 }
450 
451 //==============================================================================
452 
resizeEvent(QResizeEvent * event)453 void ptViewWindow::resizeEvent(QResizeEvent* event) {
454   if (Settings->GetInt("ZoomMode") == ptZoomMode_Fit) {
455     event->accept();
456     ZoomToFit(0);
457   } else {
458     // takes care of positioning the scene inside the viewport on non-fit zooms
459     QGraphicsView::resizeEvent(event);
460   }
461 }
462 
463 //==============================================================================
464 
465 // Top left corner overlay for the processing status
ShowStatus(short mode)466 void ptViewWindow::ShowStatus(short mode) {
467   switch (mode) {
468     case ptStatus_Done:
469       FStatusOverlay->setColors(QColor(0,130,0), QColor(120,170,120));   // green
470       FStatusOverlay->setDuration(1500);
471       FStatusOverlay->exec(QObject::tr("Done"));
472       break;
473 
474     case ptStatus_Updating:
475       FStatusOverlay->setColors(QColor(255,140,0), QColor(255,200,120));   // orange
476       FStatusOverlay->setDuration(0);
477       FStatusOverlay->exec(QObject::tr("Updating"));
478       break;
479 
480     case ptStatus_Processing:
481       FStatusOverlay->setColors(QColor(255,75,75), QColor(255,190,190));   // red
482       FStatusOverlay->setDuration(0);
483       FStatusOverlay->exec(QObject::tr("Processing"));
484       break;
485 
486     default:    // should not happen
487       FStatusOverlay->stop();
488       break;
489   }
490 }
491 
492 //==============================================================================
493 
ShowStatus(const QString text)494 void ptViewWindow::ShowStatus(const QString text) {
495   FStatusOverlay->setColors(QColor(75,150,255), QColor(190,220,255));    // blue
496   FStatusOverlay->setDuration(1500);
497   FStatusOverlay->exec(text);
498 }
499 
500 //==============================================================================
501 
502 // Start draw line interaction to determine rotation angle.
StartLine()503 void ptViewWindow::StartLine() {
504   if (FInteraction == iaNone) {
505     FDrawLine = new ptLineInteraction(this);
506     connect(FDrawLine, SIGNAL(finished(ptStatus)), this, SLOT(finishInteraction(ptStatus)));
507     connect(this, SIGNAL(mouseChanged(QMouseEvent*)), FDrawLine, SLOT(mouseAction(QMouseEvent*)));
508     connect(this, SIGNAL(keyChanged(QKeyEvent*)), FDrawLine, SLOT(keyAction(QKeyEvent*)));
509     FInteraction = iaDrawLine;
510     FCurrentInteraction = FDrawLine;
511   }
512 }
513 
514 //==============================================================================
515 
516 // Start simple selection interaction for spot WB and histogram "crop".
StartSimpleRect(void (* CB_SimpleRect)(const ptStatus,QRect))517 void ptViewWindow::StartSimpleRect(void (*CB_SimpleRect)(const ptStatus, QRect)) {
518   if (FInteraction == iaNone) {
519     assert(CB_SimpleRect != NULL);
520     FCB_SimpleRect = CB_SimpleRect;
521     FSelectRect = new ptSimpleRectInteraction(this);
522 
523     connect(FSelectRect, SIGNAL(finished(ptStatus)),
524             this,        SLOT  (finishInteraction(ptStatus)));
525     connect(this,        SIGNAL(mouseChanged(QMouseEvent*)),
526             FSelectRect, SLOT  (mouseAction(QMouseEvent*)));
527     connect(this,        SIGNAL(keyChanged(QKeyEvent*)),
528             FSelectRect, SLOT  (keyAction(QKeyEvent*)));
529 
530     FInteraction = iaSelectRect;
531     FCurrentInteraction = FSelectRect;
532   }
533 }
534 
535 //==============================================================================
536 
StartCrop()537 void ptViewWindow::StartCrop()
538 {
539   if (FInteraction != iaNone) {
540     return;
541   }
542 
543   // Get crop rect values from settings. The bit shift converts from 1:1 to
544   // current pipe size.
545   int x = Settings->GetInt("CropX") >> Settings->GetInt("Scaled");
546   int y = Settings->GetInt("CropY") >> Settings->GetInt("Scaled");
547   int width = Settings->GetInt("CropW") >> Settings->GetInt("Scaled");
548   int height = Settings->GetInt("CropH") >> Settings->GetInt("Scaled");
549 
550   // If any value is outside the allowed range reset the inital crop
551   // rectangle to the whole image.
552   if(x < 0 || x >= F8bitImageItem->pixmap().width() ||
553      y < 0 || y >= F8bitImageItem->pixmap().height() ||
554      width <= 0 || width > F8bitImageItem->pixmap().width() ||
555      height <= 0 || height > F8bitImageItem->pixmap().height() )
556   {
557     x = 0;
558     y = 0;
559     width = F8bitImageItem->pixmap().width();
560     height = F8bitImageItem->pixmap().height();
561   }
562 
563 
564   FCrop = new ptRichRectInteraction(this, x, y, width, height,
565                                      Settings->GetInt("FixedAspectRatio"),
566                                      Settings->GetInt("AspectRatioW"),
567                                      Settings->GetInt("AspectRatioH"),
568                                      Settings->GetInt("CropGuidelines") );
569 
570   connect(FCrop, SIGNAL(finished(ptStatus)),
571           this,  SLOT  (finishInteraction(ptStatus)));
572   connect(this,  SIGNAL(mouseChanged(QMouseEvent*)),
573           FCrop, SLOT  (mouseAction(QMouseEvent*)));
574   connect(this,  SIGNAL(keyChanged(QKeyEvent*)),
575           FCrop, SLOT  (keyAction(QKeyEvent*)));
576 
577   FInteraction = iaCrop;
578   FCurrentInteraction = FCrop;
579 }
580 
581 //==============================================================================
582 
StartLocalAdjust(std::function<void ()> ACleanupFunc)583 void ptViewWindow::StartLocalAdjust(std::function<void()> ACleanupFunc) {
584   if (FInteraction != iaNone) {
585     return;
586   }
587   FSpotTuning = new ptSpotInteraction(this);
588 
589   connect(FSpotTuning, SIGNAL(finished(ptStatus)),
590           this,         SLOT  (finishInteraction(ptStatus)));
591   connect(this,         SIGNAL(mouseChanged(QMouseEvent*)),
592           FSpotTuning, SLOT  (mouseAction(QMouseEvent*)));
593 
594   FInteraction = iaSpotTuning;
595   FSpotTuningCleanupFunc = ACleanupFunc;
596   FCurrentInteraction = FSpotTuning;
597 }
598 
599 //==============================================================================
600 
601 void RotateAngleDetermined(const ptStatus ExitStatus, double RotateAngle);
602 void CleanupAfterCrop(const ptStatus CropStatus, const QRect CropRect);
603 
finishInteraction(ptStatus ExitStatus)604 void ptViewWindow::finishInteraction(ptStatus ExitStatus) {
605   switch (FInteraction) {
606     case iaDrawLine: {
607       double Angle = FDrawLine->angle();
608       DelAndNull(FDrawLine);   // also disconnects all signals/slots
609       FInteraction = iaNone;
610       RotateAngleDetermined(ExitStatus, Angle);
611       break;
612     }
613 
614     case iaSelectRect: {
615       QRect sr = FSelectRect->rect();
616       DelAndNull(FSelectRect);   // also disconnects all signals/slots
617       FInteraction = iaNone;
618       FCB_SimpleRect(ExitStatus, sr);
619       break;
620     }
621 
622     case iaCrop: {
623       QRect cr = FCrop->rect();
624       DelAndNull(FCrop);   // also disconnects all signals/slots
625       FInteraction = iaNone;
626       CleanupAfterCrop(ExitStatus, cr);
627       break;
628     }
629 
630     case iaSpotTuning: {
631       DelAndNull(FSpotTuning);
632       FInteraction = iaNone;
633       FSpotTuningCleanupFunc();
634       break;
635     }
636 
637     default:
638       assert(!"Unknown FInteraction");
639       break;
640   }
641 
642   FCurrentInteraction = nullptr;
643 }
644 
645 //==============================================================================
646 
647 // Convenience function to keep constructor short. Is only called once from the
648 // constructor.
ConstructContextMenu()649 void ptViewWindow::ConstructContextMenu() {
650   // Create actions for context menu
651 
652   ac_Copy = new QAction(tr("Copy settings") + "\t" + tr("Ctrl+Shift+C"), this);
653   connect(ac_Copy, SIGNAL(triggered()), this, SLOT(Menu_Copy()));
654 
655   ac_Paste = new QAction(tr("Paste settings") + "\t" + tr("Ctrl+Shift+V"), this);
656   connect(ac_Paste, SIGNAL(triggered()), this, SLOT(Menu_Paste()));
657 
658   ac_Reset = new QAction(tr("Reset settings") + "\t" + tr("Ctrl+Shift+R"), this);
659   connect(ac_Reset, SIGNAL(triggered()), this, SLOT(Menu_Reset()));
660 
661   ac_UserReset = new QAction(tr("Reset settings to last saved") + "\t" + tr("Ctrl+Shift+U"), this);
662   connect(ac_UserReset, SIGNAL(triggered()), this, SLOT(Menu_UserReset()));
663 
664   ac_ZoomIn = new QAction(tr("Zoom &in") + "\t" + tr("1"), this);
665   ac_ZoomIn->setIcon(QIcon(QString::fromUtf8(":/dark/icons/zoom-in.png")));
666   connect(ac_ZoomIn, SIGNAL(triggered()), this, SLOT(Menu_ZoomIn()));
667 
668   ac_Zoom100 = new QAction(tr("Zoom &100%") + "\t" + tr("2"), this);
669   connect(ac_Zoom100, SIGNAL(triggered()), this, SLOT(Menu_Zoom100()));
670   ac_Zoom100->setIcon(QIcon(QString::fromUtf8(":/dark/icons/zoom-original.png")));
671 
672   ac_ZoomOut = new QAction(tr("Zoom &out") + "\t" + tr("3"), this);
673   ac_ZoomOut->setIcon(QIcon(QString::fromUtf8(":/dark/icons/zoom-out.png")));
674   connect(ac_ZoomOut, SIGNAL(triggered()), this, SLOT(Menu_ZoomOut()));
675 
676   ac_ZoomFit = new QAction(tr("Zoom &fit") + "\t" + tr("4"), this);
677   connect(ac_ZoomFit, SIGNAL(triggered()), this, SLOT(Menu_ZoomFit()));
678   ac_ZoomFit->setIcon(QIcon(QString::fromUtf8(":/dark/icons/zoom-fit.png")));
679 
680 
681   ac_Mode_RGB = new QAction(tr("&RGB") + "\t" + tr("0"), this);
682   ac_Mode_RGB->setCheckable(true);
683   connect(ac_Mode_RGB, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
684 
685   ac_Mode_Structure = new QAction(tr("&Structure") + "\t" + tr("9"), this);
686   ac_Mode_Structure->setCheckable(true);
687   connect(ac_Mode_Structure, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
688 
689   ac_Mode_L = new QAction(tr("&L*") + "\t" + tr("8"), this);
690   ac_Mode_L->setCheckable(true);
691   connect(ac_Mode_L, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
692 
693   ac_Mode_A = new QAction(tr("&a*") + "\t" + tr("7"), this);
694   ac_Mode_A->setCheckable(true);
695   connect(ac_Mode_A, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
696 
697   ac_Mode_B = new QAction(tr("&b*") + "\t" + tr("6"), this);
698   ac_Mode_B->setCheckable(true);
699   connect(ac_Mode_B, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
700 
701   ac_Mode_Gradient = new QAction(tr("&Gradient") + "\t" + tr("5"), this);
702   ac_Mode_Gradient->setCheckable(true);
703   connect(ac_Mode_Gradient, SIGNAL(triggered()), this, SLOT(Menu_Mode()));
704 
705   ac_ModeGroup = new QActionGroup(this);
706   ac_ModeGroup->addAction(ac_Mode_RGB);
707   ac_ModeGroup->addAction(ac_Mode_L);
708   ac_ModeGroup->addAction(ac_Mode_A);
709   ac_ModeGroup->addAction(ac_Mode_B);
710   ac_ModeGroup->addAction(ac_Mode_Gradient);
711   ac_ModeGroup->addAction(ac_Mode_Structure);
712 
713   ac_PRead_None = new QAction(tr("&disabled"), this);
714   ac_PRead_None->setCheckable(true);
715   connect(ac_PRead_None, SIGNAL(triggered()), this, SLOT(Menu_PixelReading()));
716 
717   ac_PRead_Linear = new QAction(tr("&linear"), this);
718   ac_PRead_Linear->setCheckable(true);
719   connect(ac_PRead_Linear, SIGNAL(triggered()), this, SLOT(Menu_PixelReading()));
720 
721   ac_PRead_Preview = new QAction(tr("&preview"), this);
722   ac_PRead_Preview->setCheckable(true);
723   connect(ac_PRead_Preview, SIGNAL(triggered()), this, SLOT(Menu_PixelReading()));
724 
725   ac_PReadGroup = new QActionGroup(this);
726   ac_PReadGroup->addAction(ac_PRead_None);
727   ac_PReadGroup->addAction(ac_PRead_Linear);
728   ac_PReadGroup->addAction(ac_PRead_Preview);
729   switch (Settings->GetInt("PixelReader")) {
730     case (int)prLinear:  ac_PRead_Linear->setChecked(true);  break;
731     case (int)prPreview: ac_PRead_Preview->setChecked(true); break;
732     default:             ac_PRead_None->setChecked(true);
733   };
734   Menu_PixelReading();
735 
736   ac_Clip_Indicate = new QAction(tr("Highlight &clipped pixels") + "\t" + tr("C"), this);
737   ac_Clip_Indicate->setCheckable(true);
738   ac_Clip_Indicate->setChecked(Settings->GetInt("ExposureIndicator"));
739   connect(ac_Clip_Indicate, SIGNAL(triggered()), this, SLOT(Menu_Clip_Indicate()));
740 
741   ac_Clip_Over = new QAction(tr("&Over exposure"), this);
742   ac_Clip_Over->setCheckable(true);
743   ac_Clip_Over->setChecked(Settings->GetInt("ExposureIndicatorOver"));
744   connect(ac_Clip_Over, SIGNAL(triggered()), this, SLOT(Menu_Clip_Over()));
745 
746   ac_Clip_Under = new QAction(tr("&Under exposure"), this);
747   ac_Clip_Under->setCheckable(true);
748   ac_Clip_Under->setChecked(Settings->GetInt("ExposureIndicatorUnder"));
749   connect(ac_Clip_Under, SIGNAL(triggered()), this, SLOT(Menu_Clip_Under()));
750 
751   ac_Clip_R = new QAction(tr("&R"), this);
752   ac_Clip_R->setCheckable(true);
753   ac_Clip_R->setChecked(Settings->GetInt("ExposureIndicatorR"));
754   connect(ac_Clip_R, SIGNAL(triggered()), this, SLOT(Menu_Clip_R()));
755 
756   ac_Clip_G = new QAction(tr("&G"), this);
757   ac_Clip_G->setCheckable(true);
758   ac_Clip_G->setChecked(Settings->GetInt("ExposureIndicatorG"));
759   connect(ac_Clip_G, SIGNAL(triggered()), this, SLOT(Menu_Clip_G()));
760 
761   ac_Clip_B = new QAction(tr("&B"), this);
762   ac_Clip_B->setCheckable(true);
763   ac_Clip_B->setChecked(Settings->GetInt("ExposureIndicatorB"));
764   connect(ac_Clip_B, SIGNAL(triggered()), this, SLOT(Menu_Clip_B()));
765 
766   ac_SensorClip = new QAction(tr("&Sensor"), this);
767   ac_SensorClip->setCheckable(true);
768   ac_SensorClip->setChecked(Settings->GetInt("ExposureIndicatorSensor"));
769   connect(ac_SensorClip, SIGNAL(triggered()), this, SLOT(Menu_SensorClip()));
770   ac_SensorClipSep = new QAction(this);
771   ac_SensorClipSep->setSeparator(true);
772 
773   ac_ShowZoomBar = new QAction(tr("Show &bottom bar"), this);
774   ac_ShowZoomBar->setCheckable(true);
775   ac_ShowZoomBar->setChecked(Settings->GetInt("ShowBottomContainer"));
776   connect(ac_ShowZoomBar, SIGNAL(triggered()), this, SLOT(Menu_ShowZoomBar()));
777 
778   ac_ShowTools = new QAction(tr("Show &tool pane") + "\t" + tr("Space"), this);
779   ac_ShowTools->setCheckable(true);
780   ac_ShowTools->setChecked(Settings->GetInt("ShowToolContainer"));
781   connect(ac_ShowTools, SIGNAL(triggered()), this, SLOT(Menu_ShowTools()));
782 
783 #ifndef PT_WITHOUT_FILEMGR
784   ac_OpenFileMgr = new QAction(tr("Open file m&anager") + "\t" + tr("Ctrl+M"), this);
785   connect(ac_OpenFileMgr, SIGNAL(triggered()), this, SLOT(Menu_OpenFileMgr()));
786 #endif
787 
788   ac_OpenBatch = new QAction(tr("Open &batch processing") + "\t" + tr("Ctrl+B"), this);
789   connect(ac_OpenBatch, SIGNAL(triggered()), this, SLOT(Menu_OpenBatch()));
790 
791   ac_Fullscreen = new QAction(tr("Full&screen") + "\t" + tr("F11"), this);
792   ac_Fullscreen->setCheckable(true);
793   ac_Fullscreen->setChecked(0);
794   connect(ac_Fullscreen, SIGNAL(triggered()), this, SLOT(Menu_Fullscreen()));
795 }
796 
797 //==============================================================================
798 
contextMenuEvent(QContextMenuEvent * event)799 void ptViewWindow::contextMenuEvent(QContextMenuEvent* event) {
800   if (FInteraction == iaSelectRect || FInteraction == iaDrawLine) {
801     return;
802   }
803 
804   // Create the menus themselves
805   // Note: Menus cannot be created with new. That breaks the theming.
806   QMenu Menu_Mode(tr("Display &mode"), this);
807   Menu_Mode.setPalette(Theme->menuPalette());
808   Menu_Mode.setStyle(Theme->style());
809   Menu_Mode.addAction(ac_Mode_RGB);
810   Menu_Mode.addAction(ac_Mode_Structure);
811   Menu_Mode.addAction(ac_Mode_L);
812   Menu_Mode.addAction(ac_Mode_A);
813   Menu_Mode.addAction(ac_Mode_B);
814   Menu_Mode.addAction(ac_Mode_Gradient);
815 
816   QMenu Menu_Clip(tr("Show &clipping"), this);
817   Menu_Clip.setPalette(Theme->menuPalette());
818   Menu_Clip.setStyle(Theme->style());
819   Menu_Clip.addAction(ac_Clip_Indicate);
820   Menu_Clip.addSeparator();
821   Menu_Clip.addAction(ac_Clip_Over);
822   Menu_Clip.addAction(ac_Clip_Under);
823   Menu_Clip.addSeparator();
824   Menu_Clip.addAction(ac_Clip_R);
825   Menu_Clip.addAction(ac_Clip_G);
826   Menu_Clip.addAction(ac_Clip_B);
827 
828   QMenu Menu_PRead(tr("Pixel values"), this);
829   Menu_PRead.setPalette(Theme->menuPalette());
830   Menu_PRead.setStyle(Theme->style());
831   Menu_PRead.addAction(ac_PRead_None);
832   Menu_PRead.addAction(ac_PRead_Linear);
833   Menu_PRead.addAction(ac_PRead_Preview);
834 
835   QMenu Menu(this);
836   Menu.setPalette(Theme->menuPalette());
837   Menu.setStyle(Theme->style());
838   Menu.addAction(ac_Copy);
839   Menu.addAction(ac_Paste);
840   Menu.addAction(ac_Reset);
841   Menu.addAction(ac_UserReset);
842   Menu.addSeparator();
843   Menu.addAction(ac_ZoomIn);
844   Menu.addAction(ac_Zoom100);
845   Menu.addAction(ac_ZoomOut);
846   Menu.addAction(ac_ZoomFit);
847   Menu.addSeparator();
848   if (FInteraction == iaNone) {
849     Menu.addMenu(&Menu_Mode);
850     Menu.addMenu(&Menu_Clip);
851     Menu.addMenu(&Menu_PRead);
852     Menu.addSeparator();
853     Menu.addAction(ac_SensorClip);
854     Menu.addAction(ac_SensorClipSep);
855   }
856   Menu.addAction(ac_ShowTools);
857   Menu.addAction(ac_ShowZoomBar);
858 #ifndef PT_WITHOUT_FILEMGR
859   Menu.addAction(ac_OpenFileMgr);
860 #endif
861   Menu.addAction(ac_OpenBatch);
862   Menu.addSeparator();
863   Menu.addAction(ac_Fullscreen);
864 
865   if (Settings->GetInt("SpecialPreview") == ptSpecialPreview_Structure) {
866     ac_Mode_Structure->setChecked(true);
867   } else if (Settings->GetInt("SpecialPreview") == ptSpecialPreview_Gradient) {
868     ac_Mode_Gradient->setChecked(true);
869   } else if (Settings->GetInt("SpecialPreview") == ptSpecialPreview_L) {
870     ac_Mode_L->setChecked(true);
871   } else if (Settings->GetInt("SpecialPreview") == ptSpecialPreview_A) {
872     ac_Mode_A->setChecked(true);
873   } else if (Settings->GetInt("SpecialPreview") == ptSpecialPreview_B) {
874     ac_Mode_B->setChecked(true);
875   } else {
876     ac_Mode_RGB->setChecked(true);
877   }
878 
879   ac_ShowTools->setChecked(Settings->GetInt("ShowToolContainer"));
880   ac_ShowZoomBar->setChecked(Settings->GetInt("ShowBottomContainer"));
881   ac_Fullscreen->setChecked(Settings->GetInt("FullscreenActive"));
882   ac_Clip_Indicate->setChecked(Settings->GetInt("ExposureIndicator"));
883 
884   if (Settings->GetInt("ShowExposureIndicatorSensor") == 1) {
885     ac_SensorClip->setEnabled(Settings->GetInt("ShowExposureIndicatorSensor"));
886     ac_SensorClip->setVisible(true);
887     ac_SensorClipSep->setVisible(true);
888   } else {
889     ac_SensorClip->setVisible(false);
890     ac_SensorClipSep->setVisible(false);
891   }
892 
893   Menu.exec(event->globalPos());
894 }
895 
896 //==============================================================================
897 // slots for the context menu
898 
899 void Update(short Phase, short SubPhase = -1, short WithIdentify  = 1, short ProcessorMode = ptProcessorMode_Preview);
Menu_Clip_Indicate()900 void ptViewWindow::Menu_Clip_Indicate() {
901   Settings->SetValue("ExposureIndicator",(int)ac_Clip_Indicate->isChecked());
902   Update(ptProcessorPhase_Preview);
903 }
904 
Menu_Clip_Over()905 void ptViewWindow::Menu_Clip_Over() {
906   Settings->SetValue("ExposureIndicatorOver",(int)ac_Clip_Over->isChecked());
907   if (Settings->GetInt("ExposureIndicator"))
908     Update(ptProcessorPhase_Preview);
909 }
910 
Menu_Clip_Under()911 void ptViewWindow::Menu_Clip_Under() {
912   Settings->SetValue("ExposureIndicatorUnder",(int)ac_Clip_Under->isChecked());
913   if (Settings->GetInt("ExposureIndicator"))
914     Update(ptProcessorPhase_Preview);
915 }
916 
Menu_Clip_R()917 void ptViewWindow::Menu_Clip_R() {
918   Settings->SetValue("ExposureIndicatorR",(int)ac_Clip_R->isChecked());
919   if (Settings->GetInt("ExposureIndicator"))
920     Update(ptProcessorPhase_Preview);
921 }
922 
Menu_Clip_G()923 void ptViewWindow::Menu_Clip_G() {
924   Settings->SetValue("ExposureIndicatorG",(int)ac_Clip_G->isChecked());
925   if (Settings->GetInt("ExposureIndicator"))
926     Update(ptProcessorPhase_Preview);
927 }
928 
Menu_Clip_B()929 void ptViewWindow::Menu_Clip_B() {
930   Settings->SetValue("ExposureIndicatorB",(int)ac_Clip_B->isChecked());
931   if (Settings->GetInt("ExposureIndicator"))
932     Update(ptProcessorPhase_Preview);
933 }
934 
Menu_SensorClip()935 void ptViewWindow::Menu_SensorClip() {
936   Settings->SetValue("ExposureIndicatorSensor",(int)ac_SensorClip->isChecked());
937   Update(ptProcessorPhase_Preview);
938 }
939 
Menu_ShowZoomBar()940 void ptViewWindow::Menu_ShowZoomBar() {
941   Settings->SetValue("ShowBottomContainer",(int)ac_ShowZoomBar->isChecked());
942   FMainWindow->UpdateSettings();
943 }
944 
Menu_ShowTools()945 void ptViewWindow::Menu_ShowTools() {
946   Settings->SetValue("ShowToolContainer",(int)ac_ShowTools->isChecked());
947   FMainWindow->UpdateSettings();
948 }
949 
Menu_OpenFileMgr()950 void ptViewWindow::Menu_OpenFileMgr() {
951   FCtrlIsPressed = 0;
952   emit openFileMgr();
953 }
954 
Menu_OpenBatch()955 void ptViewWindow::Menu_OpenBatch() {
956   FCtrlIsPressed = 0;
957   emit openBatch();
958 }
959 
960 void CB_FullScreenButton(const int State);
Menu_Fullscreen()961 void ptViewWindow::Menu_Fullscreen() {
962   CB_FullScreenButton((int)ac_Fullscreen->isChecked());
963 }
964 
Menu_ZoomFit()965 void ptViewWindow::Menu_ZoomFit() {
966   ZoomToFit();
967 }
968 
Menu_Zoom100()969 void ptViewWindow::Menu_Zoom100() {
970   ZoomTo(1.0);
971 }
972 
Menu_ZoomIn()973 void ptViewWindow::Menu_ZoomIn() {
974   ZoomStep(1);
975 }
976 
Menu_ZoomOut()977 void ptViewWindow::Menu_ZoomOut() {
978   ZoomStep(-1);
979 }
980 
Menu_Mode()981 void ptViewWindow::Menu_Mode() {
982   if (ac_Mode_RGB->isChecked())
983     Settings->SetValue("SpecialPreview", ptSpecialPreview_RGB);
984   else if (ac_Mode_L->isChecked())
985     Settings->SetValue("SpecialPreview", ptSpecialPreview_L);
986   else if (ac_Mode_A->isChecked())
987     Settings->SetValue("SpecialPreview", ptSpecialPreview_A);
988   else if (ac_Mode_B->isChecked())
989     Settings->SetValue("SpecialPreview", ptSpecialPreview_B);
990   else if (ac_Mode_Gradient->isChecked())
991     Settings->SetValue("SpecialPreview", ptSpecialPreview_Gradient);
992   else if (ac_Mode_Structure->isChecked())
993     Settings->SetValue("SpecialPreview", ptSpecialPreview_Structure);
994 
995   Update(ptProcessorPhase_Preview);
996 }
997 
Menu_PixelReading()998 void ptViewWindow::Menu_PixelReading() {
999   if (ac_PRead_None->isChecked()) {
1000     if (FPixelReader) FPixelReader(QPoint(), prNone);
1001     FPixelReading = prNone;
1002   }
1003   else if (ac_PRead_Linear->isChecked())  FPixelReading = prLinear;
1004   else if (ac_PRead_Preview->isChecked()) FPixelReading = prPreview;
1005 
1006   Settings->SetValue("PixelReader", (int)FPixelReading);
1007 }
1008 
1009 void ptCopySettingsToClipboard();
Menu_Copy()1010 void ptViewWindow::Menu_Copy() {
1011   ptCopySettingsToClipboard();
1012 }
1013 
1014 void ptPasteSettingsFromClipboard();
Menu_Paste()1015 void ptViewWindow::Menu_Paste() {
1016   ptPasteSettingsFromClipboard();
1017 }
1018 
1019 void ptResetSettingsToDefault();
Menu_Reset()1020 void ptViewWindow::Menu_Reset() {
1021   ptResetSettingsToDefault();
1022 }
1023 
1024 void ptMakeFullUndo();
Menu_UserReset()1025 void ptViewWindow::Menu_UserReset() {
1026   ptMakeFullUndo();
1027 }
1028 
1029 //==============================================================================
1030