1 /*******************************************************************************************************
2  DkViewPort.cpp
3  Created on:	05.05.2011
4 
5  nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
6 
7  Copyright (C) 2011-2013 Markus Diem <markus@nomacs.org>
8  Copyright (C) 2011-2013 Stefan Fiel <stefan@nomacs.org>
9  Copyright (C) 2011-2013 Florian Kleber <florian@nomacs.org>
10 
11  This file is part of nomacs.
12 
13  nomacs is free software: you can redistribute it and/or modify
14  it under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  nomacs is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 
26  *******************************************************************************************************/
27 
28 #include "DkViewPort.h"
29 
30 #include "DkControlWidget.h"
31 #include "DkImageLoader.h"
32 #include "DkWidgets.h"
33 #include "DkSettings.h"
34 #include "DkNetwork.h"
35 #include "DkThumbsWidgets.h"		// needed in the connects -> shall we move them to mController?
36 #include "DkMetaDataWidgets.h"
37 #include "DkToolbars.h"
38 #include "DkMetaData.h"
39 #include "DkPluginManager.h"
40 #include "DkActionManager.h"
41 #include "DkStatusBar.h"
42 #include "DkUtils.h"
43 #include "DkBasicLoader.h"
44 #include "DkDialog.h"
45 #include "DkMessageBox.h"
46 #include "DkToolbars.h"
47 
48 #pragma warning(push, 0)	// no warnings from includes - begin
49 #include <QClipboard>
50 #include <QMovie>
51 #include <QMimeData>
52 #include <QAction>
53 #include <QApplication>
54 #include <QVBoxLayout>
55 #include <QDragLeaveEvent>
56 #include <QDrag>
57 #include <QInputDialog>
58 #include <QMessageBox>
59 #include <QDesktopWidget>
60 #include <QSvgRenderer>
61 #include <QMenu>
62 #include <QtConcurrentRun>
63 #include <QPainterPath>
64 
65 #include <qmath.h>
66 #pragma warning(pop)		// no warnings from includes - end
67 
68 #include <assert.h>
69 
70 #ifdef Q_OS_WIN
71 #include <windows.h>
72 #endif
73 
74 namespace nmc {
75 
76 // DkViewPort --------------------------------------------------------------------
DkViewPort(QWidget * parent)77 DkViewPort::DkViewPort(QWidget *parent) : DkBaseViewPort(parent) {
78 
79 	mRepeatZoomTimer = new QTimer(this);
80 	mAnimationTimer = new QTimer(this);
81 
82 	// try loading a custom file
83 	mImgBg.load(QFileInfo(QApplication::applicationDirPath(), "bg.png").absoluteFilePath());
84 	if (mImgBg.isNull() && DkSettingsManager::param().global().showBgImage) {
85 		QColor col = backgroundBrush().color().darker();
86 		mImgBg = DkImage::loadIcon(":/nomacs/img/nomacs-bg.svg", col, QSize(100, 100)).toImage();
87 	}
88 
89 	mRepeatZoomTimer->setInterval(20);
90 	connect(mRepeatZoomTimer, SIGNAL(timeout()), this, SLOT(repeatZoom()));
91 
92 	mAnimationTimer->setInterval(5);
93 	connect(mAnimationTimer, SIGNAL(timeout()), this, SLOT(animateFade()));
94 
95 	//no border
96 	setMouseTracking(true);//receive mouse event everytime
97 
98 	mPaintLayout = new QVBoxLayout(this);
99 	mPaintLayout->setContentsMargins(0,0,0,0);
100 
101 	createShortcuts();
102 
103 	mController = new DkControlWidget(this);
104 
105 	mLoader = QSharedPointer<DkImageLoader>(new DkImageLoader());
106 	connectLoader(mLoader);
107 
108 	if (DkSettingsManager::param().display().showScrollBars) {
109 		setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
110 		setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
111 	}
112 
113 	mController->getOverview()->setTransforms(&mWorldMatrix, &mImgMatrix);
114 	mController->getCropWidget()->setWorldTransform(&mWorldMatrix);
115 	mController->getCropWidget()->setImageTransform(&mImgMatrix);
116 	mController->getCropWidget()->setImageRect(&mImgViewRect);
117 
118 	// this must be initialized after mController to be above it
119 	mNavigationWidget = new DkHudNavigation(this);
120     mPaintLayout->addWidget(mNavigationWidget);
121 	// TODO: if visible, currently mNavigationWidget eats all mouse events that are supposed for control widget
122 
123 	// add actions
124 	DkActionManager& am = DkActionManager::instance();
125 	addActions(am.fileActions().toList());
126 	addActions(am.viewActions().toList());
127 	addActions(am.editActions().toList());
128 	addActions(am.manipulatorActions().toList());
129 	addActions(am.sortActions().toList());
130 	addActions(am.toolsActions().toList());
131 	addActions(am.panelActions().toList());
132 	addActions(am.syncActions().toList());
133 	addActions(am.pluginActions().toList());
134 	addActions(am.helpActions().toList());
135 	addActions(am.hiddenActions().toList());
136 
137 	addActions(am.openWithActions().toList());
138 #ifdef WITH_PLUGINS
139 	addActions(am.pluginActionManager()->pluginDummyActions().toList());
140 #endif
141 
142 	connect(&mImgStorage, SIGNAL(infoSignal(const QString&)), this, SIGNAL(infoSignal(const QString&)));
143 
144 	if (am.pluginActionManager())
145 		connect(am.pluginActionManager(), SIGNAL(runPlugin(DkPluginContainer*, const QString&)), this, SLOT(applyPlugin(DkPluginContainer*, const QString&)));
146 
147 	// connect
148 	connect(am.action(DkActionManager::menu_file_reload), SIGNAL(triggered()), this, SLOT(reloadFile()));
149 	connect(am.action(DkActionManager::menu_file_next), SIGNAL(triggered()), this, SLOT(loadNextFileFast()));
150 	connect(am.action(DkActionManager::menu_file_prev), SIGNAL(triggered()), this, SLOT(loadPrevFileFast()));
151 	connect(am.action(DkActionManager::menu_file_save), SIGNAL(triggered()), this, SLOT(saveFile()));
152 	connect(am.action(DkActionManager::menu_file_save_as), SIGNAL(triggered()), this, SLOT(saveFileAs()));
153 	connect(am.action(DkActionManager::menu_file_save_web), SIGNAL(triggered()), this, SLOT(saveFileWeb()));
154 	connect(am.action(DkActionManager::menu_tools_wallpaper), SIGNAL(triggered()), this, SLOT(setAsWallpaper()));
155 
156 	connect(am.action(DkActionManager::menu_edit_rotate_cw), SIGNAL(triggered()), this, SLOT(rotateCW()));
157 	connect(am.action(DkActionManager::menu_edit_rotate_ccw), SIGNAL(triggered()), this, SLOT(rotateCCW()));
158 	connect(am.action(DkActionManager::menu_edit_rotate_180), SIGNAL(triggered()), this, SLOT(rotate180()));
159 	connect(am.action(DkActionManager::menu_edit_transform), SIGNAL(triggered()), this, SLOT(resizeImage()));
160 	connect(am.action(DkActionManager::menu_edit_delete), SIGNAL(triggered()), this, SLOT(deleteImage()));
161 	connect(am.action(DkActionManager::menu_edit_copy), SIGNAL(triggered()), this, SLOT(copyImage()));
162 	connect(am.action(DkActionManager::menu_edit_copy_buffer), SIGNAL(triggered()), this, SLOT(copyImageBuffer()));
163 	connect(am.action(DkActionManager::menu_edit_copy_color), SIGNAL(triggered()), this, SLOT(copyPixelColorValue()));
164 
165 	connect(am.action(DkActionManager::menu_view_reset), SIGNAL(triggered()), this, SLOT(zoomToFit()));
166 	connect(am.action(DkActionManager::menu_view_100), SIGNAL(triggered()), this, SLOT(fullView()));
167 	connect(am.action(DkActionManager::menu_view_zoom_in), SIGNAL(triggered()), this, SLOT(zoomIn()));
168 	connect(am.action(DkActionManager::menu_view_zoom_out), SIGNAL(triggered()), this, SLOT(zoomOut()));
169 	connect(am.action(DkActionManager::menu_view_tp_pattern), SIGNAL(toggled(bool)), this, SLOT(togglePattern(bool)));
170 	connect(am.action(DkActionManager::menu_view_movie_pause), SIGNAL(triggered(bool)), this, SLOT(pauseMovie(bool)));
171 	connect(am.action(DkActionManager::menu_view_movie_prev), SIGNAL(triggered()), this, SLOT(previousMovieFrame()));
172 	connect(am.action(DkActionManager::menu_view_movie_next), SIGNAL(triggered()), this, SLOT(nextMovieFrame()));
173 
174 	connect(am.action(DkActionManager::sc_test_img), SIGNAL(triggered()), this, SLOT(loadLena()));
175 	connect(am.action(DkActionManager::menu_sync_view), SIGNAL(triggered()), this, SLOT(tcpForceSynchronize()));
176 
177 	// playing
178 	connect(mNavigationWidget, SIGNAL(previousSignal()), this, SLOT(loadPrevFileFast()));
179 	connect(mNavigationWidget, SIGNAL(nextSignal()), this, SLOT(loadNextFileFast()));
180 
181 	// trivial connects
182 	connect(this, &DkViewPort::movieLoadedSignal,
183 		[this](bool movie) { DkActionManager::instance().enableMovieActions(movie); });
184 
185 	// connect sync
186 	auto cm = DkSyncManager::inst().client();
187 
188 	connect(this, SIGNAL(sendTransformSignal(QTransform, QTransform, QPointF)), cm, SLOT(sendTransform(QTransform, QTransform, QPointF)));
189 	connect(this, SIGNAL(sendNewFileSignal(qint16, const QString&)), cm, SLOT(sendNewFile(qint16, const QString&)));
190 	connect(cm, SIGNAL(receivedNewFile(qint16, const QString&)), this, SLOT(tcpLoadFile(qint16, const QString&)));
191 	connect(cm, SIGNAL(updateConnectionSignal(const QString&)), mController, SLOT(setInfo(const QString&)));
192 	connect(cm, SIGNAL(receivedTransformation(QTransform, QTransform, QPointF)), this, SLOT(tcpSetTransforms(QTransform, QTransform, QPointF)));
193 
194 	for (auto action : am.manipulatorActions())
195 		connect(action, SIGNAL(triggered()), this, SLOT(applyManipulator()));
196 
197 	connect(&mManipulatorWatcher, SIGNAL(finished()), this, SLOT(manipulatorApplied()));
198 
199 	// TODO:
200 	// one could blur the canvas if a transparent GUI is present
201 	// what we would need: QGraphicsBlurEffect...
202 	// render all widgets to the alpha channel (bw)
203 	// pre-render the mViewport to that image... apply blur
204 	// and then render the blurred image after the widget is rendered...
205 	// performance?!
206 
207 }
208 
~DkViewPort()209 DkViewPort::~DkViewPort() {
210 
211 	mController->closePlugin(false, true);
212 
213 	mManipulatorWatcher.cancel();
214 	mManipulatorWatcher.blockSignals(true);
215 }
216 
createShortcuts()217 void DkViewPort::createShortcuts() {
218 
219 	DkActionManager& am = DkActionManager::instance();
220 	connect(am.action(DkActionManager::sc_first_file), SIGNAL(triggered()), this, SLOT(loadFirst()));
221 	connect(am.action(DkActionManager::sc_last_file), SIGNAL(triggered()), this, SLOT(loadLast()));
222 	connect(am.action(DkActionManager::sc_skip_prev), SIGNAL(triggered()), this, SLOT(loadSkipPrev10()));
223 	connect(am.action(DkActionManager::sc_skip_next), SIGNAL(triggered()), this, SLOT(loadSkipNext10()));
224 	connect(am.action(DkActionManager::sc_first_file_sync), SIGNAL(triggered()), this, SLOT(loadFirst()));
225 	connect(am.action(DkActionManager::sc_last_file_sync), SIGNAL(triggered()), this, SLOT(loadLast()));
226 	connect(am.action(DkActionManager::sc_skip_next_sync), SIGNAL(triggered()), this, SLOT(loadNextFileFast()));
227 	connect(am.action(DkActionManager::sc_skip_prev_sync), SIGNAL(triggered()), this, SLOT(loadPrevFileFast()));
228 
229 }
230 
setPaintWidget(QWidget * widget,bool removeWidget)231 void DkViewPort::setPaintWidget(QWidget* widget, bool removeWidget) {
232 
233 	if (!removeWidget) {
234 		mPaintLayout->addWidget(widget);
235 		//pluginImageWasApplied = false;
236 	} else {
237 		mPaintLayout->removeWidget(widget);
238 		//widget->deleteLater();
239 	}
240 
241 	mController->raise();
242 }
243 
244 #ifdef WITH_OPENCV
setImage(cv::Mat newImg)245 void DkViewPort::setImage(cv::Mat newImg) {
246 
247 	QImage imgQt = DkImage::mat2QImage(newImg);
248 	setImage(imgQt);
249 }
250 #endif
251 
updateImage(QSharedPointer<DkImageContainerT> image,bool loaded)252 void DkViewPort::updateImage(QSharedPointer<DkImageContainerT> image, bool loaded) {
253 
254 	// things todo if a file was not loaded...
255 	if (!loaded) {
256 		mController->getPlayer()->startTimer();
257 		return;
258 	}
259 
260 	// should not happen -> the mLoader should send this signal
261 	if (!mLoader)
262 		return;
263 
264 	if (mLoader->hasImage()) {
265 		setImage(mLoader->getImage());
266 	}
267 }
268 
loadImage(const QImage & newImg)269 void DkViewPort::loadImage(const QImage& newImg) {
270 
271 	// delete current information
272 	if (mLoader) {
273 		if (!unloadImage(true))
274 			return;	// user canceled
275 
276 		mLoader->setImage(newImg, tr("Original Image"));
277 		setImage(newImg);
278 
279 		// save to temp folder
280 		mLoader->saveTempFile(newImg);
281 	}
282 }
283 
loadImage(QSharedPointer<DkImageContainerT> img)284 void DkViewPort::loadImage(QSharedPointer<DkImageContainerT> img) {
285 
286 	if (mLoader) {
287 
288 		if (!unloadImage(true))
289 			return;
290 
291 		if (img->hasImage()) {
292 			mLoader->setCurrentImage(img);
293 			setImage(img->image());
294 		}
295 		mLoader->load(img);
296 	}
297 
298 }
299 
setImage(QImage newImg)300 void DkViewPort::setImage(QImage newImg) {
301 
302 	// calling show here fixes issues with the HUD
303 	show();
304 
305 	DkTimer dt;
306 
307 	emit movieLoadedSignal(false);
308 	stopMovie();	// just to be sure
309 
310 	if (mManipulatorWatcher.isRunning())
311 		mManipulatorWatcher.cancel();
312 
313 	mController->getOverview()->setImage(QImage());	// clear overview
314 
315 	mImgStorage.setImage(newImg);
316 
317 	if (mLoader->hasMovie() && !mLoader->isEdited())
318 		loadMovie();
319 	if (mLoader->hasSvg() && !mLoader->isEdited())
320 		loadSvg();
321 
322 	mImgRect = QRectF(QPoint(), getImageSize());
323 
324 	DkActionManager::instance().enableImageActions(!newImg.isNull());
325 	mController->imageLoaded(!newImg.isNull());
326 
327 	double oldZoom = mWorldMatrix.m11();// *mImgMatrix.m11();
328 
329 	if (!(DkSettingsManager::param().display().keepZoom == DkSettings::zoom_keep_same_size && mOldImgRect == mImgRect))
330 		mWorldMatrix.reset();
331 
332 	updateImageMatrix();
333 
334 	// if image is not inside, we'll align it at the top left border
335 	if (!mViewportRect.intersects(mWorldMatrix.mapRect(mImgViewRect))) {
336 		mWorldMatrix.translate(-mWorldMatrix.dx(), -mWorldMatrix.dy());
337 		centerImage();
338 	}
339 
340 	if (DkSettingsManager::param().display().keepZoom == DkSettings::zoom_always_keep) {
341 		zoomToPoint(oldZoom, mImgViewRect.center().toPoint(), mWorldMatrix);
342 	}
343 
344 	mController->getPlayer()->startTimer();
345 	mController->getOverview()->setImage(newImg);	// TODO: maybe we could make use of the image pyramid here
346 
347 	mOldImgRect = mImgRect;
348 
349 	// init fading
350 	if (DkSettingsManager::param().display().animationDuration &&
351 		DkSettingsManager::param().display().transition != DkSettingsManager::param().trans_appear &&
352 		(mController->getPlayer()->isPlaying() ||
353 			DkUtils::getMainWindow()->isFullScreen() ||
354 			DkSettingsManager::param().display().alwaysAnimate)) {
355 		mAnimationTimer->start();
356 		mAnimationTime.start();
357 	}
358 	else
359 		mAnimationValue = 0.0f;
360 
361 	// set/clear crop rect
362 	if (mLoader->getCurrentImage())
363 		mCropRect = mLoader->getCurrentImage()->cropRect();
364 	else
365 		mCropRect = DkRotatingRect();
366 
367 	update();
368 
369 	// draw a histogram from the image -> does nothing if the histogram is invisible
370 	if (mController->getHistogram())
371 		mController->getHistogram()->drawHistogram(newImg);
372 
373 	emit newImageSignal(&newImg);
374 	emit zoomSignal(mWorldMatrix.m11()*mImgMatrix.m11()*100);
375 
376 	// status info
377 	if (!newImg.isNull()) {
378 		DkStatusBarManager::instance().setMessage(QString::number(qRound((float)(mWorldMatrix.m11()*mImgMatrix.m11() * 100))) + "%", DkStatusBar::status_zoom_info);
379 		DkStatusBarManager::instance().setMessage(DkUtils::formatToString(newImg.format()), DkStatusBar::status_format_info);
380 		DkStatusBarManager::instance().setMessage(QString::number(newImg.width()) + " x " + QString::number(newImg.height()), DkStatusBar::status_dimension_info);
381 
382 		if (imageContainer())
383 			DkStatusBarManager::instance().setMessage(imageContainer()->fileName(), DkStatusBar::status_file_info);
384 	}
385 	else {
386 		DkStatusBarManager::instance().setMessage("", DkStatusBar::status_zoom_info);
387 		DkStatusBarManager::instance().setMessage("", DkStatusBar::status_format_info);
388 		DkStatusBarManager::instance().setMessage("", DkStatusBar::status_dimension_info);
389 		DkStatusBarManager::instance().setMessage("", DkStatusBar::status_file_info);
390 	}
391 }
392 
zoom(double factor,const QPointF & center,bool force)393 void DkViewPort::zoom(double factor, const QPointF& center, bool force) {
394 
395 	if (mImgStorage.isEmpty() || mBlockZooming)
396 		return;
397 
398 	//factor/=5;//-0.1 <-> 0.1
399 	//factor+=1;//0.9 <-> 1.1
400 
401 	//limit zoom out ---
402 	if (mWorldMatrix.m11()*factor < mMinZoom && factor < 1)
403 		return;
404 
405 	// reset view & block if we pass the 'image fit to screen' on zoom out
406 	if (mWorldMatrix.m11() > 1 && mWorldMatrix.m11()*factor < 1 && !force) {
407 
408 		mBlockZooming = true;
409 		mZoomTimer->start(500);
410 		resetView();
411 		return;
412 	}
413 
414 	// reset view if we pass the 'image fit to screen' on zoom in
415 	if (mWorldMatrix.m11() < 1 && mWorldMatrix.m11()*factor > 1 && !force) {
416 
417 		resetView();
418 		return;
419 	}
420 
421 	// TODO: the reset in mWorldMatrix introduces wrong pans
422 	//// reset view & block if we pass the '100%' on zoom out
423 	//if (mWorldMatrix.m11()*mImgMatrix.m11()-FLT_EPSILON > 1 && mWorldMatrix.m11()*mImgMatrix.m11()*factor < 1) {
424 	//
425 	//	mBlockZooming = true;
426 	//	mZoomTimer->start(500);
427 	//	mWorldMatrix.reset();
428 	//	factor = 1.0f / (float)mImgMatrix.m11();
429 	//}
430 
431 	//// reset view if we pass the '100%' on zoom in
432 	//if (mWorldMatrix.m11()*mImgMatrix.m11()+FLT_EPSILON < 1 && mWorldMatrix.m11()*mImgMatrix.m11()*factor > 1) {
433 
434 	//	mBlockZooming = true;
435 	//	mZoomTimer->start(500);
436 	//	mWorldMatrix.reset();
437 	//	factor = 1.0f / (float)mImgMatrix.m11();
438 	//}
439 
440 
441 	//limit zoom in ---
442 	if (mWorldMatrix.m11()*mImgMatrix.m11() > mMaxZoom && factor > 1)
443 		return;
444 
445 	bool blackBorder = false;
446 
447 	QPointF pos = center;
448 
449 	// if no center assigned: zoom in at the image center
450 	if (pos.x() == -1 || pos.y() == -1)
451 		pos = mImgViewRect.center();
452 	else {
453 
454 		// if black border - do not zoom to the mouse coordinate
455 		if (mImgViewRect.width()*(mWorldMatrix.m11()*factor) < width()) {
456 			pos.setX(mImgViewRect.center().x());
457 			blackBorder = true;
458 		}
459 		if ((mImgViewRect.height()*mWorldMatrix.m11()*factor) < height()) {
460 			pos.setY(mImgViewRect.center().y());
461 			blackBorder = true;
462 		}
463 	}
464 
465 	zoomToPoint(factor, pos, mWorldMatrix);
466 
467 	controlImagePosition();
468 	if (blackBorder && factor < 1) centerImage();	// TODO: geht auch sch�ner
469 	showZoom();
470 	changeCursor();
471 
472 	mController->update();	// why do we need to update the mController manually?
473 	update();
474 
475 	tcpSynchronize();
476 
477 	emit zoomSignal(mWorldMatrix.m11()*mImgMatrix.m11()*100);
478 	DkStatusBarManager::instance().setMessage(QString::number(qRound(mWorldMatrix.m11()*mImgMatrix.m11() * 100)) + "%", DkStatusBar::status_zoom_info);
479 }
480 
zoomTo(double zoomLevel)481 void DkViewPort::zoomTo(double zoomLevel) {
482 
483 	mWorldMatrix.reset();
484 	zoom(zoomLevel/mImgMatrix.m11());
485 }
486 
zoomToFit()487 void DkViewPort::zoomToFit() {
488 
489 	QSizeF imgSize = getImageSize();
490 	QSizeF winSize = size();
491 	double zoomLevel = qMin(winSize.width() / imgSize.width(), winSize.height() / imgSize.height());
492 
493 	if (zoomLevel > 1)
494 		zoomTo(zoomLevel);
495 	else if (zoomLevel < 1)
496 		resetView();
497 	else if (zoomLevel == 1 && mLoader && mLoader->hasSvg())
498 		resetView();
499 }
500 
resetView()501 void DkViewPort::resetView() {
502 
503 	mWorldMatrix.reset();
504 	showZoom();
505 	changeCursor();
506 
507 	update();
508 	controlImagePosition();
509 
510 	emit zoomSignal(mWorldMatrix.m11()*mImgMatrix.m11() * 100);
511 	tcpSynchronize();
512 }
513 
fullView()514 void DkViewPort::fullView() {
515 
516 	QPointF p = mViewportRect.center();
517 	zoom(1.0/(mImgMatrix.m11()*mWorldMatrix.m11()), p.toPoint(), true);
518 
519 	emit zoomSignal(mWorldMatrix.m11()*mImgMatrix.m11() * 100);
520 	changeCursor();
521 	update();
522 }
523 
showZoom()524 void DkViewPort::showZoom() {
525 
526 	// don't show zoom if we are in fullscreen mode
527 	if (isFullScreen() || DkSettingsManager::param().app().hideAllPanels)
528 		return;
529 
530 	QString zoomStr;
531 	zoomStr.sprintf("%.1f%%", mImgMatrix.m11()*mWorldMatrix.m11()*100);
532 
533 	if (!mController->getZoomWidget()->isVisible())
534 		mController->setInfo(zoomStr, 3000, DkControlWidget::bottom_left_label);
535 }
536 
repeatZoom()537 void DkViewPort::repeatZoom() {
538 
539 	qDebug() << "repeating...";
540 	if ( (DkSettingsManager::param().display().invertZoom && QApplication::mouseButtons() == Qt::XButton1) ||
541 		(!DkSettingsManager::param().display().invertZoom && QApplication::mouseButtons() == Qt::XButton2)) {
542 		zoom(1.1f);
543 	}
544 	else if (	(!DkSettingsManager::param().display().invertZoom && QApplication::mouseButtons() == Qt::XButton1) ||
545 				( DkSettingsManager::param().display().invertZoom && QApplication::mouseButtons() == Qt::XButton2)) {
546 		zoom(0.9f);
547 	}
548 	else {
549 		mRepeatZoomTimer->stop();	// safety if we don't catch the release
550 	}
551 }
552 
toggleResetMatrix()553 void DkViewPort::toggleResetMatrix() {
554 
555 	DkSettingsManager::param().display().keepZoom = !DkSettingsManager::param().display().keepZoom;
556 }
557 
updateImageMatrix()558 void DkViewPort::updateImageMatrix() {
559 
560 	if (mImgStorage.isEmpty())
561 		return;
562 
563 	QRectF oldImgRect = mImgViewRect;
564 	QTransform oldImgMatrix = mImgMatrix;
565 
566 	mImgMatrix.reset();
567 
568 	QSize imgSize = getImageSize();
569 
570 	// if the image is smaller or zoom is active: paint the image as is
571 	if (!mViewportRect.contains(mImgRect.toRect()))
572 		mImgMatrix = getScaledImageMatrix();
573 	else {
574 		mImgMatrix.translate((float)(getMainGeometry().width()-imgSize.width())*0.5f, (float)(getMainGeometry().height()-imgSize.height())*0.5f);
575 		mImgMatrix.scale(1.0f, 1.0f);
576 	}
577 
578 	mImgViewRect = mImgMatrix.mapRect(mImgRect);
579 
580 	// update world matrix?
581 	// mWorldMatrix.m11() != 1
582 	if (qAbs(mWorldMatrix.m11()-1.0) > 1e-4) {
583 
584 		float scaleFactor = (float)(oldImgMatrix.m11()/mImgMatrix.m11());
585 		double dx = oldImgRect.x()/scaleFactor-mImgViewRect.x();
586 		double dy = oldImgRect.y()/scaleFactor-mImgViewRect.y();
587 
588 		mWorldMatrix.scale(scaleFactor, scaleFactor);
589 		mWorldMatrix.translate(dx, dy);
590 	}
591 	// NOTE: this is not the same as resetView!
592 	else if (DkSettingsManager::param().display().zoomToFit)
593 		zoomToFit();
594 
595 }
596 
tcpSetTransforms(QTransform newWorldMatrix,QTransform newImgMatrix,QPointF canvasSize)597 void DkViewPort::tcpSetTransforms(QTransform newWorldMatrix, QTransform newImgMatrix, QPointF canvasSize) {
598 
599 	// ok relative transform
600 	if (canvasSize.isNull()) {
601 		moveView(QPointF(newWorldMatrix.dx(), newWorldMatrix.dy())/mWorldMatrix.m11());
602 	}
603 	else {
604 		mWorldMatrix = newWorldMatrix;
605 		mImgMatrix = newImgMatrix;
606 		updateImageMatrix();
607 
608 		QPointF imgPos = QPointF(canvasSize.x()*getImageSize().width(), canvasSize.y()*getImageSize().height());
609 
610 		// go to screen coordinates
611 		imgPos = mImgMatrix.map(imgPos);
612 
613 		// go to world coordinates
614 		imgPos = mWorldMatrix.map(imgPos);
615 
616 		// compute difference to current mViewport center - in world coordinates
617 		imgPos = QPointF(width()*0.5f, height()*0.5f) - imgPos;
618 
619 		// back to screen coordinates
620 		float s = (float)mWorldMatrix.m11();
621 		mWorldMatrix.translate(imgPos.x()/s, imgPos.y()/s);
622 	}
623 
624 	update();
625 }
626 
tcpSetWindowRect(QRect rect)627 void DkViewPort::tcpSetWindowRect(QRect rect) {
628 	this->setGeometry(rect);
629 }
630 
tcpForceSynchronize()631 void DkViewPort::tcpForceSynchronize() {
632 	tcpSynchronize(QTransform(), true);
633 }
634 
tcpSynchronize(QTransform relativeMatrix,bool force)635 void DkViewPort::tcpSynchronize(QTransform relativeMatrix, bool force) {
636 
637 	if (!relativeMatrix.isIdentity()) {
638 		emit sendTransformSignal(relativeMatrix, QTransform(), QPointF());
639 		return;
640 	}
641 
642 	// check if we need a synchronization
643 	if ((force || qApp->keyboardModifiers() == mAltMod ||
644 		DkSettingsManager::param().sync().syncActions) &&
645 		(hasFocus() || mController->hasFocus())) {
646 		QPointF size = QPointF(geometry().width()/2.0f, geometry().height()/2.0f);
647 		size = mWorldMatrix.inverted().map(size);
648 		size = mImgMatrix.inverted().map(size);
649 		size = QPointF(size.x()/(float)getImageSize().width(), size.y()/(float)getImageSize().height());
650 
651 		emit sendTransformSignal(mWorldMatrix, mImgMatrix, size);
652 	}
653 }
654 
applyPlugin(DkPluginContainer * plugin,const QString & key)655 void DkViewPort::applyPlugin(DkPluginContainer* plugin, const QString& key) {
656 
657 #ifdef WITH_PLUGINS
658 	if (!plugin)
659 		return;
660 
661 	DkBatchPluginInterface* bPlugin = plugin->batchPlugin();
662 	if (bPlugin)
663 		bPlugin->loadSettings();
664 
665 	QSharedPointer<DkImageContainerT> result = DkImageContainerT::fromImageContainer(plugin->plugin()->runPlugin(key, imageContainer()));
666 	if (result)
667 		setEditedImage(result);
668 
669 	plugin->setActive(false);
670 #else
671 	Q_UNUSED(plugin);
672 	Q_UNUSED(key);
673 #endif
674 }
675 
getImage() const676 QImage DkViewPort::getImage() const {
677 
678 	if (imageContainer() && (!mSvg || !mSvg->isValid()) && (!mMovie || !mMovie->isValid()))
679 		return imageContainer()->image();
680 
681 	return DkBaseViewPort::getImage();
682 }
683 
resizeImage()684 void DkViewPort::resizeImage() {
685 
686 	if (!mResizeDialog)
687 		mResizeDialog = new DkResizeDialog(this);
688 
689 	QSharedPointer<DkImageContainerT> imgC = imageContainer();
690 	QSharedPointer<DkMetaDataT> metaData;
691 
692 	if (imgC) {
693 		metaData = imgC->getMetaData();
694 		QVector2D res = metaData->getResolution();
695 		mResizeDialog->setExifDpi((float)res.x());
696 	}
697 
698 	if (!imgC) {
699 		qWarning() << "cannot resize empty image...";
700 		return;
701 	}
702 
703 	mResizeDialog->setImage(imgC->image());
704 
705 	if (!mResizeDialog->exec())
706 		return;
707 
708 	if (mResizeDialog->resample()) {
709 
710 		QImage rImg = mResizeDialog->getResizedImage();
711 
712 		if (!rImg.isNull()) {
713 
714 			// this reloads the image -> that's not what we want!
715 			if (metaData)
716 				metaData->setResolution(QVector2D(mResizeDialog->getExifDpi(), mResizeDialog->getExifDpi()));
717 
718 			imgC->setImage(rImg, tr("Resize"));
719 			setEditedImage(imgC);
720 		}
721 	}
722 	else if (metaData) {
723 		// ok, user just wants to change the resolution
724 		metaData->setResolution(QVector2D(mResizeDialog->getExifDpi(), mResizeDialog->getExifDpi()));
725 		qDebug() << "setting resolution to: " << mResizeDialog->getExifDpi();
726 	}
727 }
728 
deleteImage()729 void DkViewPort::deleteImage() {
730 
731 	auto imgC = imageContainer();
732 
733 	if (!imgC || !imgC->hasImage())
734 		return;
735 
736 	getController()->applyPluginChanges(true);
737 
738 	QFileInfo fileInfo(imgC->filePath());
739 	QString question;
740 
741 #if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
742 	question = tr("Shall I move %1 to trash?").arg(fileInfo.fileName());
743 #else
744 	question = tr("Do you want to permanently delete %1?").arg(fileInfo.fileName());
745 #endif
746 
747 	DkMessageBox* msgBox = new DkMessageBox(
748 		QMessageBox::Question,
749 		tr("Delete File"),
750 		question,
751 		(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel),
752 		this);
753 
754 	msgBox->setDefaultButton(QMessageBox::Yes);
755 	msgBox->setObjectName("deleteFileDialog");
756 
757 	int answer = msgBox->exec();
758 
759 	if (answer == QMessageBox::Accepted || answer == QMessageBox::Yes) {
760 		stopMovie();	// movies keep file handles so stop it before we can delete files
761 
762 		if (!mLoader->deleteFile())
763 			loadMovie();	// load the movie again, if we could not delete it
764 	}
765 }
766 
saveFile()767 void DkViewPort::saveFile() {
768 	saveFileAs(true);
769 }
770 
saveFileAs(bool silent)771 void DkViewPort::saveFileAs(bool silent) {
772 
773 	if (mLoader) {
774 		mController->closePlugin(false);
775 
776 		QImage img = getImage();
777 
778 		if (mLoader->hasSvg() && !mLoader->isEdited()) {
779 
780 			DkSvgSizeDialog* sd = new DkSvgSizeDialog(img.size(), DkUtils::getMainWindow());
781 			sd->resize(270, 120);
782 
783 			int answer = sd->exec();
784 
785 			if (answer == QDialog::Accepted) {
786 
787 				img = QImage(sd->size(), QImage::Format_ARGB32);
788 				img.fill(QColor(0, 0, 0, 0));
789 
790 				QPainter p(&img);
791 				mSvg->render(&p, QRectF(QPointF(), sd->size()));
792 			}
793 		}
794 
795 		mLoader->saveUserFileAs(img, silent);
796 	}
797 }
798 
saveFileWeb()799 void DkViewPort::saveFileWeb() {
800 	if (mLoader) {
801 		mController->closePlugin(false);
802 		mLoader->saveFileWeb(getImage());
803 	}
804 }
805 
setAsWallpaper()806 void DkViewPort::setAsWallpaper() {
807 
808 	// based on code from: http://qtwiki.org/Set_windows_background_using_QT
809 	auto imgC = imageContainer();
810 
811 	if (!imgC || !imgC->hasImage()) {
812 		qWarning() << "cannot create wallpaper because there is no image loaded...";
813 	}
814 
815 	QImage img = imgC->image();
816 	QString tmpPath = mLoader->saveTempFile(img, "wallpaper", ".jpg", true, false);
817 
818 	// is there a more elegant way to see if saveTempFile returned an empty path
819 	if (tmpPath.isEmpty()) {
820 		QMessageBox::critical(this, tr("Error"), tr("Sorry, I could not create a wallpaper..."));
821 		return;
822 	}
823 
824 #ifdef Q_OS_WIN
825 
826 	//Read current windows background image path
827 	QSettings appSettings("HKEY_CURRENT_USER\\Control Panel\\Desktop", QSettings::NativeFormat);
828 	appSettings.setValue("Wallpaper", tmpPath);
829 
830 	QByteArray ba = tmpPath.toLatin1();
831 	SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, (void*)ba.data(), SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
832 #endif
833 	// TODO: add functionality for unix based systems
834 }
835 
applyManipulator()836 void DkViewPort::applyManipulator() {
837 
838 	QAction* action = dynamic_cast<QAction*>(QObject::sender());
839 
840 	if (!action) {
841 		qWarning() << "applyManipulator is not called from its action!";
842 		return;
843 	}
844 
845 	DkActionManager& am = DkActionManager::instance();
846 	QSharedPointer<DkBaseManipulator> mpl = am.manipulatorManager().manipulator(action);
847 
848 	if (!mpl) {
849 		qWarning() << "could not find manipulator for:" << action;
850 		return;
851 	}
852 
853 	// try to cast up
854 	QSharedPointer<DkBaseManipulatorExt> mplExt = qSharedPointerDynamicCast<DkBaseManipulatorExt>(mpl);
855 
856 	// mark dirty
857 	if (mManipulatorWatcher.isRunning() && mplExt && mActiveManipulator == mpl) {
858 		mplExt->setDirty(true);
859 		return;
860 	}
861 
862 	if (mManipulatorWatcher.isRunning()) {
863 		mController->setInfo(tr("Busy"));
864 		return;
865 	}
866 
867 	// show the dock (in case it's not shown yet)
868 	if (mplExt) {
869 		am.action(DkActionManager::menu_edit_image)->setChecked(true);
870 	}
871 
872 	// undo last if it is an extended manipulator
873 	QImage img;
874 	if (mplExt && imageContainer()) {
875 
876 		auto l = imageContainer()->getLoader();
877 		l->setMinHistorySize(3);	// increase the min history size to 3 for correctly popping back
878 		if (!l->history()->isEmpty() && l->lastEdit().editName() == mplExt->name()) {
879 			imageContainer()->undo();
880 		}
881 
882 		img = imageContainer()->image();
883 	}
884 	else
885 		img = getImage();
886 
887 	mManipulatorWatcher.setFuture(
888 		QtConcurrent::run(
889 			mpl.data(),
890 			&nmc::DkBaseManipulator::apply,
891 			img));
892 
893 	mActiveManipulator = mpl;
894 
895 	emit showProgress(true, 500);
896 }
897 
manipulatorApplied()898 void DkViewPort::manipulatorApplied() {
899 
900 	DkGlobalProgress::instance().stop();
901 
902 	if (mManipulatorWatcher.isCanceled() || !mActiveManipulator) {
903 		qDebug() << "manipulator applied - but it's canceled";
904 		return;
905 	}
906 
907 	// trigger again if it's dirty
908 	QSharedPointer<DkBaseManipulatorExt> mplExt = qSharedPointerDynamicCast<DkBaseManipulatorExt>(mActiveManipulator);
909 
910 	// set the edited image
911 	QImage img = mManipulatorWatcher.result();
912 
913 	if (!img.isNull())
914 		setEditedImage(img, mActiveManipulator->name());
915 	else
916 		mController->setInfo(mActiveManipulator->errorMessage());
917 
918 	if (mplExt && mplExt->isDirty()) {
919 		mplExt->setDirty(false);
920 		mplExt->action()->trigger();
921 		qDebug() << "triggering manipulator - it's dirty";
922 	}
923 
924 	emit showProgress(false);
925 }
926 
paintEvent(QPaintEvent * event)927 void DkViewPort::paintEvent(QPaintEvent* event) {
928 
929 	QPainter painter(viewport());
930 
931 	if (!mImgStorage.isEmpty()) {
932 
933 		// usually the QGraphicsView should do this - but we have seen issues(e.g. #706)
934 		painter.setPen(Qt::NoPen);
935 		painter.setBrush(backgroundBrush());
936 		painter.drawRect(QRect(QPoint(), size()));
937 
938 		painter.setWorldTransform(mWorldMatrix);
939 
940 		//	interpolate between 100% and max interpolate level
941 		if (!mForceFastRendering && // force?
942 			mImgMatrix.m11()*mWorldMatrix.m11() - DBL_EPSILON > 1.0 && // @100% ?
943 			mImgMatrix.m11()*mWorldMatrix.m11() <= DkSettingsManager::param().display().interpolateZoomLevel / 100.0) {	// > max zoom level
944 			painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing);
945 		}
946 
947 		if (DkSettingsManager::param().display().transition == DkSettings::trans_swipe &&
948 			!mAnimationBuffer.isNull()) {
949 
950 			double dx = mNextSwipe ? width()*(mAnimationValue) : -width()*(mAnimationValue);
951 
952 			QTransform swipeTransform;
953 			swipeTransform.translate(dx, 0);
954 			painter.setTransform(swipeTransform);
955 		}
956 
957 		// TODO: if fading is active we interpolate with background instead of the other image
958 		double opacity = (DkSettingsManager::param().display().transition == DkSettings::trans_fade) ? 1.0 - mAnimationValue : 1.0;
959 		draw(painter, opacity);
960 
961 		if (!mAnimationBuffer.isNull() && mAnimationValue > 0) {
962 
963 			float oldOp = (float)painter.opacity();
964 
965 			// fade transition
966 			if (DkSettingsManager::param().display().transition == DkSettings::trans_fade) {
967 				painter.setOpacity(mAnimationValue);
968 			}
969 			else if (DkSettingsManager::param().display().transition == DkSettings::trans_swipe) {
970 
971 				double dx = mNextSwipe ? -width()*(1.0-mAnimationValue) : width()*(1.0-mAnimationValue);
972 				QTransform swipeTransform;
973 				swipeTransform.translate(dx, 0);
974 				painter.setTransform(swipeTransform);
975 			}
976 
977 			qDebug() << "render hints:" << painter.renderHints();
978 			painter.drawImage(mFadeImgViewRect, mAnimationBuffer, mAnimationBuffer.rect());
979 			painter.setOpacity(oldOp);
980 		}
981 
982 		// now disable world matrix for overlay display
983 		painter.setWorldMatrixEnabled(false);
984 	}
985 	else
986 		drawBackground(painter);
987 
988 	// draw the cropping rect
989 	if (!mCropRect.isEmpty() && DkSettingsManager::param().display().showCrop && imageContainer()) {
990 
991 		// create path
992 		QPainterPath path;
993 		path.addRect(getImageViewRect().toRect());
994 
995 		DkRotatingRect r = mCropRect;
996 		QPolygonF polyF;
997 		polyF = r.getClosedPoly();
998 		polyF = mImgMatrix.map(polyF);
999 		polyF = mWorldMatrix.map(polyF);
1000 		path.addPolygon(polyF.toPolygon());
1001 
1002 		painter.setPen(Qt::NoPen);
1003 		painter.setBrush(QColor(0,0,0,100));
1004 		painter.drawPath(path);
1005 	}
1006 
1007 	painter.end();
1008 
1009 	// propagate
1010 	QGraphicsView::paintEvent(event);
1011 }
1012 
leaveEvent(QEvent * event)1013 void DkViewPort::leaveEvent(QEvent* event) {
1014 
1015 	// hide navigation buttons if the mouse leaves the viewport
1016 	mNavigationWidget->hide();
1017 	DkBaseViewPort::leaveEvent(event);
1018 }
1019 
1020 // drawing functions --------------------------------------------------------------------
drawBackground(QPainter & painter)1021 void DkViewPort::drawBackground(QPainter & painter) {
1022 
1023 	// fit to mViewport
1024 	QSize s = mImgBg.size();
1025 	if (s.width() > (float)(size().width()*0.5))
1026 		s = s*((size().width()*0.5)/s.width());
1027 
1028 	if (s.height() > size().height()*0.6)
1029 		s = s*((size().height()*0.6)/s.height());
1030 
1031 	QRect bgRect(QPoint(), s);
1032 	bgRect.moveBottomRight(QPoint(width()-20, height()-20));
1033 
1034 	painter.drawImage(bgRect, mImgBg, QRect(QPoint(), mImgBg.size()));
1035 }
1036 
loadMovie()1037 void DkViewPort::loadMovie() {
1038 
1039 	if (!mLoader)
1040 		return;
1041 
1042 	if (mMovie)
1043 		mMovie->stop();
1044 
1045 	// check if it truely a movie (we need this for we don't know if webp is actually animated)
1046 	QSharedPointer<QMovie> m(new QMovie(mLoader->filePath()));
1047 	if (m->frameCount() == 1)
1048 		return;
1049 
1050 	mMovie = m;
1051 
1052 	connect(mMovie.data(), SIGNAL(frameChanged(int)), this, SLOT(update()));
1053 	mMovie->start();
1054 
1055 	emit movieLoadedSignal(true);
1056 }
1057 
loadSvg()1058 void DkViewPort::loadSvg() {
1059 
1060 	if (!mLoader)
1061 		return;
1062 
1063 	auto cc = mLoader->getCurrentImage();
1064 	if (cc) {
1065 		mSvg = QSharedPointer<QSvgRenderer>(
1066 			new QSvgRenderer(
1067 			*cc->getFileBuffer()
1068 			));
1069 	}
1070 	else {
1071 		mSvg = QSharedPointer<QSvgRenderer>(
1072 			new QSvgRenderer(
1073 			mLoader->filePath()
1074 			));
1075 	}
1076 
1077 	connect(mSvg.data(), SIGNAL(repaintNeeded()), this, SLOT(update()));
1078 
1079 }
1080 
pauseMovie(bool pause)1081 void DkViewPort::pauseMovie(bool pause) {
1082 
1083 	if (!mMovie)
1084 		return;
1085 
1086 	mMovie->setPaused(pause);
1087 }
1088 
nextMovieFrame()1089 void DkViewPort::nextMovieFrame() {
1090 
1091 	if (!mMovie)
1092 		return;
1093 
1094 	mMovie->jumpToNextFrame();
1095 	update();
1096 }
1097 
previousMovieFrame()1098 void DkViewPort::previousMovieFrame() {
1099 
1100 	if (!mMovie)
1101 		return;
1102 
1103 
1104 	int fn = mMovie->currentFrameNumber()-1;
1105 	if (fn == -1)
1106 		fn = mMovie->frameCount()-1;
1107 	//qDebug() << "retrieving frame: " << fn;
1108 
1109 	while(mMovie->currentFrameNumber() != fn)
1110 		mMovie->jumpToNextFrame();
1111 
1112 	//// the subsequent thing is not working if the movie is paused
1113 	//bool success = movie->jumpToFrame(movie->currentFrameNumber()-1);
1114 	update();
1115 }
1116 
stopMovie()1117 void DkViewPort::stopMovie() {
1118 
1119 	if (!mMovie)
1120 		return;
1121 
1122 	mMovie->stop();
1123 	mMovie = QSharedPointer<QMovie>();
1124 }
1125 
drawPolygon(QPainter & painter,const QPolygon & polygon)1126 void DkViewPort::drawPolygon(QPainter & painter, const QPolygon & polygon) {
1127 
1128 	QPoint lastPoint;
1129 
1130 	for (const QPoint& p : polygon) {
1131 
1132 		if (!lastPoint.isNull())
1133 			painter.drawLine(p, lastPoint);
1134 
1135 		lastPoint = p;
1136 	}
1137 
1138 }
1139 
1140 // event listeners --------------------------------------------------------------------
resizeEvent(QResizeEvent * event)1141 void DkViewPort::resizeEvent(QResizeEvent *event) {
1142 
1143 	mViewportRect = QRect(0, 0, width(), height());
1144 
1145 	// >DIR: diem - bug if zoom factor is large and window becomes small
1146 	updateImageMatrix();
1147 	centerImage();
1148 	changeCursor();
1149 
1150 	mController->getOverview()->setViewPortRect(geometry());
1151 	mController->resize(width(), height());
1152 
1153 	return QGraphicsView::resizeEvent(event);
1154 }
1155 
1156 // mouse events --------------------------------------------------------------------
event(QEvent * event)1157 bool DkViewPort::event(QEvent *event) {
1158 
1159 	// ok obviously QGraphicsView eats all mouse events -> so we simply redirect these to QWidget in order to get them delivered here
1160 	if (event->type() == QEvent::MouseButtonPress ||
1161 		event->type() == QEvent::MouseButtonDblClick ||
1162 		event->type() == QEvent::MouseButtonRelease ||
1163 		event->type() == QEvent::MouseMove ||
1164 		event->type() == QEvent::Wheel ||
1165 		event->type() == QEvent::KeyPress ||
1166 		event->type() == QEvent::KeyRelease ||
1167 		event->type() == QEvent::DragEnter ||
1168 
1169 		event->type() == QEvent::Drop) {
1170 
1171 		//qDebug() << "redirecting event...";
1172 		// mouse events that double are now fixed, since the mViewport is now overlayed by the mController
1173 		return QWidget::event(event);
1174 	}
1175 	else {
1176 		//qDebug() << "not redirecting - type: " << event->type();
1177 		return DkBaseViewPort::event(event);
1178 	}
1179 }
1180 
dragLeaveEvent(QDragLeaveEvent * event)1181 void DkViewPort::dragLeaveEvent(QDragLeaveEvent *event) {
1182 
1183 	event->accept();
1184 }
1185 
mousePressEvent(QMouseEvent * event)1186 void DkViewPort::mousePressEvent(QMouseEvent *event) {
1187 
1188 	// if zoom on wheel, the additional keys should be used for switching files
1189 	if (DkSettingsManager::param().global().zoomOnWheel) {
1190 		if(event->buttons() == Qt::XButton1)
1191 			loadPrevFileFast();
1192 		else if(event->buttons() == Qt::XButton2)
1193 			loadNextFileFast();
1194 	}
1195 	else if(event->buttons() == Qt::XButton1 || event->buttons() == Qt::XButton2) {
1196 		repeatZoom();
1197 		mRepeatZoomTimer->start();
1198 	}
1199 
1200 	// ok, start panning
1201 	if (mWorldMatrix.m11() > 1 && !imageInside() && event->buttons() == Qt::LeftButton) {
1202 		setCursor(Qt::ClosedHandCursor);
1203 		mPosGrab = event->pos();
1204 	}
1205 
1206 	// keep in mind if the gesture was started in the mViewport
1207 	// this fixes issues if some HUD widgets or child widgets
1208 	// do not implement mouse events correctly
1209 	if (event->buttons() == Qt::LeftButton)
1210 		mGestureStarted = true;
1211 	else
1212 		mGestureStarted = false;
1213 
1214 	// should be sent to QWidget?!
1215 	DkBaseViewPort::mousePressEvent(event);
1216 }
1217 
mouseReleaseEvent(QMouseEvent * event)1218 void DkViewPort::mouseReleaseEvent(QMouseEvent *event) {
1219 
1220 	mRepeatZoomTimer->stop();
1221 
1222 	int sa = swipeRecognition(event->pos(), mPosGrab.toPoint());
1223 	QPoint pos = mapToImage(event->pos());
1224 
1225 	if (imageInside() && mGestureStarted) {
1226 		swipeAction(sa);
1227 	}
1228 
1229 	// needed for scientific projects...
1230 	if (pos.x() != -1 && pos.y() != -1)
1231 		emit mouseClickSignal(event, pos);
1232 
1233 	mGestureStarted = false;
1234 
1235 	DkBaseViewPort::mouseReleaseEvent(event);
1236 }
1237 
mouseMoveEvent(QMouseEvent * event)1238 void DkViewPort::mouseMoveEvent(QMouseEvent *event) {
1239 
1240 	if (DkSettingsManager::param().display().showNavigation &&
1241 		event->modifiers() == Qt::NoModifier &&
1242 		event->buttons() == Qt::NoButton) {
1243 
1244 		int left = qMin(100, qRound(0.1 * width()));
1245 		int right = qMax(width()-100, qRound(0.9 * width()));
1246 
1247 		if (event->pos().x() < left)
1248 			mNavigationWidget->showPrevious();
1249 		else if (event->pos().x() > right)
1250 			mNavigationWidget->showNext();
1251 		else if (mNavigationWidget->isVisible())
1252 			mNavigationWidget->hide();
1253 	}
1254 
1255 	//qDebug() << "mouse move (DkViewPort)";
1256 	//changeCursor();
1257 	mCurrentPixelPos = event->pos();
1258 
1259 	if (DkStatusBarManager::instance().statusbar()->isVisible())
1260 		getPixelInfo(event->pos());
1261 
1262 	if (mWorldMatrix.m11() > 1 && event->buttons() == Qt::LeftButton) {
1263 
1264 		QPointF cPos = event->pos();
1265 		QPointF dxy = (cPos - mPosGrab);
1266 		mPosGrab = cPos;
1267 		moveView(dxy/mWorldMatrix.m11());
1268 
1269 		// with shift also a hotkey for fast switching...
1270 		if ((DkSettingsManager::param().sync().syncAbsoluteTransform &&
1271 			event->modifiers() == (mAltMod | Qt::ShiftModifier)) ||
1272 			(!DkSettingsManager::param().sync().syncAbsoluteTransform &&
1273 			event->modifiers() == (mAltMod))) {
1274 
1275 			if (dxy.x() != 0 || dxy.y() != 0) {
1276 				QTransform relTransform;
1277 				relTransform.translate(dxy.x(), dxy.y());
1278 				tcpSynchronize(relTransform);
1279 			}
1280 		}
1281         // absolute transformation
1282         else
1283 		    tcpSynchronize();
1284 	}
1285 
1286 	int dist = QPoint(event->pos()-mPosGrab.toPoint()).manhattanLength();
1287 
1288 	// drag & drop action
1289 	if (event->buttons() == Qt::LeftButton
1290 		&& dist > QApplication::startDragDistance()
1291 		&& imageInside()
1292 		&& !getImage().isNull()
1293 		&& mLoader
1294 		&& !QApplication::widgetAt(event->globalPos())) {	// is NULL if the mouse leaves the window
1295 
1296 			QMimeData* mimeData = createMime();
1297 
1298 			QPixmap pm;
1299 			if (!getImage().isNull())
1300 				pm = QPixmap::fromImage(mImgStorage.image().scaledToHeight(73, Qt::SmoothTransformation));
1301 			if (pm.width() > 130)
1302 				pm = pm.scaledToWidth(100, Qt::SmoothTransformation);
1303 
1304 			QDrag* drag = new QDrag(this);
1305 			drag->setMimeData(mimeData);
1306 			drag->setPixmap(pm);
1307 			drag->exec(Qt::CopyAction);
1308 	}
1309 
1310 	// send to parent
1311 	DkBaseViewPort::mouseMoveEvent(event);
1312 }
1313 
wheelEvent(QWheelEvent * event)1314 void DkViewPort::wheelEvent(QWheelEvent *event) {
1315 
1316 	if ((!DkSettingsManager::param().global().zoomOnWheel && event->modifiers() != mCtrlMod) ||
1317 		(DkSettingsManager::param().global().zoomOnWheel && (event->modifiers() & mCtrlMod ||
1318 		(DkSettingsManager::param().global().horZoomSkips && event->orientation() == Qt::Horizontal && !(event->modifiers() & mAltMod))))) {
1319 
1320 		if (event->delta() < 0)
1321 			loadNextFileFast();
1322 		else
1323 			loadPrevFileFast();
1324 	}
1325 	else
1326 		DkBaseViewPort::wheelEvent(event);
1327 
1328 	tcpSynchronize();
1329 
1330 }
1331 
swipeRecognition(QPoint start,QPoint end)1332 int DkViewPort::swipeRecognition(QPoint start, QPoint end) {
1333 
1334 	DkVector vec((float)(start.x()-end.x()), (float)(start.y()-end.y()));
1335 
1336 	if (fabs(vec.norm()) < 100)
1337 		return no_swipe;
1338 
1339 	double angle = DkMath::normAngleRad(vec.angle(DkVector(0,1)), 0.0, CV_PI);
1340 	bool horizontal = false;
1341 
1342 	if (angle > CV_PI*0.3 && angle < CV_PI*0.6)
1343 		horizontal = true;
1344 	else if (angle < 0.2*CV_PI || angle > 0.8*CV_PI)
1345 		horizontal = false;
1346 	else
1347 		return no_swipe;	// angles ~45� are not accepted
1348 
1349 	QPoint startPos = QWidget::mapFromGlobal(end);
1350 	qDebug() << "vec: " << vec.x << ", " << vec.y;
1351 
1352 	if (horizontal) {
1353 
1354 		if (vec.x < 0)
1355 			return next_image;
1356 		else
1357 			return prev_image;
1358 
1359 	}
1360 	// upper part of the canvas is thumbs
1361 	else if (!horizontal && startPos.y() < height()*0.5f) {
1362 
1363 		// downward gesture is opening
1364 		if (vec.y > 0)
1365 			return open_thumbs;
1366 		else
1367 			return close_thumbs;
1368 	}
1369 	// lower part of the canvas is thumbs
1370 	else if (!horizontal && startPos.y() > height()*0.5f) {
1371 
1372 		// upward gesture is opening
1373 		if (vec.y < 0)
1374 			return open_metadata;
1375 		else
1376 			return close_metadata;
1377 	}
1378 
1379 	return no_swipe;
1380 }
1381 
swipeAction(int swipeGesture)1382 void DkViewPort::swipeAction(int swipeGesture) {
1383 
1384 	assert(mController);
1385 
1386 	switch (swipeGesture) {
1387 	case next_image:
1388 		loadNextFileFast();
1389 		break;
1390 	case prev_image:
1391 		loadPrevFileFast();
1392 		break;
1393 	case open_thumbs:
1394 		mController->showPreview(true);
1395 		break;
1396 	case close_thumbs:
1397 		mController->showPreview(false);
1398 		break;
1399 	case open_metadata:
1400 		mController->showMetaData(true);
1401 		break;
1402 	case close_metadata:
1403 		mController->showMetaData(false);
1404 		break;
1405 	default:
1406 		break;
1407 	}
1408 }
1409 
setFullScreen(bool fullScreen)1410 void DkViewPort::setFullScreen(bool fullScreen) {
1411 
1412 	assert(mController);
1413 	mController->setFullScreen(fullScreen);
1414 	toggleLena(fullScreen);
1415 
1416 	if (fullScreen)
1417 		QWidget::setWindowState(windowState() ^ Qt::WindowFullScreen);
1418 	else
1419 		QWidget::setWindowState(windowState() & ~Qt::WindowFullScreen);
1420 
1421 	if (fullScreen)
1422 		mHideCursorTimer->start();
1423 	else
1424 		unsetCursor();
1425 }
1426 
mapToImage(const QPoint & windowPos) const1427 QPoint DkViewPort::mapToImage(const QPoint& windowPos) const {
1428 
1429 	QPointF imgPos = mWorldMatrix.inverted().map(QPointF(windowPos));
1430 	imgPos = mImgMatrix.inverted().map(imgPos);
1431 
1432 	QPoint xy(qFloor(imgPos.x()), qFloor(imgPos.y()));
1433 
1434 	if (xy.x() < 0 || xy.y() < 0 || xy.x() >= getImageSize().width() || xy.y() >= getImageSize().height())
1435 		return QPoint(-1,-1);
1436 
1437 	return xy;
1438 }
1439 
getPixelInfo(const QPoint & pos)1440 void DkViewPort::getPixelInfo(const QPoint& pos) {
1441 
1442 	if (mImgStorage.isEmpty())
1443 		return;
1444 
1445 	QPoint xy = mapToImage(pos);
1446 
1447 	if (xy.x() == -1 || xy.y() == -1)
1448 		return;
1449 
1450 	QColor col = getImage().pixel(xy);
1451 
1452 	QString msg = "x: " + QString::number(xy.x()) + " y: " + QString::number(xy.y()) +
1453 		" | r: " + QString::number(col.red()) + " g: " + QString::number(col.green()) + " b: " + QString::number(col.blue());
1454 
1455 	if (mImgStorage.image().hasAlphaChannel())
1456 		msg += " a: " + QString::number(col.alpha());
1457 
1458 	msg += " | " + col.name().toUpper();
1459 
1460 	DkStatusBarManager::instance().setMessage(msg, DkStatusBar::status_pixel_info);
1461 }
1462 
getCurrentPixelHexValue()1463 QString DkViewPort::getCurrentPixelHexValue() {
1464 
1465 	if (mImgStorage.isEmpty() || mCurrentPixelPos.isNull())
1466 		return QString();
1467 
1468 	QPointF imgPos = mWorldMatrix.inverted().map(QPointF(mCurrentPixelPos));
1469 	imgPos = mImgMatrix.inverted().map(imgPos);
1470 
1471 	QPoint xy(qFloor(imgPos.x()), qFloor(imgPos.y()));
1472 
1473 	if (xy.x() < 0 || xy.y() < 0 || xy.x() >= getImageSize().width() || xy.y() >= getImageSize().height())
1474 		return QString();
1475 
1476 	QColor col = getImage().pixel(xy);
1477 
1478 	return col.name().toUpper().remove(0,1);
1479 }
1480 
1481 // Copy & Paste --------------------------------------------------------
copyPixelColorValue()1482 void DkViewPort::copyPixelColorValue() {
1483 
1484 	if (getImage().isNull())
1485 		return;
1486 
1487 	QMimeData* mimeData = new QMimeData;
1488 
1489 	if (!getImage().isNull())
1490 		mimeData->setText(getCurrentPixelHexValue());
1491 
1492 	QClipboard* clipboard = QApplication::clipboard();
1493 	clipboard->setMimeData(mimeData);
1494 }
1495 
copyImage()1496 void DkViewPort::copyImage() {
1497 
1498 	QMimeData* mimeData = createMime();
1499 
1500 	QClipboard* clipboard = QApplication::clipboard();
1501 	clipboard->setMimeData(mimeData);
1502 }
1503 
createMime() const1504 QMimeData * DkViewPort::createMime() const {
1505 
1506 	if (getImage().isNull() || !mLoader)
1507 		return 0;
1508 
1509 	// NOTE: if we do the file:/// thingy, we will get into problems with mounted drives (e.g. //hermes...)
1510 	QUrl fileUrl = QUrl::fromLocalFile(mLoader->filePath());
1511 
1512 	QList<QUrl> urls;
1513 	urls.append(fileUrl);
1514 
1515 	QMimeData* mimeData = new QMimeData;
1516 
1517 	if (QFileInfo(mLoader->filePath()).exists() && !mLoader->isEdited()) {
1518 		mimeData->setUrls(urls);
1519 		mimeData->setText(fileUrl.toLocalFile());
1520 	}
1521 	else if (!getImage().isNull())
1522 		mimeData->setImageData(getImage());
1523 
1524 	return mimeData;
1525 }
1526 
copyImageBuffer()1527 void DkViewPort::copyImageBuffer() {
1528 
1529 	if (getImage().isNull())
1530 		return;
1531 
1532 	QMimeData* mimeData = new QMimeData;
1533 
1534 	if (!getImage().isNull())
1535 		mimeData->setImageData(getImage());
1536 
1537 	QClipboard* clipboard = QApplication::clipboard();
1538 	clipboard->setMimeData(mimeData);
1539 }
1540 
animateFade()1541 void DkViewPort::animateFade() {
1542 
1543 	mAnimationValue = 1.0f-(float)(mAnimationTime.elapsed()/1000.0)/DkSettingsManager::param().display().animationDuration;
1544 
1545 	// slow in - slow out
1546 	double speed = mAnimationValue > 0.5 ? fabs(1.0 - mAnimationValue) : fabs(mAnimationValue);
1547 	speed *= .05;
1548 
1549 	mAnimationValue += (float)speed;
1550 
1551 	if (mAnimationValue <= 0) {
1552 		mAnimationBuffer = QImage();
1553 		mAnimationTimer->stop();
1554 		mAnimationValue = 0;
1555 	}
1556 
1557 	update();
1558 }
1559 
togglePattern(bool show)1560 void DkViewPort::togglePattern(bool show) {
1561 
1562 	emit infoSignal((show) ? tr("Transparency Pattern Enabled") : tr("Transparency Pattern Disabled"));
1563 	DkBaseViewPort::togglePattern(show);
1564 }
1565 
1566 // edit image --------------------------------------------------------------------
rotateCW()1567 void DkViewPort::rotateCW() {
1568 
1569 	if (!mController->applyPluginChanges(true))
1570 		return;
1571 
1572 	if (mLoader)
1573 		mLoader->rotateImage(90);
1574 }
1575 
rotateCCW()1576 void DkViewPort::rotateCCW() {
1577 
1578 	if (!mController->applyPluginChanges(true))
1579 		return;
1580 
1581 	if (mLoader)
1582 		mLoader->rotateImage(-90);
1583 
1584 }
1585 
rotate180()1586 void DkViewPort::rotate180() {
1587 
1588 	if (!mController->applyPluginChanges(true))
1589 		return;
1590 
1591 	if (mLoader)
1592 		mLoader->rotateImage(180);
1593 
1594 }
1595 
1596 // file handling --------------------------------------------------------------------
loadLena()1597 void DkViewPort::loadLena() {
1598 
1599 	bool ok;
1600 	QString text = QInputDialog::getText(this, tr("Lenna"), tr("A remarkable woman"), QLineEdit::Normal, 0, &ok);
1601 
1602 	// pass phrase
1603 	if (ok && !text.isEmpty() && text == "lenna") {
1604 		mTestLoaded = true;
1605 		toggleLena(DkUtils::getMainWindow()->isFullScreen());
1606 	}
1607 	else if (!ok) {
1608 		QMessageBox warningDialog(DkUtils::getMainWindow());
1609 		warningDialog.setIcon(QMessageBox::Warning);
1610 		warningDialog.setText(tr("you cannot cancel this"));
1611 		warningDialog.exec();
1612 		loadLena();
1613 	}
1614 	else {
1615 		QApplication::beep();
1616 
1617 		if (text.isEmpty())
1618 			mController->setInfo(tr("did you understand the brainteaser?"));
1619 		else
1620 			mController->setInfo(tr("%1 is wrong...").arg(text));
1621 	}
1622 }
1623 
toggleLena(bool fullscreen)1624 void DkViewPort::toggleLena(bool fullscreen) {
1625 
1626 	if (!mTestLoaded)
1627 		return;
1628 
1629 	if (mLoader) {
1630 		if (fullscreen)
1631 			mLoader->downloadFile(QUrl("http://www.lenna.org/lena_std.tif"));
1632 		else
1633 			mLoader->load(":/nomacs/img/we.jpg");
1634 	}
1635 }
1636 
settingsChanged()1637 void DkViewPort::settingsChanged() {
1638 
1639 	reloadFile();
1640 
1641 	mAltMod = DkSettingsManager::param().global().altMod;
1642 	mCtrlMod = DkSettingsManager::param().global().ctrlMod;
1643 
1644 	mController->settingsChanged();
1645 }
1646 
setEditedImage(const QImage & newImg,const QString & editName)1647 void DkViewPort::setEditedImage(const QImage& newImg, const QString& editName) {
1648 
1649 	if (!mController->applyPluginChanges(true))		// user wants to first apply the plugin
1650 		return;
1651 
1652 	if (newImg.isNull()) {
1653 		emit infoSignal(tr("Attempted to set NULL image"));	// not sure if users understand that
1654 		return;
1655 	}
1656 
1657 	if (mManipulatorWatcher.isRunning())
1658 		mManipulatorWatcher.cancel();
1659 
1660 	QSharedPointer<DkImageContainerT> imgC = mLoader->getCurrentImage();
1661 
1662 	if (!imgC)
1663 		imgC = QSharedPointer<DkImageContainerT>(new DkImageContainerT(""));
1664 
1665 	if (!imgC)
1666 		imgC = QSharedPointer<DkImageContainerT>();
1667 	imgC->setImage(newImg, editName);
1668 	unloadImage(false);
1669 	mLoader->setImage(imgC);
1670 	qDebug() << "mLoader gets this size: " << newImg.size();
1671 }
1672 
setEditedImage(QSharedPointer<DkImageContainerT> img)1673 void DkViewPort::setEditedImage(QSharedPointer<DkImageContainerT> img) {
1674 
1675 	if (!img) {
1676 		emit infoSignal(tr("Attempted to set NULL image"));	// not sure if users understand that
1677 		return;
1678 	}
1679 
1680 	unloadImage(false);
1681 	mLoader->setImage(img);
1682 }
1683 
unloadImage(bool fileChange)1684 bool DkViewPort::unloadImage(bool fileChange) {
1685 
1686 	if (DkSettingsManager::param().display().animationDuration > 0 &&
1687 			(mController->getPlayer()->isPlaying() ||
1688 			DkUtils::getMainWindow()->isFullScreen() ||
1689 			DkSettingsManager::param().display().alwaysAnimate)) {
1690 		mAnimationBuffer = mImgStorage.image((float)(mImgMatrix.m11()*mWorldMatrix.m11()));
1691 		mFadeImgViewRect = mImgViewRect;
1692 		mFadeImgRect = mImgRect;
1693 		mAnimationValue = 1.0f;
1694 	}
1695 
1696 	int success = true;
1697 
1698 	if (!mController->applyPluginChanges(true))		// user wants to apply changes first
1699 		return false;
1700 
1701 	if (fileChange)
1702 		success = mLoader->unloadFile();		// returns false if the user cancels
1703 
1704 	// notify controller
1705 	mController->updateImage(imageContainer());
1706 
1707 	if (mMovie && success) {
1708 		mMovie->stop();
1709 		mMovie = QSharedPointer<QMovie>();
1710 	}
1711 
1712 	if (mSvg && success)
1713 		mSvg = QSharedPointer<QSvgRenderer>();
1714 
1715 	return success != 0;
1716 }
1717 
deactivate()1718 void DkViewPort::deactivate() {
1719 
1720 	setImage(QImage());
1721 }
1722 
loadFile(const QString & filePath)1723 void DkViewPort::loadFile(const QString& filePath) {
1724 
1725 	if (!unloadImage())
1726 		return;
1727 
1728 	mTestLoaded = false;
1729 
1730 	if (mLoader && !filePath.isEmpty() && QFileInfo(filePath).isDir()) {
1731 		mLoader->setDir(filePath);
1732 	}
1733 	else if (mLoader)
1734 		mLoader->load(filePath);
1735 
1736 	// diem: I removed this line for a) we don't support remote displays anymore and be: https://github.com/nomacs/nomacs/issues/219
1737 	//qDebug() << "sync mode: " << (DkSettingsManager::param().sync().syncMode == DkSettings::sync_mode_remote_display);
1738 	//if ((qApp->keyboardModifiers() == mAltMod || DkSettingsManager::param().sync().syncMode == DkSettings::sync_mode_remote_display) &&
1739 	//	(hasFocus() || mController->hasFocus()) &&
1740 	//	mLoader->hasFile())
1741 	//	tcpLoadFile(0, filePath);
1742 }
1743 
reloadFile()1744 void DkViewPort::reloadFile() {
1745 
1746 	if (mLoader) {
1747 
1748 		if (unloadImage())
1749 			mLoader->reloadImage();
1750 	}
1751 }
1752 
loadFile(int skipIdx)1753 void DkViewPort::loadFile(int skipIdx) {
1754 
1755 	if (!unloadImage())
1756 		return;
1757 
1758 	if (mLoader && !mTestLoaded)
1759 		mLoader->changeFile(skipIdx);
1760 
1761 	// alt mod
1762 	if ((qApp->keyboardModifiers() == mAltMod || DkSettingsManager::param().sync().syncActions) && (hasFocus() || mController->hasFocus())) {
1763 		emit sendNewFileSignal((qint16)skipIdx);
1764 		qDebug() << "emitting load next";
1765 	}
1766 }
1767 
loadPrevFileFast()1768 void DkViewPort::loadPrevFileFast() {
1769 
1770 	loadFileFast(-1);
1771 }
1772 
loadNextFileFast()1773 void DkViewPort::loadNextFileFast() {
1774 
1775 	loadFileFast(1);
1776 }
1777 
1778 
loadFileFast(int skipIdx)1779 void DkViewPort::loadFileFast(int skipIdx) {
1780 
1781 	if (!unloadImage())
1782 		return;
1783 
1784 	mNextSwipe = skipIdx > 0;
1785 
1786 	QApplication::sendPostedEvents();
1787 
1788 	int sIdx = skipIdx;
1789 	QSharedPointer<DkImageContainerT> lastImg;
1790 
1791 	for (int idx = 0; idx < mLoader->getImages().size(); idx++) {
1792 
1793 		QSharedPointer<DkImageContainerT> imgC = mLoader->getSkippedImage(sIdx);
1794 
1795 		if (!imgC)
1796 			break;
1797 
1798 		mLoader->setCurrentImage(imgC);
1799 
1800 		if (imgC && imgC->getLoadState() != DkImageContainer::exists_not) {
1801 			mLoader->load(imgC);
1802 			break;
1803 		}
1804 		else if (lastImg == imgC) {
1805 			sIdx += skipIdx;	// get me out of endless loops (self referencing shortcuts)
1806 		}
1807 		else {
1808 			qDebug() << "image does not exist - skipping";
1809 		}
1810 
1811 		lastImg = imgC;
1812 	}
1813 
1814 	if ((qApp->keyboardModifiers() == mAltMod || DkSettingsManager::param().sync().syncActions) && (hasFocus() || mController->hasFocus())) {
1815 		emit sendNewFileSignal((qint16)skipIdx);
1816 		QCoreApplication::sendPostedEvents();
1817 	}
1818 }
1819 
loadFirst()1820 void DkViewPort::loadFirst() {
1821 
1822 	if (!unloadImage())
1823 		return;
1824 
1825 	if (mLoader && !mTestLoaded)
1826 		mLoader->firstFile();
1827 
1828 	if ((qApp->keyboardModifiers() == mAltMod || DkSettingsManager::param().sync().syncActions) && (hasFocus() || mController->hasFocus()))
1829 		emit sendNewFileSignal(SHRT_MIN);
1830 }
1831 
loadLast()1832 void DkViewPort::loadLast() {
1833 
1834 	if (!unloadImage())
1835 		return;
1836 
1837 	if (mLoader && !mTestLoaded)
1838 		mLoader->lastFile();
1839 
1840 	if ((qApp->keyboardModifiers() == mAltMod || DkSettingsManager::param().sync().syncActions) && (hasFocus() || mController->hasFocus()))
1841 		emit sendNewFileSignal(SHRT_MAX);
1842 
1843 }
1844 
loadSkipPrev10()1845 void DkViewPort::loadSkipPrev10() {
1846 
1847 	loadFileFast(-DkSettingsManager::param().global().skipImgs);
1848 	//unloadImage();
1849 
1850 	//if (mLoader && !testLoaded)
1851 	//	mLoader->changeFile(-DkSettingsManager::param().global().skipImgs, (parent->isFullScreen() && DkSettingsManager::param().slideShow().silentFullscreen));
1852 
1853 	if (qApp->keyboardModifiers() == mAltMod && (hasFocus() || mController->hasFocus()))
1854 		emit sendNewFileSignal((qint16)-DkSettingsManager::param().global().skipImgs);
1855 }
1856 
loadSkipNext10()1857 void DkViewPort::loadSkipNext10() {
1858 
1859 	loadFileFast(DkSettingsManager::param().global().skipImgs);
1860 	//unloadImage();
1861 
1862 	//if (mLoader && !testLoaded)
1863 	//	mLoader->changeFile(DkSettingsManager::param().global().skipImgs, (parent->isFullScreen() && DkSettingsManager::param().slideShow().silentFullscreen));
1864 
1865 	if (qApp->keyboardModifiers() == mAltMod && (hasFocus() || mController->hasFocus()))
1866 		emit sendNewFileSignal((qint16)DkSettingsManager::param().global().skipImgs);
1867 }
1868 
tcpLoadFile(qint16 idx,QString filename)1869 void DkViewPort::tcpLoadFile(qint16 idx, QString filename) {
1870 
1871 	qDebug() << "I got a file request??";
1872 
1873 	// some hack: set the mode to default in order to prevent loops (if both are auto connected)
1874 	// should be mostly harmless
1875 	//int oldMode = DkSettingsManager::param().sync().syncMode;
1876 	//DkSettingsManager::param().sync().syncMode = DkSettings::sync_mode_receiveing_command;
1877 
1878 	if (filename.isEmpty()) {
1879 
1880 		// change the file idx according to my brother
1881 		switch (idx) {
1882 			case SHRT_MIN:
1883 				loadFirst();
1884 				break;
1885 			case SHRT_MAX:
1886 				loadLast();
1887 				break;
1888 			//case 1:
1889 			//	loadNextFileFast();
1890 			//	break;
1891 			//case -1:
1892 			//	loadPrevFileFast();
1893 			//	break;
1894 			default:
1895 				loadFileFast(idx);
1896 				//if (mLoader) mLoader->loadFileAt(idx);
1897 		}
1898 	}
1899 	else
1900 		loadFile(filename);
1901 
1902 	qDebug() << "loading file: " << filename;
1903 
1904 	//DkSettingsManager::param().sync().syncMode = oldMode;
1905 }
1906 
imageContainer() const1907 QSharedPointer<DkImageContainerT> DkViewPort::imageContainer() const {
1908 
1909 	if (!mLoader)
1910 		return QSharedPointer<DkImageContainerT>();
1911 
1912 	return mLoader->getCurrentImage();
1913 }
1914 
setImageLoader(QSharedPointer<DkImageLoader> newLoader)1915 void DkViewPort::setImageLoader(QSharedPointer<DkImageLoader> newLoader) {
1916 
1917 	mLoader = newLoader;
1918 	connectLoader(newLoader);
1919 
1920 	if (mLoader)
1921 		mLoader->activate();
1922 }
1923 
connectLoader(QSharedPointer<DkImageLoader> loader,bool connectSignals)1924 void DkViewPort::connectLoader(QSharedPointer<DkImageLoader> loader, bool connectSignals) {
1925 
1926 	assert(mController);
1927 
1928 	if (!loader)
1929 		return;
1930 
1931 	if (connectSignals) {
1932 		//connect(mLoader.data(), SIGNAL(imageLoadedSignal(QSharedPointer<DkImageContainerT>, bool)), this, SLOT(updateImage(QSharedPointer<DkImageContainerT>, bool)), Qt::UniqueConnection);
1933 		connect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), this, SLOT(updateImage(QSharedPointer<DkImageContainerT>)), Qt::UniqueConnection);
1934 
1935 		connect(loader.data(), SIGNAL(updateDirSignal(QVector<QSharedPointer<DkImageContainerT> >)), mController->getFilePreview(), SLOT(updateThumbs(QVector<QSharedPointer<DkImageContainerT> >)), Qt::UniqueConnection);
1936 		connect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController->getFilePreview(), SLOT(setFileInfo(QSharedPointer<DkImageContainerT>)), Qt::UniqueConnection);
1937 		connect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController->getMetaDataWidget(), SLOT(updateMetaData(QSharedPointer<DkImageContainerT>)), Qt::UniqueConnection);
1938 		connect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController, SLOT(updateImage(QSharedPointer<DkImageContainerT>)), Qt::UniqueConnection);
1939 
1940 		connect(loader.data(), SIGNAL(showInfoSignal(const QString&, int, int)), mController, SLOT(setInfo(const QString&, int, int)), Qt::UniqueConnection);
1941 
1942 		connect(loader.data(), SIGNAL(setPlayer(bool)), mController->getPlayer(), SLOT(play(bool)), Qt::UniqueConnection);
1943 
1944 		connect(loader.data(), SIGNAL(updateDirSignal(QVector<QSharedPointer<DkImageContainerT> >)), mController->getScroller(), SLOT(updateDir(QVector<QSharedPointer<DkImageContainerT> >)), Qt::UniqueConnection);
1945 		connect(loader.data(), SIGNAL(imageUpdatedSignal(int)), mController->getScroller(), SLOT(updateFile(int)), Qt::UniqueConnection);
1946 		connect(mController->getScroller(), SIGNAL(valueChanged(int)), loader.data(), SLOT(loadFileAt(int)));
1947 	}
1948 	else {
1949 		//connect(mLoader.data(), SIGNAL(imageLoadedSignal(QSharedPointer<DkImageContainerT>, bool)), this, SLOT(updateImage(QSharedPointer<DkImageContainerT>, bool)), Qt::UniqueConnection);
1950 		disconnect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), this, SLOT(updateImage(QSharedPointer<DkImageContainerT>)));
1951 
1952 		disconnect(loader.data(), SIGNAL(updateDirSignal(QVector<QSharedPointer<DkImageContainerT> >)), mController->getFilePreview(), SLOT(updateThumbs(QVector<QSharedPointer<DkImageContainerT> >)));
1953 		disconnect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController->getFilePreview(), SLOT(setFileInfo(QSharedPointer<DkImageContainerT>)));
1954 		disconnect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController->getMetaDataWidget(), SLOT(updateMetaData(QSharedPointer<DkImageContainerT>)));
1955 		disconnect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController, SLOT(setFileInfo(QSharedPointer<DkImageContainerT>)));
1956 
1957 		disconnect(loader.data(), SIGNAL(showInfoSignal(const QString&, int, int)), mController, SLOT(setInfo(const QString&, int, int)));
1958 		disconnect(loader.data(), SIGNAL(updateSpinnerSignalDelayed(bool, int)), mController, SLOT(setSpinnerDelayed(bool, int)));
1959 
1960 		disconnect(loader.data(), SIGNAL(setPlayer(bool)), mController->getPlayer(), SLOT(play(bool)));
1961 
1962 		disconnect(loader.data(), SIGNAL(updateDirSignal(QVector<QSharedPointer<DkImageContainerT> >)), mController->getScroller(), SLOT(updateDir(QVector<QSharedPointer<DkImageContainerT> >)));
1963 		disconnect(loader.data(), SIGNAL(imageUpdatedSignal(QSharedPointer<DkImageContainerT>)), mController->getScroller(), SLOT(updateFile(QSharedPointer<DkImageContainerT>)));
1964 	}
1965 }
1966 
1967 
getController()1968 DkControlWidget* DkViewPort::getController() {
1969 
1970 	return mController;
1971 }
1972 
cropImage(const DkRotatingRect & rect,const QColor & bgCol,bool cropToMetaData)1973 void DkViewPort::cropImage(const DkRotatingRect& rect, const QColor& bgCol, bool cropToMetaData) {
1974 
1975 	QSharedPointer<DkImageContainerT> imgC = mLoader->getCurrentImage();
1976 
1977 	if (!imgC) {
1978 		qWarning() << "cannot crop NULL image...";
1979 		return;
1980 	}
1981 
1982 	imgC->cropImage(rect, bgCol, cropToMetaData);
1983 	setEditedImage(imgC);
1984 }
1985 
1986 // DkViewPortFrameless --------------------------------------------------------------------
DkViewPortFrameless(QWidget * parent)1987 DkViewPortFrameless::DkViewPortFrameless(QWidget *parent) : DkViewPort(parent) {
1988 
1989 #ifdef Q_OS_MAC
1990 	parent->setAttribute(Qt::WA_MacNoShadow);
1991 #endif
1992 
1993 	setAttribute(Qt::WA_TranslucentBackground, true);
1994 	mImgBg.load(QFileInfo(QApplication::applicationDirPath(), "bgf.png").absoluteFilePath());
1995 
1996 	if (mImgBg.isNull())
1997 		mImgBg.load(":/nomacs/img/splash-screen.png");
1998 
1999 	DkActionManager& am = DkActionManager::instance();
2000 	mStartActions.append(am.action(DkActionManager::menu_file_open));
2001 	mStartActions.append(am.action(DkActionManager::menu_file_open_dir));
2002 
2003 	mStartIcons.append(am.icon(DkActionManager::icon_file_open_large));
2004 	mStartIcons.append(am.icon(DkActionManager::icon_file_dir_large));
2005 }
2006 
~DkViewPortFrameless()2007 DkViewPortFrameless::~DkViewPortFrameless() {
2008 }
2009 
zoom(double factor,const QPointF & center,bool force)2010 void DkViewPortFrameless::zoom(double factor, const QPointF& center, bool force) {
2011 
2012 	if (mImgStorage.isEmpty() || mBlockZooming)
2013 		return;
2014 
2015 	//limit zoom out ---
2016 	if (mWorldMatrix.m11()*factor <= mMinZoom && factor < 1)
2017 		return;
2018 
2019 	// reset view & block if we pass the 'image fit to screen' on zoom out
2020 	if (mWorldMatrix.m11() > 1 && mWorldMatrix.m11()*factor < 1 && !force) {
2021 
2022 		mBlockZooming = true;
2023 		mZoomTimer->start(500);
2024 	}
2025 
2026 	//limit zoom in ---
2027 	if (mWorldMatrix.m11()*mImgMatrix.m11() > mMaxZoom && factor > 1)
2028 		return;
2029 
2030 	QRectF viewRect = mWorldMatrix.mapRect(mImgViewRect);
2031 	QPointF pos = center;
2032 
2033 	// if no center assigned: zoom in at the image center
2034 	if (pos.x() == -1 || pos.y() == -1)
2035 		pos = viewRect.center();
2036 
2037 	if (pos.x() < viewRect.left())			pos.setX(viewRect.left());
2038 	else if (pos.x() > viewRect.right())	pos.setX(viewRect.right());
2039 	if (pos.y() < viewRect.top())			pos.setY(viewRect.top());
2040 	else if (pos.y() > viewRect.bottom())	pos.setY(viewRect.bottom());
2041 
2042 	zoomToPoint(factor, pos, mWorldMatrix);
2043 
2044 	controlImagePosition();
2045 	showZoom();
2046 	changeCursor();
2047 
2048 	update();
2049 
2050 	tcpSynchronize();
2051 	emit zoomSignal(mWorldMatrix.m11()*mImgMatrix.m11()*100);
2052 }
2053 
resetView()2054 void DkViewPortFrameless::resetView() {
2055 
2056 	// maybe we can delete this function...
2057 	DkViewPort::resetView();
2058 }
2059 
paintEvent(QPaintEvent * event)2060 void DkViewPortFrameless::paintEvent(QPaintEvent* event) {
2061 
2062 	if (!DkUtils::getMainWindow()->isFullScreen()) {
2063 
2064 		QPainter painter(viewport());
2065 		painter.setWorldTransform(mWorldMatrix);
2066 		drawFrame(painter);
2067 		painter.end();
2068 	}
2069 
2070 	DkViewPort::paintEvent(event);
2071 }
2072 
draw(QPainter & painter,double)2073 void DkViewPortFrameless::draw(QPainter & painter, double) {
2074 
2075 	if (DkUtils::getMainWindow()->isFullScreen()) {
2076 		QColor col = QColor(0,0,0);
2077 		col.setAlpha(150);
2078 		painter.setWorldMatrixEnabled(false);
2079 		painter.fillRect(QRect(QPoint(), size()), col);
2080 		painter.setWorldMatrixEnabled(true);
2081 	}
2082 
2083 	if (mSvg && mSvg->isValid()) {
2084 		mSvg->render(&painter, mImgViewRect);
2085 	}
2086 	else if (mMovie && mMovie->isValid()) {
2087 		painter.drawPixmap(mImgViewRect, mMovie->currentPixmap(), mMovie->frameRect());
2088 	}
2089 	else {
2090 		QImage img = mImgStorage.image((float)(mImgMatrix.m11()*mWorldMatrix.m11()));
2091 
2092 		// opacity == 1.0f -> do not show pattern if we crossfade two images
2093 		if (DkSettingsManager::param().display().tpPattern && img.hasAlphaChannel())
2094 			drawPattern(painter);
2095 
2096 		painter.drawImage(mImgViewRect, img, QRect(QPoint(), img.size()));
2097 	}
2098 
2099 }
2100 
drawBackground(QPainter & painter)2101 void DkViewPortFrameless::drawBackground(QPainter & painter) {
2102 
2103 	painter.setWorldTransform(mImgMatrix);
2104 	painter.setBrush(QColor(127, 144, 144, 200));
2105 	painter.setPen(QColor(100, 100, 100, 255));
2106 
2107 	QRectF initialRect = rect();
2108 	QPointF oldCenter = initialRect.center();
2109 
2110 	QTransform cT;
2111 	cT.scale(400/initialRect.width(), 400/initialRect.width());
2112 	initialRect = cT.mapRect(initialRect);
2113 	initialRect.moveCenter(oldCenter);
2114 
2115 	// fit to mViewport
2116 	QSize s = mImgBg.size();
2117 
2118 	QRectF bgRect(QPoint(), s);
2119 	bgRect.moveCenter(initialRect.center());//moveTopLeft(QPointF(size().width(), size().height())*0.5 - initialRect.bottomRight()*0.5);
2120 
2121 	//painter.drawRect(initialRect);
2122 	painter.drawImage(bgRect, mImgBg, QRect(QPoint(), mImgBg.size()));
2123 
2124 	if (mStartActions.isEmpty())
2125 		return;
2126 
2127 	// first time?
2128 	if (mStartActionsRects.isEmpty()) {
2129 		float margin = 40;
2130 		float iconSizeMargin = (float)((initialRect.width()-3*margin)/mStartActions.size());
2131 		QSize iconSize = QSize(qRound(iconSizeMargin - margin), qRound(iconSizeMargin - margin));
2132 		QPointF offset = QPointF(bgRect.left() + 50, initialRect.center().y()+iconSizeMargin*0.25f);
2133 
2134 		for (int idx = 0; idx < mStartActions.size(); idx++) {
2135 
2136 			QRectF iconRect = QRectF(offset, iconSize);
2137 			QPixmap ci = !mStartIcons[idx].isNull() ? mStartIcons[idx].pixmap(iconSize) : mStartActions[idx]->icon().pixmap(iconSize);
2138 			mStartActionsRects.push_back(iconRect);
2139 			mStartActionsIcons.push_back(ci);
2140 
2141 			offset.setX(offset.x() + margin + iconSize.width());
2142 		}
2143 	}
2144 
2145 	// draw start actions
2146 	for (int idx = 0; idx < mStartActions.size(); idx++) {
2147 
2148 		if (!mStartIcons[idx].isNull())
2149 			painter.drawPixmap(mStartActionsRects[idx], mStartActionsIcons[idx], QRect(QPoint(), mStartActionsIcons[idx].size()));
2150 		else
2151 			painter.drawPixmap(mStartActionsRects[idx], mStartActionsIcons[idx], QRect(QPoint(), mStartActionsIcons[idx].size()));
2152 
2153 		QRectF tmpRect = mStartActionsRects[idx];
2154 		QString text = mStartActions[idx]->text().remove("&");
2155 		tmpRect.moveTop(tmpRect.bottom()+10);
2156 		painter.drawText(tmpRect, text);
2157 	}
2158 
2159 	QString infoText = tr("Press F10 to exit Frameless view");
2160 	QRectF tmpRect(bgRect.left()+50, bgRect.bottom()-60, bgRect.width()-100, 20);
2161 	painter.drawText(tmpRect, infoText);
2162 }
2163 
drawFrame(QPainter & painter)2164 void DkViewPortFrameless::drawFrame(QPainter & painter) {
2165 
2166 	// TODO: replace hasAlphaChannel with has alphaBorder
2167 	if ((!mImgStorage.isEmpty() && mImgStorage.image().hasAlphaChannel()) || !DkSettingsManager::param().display().showBorder)	// braces
2168 		return;
2169 
2170 	painter.setBrush(QColor(255, 255, 255, 200));
2171 	painter.setPen(QColor(100, 100, 100, 255));
2172 
2173 	QRectF frameRect;
2174 
2175 	float fs = qMin((float)mImgViewRect.width(), (float)mImgViewRect.height())*0.1f;
2176 
2177 	// looks pretty bad if the frame is too small
2178 	if (fs < 4)
2179 		return;
2180 
2181 	frameRect = mImgViewRect;
2182 	frameRect.setSize(frameRect.size() + QSize(qRound(fs), qRound(fs)));
2183 	frameRect.moveCenter(mImgViewRect.center());
2184 
2185 	painter.drawRect(frameRect);
2186 }
2187 
mousePressEvent(QMouseEvent * event)2188 void DkViewPortFrameless::mousePressEvent(QMouseEvent *event) {
2189 
2190 	// move the window - todo: NOT full screen, window inside...
2191 	setCursor(Qt::ClosedHandCursor);
2192 	mPosGrab = event->pos();
2193 
2194 	DkViewPort::mousePressEvent(event);
2195 }
2196 
mouseReleaseEvent(QMouseEvent * event)2197 void DkViewPortFrameless::mouseReleaseEvent(QMouseEvent *event) {
2198 
2199 	if (mImgStorage.isEmpty()) {
2200 
2201 		QPointF pos = mImgMatrix.inverted().map(event->pos());
2202 
2203 		for (int idx = 0; idx < mStartActionsRects.size(); idx++) {
2204 
2205 			if (mStartActionsRects[idx].contains(pos)) {
2206 
2207 				mStartActions[idx]->trigger();
2208 				break;
2209 			}
2210 		}
2211 	}
2212 
2213 	unsetCursor();
2214 	//setCursor(Qt::OpenHandCursor);
2215 	DkViewPort::mouseReleaseEvent(event);
2216 }
2217 
2218 
mouseMoveEvent(QMouseEvent * event)2219 void DkViewPortFrameless::mouseMoveEvent(QMouseEvent *event) {
2220 
2221 	if (mImgStorage.isEmpty()) {
2222 
2223 		QPointF pos = mImgMatrix.inverted().map(event->pos());
2224 
2225 		int idx;
2226 		for (idx = 0; idx < mStartActionsRects.size(); idx++) {
2227 
2228 			if (mStartActionsRects[idx].contains(pos)) {
2229 				setCursor(Qt::PointingHandCursor);
2230 				break;
2231 			}
2232 		}
2233 
2234 		//// TODO: change if closed hand cursor is present...
2235 		//if (idx == startActionsRects.size())
2236 		//	setCursor(Qt::OpenHandCursor);
2237 	}
2238 
2239 	if (DkStatusBarManager::instance().statusbar()->isVisible())
2240 		getPixelInfo(event->pos());
2241 
2242 	if (event->buttons() == Qt::LeftButton) {
2243 
2244 		QPointF cPos = event->pos();
2245 		QPointF dxy = (cPos - mPosGrab);
2246 		mPosGrab = cPos;
2247 		moveView(dxy/mWorldMatrix.m11());
2248 	}
2249 
2250 	QGraphicsView::mouseMoveEvent(event);
2251 }
2252 
moveView(QPointF delta)2253 void DkViewPortFrameless::moveView(QPointF delta) {
2254 
2255 	// if no zoom is present -> the translation is like a move window
2256 	if (mWorldMatrix.m11() == 1.0f) {
2257 		float s = (float)mImgMatrix.m11();
2258 		mImgMatrix.translate(delta.x()/s, delta.y()/s);
2259 		mImgViewRect = mImgMatrix.mapRect(mImgRect);
2260 	}
2261 	else
2262 		mWorldMatrix.translate(delta.x(), delta.y());
2263 
2264 	controlImagePosition();
2265 	update();
2266 }
2267 
2268 
controlImagePosition(float,float)2269 void DkViewPortFrameless::controlImagePosition(float, float) {
2270 	// dummy method
2271 }
2272 
centerImage()2273 void DkViewPortFrameless::centerImage() {
2274 }
2275 
updateImageMatrix()2276 void DkViewPortFrameless::updateImageMatrix() {
2277 
2278 	if (mImgStorage.isEmpty())
2279 		return;
2280 
2281 	QRectF oldImgRect = mImgViewRect;
2282 	QTransform oldImgMatrix = mImgMatrix;
2283 
2284 	mImgMatrix.reset();
2285 
2286 	QSize imgSize = getImageSize();
2287 
2288 	// if the image is smaller or zoom is active: paint the image as is
2289 	if (!mViewportRect.contains(mImgRect.toRect())) {
2290 		mImgMatrix = getScaledImageMatrix(size()*0.9f);
2291 		QSize shift = size()*0.1f;
2292 		mImgMatrix.translate(shift.width(), shift.height());
2293 	}
2294 	else {
2295 		mImgMatrix.translate((float)(getMainGeometry().width()-imgSize.width())*0.5f, (float)(getMainGeometry().height()-imgSize.height())*0.5f);
2296 		mImgMatrix.scale(1.0f, 1.0f);
2297 	}
2298 
2299 	mImgViewRect = mImgMatrix.mapRect(mImgRect);
2300 
2301 	// update world matrix
2302 	if (mWorldMatrix.m11() != 1) {
2303 
2304 		float scaleFactor = (float)(oldImgMatrix.m11()/mImgMatrix.m11());
2305 		double dx = oldImgRect.x()/scaleFactor-mImgViewRect.x();
2306 		double dy = oldImgRect.y()/scaleFactor-mImgViewRect.y();
2307 
2308 		mWorldMatrix.scale(scaleFactor, scaleFactor);
2309 		mWorldMatrix.translate(dx, dy);
2310 	}
2311 }
2312 
2313 // DkViewPortContrast --------------------------------------------------------------------
DkViewPortContrast(QWidget * parent)2314 DkViewPortContrast::DkViewPortContrast(QWidget *parent) : DkViewPort(parent) {
2315 
2316 	mColorTable = QVector<QRgb>(256);
2317 	for (int i = 0; i < mColorTable.size(); i++)
2318 		mColorTable[i] = qRgb(i, i, i);
2319 
2320 	// connect
2321 	auto ttb = DkToolBarManager::inst().transferToolBar();
2322 	connect(ttb, SIGNAL(colorTableChanged(QGradientStops)), this, SLOT(changeColorTable(QGradientStops)));
2323 	connect(ttb, SIGNAL(channelChanged(int)),				this, SLOT(changeChannel(int)));
2324 	connect(ttb, SIGNAL(pickColorRequest(bool)),			this, SLOT(pickColor(bool)));
2325 	connect(ttb, SIGNAL(tFEnabled(bool)),					this, SLOT(enableTF(bool)));
2326 	connect(this, SIGNAL(tFSliderAdded(qreal)),				ttb, SLOT(insertSlider(qreal)));
2327 	connect(this, SIGNAL(imageModeSet(int)),				ttb, SLOT(setImageMode(int)));
2328 }
2329 
~DkViewPortContrast()2330 DkViewPortContrast::~DkViewPortContrast() {
2331 }
2332 
changeChannel(int channel)2333 void DkViewPortContrast::changeChannel(int channel) {
2334 
2335 	if (channel < 0 || channel >= mImgs.size())
2336 		return;
2337 
2338 	if (!mImgStorage.isEmpty()) {
2339 
2340 		mFalseColorImg = mImgs[channel];
2341 		mFalseColorImg.setColorTable(mColorTable);
2342 		mDrawFalseColorImg = true;
2343 
2344 		update();
2345 
2346 		drawImageHistogram();
2347 	}
2348 }
2349 
changeColorTable(QGradientStops stops)2350 void DkViewPortContrast::changeColorTable(QGradientStops stops) {
2351 
2352 	qreal fac;
2353 
2354 	qreal actPos, leftStop, rightStop;
2355 	QColor tmp;
2356 
2357 	int rLeft, gLeft, bLeft, rRight, gRight, bRight;
2358 	int rAct, gAct, bAct;
2359 
2360 	// At least one stop has to be set:
2361 	tmp = stops.at(0).second;
2362 	tmp.getRgb(&rLeft, &gLeft, &bLeft);
2363 	leftStop = stops.at(0).first;
2364 
2365 	// If just one stop is set, we can speed things up:
2366 	if (stops.size() == 1) {
2367 		for (int i = 0; i < mColorTable.size(); i++)
2368 			mColorTable[i] = qRgb(rLeft, gLeft, bLeft);
2369 	}
2370 	// Otherwise interpolate:
2371 	else {
2372 
2373 		int rightStopIdx = 1;
2374 		tmp = stops.at(rightStopIdx).second;
2375 		tmp.getRgb(&rRight, &gRight, &bRight);
2376 		rightStop = stops.at(rightStopIdx).first;
2377 
2378 		for (int i = 0; i < mColorTable.size(); i++) {
2379 			actPos = (qreal) i / mColorTable.size();
2380 
2381 			if (actPos > rightStop) {
2382 				leftStop = rightStop;
2383 
2384 				rLeft = rRight;
2385 				gLeft = gRight;
2386 				bLeft = bRight;
2387 
2388 				if (stops.size() > rightStopIdx + 1) {
2389 					rightStopIdx++;
2390 					rightStop = stops.at(rightStopIdx).first;
2391 					tmp = stops.at(rightStopIdx).second;
2392 					tmp.getRgb(&rRight, &gRight, &bRight);
2393 				}
2394 
2395 			}
2396 
2397 			if (actPos <= leftStop)
2398 				mColorTable[i] = qRgb(rLeft, gLeft, bLeft);
2399 			else if (actPos >= rightStop)
2400 				mColorTable[i] = qRgb(rRight, gRight, bRight);
2401 			else {
2402 				fac = (actPos - leftStop) / (rightStop - leftStop);
2403 				rAct = qRound(rLeft + (rRight - rLeft) * fac);
2404 				gAct = qRound(gLeft + (gRight - gLeft) * fac);
2405 				bAct = qRound(bLeft + (bRight - bLeft) * fac);
2406 				mColorTable[i] = qRgb(rAct, gAct, bAct);
2407 			}
2408 		}
2409 	}
2410 
2411 
2412 	mFalseColorImg.setColorTable(mColorTable);
2413 
2414 	update();
2415 
2416 }
2417 
draw(QPainter & painter,double opacity)2418 void DkViewPortContrast::draw(QPainter & painter, double opacity) {
2419 
2420 	if (!mDrawFalseColorImg || mSvg || mMovie) {
2421 		DkBaseViewPort::draw(painter, opacity);
2422 		return;
2423 	}
2424 
2425 	if (DkUtils::getMainWindow()->isFullScreen())
2426 		painter.setBackground(DkSettingsManager::param().slideShow().backgroundColor);
2427 
2428 	QImage img = mImgStorage.image((float)(mImgMatrix.m11()*mWorldMatrix.m11()));
2429 
2430 	// opacity == 1.0f -> do not show pattern if we crossfade two images
2431 	if (DkSettingsManager::param().display().tpPattern && img.hasAlphaChannel() && opacity == 1.0)
2432 		drawPattern(painter);
2433 
2434 	if (mDrawFalseColorImg)
2435 		painter.drawImage(mImgViewRect, mFalseColorImg, mImgRect);
2436 }
2437 
setImage(QImage newImg)2438 void DkViewPortContrast::setImage(QImage newImg) {
2439 
2440 	DkViewPort::setImage(newImg);
2441 
2442 	if (newImg.isNull())
2443 		return;
2444 
2445 	if (mImgStorage.image().format() == QImage::Format_Indexed8) {
2446 		mImgs = QVector<QImage>(1);
2447 		mImgs[0] = mImgStorage.image();
2448 		mActiveChannel = 0;
2449 	}
2450 #ifdef WITH_OPENCV
2451 
2452 	else {
2453 
2454 			mImgs = QVector<QImage>(4);
2455 			std::vector<cv::Mat> planes;
2456 
2457 			cv::Mat imgUC3 = DkImage::qImage2Mat(mImgStorage.image());
2458 			//int format = imgQt.format();
2459 			//if (format == QImage::Format_RGB888)
2460 			//	imgUC3 = Mat(imgQt.height(), imgQt.width(), CV_8UC3, (uchar*)imgQt.bits(), imgQt.bytesPerLine());
2461 			//else
2462 			//	imgUC3 = Mat(imgQt.height(), imgQt.width(), CV_8UC4, (uchar*)imgQt.bits(), imgQt.bytesPerLine());
2463 			split(imgUC3, planes);
2464 			// Store the 3 channels in a QImage Vector.
2465 			//Be aware that OpenCV 'swaps' the rgb triplet, hence process it in a descending way:
2466 			int idx = 1;
2467 			for (int i = 2; i >= 0; i--) {
2468 
2469 				// dirty hack
2470 				if (i >= (int)planes.size()) i = 0;
2471 				mImgs[idx] = QImage((const unsigned char*)planes[i].data, (int)planes[i].cols, (int)planes[i].rows, (int)planes[i].step,  QImage::Format_Indexed8);
2472 				mImgs[idx] = mImgs[idx].copy();
2473 				idx++;
2474 
2475 			}
2476 			// The first element in the vector contains the gray scale 'average' of the 3 channels:
2477 			cv::Mat grayMat;
2478 			cv::cvtColor(imgUC3, grayMat, CV_BGR2GRAY);
2479 			mImgs[0] = QImage((const unsigned char*)grayMat.data, (int)grayMat.cols, (int)grayMat.rows, (int)grayMat.step,  QImage::Format_Indexed8);
2480 			mImgs[0] = mImgs[0].copy();
2481 			planes.clear();
2482 
2483 	}
2484 #else
2485 
2486 	else {
2487 		mDrawFalseColorImg = false;
2488 		emit imageModeSet(mode_invalid_format);
2489 		return;
2490 	}
2491 
2492 #endif
2493 
2494 	mFalseColorImg = mImgs[mActiveChannel];
2495 	mFalseColorImg.setColorTable(mColorTable);
2496 
2497 	// images with valid color table return img.isGrayScale() false...
2498 	if (mSvg || mMovie)
2499 		emit imageModeSet(mode_invalid_format);
2500 	else if (mImgs.size() == 1)
2501 		emit imageModeSet(mode_gray);
2502 	else
2503 		emit imageModeSet(mode_rgb);
2504 
2505 	update();
2506 
2507 
2508 }
2509 
pickColor(bool enable)2510 void DkViewPortContrast::pickColor(bool enable) {
2511 
2512 	mIsColorPickerActive = enable;
2513 	this->setCursor(Qt::CrossCursor);
2514 
2515 }
2516 
enableTF(bool enable)2517 void DkViewPortContrast::enableTF(bool enable) {
2518 
2519 	mDrawFalseColorImg = enable;
2520 	update();
2521 
2522 	drawImageHistogram();
2523 
2524 }
2525 
mousePressEvent(QMouseEvent * event)2526 void DkViewPortContrast::mousePressEvent(QMouseEvent *event) {
2527 
2528 	if (!mIsColorPickerActive)
2529 		DkViewPort::mousePressEvent(event);	// just propagate events, if the color picker is not active
2530 }
2531 
mouseMoveEvent(QMouseEvent * event)2532 void DkViewPortContrast::mouseMoveEvent(QMouseEvent *event) {
2533 
2534 	if (!mIsColorPickerActive)
2535 		DkViewPort::mouseMoveEvent(event); // just propagate events, if the color picker is not active
2536 	else if (DkStatusBarManager::instance().statusbar()->isVisible())
2537 		getPixelInfo(event->pos());
2538 }
2539 
mouseReleaseEvent(QMouseEvent * event)2540 void DkViewPortContrast::mouseReleaseEvent(QMouseEvent *event) {
2541 
2542 	if (mIsColorPickerActive) {
2543 
2544 		QPointF imgPos = mWorldMatrix.inverted().map(event->pos());
2545 		imgPos = mImgMatrix.inverted().map(imgPos);
2546 
2547 		QPoint xy = imgPos.toPoint();
2548 
2549 		bool isPointValid = true;
2550 
2551 		if (xy.x() < 0 || xy.y() < 0 || xy.x() >= getImageSize().width() || xy.y() >= getImageSize().height())
2552 			isPointValid = false;
2553 
2554 		if (isPointValid) {
2555 
2556 			int colorIdx = mImgs[mActiveChannel].pixelIndex(xy);
2557 			qreal normedPos = (qreal) colorIdx / 255;
2558 			emit tFSliderAdded(normedPos);
2559 		}
2560 
2561 		//unsetCursor();
2562 		//isColorPickerActive = false;
2563 	}
2564 	else
2565 		DkViewPort::mouseReleaseEvent(event);
2566 }
2567 
keyPressEvent(QKeyEvent * event)2568 void DkViewPortContrast::keyPressEvent(QKeyEvent* event) {
2569 
2570 	if ((event->key() == Qt::Key_Escape) && mIsColorPickerActive) {
2571 		unsetCursor();
2572 		mIsColorPickerActive = false;
2573 		update();
2574 		return;
2575 	}
2576 	else
2577 		DkViewPort::keyPressEvent(event);
2578 }
2579 
getImage() const2580 QImage DkViewPortContrast::getImage() const {
2581 
2582 	if (mDrawFalseColorImg)
2583 		return mFalseColorImg;
2584 	else
2585 		return imageContainer() ? imageContainer()->image() : QImage();
2586 
2587 }
2588 
2589 // in contrast mode: if the histogram widget is visible redraw the histogram from the selected image channel data
drawImageHistogram()2590 void DkViewPortContrast::drawImageHistogram() {
2591 
2592 	if (mController->getHistogram() && mController->getHistogram()->isVisible()) {
2593 		if (mDrawFalseColorImg)
2594 			mController->getHistogram()->drawHistogram(mFalseColorImg);
2595 		else
2596 			mController->getHistogram()->drawHistogram(getImage());
2597 	}
2598 }
2599 
2600 }
2601