/*******************************************************************************************************
 DkWidgets.cpp
 Created on:	17.05.2011
 
 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
 
 Copyright (C) 2011-2013 Markus Diem <markus@nomacs.org>
 Copyright (C) 2011-2013 Stefan Fiel <stefan@nomacs.org>
 Copyright (C) 2011-2013 Florian Kleber <florian@nomacs.org>

 This file is part of nomacs.

 nomacs is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 nomacs is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 *******************************************************************************************************/

#include "DkWidgets.h"

#include "DkUtils.h"
#include "DkTimer.h"
#include "DkThumbs.h"
#include "DkImageContainer.h"
#include "DkToolbars.h"
#include "DkImageStorage.h"
#include "DkSettings.h"

#pragma warning(push, 0)	// no warnings from includes - begin
#include <QObject>
#include <QColor>
#include <QDoubleSpinBox>
#include <QApplication>
#include <QRadioButton>
#include <QAction>
#include <QBoxLayout>
#include <QDialog>
#include <QGraphicsBlurEffect>
#include <QGraphicsPixmapItem>
#include <QLabel>
#include <QPainter>
#include <QPushButton>
#include <QMouseEvent>
#include <QToolButton>
#include <QComboBox>
#include <QMessageBox>
#include <QStringBuilder>
#include <QPointer>
#include <QTimer>
#include <QMap>
#include <QVector2D>
#include <qmath.h>
#include <QScrollBar>
#include <QFileSystemModel>
#include <QDockWidget>
#include <QTreeView>
#include <QSortFilterProxyModel>
#include <QToolTip>
#include <QProgressDialog>
#include <QHeaderView>
#include <QMenu>
#include <QScrollArea>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsItem>
#include <QtConcurrentRun>
#include <QMimeData>
#include <QTimeLine>
#include <QGraphicsItemAnimation>
#include <QLineEdit>
#include <QThread>
#include <QFuture>
#include <QFutureWatcher>
#include <qtconcurrentmap.h>
#include <QColor>
#include <QVBoxLayout>
#include <QLabel>
#include <QMovie>
#include <QColorDialog>
#include <QCompleter>
#include <QDirModel>

#if QT_VERSION < 0x050000
#include <QPlastiqueStyle>
#endif

#pragma warning(pop)		// no warnings from includes - end

namespace nmc {

// DkFolderScrollBar --------------------------------------------------------------------
DkFolderScrollBar::DkFolderScrollBar(QWidget* parent) : QSlider(Qt::Horizontal, parent) {
	
	setObjectName("DkFolderScrollBar");
	init();
	mMouseDown = false;
}

DkFolderScrollBar::~DkFolderScrollBar() {

}

// DkWidget stuff
void DkFolderScrollBar::registerAction(QAction* action) {
	connect(this, SIGNAL(visibleSignal(bool)), action, SLOT(setChecked(bool)));
}

void DkFolderScrollBar::block(bool blocked) {
	this->mBlocked = blocked;
	setVisible(false);
}

void DkFolderScrollBar::setDisplaySettings(QBitArray* displayBits) {
	mDisplaySettingsBits = displayBits;
}

bool DkFolderScrollBar::getCurrentDisplaySetting() {

	if (!mDisplaySettingsBits)
		return false;

	if (DkSettings::app.currentAppMode < 0 || DkSettings::app.currentAppMode >= mDisplaySettingsBits->size()) {
		qDebug() << "[WARNING] illegal app mode: " << DkSettings::app.currentAppMode;
		return false;
	}

	return mDisplaySettingsBits->testBit(DkSettings::app.currentAppMode);
}

void DkFolderScrollBar::updateDir(QVector<QSharedPointer<DkImageContainerT> > images) {

	setMaximum(images.size()-1);
}

void DkFolderScrollBar::updateFile(int idx) {
	
	if (mMouseDown)
		return;

	if (isVisible()) {
		blockSignals(true);
		QSlider::setValue(idx);
		blockSignals(false);
	}
}

void DkFolderScrollBar::setValue(int idx) {

	QSlider::setValue(idx);
}

void DkFolderScrollBar::mousePressEvent(QMouseEvent *event) {

	blockSignals(true);
	QSlider::mousePressEvent(event);
}

void DkFolderScrollBar::mouseReleaseEvent(QMouseEvent *event) {

	mMouseDown = false;
	blockSignals(false);
	emit valueChanged(value());
	QSlider::mouseReleaseEvent(event);
}

void DkFolderScrollBar::init() {

	setMouseTracking(true);

	mBgCol = (DkSettings::app.appMode == DkSettings::mode_frameless) ?
		DkSettings::display.bgColorFrameless :
		DkSettings::display.bgColorWidget;

	mShowing = false;
	mHiding = false;
	mBlocked = false;
	mDisplaySettingsBits = 0;
	mOpacityEffect = 0;

	// painter problems if the widget is a child of another that has the same graphicseffect
	// widget starts on hide
	mOpacityEffect = new QGraphicsOpacityEffect(this);
	mOpacityEffect->setOpacity(0);
	mOpacityEffect->setEnabled(false);
	setGraphicsEffect(mOpacityEffect);

	setVisible(false);
}

void DkFolderScrollBar::show(bool saveSettings) {

	// here is a strange problem if you add a DkWidget to another DkWidget -> painters crash
	if (!mBlocked && !mShowing) {
		mHiding = false;
		mShowing = true;
		setVisible(true, saveSettings);
		animateOpacityUp();
	}
}

void DkFolderScrollBar::hide(bool saveSettings) {

	if (!mHiding) {
		mHiding = true;
		mShowing = false;
		animateOpacityDown();

		// set display bit here too -> since the final call to setVisible takes a few seconds
		if (saveSettings && mDisplaySettingsBits && mDisplaySettingsBits->size() > DkSettings::app.currentAppMode) {
			mDisplaySettingsBits->setBit(DkSettings::app.currentAppMode, false);
		}
	}
}

void DkFolderScrollBar::setVisible(bool visible, bool saveSettings) {

	if (mBlocked) {
		QWidget::setVisible(false);
		return;
	}

	if (visible && !isVisible() && !mShowing)
		mOpacityEffect->setOpacity(100);

	QWidget::setVisible(visible);
	emit visibleSignal(visible);	// if this gets slow -> put it into hide() or show()

	if (saveSettings && mDisplaySettingsBits && mDisplaySettingsBits->size() > DkSettings::app.currentAppMode) {
		mDisplaySettingsBits->setBit(DkSettings::app.currentAppMode, visible);
	}
}

void DkFolderScrollBar::animateOpacityUp() {

	if (!mShowing)
		return;

	mOpacityEffect->setEnabled(true);
	if (mOpacityEffect->opacity() >= 1.0f || !mShowing) {
		mOpacityEffect->setOpacity(1.0f);
		mShowing = false;
		mOpacityEffect->setEnabled(false);
		return;
	}

	QTimer::singleShot(20, this, SLOT(animateOpacityUp()));
	mOpacityEffect->setOpacity(mOpacityEffect->opacity()+0.05);
}

void DkFolderScrollBar::animateOpacityDown() {

	if (!mHiding)
		return;

	mOpacityEffect->setEnabled(true);
	if (mOpacityEffect->opacity() <= 0.0f) {
		mOpacityEffect->setOpacity(0.0f);
		mHiding = false;
		setVisible(false, false);	// finally hide the widget
		mOpacityEffect->setEnabled(false);
		return;
	}

	QTimer::singleShot(20, this, SLOT(animateOpacityDown()));
	mOpacityEffect->setOpacity(mOpacityEffect->opacity()-0.05);
}

// DkThumbsSaver --------------------------------------------------------------------
DkThumbsSaver::DkThumbsSaver(QWidget* parent) : DkWidget(parent) {
	mStop = false;
	mCLoadIdx = 0;
	mNumSaved = 0;
}

void DkThumbsSaver::processDir(QVector<QSharedPointer<DkImageContainerT> > images, bool forceSave) {

	if (images.empty())
		return;

	mStop = false;
	mCLoadIdx = 0;
	mNumSaved = 0;

	mPd = new QProgressDialog(tr("\nCreating thumbnails...\n") + images.first()->filePath(), tr("Cancel"), 0, (int)images.size(), QApplication::activeWindow());
	mPd->setWindowTitle(tr("Thumbnails"));

	//pd->setWindowModality(Qt::WindowModal);

	connect(this, SIGNAL(numFilesSignal(int)), mPd, SLOT(setValue(int)));
	connect(mPd, SIGNAL(canceled()), this, SLOT(stopProgress()));

	mPd->show();

	this->mForceSave = forceSave;
	this->mImages = images;
	loadNext();

}

void DkThumbsSaver::thumbLoaded(bool) {

	mNumSaved++;
	emit numFilesSignal(mNumSaved);

	if (mNumSaved == mImages.size() || mStop) {
		if (mPd) {
			mPd->close();
			mPd->deleteLater();
			mPd = 0;
		}
		mStop = true;
	}
	else
		loadNext();
}

void DkThumbsSaver::loadNext() {
	
	if (mStop)
		return;

	int missing = DkSettings::resources.maxThumbsLoading-DkSettings::resources.numThumbsLoading;
	int numLoading = mCLoadIdx+missing;
	int force = (mForceSave) ? DkThumbNail::force_save_thumb : DkThumbNail::save_thumb;

	qDebug() << "missing: " << missing << " num loading: " << numLoading;
	qDebug() << "loading bounds: " << mCLoadIdx << " - " << numLoading;

	for (int idx = mCLoadIdx; idx < mImages.size() && idx < numLoading; idx++) {
		mCLoadIdx++;
		connect(mImages.at(idx)->getThumb().data(), SIGNAL(thumbLoadedSignal(bool)), this, SLOT(thumbLoaded(bool)));
		mImages.at(idx)->getThumb()->fetchThumb(force);
	}
}

void DkThumbsSaver::stopProgress() {

	mStop = true;
}

// DkFileSystemModel --------------------------------------------------------------------
DkFileSystemModel::DkFileSystemModel(QObject* parent /* = 0 */) : QFileSystemModel(parent) {

	// some custom settings
	setRootPath(QDir::rootPath());
	setNameFilters(DkSettings::app.fileFilters);
	setReadOnly(false);
	//setSupportedDragActions(Qt::CopyAction | Qt::MoveAction);

}

// DkSortFileProxyModel --------------------------------------------------------------------
DkSortFileProxyModel::DkSortFileProxyModel(QObject* parent /* = 0 */) : QSortFilterProxyModel(parent) {

}

bool DkSortFileProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {

	if (left.data().canConvert(QVariant::Url)) {

		QFileInfo lf = left.data().toString();
		QFileInfo rf = right.data().toString();

		// could not find a better way to tell files from dirs appart (isDir() is not what we expect)
		if (lf.suffix().isEmpty() && !rf.suffix().isEmpty())
			return true;
		else if (!lf.suffix().isEmpty() && rf.suffix().isEmpty())
			return false;		


		QString ls = (!lf.fileName().isEmpty()) ? lf.fileName() : lf.absoluteFilePath();	// otherwise e.g. C: is empty
		QString rs = (!rf.fileName().isEmpty()) ? rf.fileName() : rf.absoluteFilePath();

		QString ld = ls.section(QRegExp("[A-Z]:"), 1, -1, QString::SectionIncludeLeadingSep);
		
		// sort by drive letter if present
		if (!ld.isEmpty()) {
			ld.truncate(2);
			ls = ld;
		}

		QString rd = rs.section(QRegExp("[A-Z]:"), 1, -1, QString::SectionIncludeLeadingSep);

		// sort by drive letter if present
		if (!rd.isEmpty()) {
			rd.truncate(2);
			rs = rd;
		}

		return DkUtils::compLogicQString(ls, rs);
	}

	return QSortFilterProxyModel::lessThan(left, right);
}

// DkExplorer --------------------------------------------------------------------
DkExplorer::DkExplorer(const QString& title, QWidget* parent /* = 0 */, Qt::WindowFlags flags /* = 0 */) : DkDockWidget(title, parent, flags) {

	setObjectName("DkExplorer");
	createLayout();
	readSettings();

	connect(fileTree, SIGNAL(clicked(const QModelIndex&)), this, SLOT(fileClicked(const QModelIndex&)));
}

DkExplorer::~DkExplorer() {
	writeSettings();
}

void DkExplorer::createLayout() {

	fileModel = new DkFileSystemModel(this);
	
	sortModel = new DkSortFileProxyModel(this);
	sortModel->setSourceModel(fileModel);
	sortModel->setSortLocaleAware(true);

	fileTree = new QTreeView(this);
	fileTree->setSortingEnabled(true);
	fileTree->setModel(sortModel);
	fileTree->setDragEnabled(true);
	//fileTree->setContextMenuPolicy(Qt::CustomContextMenu);

	// by default descendingOrder is set
	fileTree->header()->setSortIndicator(0, Qt::AscendingOrder);

	setWidget(fileTree);
}

void DkExplorer::setCurrentImage(QSharedPointer<DkImageContainerT> img) {

	if (!img)
		return;

	setCurrentPath(img->filePath());
}

void DkExplorer::setCurrentPath(const QString& filePath) {

	// expand folders
	if (QFileInfo(filePath).isDir())
		fileTree->expand(sortModel->mapFromSource(fileModel->index(filePath)));

	fileTree->setCurrentIndex(sortModel->mapFromSource(fileModel->index(filePath)));
}

void DkExplorer::fileClicked(const QModelIndex &index) const {

	QFileInfo cFile = fileModel->fileInfo(sortModel->mapToSource(index));

	qDebug() << "opening: " << cFile.absoluteFilePath();

	if (DkUtils::isValid(cFile))
		emit openFile(cFile.absoluteFilePath());
	else if (cFile.isDir())
		emit openDir(cFile.absoluteFilePath());
}

void DkExplorer::contextMenuEvent(QContextMenuEvent *event) {

	QMenu* cm = new QMenu();

	// enable editing
	QAction* editAction = new QAction(tr("Editable"), this);
	editAction->setCheckable(true);
	editAction->setChecked(!fileModel->isReadOnly());
	connect(editAction, SIGNAL(toggled(bool)), this, SLOT(setEditable(bool)));
	
	cm->addAction(editAction);
	cm->addSeparator();


	columnActions.clear();	// quick&dirty

	for (int idx = 0; idx < fileModel->columnCount(); idx++) {

		QAction* action = new QAction(fileModel->headerData(idx, Qt::Horizontal).toString(), this);
		action->setCheckable(true);
		action->setChecked(!fileTree->isColumnHidden(idx));
		action->setObjectName(QString::number(idx));

		connect(action, SIGNAL(toggled(bool)), this, SLOT(showColumn(bool)));
		columnActions.push_back(action);

		cm->addAction(action);
	}

	cm->exec(event->globalPos());	
}

void DkExplorer::showColumn(bool show) {

	bool ok = false;
	int idx = QObject::sender()->objectName().toInt(&ok);

	if (!ok)
		return;

	fileTree->setColumnHidden(idx, !show);
}

void DkExplorer::setEditable(bool editable) {
	fileModel->setReadOnly(!editable);	
}

void DkExplorer::closeEvent(QCloseEvent* event) {

	writeSettings();
	DkDockWidget::closeEvent(event);
}

void DkExplorer::writeSettings() {

	QSettings& settings = Settings::instance().getSettings();
	settings.beginGroup(objectName());
	
	for (int idx = 0; idx < fileModel->columnCount(QModelIndex()); idx++) {
		QString headerVal = fileModel->headerData(idx, Qt::Horizontal).toString();
		settings.setValue(headerVal + "Size", fileTree->columnWidth(idx));
		settings.setValue(headerVal + "Hidden", fileTree->isColumnHidden(idx));
	}

	settings.setValue("ReadOnly", fileModel->isReadOnly());
	settings.endGroup();
}

void DkExplorer::readSettings() {

	QSettings& settings = Settings::instance().getSettings();
	settings.beginGroup(objectName());

	for (int idx = 0; idx < fileModel->columnCount(QModelIndex()); idx++) {
		
		QString headerVal = fileModel->headerData(idx, Qt::Horizontal).toString();
		
		int colWidth = settings.value(headerVal + "Size", -1).toInt();
		if (colWidth != -1) 
			fileTree->setColumnWidth(idx, colWidth);

		bool showCol = idx != 0;	// by default, show the first column only
		fileTree->setColumnHidden(idx, settings.value(headerVal + "Hidden", showCol).toBool());
	}

	fileModel->setReadOnly(settings.value("ReadOnly", true).toBool());
	settings.endGroup();
}

// DkOverview --------------------------------------------------------------------
DkOverview::DkOverview(QWidget* parent) : QLabel(parent) {

	setObjectName("DkOverview");
	setMinimumSize(0, 0);
	setMaximumSize(200, 200);
	setCursor(Qt::ArrowCursor);
	setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
}

void DkOverview::paintEvent(QPaintEvent *event) {

	if (mImg.isNull() || !mImgMatrix || !mWorldMatrix)
		return;

	QPainter painter(this);

	int lm, tm, rm, bm;
	getContentsMargins(&lm, &tm, &rm, &bm);

	QSize viewSize = QSize(width()-lm-rm, height()-tm-bm);	// overview shall take 15% of the mViewport....
	
	if (viewSize.width() > 2 && viewSize.height() > 2) {
	
		QTransform overviewImgMatrix = getScaledImageMatrix();			// matrix that always resizes the image to the current mViewport
		QRectF overviewImgRect = getScaledImageMatrix().mapRect(QRectF(QPointF(), mImg.size()));

		// now render the current view
		QRectF viewRect = mViewPortRect;
		viewRect = mWorldMatrix->inverted().mapRect(viewRect);
		viewRect = mImgMatrix->inverted().mapRect(viewRect);
		viewRect = overviewImgMatrix.mapRect(viewRect);
		viewRect.moveTopLeft(viewRect.topLeft()+QPointF(lm, tm));

		if(viewRect.topLeft().x() < overviewImgRect.topLeft().x()) viewRect.setTopLeft(QPointF(overviewImgRect.topLeft().x(), viewRect.topLeft().y()));
		if(viewRect.topLeft().y() < overviewImgRect.topLeft().y()) viewRect.setTopLeft(QPointF(viewRect.topLeft().x(), overviewImgRect.topLeft().y()));
		if(viewRect.bottomRight().x() > overviewImgRect.bottomRight().x()) viewRect.setBottomRight(QPointF(overviewImgRect.bottomRight().x()-1, viewRect.bottomRight().y()));
		if(viewRect.bottomRight().y() > overviewImgRect.bottomRight().y()) viewRect.setBottomRight(QPointF(viewRect.bottomRight().x(), overviewImgRect.bottomRight().y()-1));		

		//draw the image's location
		painter.setRenderHints(QPainter::SmoothPixmapTransform);
		painter.setBrush(DkSettings::display.bgColorWidget);
		painter.setPen(QColor(200, 200, 200));
		//painter.drawRect(overviewRect);
		painter.setOpacity(0.8f);
		painter.drawImage(overviewImgRect, imgT, QRect(0, 0, imgT.width(), imgT.height()));

		QColor col = DkSettings::display.highlightColor;
		col.setAlpha(255);
		painter.setPen(col);
		col.setAlpha(50);
		painter.setBrush(col);

		if (viewRect.width()+1 < overviewImgRect.width() || viewRect.height()+1 < overviewImgRect.height())	// draw viewrect if we do not see all parts of the image
			painter.drawRect(viewRect);

	}
	painter.end();

	QWidget::paintEvent(event);
}

void DkOverview::mousePressEvent(QMouseEvent *event) {
	
	mEnterPos = event->pos();
	mPosGrab = event->pos();
}

void DkOverview::mouseReleaseEvent(QMouseEvent *event) {

	QPointF dxy = mEnterPos-QPointF(event->pos());

	if (dxy.manhattanLength() < 4) {
		
		int lm, tm, rm, bm;
		getContentsMargins(&lm, &tm, &rm, &bm);
		
		// move to the current position
		QRectF viewRect = mViewPortRect;
		viewRect = mWorldMatrix->inverted().mapRect(viewRect);
		viewRect = mImgMatrix->inverted().mapRect(viewRect);
		viewRect = getScaledImageMatrix().mapRect(viewRect);
		QPointF currentViewPoint = viewRect.center();

		float panningSpeed = (float)-(mWorldMatrix->m11()/(getScaledImageMatrix().m11()/mImgMatrix->m11()));

		QPointF cPos = event->pos()-QPointF(lm, tm);
		QPointF lDxy = (cPos - currentViewPoint)/mWorldMatrix->m11()*panningSpeed;
		emit moveViewSignal(lDxy);

		if (event->modifiers() == DkSettings::global.altMod)
			emit sendTransformSignal();
	}

}

void DkOverview::mouseMoveEvent(QMouseEvent *event) {

	if (event->buttons() != Qt::LeftButton)
		return;

	float panningSpeed = (float)-(mWorldMatrix->m11()/(getScaledImageMatrix().m11()/mImgMatrix->m11()));

	QPointF cPos = event->pos();
	QPointF dxy = (cPos - mPosGrab)/mWorldMatrix->m11()*panningSpeed;
	mPosGrab = cPos;
	emit moveViewSignal(dxy);

	if (event->modifiers() == DkSettings::global.altMod)
		emit sendTransformSignal();

}

void DkOverview::resizeEvent(QResizeEvent* event) {

	QWidget::resizeEvent(event);
}

QRectF DkOverview::getImageRect() const {
	
	QRectF imgRect = QRectF(QPoint(), size());			// get the overview rect

	if ((float)imgT.width()/imgT.height() < (float)imgRect.width()/imgRect.height())
		imgRect.setWidth(width() * (float)height()/(float)imgT.height());
	else
		imgRect.setHeight(height() * (float)width()/(float)imgT.width());

	//imgRect = imgRect.toRect();	// force round

	return imgRect;
}

void DkOverview::resizeImg() {

	if (mImg.isNull())
		return;

	//QRectF overviewRect = getImageRect();
	QTransform overviewImgMatrix = getScaledImageMatrix();			// matrix that always resizes the image to the current mViewport
	
	// is the overviewImgMatrix empty?
	if (overviewImgMatrix.isIdentity())
		return;
	
	//if (overviewRect.width() <= 1|| overviewRect.height() <= 1)
	//	return;

	// fast downscaling
	imgT = mImg.scaled(maximumWidth()*2, maximumHeight()*2, Qt::KeepAspectRatio, Qt::FastTransformation);
	imgT = imgT.scaled(maximumWidth(), maximumHeight(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

QTransform DkOverview::getScaledImageMatrix() {

	if (mImg.isNull())
		return QTransform();

	int lm, tm, rm, bm;
	getContentsMargins(&lm, &tm, &rm, &bm);

	QSize iSize = QSize(width()-lm-rm, height()-tm-bm);	// inner size

	if (iSize.width() < 2 || iSize.height() < 2)
		return QTransform();

	// the image resizes as we zoom
	QRectF imgRect = QRectF(QPoint(lm, tm), mImg.size());
	float ratioImg = (float)(imgRect.width()/imgRect.height());
	float ratioWin = (float)(iSize.width())/(float)(iSize.height());

	QTransform imgMatrix;
	float s;
	if (imgRect.width() == 0 || imgRect.height() == 0)
		s = 1.0f;
	else
		s = (ratioImg > ratioWin) ? (float)(iSize.width()/imgRect.width()) : (float)(iSize.height()/imgRect.height());

	imgMatrix.scale(s, s);

	QRectF imgViewRect = imgMatrix.mapRect(imgRect);
	imgMatrix.translate((iSize.width()-imgViewRect.width())*0.5f/s, (iSize.height()-imgViewRect.height())*0.5f/s);

	return imgMatrix;
}


// DkZoomWidget --------------------------------------------------------------------
DkZoomWidget::DkZoomWidget(QWidget* parent) : DkFadeLabel(parent) {

	mAutoHide = false;
	setObjectName("DkZoomWidget");
	createLayout();

	setMinimumSize(70, 0);
	setMaximumSize(200, 240);
	setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
	QMetaObject::connectSlotsByName(this);
}

void DkZoomWidget::createLayout() {

	mOverview = new DkOverview(this);

	mSlZoom = new QSlider(Qt::Horizontal, this);
	mSlZoom->setObjectName("slZoom");
	mSlZoom->setCursor(Qt::ArrowCursor);
	mSlZoom->setMinimum(0);	// add a mapping here
	mSlZoom->setMaximum(100);

	QString styleString = "QDoubleSpinBox{margin: 0px; padding: 0px; color: " + 
		DkUtils::colorToString(DkSettings::display.fontColor) + 
		"; background-color: rgba(0,0,0,0); border: none; selection-background-color: " +
		DkUtils::colorToString(DkSettings::display.highlightColor) + ";}";
	//styleString += "QDoubleSpinBox::up-arrow, QDoubleSpinBox::down-arrow {width: 0px; heihgt: 0px;}";
	//styleString += "QDoubleSpinBox::up-bottom, QDoubleSpinBox::down-bottom {width: 0px; heihgt: 0px;}";

	mSbZoom = new QDoubleSpinBox(this);
	mSbZoom->setObjectName("sbZoom");
	mSbZoom->setStyleSheet(styleString);
	mSbZoom->setButtonSymbols(QAbstractSpinBox::NoButtons);
	mSbZoom->setSuffix("%");
	mSbZoom->setDecimals(0);
	mSbZoom->setMinimum(0.2);
	mSbZoom->setValue(100);
	mSbZoom->setMaximum(6000);

	QLabel* sliderWidget = new QLabel(this);
	sliderWidget->setObjectName("DkOverviewSliderWidget");
	QHBoxLayout* sliderLayout = new QHBoxLayout(sliderWidget);
	sliderLayout->setContentsMargins(10,0,0,0);
	sliderLayout->setSpacing(10);
	sliderLayout->addWidget(mSlZoom);
	sliderLayout->addWidget(mSbZoom);

	QVBoxLayout* layout = new QVBoxLayout(this);
	//mLayout->setContentsMargins(10,10,10,10);
	layout->setSpacing(0);
	layout->addWidget(mOverview);
	layout->addWidget(sliderWidget);
}

void DkZoomWidget::on_sbZoom_valueChanged(double zoomLevel) {
	updateZoom((float)zoomLevel);
	mAutoHide = false;
	emit zoomSignal((float)zoomLevel/100.0f);
}

void DkZoomWidget::on_slZoom_valueChanged(int zoomLevel) {
	float level = (zoomLevel > 50) ? (zoomLevel-50.0f)/50.0f * (float)mSbZoom->maximum() + 200.0f : zoomLevel*4.0f;
	if (level < 0.2f) level = 0.2f;
	mAutoHide = false;
	updateZoom(level);
	emit zoomSignal(level/100.0f);
}

void DkZoomWidget::updateZoom(float zoomLevel) {

	mSlZoom->blockSignals(true);
	mSbZoom->blockSignals(true);
	
	int slVal = (zoomLevel > 200.0f) ? qRound(zoomLevel/mSbZoom->maximum()*50.0f + 50.0f) : qRound(zoomLevel*0.25f);
	mSlZoom->setValue(slVal);
	mSbZoom->setValue(zoomLevel);
	mSlZoom->blockSignals(false);
	mSbZoom->blockSignals(false);
}

DkOverview* DkZoomWidget::getOverview() const {
	return mOverview;
}

void DkZoomWidget::setVisible(bool visible, bool autoHide /* = false */) {
	
	if (!isVisible() && visible)
		this->mAutoHide = autoHide;

	if (!visible)
		autoHide = false;

	DkFadeLabel::setVisible(visible);
}

bool DkZoomWidget::isAutoHide() const {
	return mAutoHide;
}

// DkButton --------------------------------------------------------------------
DkButton::DkButton(QWidget* parent) : QPushButton(parent) {

}

DkButton::DkButton(const QString& text, QWidget* parent) : QPushButton(text, parent) {

}

DkButton::DkButton(const QIcon& icon, const QString& text, QWidget* parent) : QPushButton(icon, text, parent) {

	checkedIcon = icon;
	setText(text);

	init();
}

DkButton::DkButton(const QIcon& checkedIcon, const QIcon& uncheckedIcon, const QString& text, QWidget* parent) : QPushButton(checkedIcon, text, parent) {

	this->checkedIcon = checkedIcon;
	this->uncheckedIcon = uncheckedIcon;
	this->setCheckable(true);
	setText(text);
	
	init();
}

void DkButton::init() {

	setIcon(checkedIcon);

	if (!checkedIcon.availableSizes().empty())
		this->setMaximumSize(checkedIcon.availableSizes()[0]);	// crashes if the image is empty!!
	
	mouseOver = false;
	keepAspectRatio = true;
}

void DkButton::setFixedSize(QSize size) {
	mySize = size;
	this->setMaximumSize(size);
}

void DkButton::paintEvent(QPaintEvent*) {

 	QPainter painter(this);
	QPoint offset;
	QSize s;
	float opacity = 1.0f;

	if (!isEnabled())
		opacity = 0.5f;
	else if(!mouseOver)
		opacity = 0.7f;

	painter.setOpacity(opacity);
	painter.setRenderHint(QPainter::SmoothPixmapTransform);

	if (!mySize.isEmpty()) {
		
		offset = QPoint(qRound((float)(size().width()-mySize.width())*0.5f), qRound((float)(size().height()-mySize.height())*0.5f));
		s = mySize;
	}
	else
		s = this->size();

	// scale to parent label
	QRect r = (keepAspectRatio) ? QRect(offset, checkedIcon.actualSize(s)) : QRect(offset, s);	// actual size preserves the aspect ratio
	QPixmap pm2draw;

	if (isChecked() || !isCheckable())
		pm2draw = checkedIcon.pixmap(s);
	else
		pm2draw = uncheckedIcon.pixmap(s);

	if (this->isDown()) {
		QPixmap effect = createSelectedEffect(&pm2draw);
		painter.drawPixmap(r, effect);
	}

	painter.drawPixmap(r, pm2draw);
	painter.end();
}

QPixmap DkButton::createSelectedEffect(QPixmap* pm) {

	if (!pm || pm->isNull())
		return QPixmap();

	return DkImage::colorizePixmap(*pm, DkSettings::display.highlightColor, 1.0f);
}

void DkButton::focusInEvent(QFocusEvent*) {
	mouseOver = true;
}

void DkButton::focusOutEvent(QFocusEvent*) {
	mouseOver = false;
}

void DkButton::enterEvent(QEvent*) {
	mouseOver = true;
}

void DkButton::leaveEvent(QEvent*) {
	mouseOver = false;
}

// star label --------------------------------------------------------------------
DkRatingLabel::DkRatingLabel(int rating, QWidget* parent, Qt::WindowFlags flags) : DkWidget(parent, flags) {

	setObjectName("DkRatingLabel");
	mRating = rating;
	init();

	int iconSize = 16;

	mLayout = new QBoxLayout(QBoxLayout::LeftToRight);
	mLayout->setContentsMargins(0,0,0,0);
	mLayout->setSpacing(3);
	mLayout->addStretch();
	
	for (int idx = 0; idx < mStars.size(); idx++) {
		mStars[idx]->setFixedSize(QSize(iconSize, iconSize));
		mLayout->addWidget(mStars[idx]);
	}
	
	setLayout(mLayout);
}

void DkRatingLabel::init() {

	QPixmap starDark = QPixmap(":/nomacs/img/star-dark.png");
	QPixmap starWhite = QPixmap(":/nomacs/img/star-white.png");

	mStars.resize(5);
	
	mStars[rating_1] = new DkButton(starWhite, starDark, tr("one star"), this);
	mStars[rating_1]->setCheckable(true);
	connect(mStars[rating_1], SIGNAL(released()), this, SLOT(rating1()));

	mStars[rating_2] = new DkButton(starWhite, starDark, tr("two stars"), this);
	mStars[rating_2]->setCheckable(true);
	connect(mStars[rating_2], SIGNAL(released()), this, SLOT(rating2()));

	mStars[rating_3] = new DkButton(starWhite, starDark, tr("three star"), this);
	mStars[rating_3]->setCheckable(true);
	connect(mStars[rating_3], SIGNAL(released()), this, SLOT(rating3()));

	mStars[rating_4] = new DkButton(starWhite, starDark, tr("four star"), this);
	mStars[rating_4]->setCheckable(true);
	connect(mStars[rating_4], SIGNAL(released()), this, SLOT(rating4()));

	mStars[rating_5] = new DkButton(starWhite, starDark, tr("five star"), this);
	mStars[rating_5]->setCheckable(true);
	connect(mStars[rating_5], SIGNAL(released()), this, SLOT(rating5()));

}

// DkRatingLabelBg --------------------------------------------------------------------
DkRatingLabelBg::DkRatingLabelBg(int rating, QWidget* parent, Qt::WindowFlags flags) : DkRatingLabel(rating, parent, flags) {

	mHideTimer = new QTimer(this);
	mHideTimer->setInterval(mTimeToDisplay);
	mHideTimer->setSingleShot(true);

	// we want a margin
	mLayout->setContentsMargins(10,4,10,4);
	mLayout->setSpacing(4);

	mActions.resize(6);

	mActions[rating_0] = new QAction(tr("no rating"), this);
	mActions[rating_0]->setShortcut(Qt::Key_0);
	connect(mActions[rating_0], SIGNAL(triggered()), this, SLOT(rating0()));

	mActions[rating_1] = new QAction(tr("one star"), this);
	mActions[rating_1]->setShortcut(Qt::Key_1);
	connect(mActions[rating_1], SIGNAL(triggered()), this, SLOT(rating1()));

	mActions[rating_2] = new QAction(tr("two stars"), this);
	mActions[rating_2]->setShortcut(Qt::Key_2);
	connect(mActions[rating_2], SIGNAL(triggered()), this, SLOT(rating2()));

	mActions[rating_3] = new QAction(tr("three stars"), this);
	mActions[rating_3]->setShortcut(Qt::Key_3);
	connect(mActions[rating_3], SIGNAL(triggered()), this, SLOT(rating3()));

	mActions[rating_4] = new QAction(tr("four stars"), this);
	mActions[rating_4]->setShortcut(Qt::Key_4);
	connect(mActions[rating_4], SIGNAL(triggered()), this, SLOT(rating4()));

	mActions[rating_5] = new QAction(tr("five stars"), this);
	mActions[rating_5]->setShortcut(Qt::Key_5);
	connect(mActions[rating_5], SIGNAL(triggered()), this, SLOT(rating5()));

	mStars[rating_1]->addAction(mActions[rating_1]);
	mStars[rating_2]->addAction(mActions[rating_2]);
	mStars[rating_3]->addAction(mActions[rating_3]);
	mStars[rating_4]->addAction(mActions[rating_4]);
	mStars[rating_5]->addAction(mActions[rating_5]);
	
	connect(mHideTimer, SIGNAL(timeout()), this, SLOT(hide()));
}

DkRatingLabelBg::~DkRatingLabelBg() {}

void DkRatingLabelBg::changeRating(int newRating) {
	DkRatingLabel::changeRating(newRating);
	show();
	mHideTimer->start();
}

QVector<QAction*> DkRatingLabelBg::getActions() const {
	return mActions;
}

void DkRatingLabelBg::paintEvent(QPaintEvent *event) {

	QPainter painter(this);
	painter.fillRect(QRect(QPoint(), this->size()), mBgCol);
	painter.end();

	DkRatingLabel::paintEvent(event);
}

// title info --------------------------------------------------------------------
DkFileInfoLabel::DkFileInfoLabel(QWidget* parent) : DkFadeLabel(parent) {

	setObjectName("DkFileInfoLabel");
	setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);

	mTitleLabel = new QLabel(this);
	mTitleLabel->setMouseTracking(true);
	mTitleLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
	mDateLabel = new QLabel(this);
	mDateLabel->setMouseTracking(true);
	mDateLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
	mRatingLabel = new DkRatingLabel(0, this);
	setMinimumWidth(110);
	
	createLayout();
}

void DkFileInfoLabel::createLayout() {

	mLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
	mLayout->setSpacing(2);

	mLayout->addWidget(mTitleLabel);
	mLayout->addWidget(mDateLabel);
	mLayout->addWidget(mRatingLabel);
	//mLayout->addStretch();
}

void DkFileInfoLabel::setVisible(bool visible, bool saveSettings) {

	// nothing to display??
	if (!DkSettings::slideShow.display.testBit(DkSettings::display_file_name) &&
		!DkSettings::slideShow.display.testBit(DkSettings::display_creation_date) &&
		!DkSettings::slideShow.display.testBit(DkSettings::display_file_rating) && visible) {
			
			QMessageBox infoDialog(QApplication::activeWindow());
			infoDialog.setWindowTitle(tr("Info Box"));
			infoDialog.setText(tr("All information fields are currently hidden.\nDo you want to show them again?"));
			infoDialog.setIcon(QMessageBox::Information);
			infoDialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
			infoDialog.setDefaultButton(QMessageBox::Yes);
			infoDialog.show();
			int choice = infoDialog.exec();

			if (choice == QMessageBox::No) {
				DkFadeLabel::setVisible(false);
				return;
			}
			else {
				DkSettings::slideShow.display.setBit(DkSettings::display_file_name, true);
				DkSettings::slideShow.display.setBit(DkSettings::display_creation_date, true);
				DkSettings::slideShow.display.setBit(DkSettings::display_file_rating, true);
			}
	}

	DkFadeLabel::setVisible(visible, saveSettings);
	mTitleLabel->setVisible(DkSettings::slideShow.display.testBit(DkSettings::display_file_name));
	mDateLabel->setVisible(DkSettings::slideShow.display.testBit(DkSettings::display_creation_date));
	mRatingLabel->setVisible(DkSettings::slideShow.display.testBit(DkSettings::display_file_rating));

	int height = 32;
	if (mTitleLabel->isVisible())
		height += mTitleLabel->sizeHint().height();
	if (mDateLabel->isVisible())
		height += mDateLabel->sizeHint().height();
	if (mRatingLabel->isVisible())
		height += mRatingLabel->sizeHint().height();

	qDebug() << "my minimum height: " << height;
	setMinimumHeight(height);
	updateWidth();
}

void DkFileInfoLabel::setEdited(bool edited) {

	if (!isVisible() || !edited)
		return;

	QString cFileName = mTitleLabel->text() + "*";
	this->mTitleLabel->setText(cFileName);

}

DkRatingLabel* DkFileInfoLabel::getRatingLabel() {
	return mRatingLabel;
}

void DkFileInfoLabel::updateInfo(const QString& filePath, const QString& attr, const QString& date, const int rating) {

	mFilePath = filePath;
	updateTitle(filePath, attr);
	updateDate(date);
	updateRating(rating);

	updateWidth();
}

void DkFileInfoLabel::updateTitle(const QString& filePath, const QString& attr) {
	
	updateDate();
	mTitleLabel->setText(QFileInfo(filePath).fileName() + " " + attr);
	mTitleLabel->setAlignment(Qt::AlignRight);

	updateWidth();
}

void DkFileInfoLabel::updateDate(const QString& date) {

	QString dateConverted = DkUtils::convertDateString(date, QFileInfo(mFilePath));

	mDateLabel->setText(dateConverted);
	mDateLabel->setAlignment(Qt::AlignRight);

	updateWidth();
}

void DkFileInfoLabel::updateRating(const int rating) {
	
	mRatingLabel->setRating(rating);
}

void DkFileInfoLabel::updateWidth() {

	int width = 20;		// mar
	width += qMax(qMax(mTitleLabel->sizeHint().width(), mDateLabel->sizeHint().width()), mRatingLabel->sizeHint().width());
	
	if (width < minimumWidth())
		setMinimumWidth(width);
	
	setMaximumWidth(width);
}

// player --------------------------------------------------------------------
DkPlayer::DkPlayer(QWidget* parent) : DkWidget(parent) {

	init();
}

void DkPlayer::init() {
	
	setObjectName("DkPlayer");

	// slide show
	int timeToDisplayPlayer = 3000;
	timeToDisplay = qRound(DkSettings::slideShow.time*1000);
	playing = false;
	displayTimer = new QTimer(this);
	displayTimer->setInterval(timeToDisplay);
	displayTimer->setSingleShot(true);
	connect(displayTimer, SIGNAL(timeout()), this, SLOT(autoNext()));

	hideTimer = new QTimer(this);
	hideTimer->setInterval(timeToDisplayPlayer);
	hideTimer->setSingleShot(true);
	connect(hideTimer, SIGNAL(timeout()), this, SLOT(hide()));

	actions.resize(1);
	actions[play_action] = new QAction(tr("play"), this);
	connect(actions[play_action], SIGNAL(triggered()), this, SLOT(togglePlay()));

	QPixmap icon = QPixmap(":/nomacs/img/player-back.png");
	previousButton = new DkButton(icon, tr("previous"), this);
	previousButton->keepAspectRatio = false;
	connect(previousButton, SIGNAL(pressed()), this, SLOT(previous()));

	icon = QPixmap(":/nomacs/img/player-pause.png");
	QPixmap icon2 = QPixmap(":/nomacs/img/player-play.png");
	playButton = new DkButton(icon, icon2, tr("play"), this);
	playButton->keepAspectRatio = false;
	playButton->setChecked(false);
	playButton->addAction(actions[play_action]);
	connect(playButton, SIGNAL(toggled(bool)), this, SLOT(play(bool)));

	icon = QPixmap(":/nomacs/img/player-next.png");
	nextButton = new DkButton(icon, tr("next"), this);
	nextButton->keepAspectRatio = false;
	connect(nextButton, SIGNAL(pressed()), this, SLOT(next()));

	// now add to mLayout
	container = new QWidget(this);
	QHBoxLayout *layout = new QHBoxLayout(container);
	layout->setContentsMargins(0,0,0,0);
	layout->addWidget(previousButton);
	layout->addWidget(playButton);
	layout->addWidget(nextButton);

	setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
	setMinimumSize(15, 5);
	setMaximumSize(315, 113);
}

void DkPlayer::resizeEvent(QResizeEvent *event) {

	if (event->oldSize() == event->size())
		return;

	// always preserve the player's aspect ratio
	QSizeF s = event->size();
	QSizeF ms = maximumSize();
	float aRatio = (float)(s.width()/s.height());
	float amRatio = (float)(ms.width()/ms.height());
	
	if (aRatio != amRatio && s.width() / amRatio <= s.height()) {
		s.setHeight(s.width() / amRatio);

		QRect r = QRect(QPoint(), s.toSize());
		r.moveBottom(event->size().height()-1);
		r.moveCenter(QPoint(qRound((float)event->size().width()/2.0f), r.center().y()));
		container->setGeometry(r);
	}
	else {
		s.setWidth(s.height() * amRatio);

		QRect r = QRect(QPoint(), s.toSize());
		r.moveBottom(event->size().height()-1);
		r.moveCenter(QPoint(qRound((float)event->size().width()/2.0f), r.center().y()));
		container->setGeometry(r);
	}

	QWidget::resizeEvent(event);
}

void DkPlayer::play(bool play) {

	if (play != playing)	// emulate a click
		playButton->setChecked(play);

	playing = play;

	if (play) {
		displayTimer->start();
		hideTimer->start();
	}
	else
		displayTimer->stop();
}

void DkPlayer::togglePlay() {

	show();
	playing = !playing;
	playButton->click();
}

void DkPlayer::startTimer() {
	if (playing) {
		displayTimer->setInterval(qRound(DkSettings::slideShow.time*1000));	// if it was updated...
		displayTimer->start();
	}
}

void DkPlayer::autoNext() {
	emit nextSignal();
}

void DkPlayer::next() {
	hideTimer->stop();
	emit nextSignal();
}

void DkPlayer::previous() {
	hideTimer->stop();
	emit previousSignal();
}

bool DkPlayer::isPlaying() const {
	return playing;
}

void DkPlayer::setTimeToDisplay(int ms) {

	timeToDisplay = ms;
	displayTimer->setInterval(ms);
}

void DkPlayer::show(int ms) {		
	
	if (ms > 0 && !hideTimer->isActive()) {
		hideTimer->setInterval(ms);
		hideTimer->start();
	}

	bool showPlayer = getCurrentDisplaySetting();

	DkWidget::show();

	// automatic mShowing, don't store it in the display bits
	if (ms > 0 && mDisplaySettingsBits && mDisplaySettingsBits->size() > DkSettings::app.currentAppMode) {
		mDisplaySettingsBits->setBit(DkSettings::app.currentAppMode, showPlayer);
	}
}
 
// DkTransformRectangle --------------------------------------------------------------------
DkTransformRect::DkTransformRect(int idx, DkRotatingRect* rect, QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) {

	this->parentIdx = idx;
	this->size = QSize(12, 12);
	this->rect = rect;

	init();

	this->resize(size);
	setCursor(Qt::CrossCursor);
}

void DkTransformRect::init() {

}

void DkTransformRect::draw(QPainter *painter) {

	QPen penNoStroke;
	penNoStroke.setWidth(0);
	penNoStroke.setColor(QColor(0,0,0,0));
	
	QPen pen;
	//pen.setWidth(1);
	pen.setColor(QColor(255,255,0,100));

	QRectF visibleRect(QPointF(), QSizeF(5,5));
	QRectF whiteRect(QPointF(), QSize(7,7));
	visibleRect.moveCenter(geometry().center());
	whiteRect.moveCenter(geometry().center());

	// draw the control point
	painter->setWorldMatrixEnabled(false);
	painter->setPen(penNoStroke);
	painter->setBrush(QColor(0, 0, 0, 0));
	painter->drawRect(geometry());	// invisible rect for mouseevents...
	//painter->setPen(pen);
	painter->setBrush(QColor(255,255,255, 100));
	painter->drawRect(whiteRect);
	painter->setBrush(QColor(0,0,0));
	painter->drawRect(visibleRect);
	painter->setWorldMatrixEnabled(true);
}

void DkTransformRect::mousePressEvent(QMouseEvent *event) {
	
	if (event->buttons() == Qt::LeftButton) {
		posGrab = event->globalPos();
		initialPos = geometry().topLeft();

		emit updateDiagonal(parentIdx);
	}
	qDebug() << "mouse pressed control point";
	QWidget::mousePressEvent(event);
}

void DkTransformRect::mouseMoveEvent(QMouseEvent *event) {

	if (event->buttons() == Qt::LeftButton) {
		
		QPointF pt = initialPos+event->globalPos()-posGrab;
		emit ctrlMovedSignal(parentIdx, pt, event->modifiers(), true);
		qDebug() << "accepted false...";
	}

	QWidget::mouseMoveEvent(event);
}

void DkTransformRect::mouseReleaseEvent(QMouseEvent *event) {

	QWidget::mouseReleaseEvent(event);
}

void DkTransformRect::enterEvent(QEvent*) {

	if (rect)
		setCursor(rect->cpCursor(parentIdx));
}

// DkEditableRectangle --------------------------------------------------------------------
DkEditableRect::DkEditableRect(const QRectF& rect, QWidget* parent, Qt::WindowFlags f) : DkWidget(parent, f) {

	mRect = rect;
	mRotatingCursor = QCursor(QPixmap(":/nomacs/img/rotating-cursor.png"));
	
	setAttribute(Qt::WA_MouseTracking);

	mPen = QPen(QColor(0, 0, 0, 255), 1);
	mPen.setCosmetic(true);
	mBrush = (DkSettings::app.appMode == DkSettings::mode_frameless) ?
		DkSettings::display.bgColorFrameless :
		DkSettings::display.bgColorWidget;

	for (int idx = 0; idx < 8; idx++) {
		mCtrlPoints.push_back(new DkTransformRect(idx, &this->mRect, this));
		mCtrlPoints[idx]->hide();
		connect(mCtrlPoints[idx], SIGNAL(ctrlMovedSignal(int, const QPointF&, Qt::KeyboardModifiers, bool)), this, SLOT(updateCorner(int, const QPointF&, Qt::KeyboardModifiers, bool)));
		connect(mCtrlPoints[idx], SIGNAL(updateDiagonal(int)), this, SLOT(updateDiagonal(int)));
	}
}

void DkEditableRect::reset() {

	mRect = QRectF();
	//for (int idx = 0; idx < ctrlPoints.size(); idx++)
	//	ctrlPoints[idx]->reset();

}

QPointF DkEditableRect::map(const QPointF &pos) {

	QPointF posM = pos;
	if (mWorldTform) posM = mWorldTform->inverted().map(posM);
	if (mImgTform)	posM = mImgTform->inverted().map(posM);
	
	return posM;
}

QPointF DkEditableRect::clipToImage(const QPointF &pos) {
	
	if (!mImgRect)
		return QPointF(pos);

	QRectF imgViewRect(*mImgRect);
	if (mWorldTform) imgViewRect = mWorldTform->mapRect(imgViewRect);

	float x = (float)pos.x();
	float y = (float)pos.y();
	float magicMargin = 5;	// 5 px for now

	if (qAbs(x - imgViewRect.left()) < magicMargin)
		x = (float)imgViewRect.left();
	if (qAbs(x - imgViewRect.right()) < magicMargin)
		x = (float)imgViewRect.right();

	if (qAbs(y - imgViewRect.top()) < magicMargin)
		y = (float)imgViewRect.top();
	if (qAbs(y - imgViewRect.bottom()) < magicMargin)
		y = (float)imgViewRect.bottom();

	return QPointF(x,y);		// round
}

QPointF DkEditableRect::clipToImageForce(const QPointF &pos) {

	if (!mImgRect)
		return QPointF(pos);

	QRectF imgViewRect(*mImgRect);
	if (mWorldTform) imgViewRect = mWorldTform->mapRect(imgViewRect);

	float x = (float)pos.x();
	float y = (float)pos.y();

	if (x < imgViewRect.left())
		x = (float)imgViewRect.left();
	if (x > imgViewRect.right())
		x = (float)imgViewRect.right();

	if (y < imgViewRect.top())
		y = (float)imgViewRect.top();
	if (y > imgViewRect.bottom())
		y = (float)imgViewRect.bottom();

	return QPointF(x,y);		// round
}

void DkEditableRect::updateDiagonal(int idx) {

	// we need to store the old diagonal in order to enable "keep aspect ratio"
	if (mRect.isEmpty())
		mOldDiag = DkVector(-1.0f, -1.0f);
	else
		mOldDiag = mRect.getDiagonal(idx);
}

void DkEditableRect::setFixedDiagonal(const DkVector& diag) {

	mFixedDiag = diag;

	qDebug() << "after rotating: " << mFixedDiag.toQPointF();

	// don't update in that case
	if (diag.x == 0 || diag.y == 0)
		return;
	else
		mFixedDiag.rotate(-mRect.getAngle());

	QPointF c = mRect.getCenter();

	if (!mRect.getPoly().isEmpty()) 
		mRect.updateCorner(0, mRect.getPoly().at(0), mFixedDiag);

	mRect.setCenter(c);
	update();
}

void DkEditableRect::setPanning(bool panning) {
	this->mPanning = panning;
	setCursor(Qt::OpenHandCursor);
	qDebug() << "panning set...";
}

void DkEditableRect::updateCorner(int idx, const QPointF& point, Qt::KeyboardModifiers modifiers, bool changeState) {

	if (changeState)
		mState = scaling;

	DkVector diag = (modifiers & Qt::ShiftModifier || mFixedDiag.x != 0 && mFixedDiag.y != 0) ? mOldDiag : DkVector();	// braces
	QPointF p = point;
	
	if ((modifiers & Qt::ControlModifier) == 0)
		p = clipToImage(point);

	mRect.updateCorner(idx, map(p), diag);

	// edge control -> remove aspect ratio constraint
	if (idx >= 4 && idx < 8)
		emit aRatioSignal(QPointF(0,0));

	update();
}

void DkEditableRect::paintEvent(QPaintEvent *event) {

	// create path
	QPainterPath path;
	QRect canvas(geometry().x()-1, geometry().y()-1, geometry().width()+1, geometry().height()+1);
	path.addRect(canvas);
	
	QPolygonF p;
	if (!mRect.isEmpty()) {
		// TODO: directly map the points (it's easier and not slower at all)
		p = mRect.getClosedPoly();
		p = mTtform.map(p);
		p = mRtform.map(p); 
		p = mTtform.inverted().map(p);
		if (mImgTform) p = mImgTform->map(p);
		if (mWorldTform) p = mWorldTform->map(p);
		QPolygon pr = p.toPolygon();	// round coordinates
		path.addPolygon(pr);
	}

	// now draw
	QPainter painter(this);

	painter.setPen(mPen);
	painter.setBrush(mBrush);
	painter.drawPath(path);

	drawGuide(&painter, p, mPaintMode);
	
	//// debug
	//painter.drawPoint(rect.getCenter());

	// this changes the painter -> do it at the end
	if (!mRect.isEmpty()) {
		
		for (int idx = 0; idx < mCtrlPoints.size(); idx++) {
			
			QPointF cp;
			
			if (idx < 4) {
				QPointF c = p[idx];
				cp = c-mCtrlPoints[idx]->getCenter();
			}
			// paint control points in the middle of the edge
			else if (idx >= 4) {
				QPointF s = mCtrlPoints[idx]->getCenter();

				QPointF lp = p[idx % 4];
				QPointF rp = p[(idx+1) % 4];

				QVector2D lv = QVector2D(lp-s);
				QVector2D rv = QVector2D(rp-s);

				cp = (lv + 0.5*(rv - lv)).toPointF();
			}

			mCtrlPoints[idx]->move(qRound(cp.x()), qRound(cp.y()));
			mCtrlPoints[idx]->draw(&painter);
		}
	}
 
	painter.end();

	QWidget::paintEvent(event);
}

void DkEditableRect::drawGuide(QPainter* painter, const QPolygonF& p, int paintMode) {

	if (p.isEmpty() || paintMode == no_guide)
		return;

	QColor col = painter->pen().color();
	col.setAlpha(150);
	QPen pen = painter->pen();
	QPen cPen = pen;
	cPen.setColor(col);
	painter->setPen(cPen);

	// vertical
	DkVector lp = p[1]-p[0];	// parallel to drawing
	DkVector l9 = p[3]-p[0];	// perpendicular to drawing

	int nLines = (paintMode == rule_of_thirds) ? 3 : qRound(l9.norm()/20.0f);
	DkVector offset = l9;
	offset.normalize();
	offset *= l9.norm()/nLines;

	DkVector offsetVec = offset;

	for (int idx = 0; idx < (nLines-1); idx++) {

		// step through & paint
		QLineF l = QLineF(DkVector(p[1]+offsetVec).toQPointF(), DkVector(p[0]+offsetVec).toQPointF());
		painter->drawLine(l);
		offsetVec += offset;
	}

	// horizontal
	lp = p[3]-p[0];	// parallel to drawing
	l9 = p[1]-p[0];	// perpendicular to drawing

	nLines = (paintMode == rule_of_thirds) ? 3 : qRound(l9.norm()/20);
	offset = l9;
	offset.normalize();
	offset *= l9.norm()/nLines;

	offsetVec = offset;

	for (int idx = 0; idx < (nLines-1); idx++) {

		// step through & paint
		QLineF l = QLineF(DkVector(p[3]+offsetVec).toQPointF(), DkVector(p[0]+offsetVec).toQPointF());
		painter->drawLine(l);
		offsetVec += offset;
	}

	painter->setPen(pen);	// revert painter

}

// make events callable
void DkEditableRect::mousePressEvent(QMouseEvent *event) {

	// panning -> redirect to mViewport
	if (event->buttons() == Qt::LeftButton && 
		(event->modifiers() == DkSettings::global.altMod || mPanning)) {
		event->setModifiers(Qt::NoModifier);	// we want a 'normal' action in the mViewport
		event->ignore();
		return;
	}

	mPosGrab = map(QPointF(event->pos()));
	mClickPos = QPointF(event->pos());

	if (mRect.isEmpty()) {
		mState = initializing;
		setAngle(0);
	}
	else if (mRect.getPoly().containsPoint(mPosGrab, Qt::OddEvenFill)) {
		mState = moving;
	}
	else {
		mState = rotating;
	}

}

void DkEditableRect::mouseMoveEvent(QMouseEvent *event) {

	// panning -> redirect to mViewport
	if (event->modifiers() == DkSettings::global.altMod ||
		mPanning) {
		
		if (event->buttons() != Qt::LeftButton)
			setCursor(Qt::OpenHandCursor);
		else
			setCursor(Qt::ClosedHandCursor);

		event->setModifiers(Qt::NoModifier);
		event->ignore();
		update();
		return;
	}

	QPointF posM = map(QPointF(event->pos()));
	
	if (event->buttons() != Qt::LeftButton && !mRect.isEmpty()) {
		// show rotating - moving
		if (mRect.getPoly().containsPoint(map(event->pos()), Qt::OddEvenFill))
			setCursor(Qt::SizeAllCursor);
		else
			setCursor(mRotatingCursor);
	}
	else if (mRect.isEmpty())
		setCursor(Qt::CrossCursor);

	// additionally needed for showToolTip
	double angle = 0;

	if (mState == initializing && event->buttons() == Qt::LeftButton) {

		QPointF clipPos = clipToImageForce(QPointF(event->pos()));

		if (!mImgRect || !mRect.isEmpty() || clipPos == QPointF(event->pos())) {
			
			if (mRect.isEmpty()) {

				for (int idx = 0; idx < mCtrlPoints.size(); idx++)
					mCtrlPoints[idx]->show();

				QPointF p = map(clipToImageForce(mClickPos));
				mRect.setAllCorners(p);
			}
			
			DkVector diag;
			
			// when initializing shift should make the rect a square
			if (event->modifiers() == Qt::ShiftModifier)
				diag = DkVector(1.0f, 1.0f);
			else
				diag = mFixedDiag;
			mRect.updateCorner(2, map(clipPos), diag);
			update();
		}
 
	}
	else if (mState == moving && event->buttons() == Qt::LeftButton) {
		
		QPointF dxy = posM-mPosGrab;
		mRtform.translate(dxy.x(), dxy.y());
		mPosGrab = posM;
		update();
	}
	else if (mState == rotating && event->buttons() == Qt::LeftButton) {

		DkVector c(mRect.getCenter());
		DkVector xt(mPosGrab);
		DkVector xn(posM);

		// compute the direction vector;
		xt = c-xt;
		xn = c-xn;
		angle = xn.angle() - xt.angle();

		// just rotate in CV_PI*0.25 steps if shift is pressed
		if (event->modifiers() == Qt::ShiftModifier) {
			double angleRound = DkMath::normAngleRad(angle+mRect.getAngle(), -CV_PI*0.125, CV_PI*0.125);
			angle -= angleRound;
		}
					
		setAngle(angle, false);
	}

	if (event->buttons() == Qt::LeftButton && mState != moving) {

		QPolygonF p = mRect.getPoly();

		float sAngle = DkMath::getReadableAngle(mRect.getAngle() + angle);
		int height = qRound(DkVector(p[1]-p[0]).norm());
		int width = qRound(DkVector(p[3]-p[0]).norm());

		if (mShowInfo) {
			QToolTip::showText(event->globalPos(),
				QString::number(width) + " x " +
				QString::number(height) + " px\n" +
				QString::number(sAngle) + dk_degree_str,
				this);
		}

		emit statusInfoSignal(QString::number(width) + " x " + QString::number(height) + " px | " + QString::number(sAngle) + dk_degree_str);	// braces
	}

	//QWidget::mouseMoveEvent(event);
}

void DkEditableRect::mouseReleaseEvent(QMouseEvent *event) {

	// panning -> redirect to mViewport
	if (event->buttons() == Qt::LeftButton && 
		(event->modifiers() == DkSettings::global.altMod || mPanning)) {
		setCursor(Qt::OpenHandCursor);
		event->setModifiers(Qt::NoModifier);
		event->ignore();
		return;
	}

	mState = do_nothing;

	applyTransform();
	//QWidget::mouseReleaseEvent(event);
}

void DkEditableRect::wheelEvent(QWheelEvent* event) {

	QWidget::wheelEvent(event);
	update();	// this is an extra update - however we get rendering errors otherwise?!
}

void DkEditableRect::applyTransform() {

	// apply transform
	QPolygonF p = mRect.getPoly();
	p = mTtform.map(p);
	p = mRtform.map(p); 
	p = mTtform.inverted().map(p);

	// Cropping tool fix start

	// Check the order or vertexes
	float signedArea = (float)((p[1].x() - p[0].x()) * (p[2].y() - p[0].y()) - (p[1].y()- p[0].y()) * (p[2].x() - p[0].x()));
	// If it's wrong, just change it
	if (signedArea > 0) {
		QPointF tmp = p[1];
		p[1] = p[3];
		p[3] = tmp;
	}
	// Cropping tool fix end

	mRect.setPoly(p);

	mRtform.reset();	
	mTtform.reset();
	update();

}

void DkEditableRect::keyPressEvent(QKeyEvent *event) {

	if (event->key() == Qt::Key_Alt)
		setCursor(Qt::OpenHandCursor);

	QWidget::keyPressEvent(event);
}

void DkEditableRect::keyReleaseEvent(QKeyEvent *event) {

	//if (event->key() == Qt::Key_Escape)
	//	hide();
	//else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
	//	
	//	if (!rect.isEmpty())
	//		emit enterPressedSignal(rect);

	//	setVisible(false);
	//	setWindowOpacity(0);
	//}

	qDebug() << "key pressed rect";

	QWidget::keyPressEvent(event);
}

void DkEditableRect::setPaintHint(int paintMode /* = DkCropToolBar::no_guide */) {

	qDebug() << "painting mode: " << paintMode;
	this->mPaintMode = paintMode;
	update();
}

void DkEditableRect::setShadingHint(bool) {

	QColor col = mBrush.color();
	col = QColor(255-col.red(), 255-col.green(), 255-col.blue(), col.alpha());
	mBrush.setColor(col);

	col = mPen.color();
	col = QColor(255-col.red(), 255-col.green(), 255-col.blue(), col.alpha());
	mPen.setColor(col);

	update();
}

void DkEditableRect::setShowInfo(bool showInfo) {
	this->mShowInfo = showInfo;
}

void DkEditableRect::setAngle(double angle, bool apply) {

	DkVector c(mRect.getCenter());

	if (!mTtform.isTranslating())
		mTtform.translate(-c.x, -c.y);
	
	mRtform.reset();
	if (apply)
		mRtform.rotateRadians(angle-mRect.getAngle());
	else
		mRtform.rotateRadians(angle);
	
	if (apply)
		applyTransform();
	else {
		emit angleSignal(mRect.getAngle()+angle);
		update();
	}

}

void DkEditableRect::setVisible(bool visible) {

	if (!visible) {
		
		mRect = DkRotatingRect();
		for (int idx = 0; idx < mCtrlPoints.size(); idx++)
			mCtrlPoints[idx]->hide();
	}
	else {
		//setFocus(Qt::ActiveWindowFocusReason);
		setCursor(Qt::CrossCursor);
	}

	DkWidget::setVisible(visible);
}

// DkEditableRect --------------------------------------------------------------------
DkCropWidget::DkCropWidget(QRectF rect /* = QRect */, QWidget* parent /* = 0*/, Qt::WindowFlags f /* = 0*/) : DkEditableRect(rect, parent, f) {

	cropToolbar = 0;
}

void DkCropWidget::createToolbar() {

	cropToolbar = new DkCropToolBar(tr("Crop Toolbar"), this);

	connect(cropToolbar, SIGNAL(cropSignal()), this, SLOT(crop()));
	connect(cropToolbar, SIGNAL(cancelSignal()), this, SIGNAL(cancelSignal()));
	connect(cropToolbar, SIGNAL(aspectRatio(const DkVector&)), this, SLOT(setFixedDiagonal(const DkVector&)));
	connect(cropToolbar, SIGNAL(angleSignal(double)), this, SLOT(setAngle(double)));
	connect(cropToolbar, SIGNAL(panSignal(bool)), this, SLOT(setPanning(bool)));
	connect(cropToolbar, SIGNAL(paintHint(int)), this, SLOT(setPaintHint(int)));
	connect(cropToolbar, SIGNAL(shadingHint(bool)), this, SLOT(setShadingHint(bool)));
	connect(cropToolbar, SIGNAL(showInfo(bool)), this, SLOT(setShowInfo(bool)));
	connect(this, SIGNAL(angleSignal(double)), cropToolbar, SLOT(angleChanged(double)));
	connect(this, SIGNAL(aRatioSignal(const QPointF&)), cropToolbar, SLOT(setAspectRatio(const QPointF&)));

	cropToolbar->loadSettings();	// need to this manually after connecting the slots

}

DkCropToolBar* DkCropWidget::getToolbar() const {
	return cropToolbar;
}

void DkCropWidget::crop() {

	if (!cropToolbar)
		return;

	if (!mRect.isEmpty())
		emit enterPressedSignal(mRect, cropToolbar->getColor());

	setVisible(false);
	setWindowOpacity(0);
}

void DkCropWidget::setVisible(bool visible) {

	if (visible && !cropToolbar)
		createToolbar();

	emit showToolbar(cropToolbar, visible);
	DkEditableRect::setVisible(visible);
}


// DkAnimagionLabel --------------------------------------------------------------------
DkAnimationLabel::DkAnimationLabel(QString animationPath, QWidget* parent) : DkLabel(parent) {

	init(animationPath, QSize());
}

DkAnimationLabel::DkAnimationLabel(QString animationPath, QSize size, QWidget* parent) : DkLabel(parent) {

	init(animationPath, size);
}

DkAnimationLabel::~DkAnimationLabel() {

	animation->deleteLater();
}

void DkAnimationLabel::init(const QString& animationPath, const QSize& size) {
	
	setObjectName("DkAnimationLabel");
	animation = new QMovie(animationPath);
	margin = QSize(14, 14);

	QSize s = size;
	if(s.isEmpty()) {
		animation->jumpToNextFrame();
		s = animation->currentPixmap().size();
		animation->jumpToFrame(0);

		// padding
		s += margin;
	}

	setFixedSize(s);
	setMovie(animation);
	hide();
		
}

void DkAnimationLabel::halfSize() {

	// allows for anti-aliased edges of gif animations
	if (animation) {
		QSize s = (size()-margin)*0.5f;
		animation->setScaledSize(s);
		setFixedSize(s+margin);
	}
}

void DkAnimationLabel::showTimed(int time) {
	
	if(!this->animation.isNull() &&
		(this->animation->state() == QMovie::NotRunning ||
		 this->animation->state() == QMovie::Paused)) {
		
			this->animation->start();
	}
	DkLabel::showTimed(time);
}


void DkAnimationLabel::hide() {
	
	if(!this->animation.isNull()) {
		if(this->animation->state() == QMovie::Running) {
			
			this->animation->stop();
		}
	}

	DkLabel::hide();
}

DkColorChooser::DkColorChooser(QColor defaultColor, QString text, QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags) {

	this->defaultColor = defaultColor;
	this->text = text;

	init();

}

void DkColorChooser::init() {

	accept = false;

	colorDialog = new QColorDialog(this);
	colorDialog->setObjectName("colorDialog");
	colorDialog->setOption(QColorDialog::ShowAlphaChannel, true);

	QVBoxLayout* vLayout = new QVBoxLayout(this);
	vLayout->setContentsMargins(11,0,11,0);
	
	QLabel* colorLabel = new QLabel(text, this);
	colorButton = new QPushButton("", this);
	colorButton->setFlat(true);
	colorButton->setObjectName("colorButton");
	colorButton->setAutoDefault(false);
	
	QPushButton* resetButton = new QPushButton(tr("Reset"), this);
	resetButton->setObjectName("resetButton");
	resetButton->setAutoDefault(false);

	QWidget* colWidget = new QWidget(this);
	QHBoxLayout* hLayout = new QHBoxLayout(colWidget);
	hLayout->setContentsMargins(11,0,11,0);

	hLayout->addWidget(colorButton);
	hLayout->addWidget(resetButton);
	hLayout->addStretch();

	vLayout->addWidget(colorLabel);
	vLayout->addWidget(colWidget);

	setColor(defaultColor);
	QMetaObject::connectSlotsByName(this);
}

bool DkColorChooser::isAccept() const {
	return accept;
}

void DkColorChooser::enableAlpha(bool enable) {
	colorDialog->setOption(QColorDialog::ShowAlphaChannel, enable);
}

void DkColorChooser::setColor(QColor color) {

	colorDialog->setCurrentColor(color);
	colorButton->setStyleSheet("QPushButton {background-color: " + DkUtils::colorToString(color) + "; border: 1px solid #888; min-height: 24px}");
}

QColor DkColorChooser::getColor() {
	return colorDialog->currentColor();
}

void DkColorChooser::on_resetButton_clicked() {
	setColor(defaultColor);
	emit resetClicked();
}

void DkColorChooser::on_colorButton_clicked() {
	
	// incorrect color? - see: https://bugreports.qt.io/browse/QTBUG-42988
	colorDialog->show();
}

void DkColorChooser::on_colorDialog_accepted() {
	
	setColor(colorDialog->currentColor());
	accept = true;
	emit accepted();
}

// Image histogram  -------------------------------------------------------------------
DkHistogram::DkHistogram(QWidget *parent) : DkWidget(parent){
	
	setObjectName("DkHistogram");
	setMinimumWidth(265);
	setMinimumHeight(130);
}

DkHistogram::~DkHistogram() {
}

/**
 * Paints the image histogram
 **/
void DkHistogram::paintEvent(QPaintEvent*) {

	QPainter painter(this);
	//painter.setPen(QColor(200, 200, 200));
	painter.fillRect(1, 1, width(), height(), mBgCol);
	//painter.drawRect(1, 1, width(), height());

	int margin = 5;
	int binHeight = height() - margin * 2;

	if(mIsPainted && mMaxValue > 0){
		
		for(int x = 0; x < 256; x++){
			
			// get bounded values
			int rLineHeight = qMax(qMin(qRound((float)mHist[0][x] * binHeight * mScaleFactor/mMaxValue), binHeight), 0);
			int gLineHeight = qMax(qMin(qRound((float)mHist[1][x] * binHeight * mScaleFactor/mMaxValue), binHeight), 0);
			int bLineHeight = qMax(qMin(qRound((float)mHist[2][x] * binHeight * mScaleFactor/mMaxValue), binHeight), 0);
			int maxLineHeight = qMax(qMax(rLineHeight, gLineHeight), bLineHeight); // (rLineHeight > gLineHeight) ? ((rLineHeight > bLineHeight) ? rLineHeight : bLineHeight) : ((gLineHeight > bLineHeight) ? gLineHeight : bLineHeight);

			painter.setCompositionMode(QPainter::CompositionMode_Clear);
			painter.setPen(Qt::black);
			painter.drawLine(QPoint(x + margin, height() - margin), QPoint(x + margin, height()-margin - maxLineHeight));

			painter.setCompositionMode(QPainter::CompositionMode_Screen);
			painter.setPen(Qt::red);
			painter.drawLine(QPoint(x+margin, height()-margin), QPoint(x+margin, height()-margin-rLineHeight));
			painter.setPen(Qt::green);
			painter.drawLine(QPoint(x+margin, height()-margin), QPoint(x+margin, height()-margin-gLineHeight));
			painter.setPen(Qt::blue);
			painter.drawLine(QPoint(x+margin, height()-margin), QPoint(x+margin, height()-margin-bLineHeight));
		}
	}
}

/**
 * Goes through the image and counts pixels values. They are used to create the image histogram.
 * @param currently displayed image
 **/ 
void DkHistogram::drawHistogram(QImage imgQt) {

	if (!isVisible() || imgQt.isNull()) {
		setPainted(false);
		return;
	}

	DkTimer dt;

#ifdef WITH_OPENCV
	
	int histValues[3][256];

	for (int idx = 0; idx < 256; idx++) {
		histValues[0][idx] = 0;
		histValues[1][idx] = 0;
		histValues[2][idx] = 0;
	}
	

	// 8 bit images
	if (imgQt.depth() == 8) {

		qDebug() << "8 bit histogram -------------------";

		for (int rIdx = 0; rIdx < imgQt.height(); rIdx++) {

			const unsigned char* pixel = imgQt.constScanLine(rIdx);

			for (int cIdx = 0; cIdx < imgQt.width(); cIdx++, pixel++) {

				histValues[0][*pixel]++;
				histValues[1][*pixel]++;
				histValues[2][*pixel]++;
			}
		}
	}
	// 24 bit images
	else if (imgQt.depth() == 24) {
		
		qDebug() << "24 bit histogram -------------------";

		// TODO: not tested!!
		for (int rIdx = 0; rIdx < imgQt.height(); rIdx++) {

			const unsigned char* pixel = imgQt.constScanLine(rIdx);

			for (int cIdx = 0; cIdx < imgQt.width(); cIdx++) {

				// If I understood the api correctly, the first bits are 0 if we have 24bpp & < 8 bits per channel
				histValues[0][*pixel]++; pixel++;
				histValues[1][*pixel]++; pixel++;
				histValues[2][*pixel]++; pixel++;
			}
		}
	}
	// 32 bit images
	else if (imgQt.depth() == 32) {
		
		for (int rIdx = 0; rIdx < imgQt.height(); rIdx++) {
		
			const QRgb* pixel = (QRgb*)(imgQt.constScanLine(rIdx));
	
			for (int cIdx = 0; cIdx < imgQt.width(); cIdx++, pixel++) {

				histValues[0][qRed(*pixel)]++;
				histValues[1][qGreen(*pixel)]++;
				histValues[2][qBlue(*pixel)]++;
			}
		}
	}

	int maxHistValue = 0;

	for (int idx = 0; idx < 256; idx++) {
		
		if (histValues[0][idx] > maxHistValue)
			maxHistValue = histValues[0][idx];
		if (histValues[1][idx] > maxHistValue)
			maxHistValue = histValues[1][idx];
		if (histValues[2][idx] > maxHistValue)
			maxHistValue = histValues[2][idx];
	}

	setMaxHistogramValue(maxHistValue);
	updateHistogramValues(histValues);
	setPainted(true);

#else
	setPainted(false);
#endif
	
	qDebug() << "drawing the histogram took me: " << dt.getTotal();

	update();
}

/**
 * Clears the histogram panel
 **/ 
void DkHistogram::clearHistogram() {

	setPainted(false);
	update();
}

void DkHistogram::setPainted(bool isPainted) {

	this->mIsPainted = isPainted;
}

void DkHistogram::setMaxHistogramValue(int maxValue) {

	if (maxValue == 0)
		setPainted(false);

	this->mMaxValue = maxValue;
}

/**
 * Updates histogram values.
 * @param values to be copied
 **/ 
void DkHistogram::updateHistogramValues(int histValues[][256]) {

	for(int i = 0; i < 256; i++) {
		this->mHist[0][i] = histValues[0][i];
		this->mHist[1][i] = histValues[1][i];
		this->mHist[2][i] = histValues[2][i];
	}
}

/**
 * Mouse events for scaling the histogram - enlarge the histogram between the bottom axis and the cursor position
 **/ 
void DkHistogram::mousePressEvent(QMouseEvent *event) {

	// always propagate mouse events
	if (event->buttons() != Qt::LeftButton)
		DkWidget::mousePressEvent(event);
}

void DkHistogram::mouseMoveEvent(QMouseEvent *event) {

	if (event->buttons() == Qt::LeftButton) {
		
		float cp = (float)(height() - event->pos().y());
		
		if (cp > 0) {
			mScaleFactor = height() / cp;
			update();
		}
	}
	else
		DkWidget::mouseMoveEvent(event);

}

void DkHistogram::mouseReleaseEvent(QMouseEvent *event) {
	
	mScaleFactor = 1;
	update();

	if (event->buttons() != Qt::LeftButton)
		DkWidget::mouseReleaseEvent(event);
}

// DkSlider --------------------------------------------------------------------
DkSlider::DkSlider(QString title, QWidget* parent) : QWidget(parent) {

	createLayout();
	
	// init
	titleLabel->setText(title);
	
	// defaults
	setMinimum(0);	
	setMaximum(100);
	setTickInterval(1);
	setValue(50);
}

void DkSlider::createLayout() {

	QVBoxLayout* layout = new QVBoxLayout(this);
	layout->setSpacing(0);
	layout->setContentsMargins(0,0,0,0);
	
	QWidget* dummy = new QWidget(this);
	QHBoxLayout* titleLayout = new QHBoxLayout(dummy);
	titleLayout->setContentsMargins(0,0,0,5);

	QWidget* dummyBounds = new QWidget(this);
	QHBoxLayout* boundsLayout = new QHBoxLayout(dummyBounds);
	boundsLayout->setContentsMargins(0,0,0,0);

	titleLabel = new QLabel(this);
	
	sliderBox = new QSpinBox(this);

	slider = new QSlider(this);
	slider->setOrientation(Qt::Horizontal);

	minValLabel = new QLabel(this);
	maxValLabel = new QLabel(this);
	
	titleLayout->addWidget(titleLabel);
	titleLayout->addStretch();
	titleLayout->addWidget(sliderBox);

	boundsLayout->addWidget(minValLabel);
	boundsLayout->addStretch();
	boundsLayout->addWidget(maxValLabel);

	layout->addWidget(dummy);
	layout->addWidget(slider);
	layout->addWidget(dummyBounds);

	// connect
	connect(slider, SIGNAL(valueChanged(int)), this, SLOT(setValue(int)));
	connect(sliderBox, SIGNAL(valueChanged(int)), this, SLOT(setValue(int)));
}

QSlider* DkSlider::getSlider() const {
	return slider;
}

void DkSlider::setMinimum(int minValue) {
	slider->setMinimum(minValue);
	sliderBox->setMinimum(minValue);
	minValLabel->setText(QString::number(minValue));
}

void DkSlider::setMaximum(int maxValue) {
	slider->setMaximum(maxValue);
	sliderBox->setMaximum(maxValue);
	maxValLabel->setText(QString::number(maxValue));
}

void DkSlider::setTickInterval(int ticValue) {
	slider->setTickInterval(ticValue);
}

int DkSlider::value() const {
	return slider->value();
}

void DkSlider::setFocus(Qt::FocusReason reason) {
	sliderBox->setFocus(reason);
}

void DkSlider::setValue(int value) {

	slider->blockSignals(true);
	slider->setValue(value);
	slider->blockSignals(false);

	sliderBox->blockSignals(true);
	sliderBox->setValue(value);
	sliderBox->blockSignals(false);

	emit valueChanged(value);
}

// DkFileInfo --------------------------------------------------------------------
DkFileInfo::DkFileInfo() {
	mFileExists = false;
	mUsed = false;
}

DkFileInfo::DkFileInfo(const QFileInfo& fileInfo) {

	mFileInfo = fileInfo;
}

bool DkFileInfo::exists() const {
	return mFileExists;
}

void DkFileInfo::setExists(bool fileExists) {
	mFileExists = fileExists;
}

bool DkFileInfo::inUse() const {
	return mUsed;
}

void DkFileInfo::setInUse(bool inUse) {
	mUsed = inUse;
}

QString DkFileInfo::getFilePath() const {
	return mFileInfo.absoluteFilePath();
}


// DkFileLabel --------------------------------------------------------------------
DkFolderLabel::DkFolderLabel(const DkFileInfo& fileInfo, QWidget* parent /* = 0 */, Qt::WindowFlags f /* = 0 */) : QLabel(parent, f) {

	// we don't use the file labels anymore
	// + isDir() might hang - if we try to get an unavailable network resource on windows
	//QFileInfo fInfo(fileInfo.getFilePath());
	//if (fInfo.isDir())
		setText(fileInfo.getFilePath());
	//else
	//	setText(fInfo.fileName());

	this->fileInfo = fileInfo;
	setObjectName("DkFileLabel");
}

void DkFolderLabel::mousePressEvent(QMouseEvent *ev) {

	emit loadFileSignal(fileInfo.getFilePath());

	QLabel::mousePressEvent(ev);
}

// DkImageLabel --------------------------------------------------------------------
DkImageLabel::DkImageLabel(const QString& filePath, QWidget* parent /* = 0 */, Qt::WindowFlags f /* = 0 */) : QLabel(parent, f) {

	mThumbSize = DkSettings::display.thumbSize;

	thumb = QSharedPointer<DkThumbNailT>(new DkThumbNailT(filePath));
	connect(thumb.data(), SIGNAL(thumbLoadedSignal()), this, SIGNAL(labelLoaded()));
	connect(thumb.data(), SIGNAL(thumbLoadedSignal()), this, SLOT(thumbLoaded()));

	setFixedSize(mThumbSize, mThumbSize);
	setMouseTracking(true);

	QFileInfo fInfo(filePath);
	setStatusTip(fInfo.fileName());
	setToolTip(fInfo.fileName());
	
	createLayout();
}

void DkImageLabel::createLayout() {

	imageLabel = new QLabel(this);
	imageLabel->setFixedSize(mThumbSize, mThumbSize);
	imageLabel->setScaledContents(true);
	imageLabel->setStyleSheet("QLabel{margin: 0 0 0 0; padding: 0 0 0 0; border: 1px solid " + DkUtils::colorToString(DkSettings::display.bgColorWidget) + ";}");

	QColor cA = DkSettings::display.highlightColor;
	cA.setAlpha(100);
	highLightLabel = new QLabel(this);
	highLightLabel->setFixedSize(mThumbSize, mThumbSize);
	highLightLabel->setStyleSheet("QLabel{background: " + DkUtils::colorToString(cA) + "; border: 1px solid " + DkUtils::colorToString(DkSettings::display.highlightColor) + ";}");
	highLightLabel->hide();

	removeFileButton = new QPushButton(QIcon(":/nomacs/img/close.png"), tr(""), this);
	removeFileButton->setFlat(true);
	removeFileButton->setObjectName("removeFileButton");
	removeFileButton->move(width()-17, 0);
	removeFileButton->hide();

	connect(removeFileButton, SIGNAL(clicked()), this, SLOT(removeFileFromList()));
}

bool DkImageLabel::hasFile() const {

	return !thumb->getImage().isNull();
}

QSharedPointer<DkThumbNailT> DkImageLabel::getThumb() const {
	
	return thumb;
}

void DkImageLabel::thumbLoaded() {

	if (thumb->getImage().isNull()) {
		qDebug() << QFileInfo(thumb->getFilePath()).fileName() << " not loaded...";
		return;
	}
	
	QPixmap pm = QPixmap::fromImage(thumb->getImage());

	QRect r(QPoint(), pm.size());

	if (r.width() > r.height()) {
		r.setX(qFloor((r.width()-r.height())*0.5f));
		r.setWidth(r.height());
	}
	else {
		r.setY(qFloor((r.height()-r.width())*0.5f));
		r.setHeight(r.width());
	}
	pm = pm.copy(r);

	imageLabel->setPixmap(pm);
}

void DkImageLabel::removeFileFromList() {

	imageLabel->hide();
	//highLightLabel->hide();
	removeFileButton->hide();
	highLightLabel->setStyleSheet("QLabel{background: " + DkUtils::colorToString(DkSettings::display.bgColorWidget) + "; border: 1px solid black;}");
	highLightLabel->show();

	for (int idx = 0; idx < DkSettings::global.recentFiles.size(); idx++) {

		if (thumb->getFilePath() == DkSettings::global.recentFiles.at(idx))
			DkSettings::global.recentFiles.removeAt(idx);
	}
}

void DkImageLabel::enterEvent(QEvent *ev) {
	
	if (imageLabel->isVisible()) {
		highLightLabel->show();
		removeFileButton->show();
	}
	QLabel::enterEvent(ev);
}

void DkImageLabel::leaveEvent(QEvent *ev) {
	
	if (imageLabel->isVisible())
		highLightLabel->hide();
	removeFileButton->hide();
	QLabel::leaveEvent(ev);
}

void DkImageLabel::mousePressEvent(QMouseEvent *ev) {

	emit loadFileSignal(thumb->getFilePath());

	QLabel::mousePressEvent(ev);
}


// Recent Files Widget --------------------------------------------------------------------
DkRecentFilesWidget::DkRecentFilesWidget(QWidget* parent /* = 0 */) : DkWidget(parent) {

	setObjectName("DkRecentFilesWidget");
	rFileIdx = 0;

	createLayout();

	//connect(&fileWatcher, SIGNAL(finished()), this, SLOT(updateFiles()));
}

DkRecentFilesWidget::~DkRecentFilesWidget() {

	//fileWatcher.blockSignals(true);
	//fileWatcher.cancel();
	//fileWatcher.waitForFinished();
}

void DkRecentFilesWidget::createLayout() {

	filesWidget = new QWidget(this);
	filesLayout = new QGridLayout(filesWidget);
	filesLayout->setAlignment(Qt::AlignTop);
	//filesWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);

	folderWidget = new QWidget(this);
	folderLayout = new QVBoxLayout(folderWidget);
	
	filesTitle = new QLabel(tr("Recent Files"), this);
	filesTitle->setObjectName("DkRecentFilesTitle");
	filesTitle->setStyleSheet("QLabel{font-size: 15pt;}" + filesTitle->styleSheet());

	folderTitle = new QLabel(tr("Recent Folders"), this);
	folderTitle->setObjectName("DkRecentFilesTitle");
	folderTitle->setStyleSheet("QLabel{font-size: 15pt;}" + folderTitle->styleSheet());

	bgLabel = new QLabel(this);
	bgLabel->setObjectName("bgLabel");
	QHBoxLayout* layout = new QHBoxLayout(this);
	layout->setContentsMargins(0,0,0,0);
	layout->addWidget(bgLabel);
	QHBoxLayout* hLayout = new QHBoxLayout(bgLabel);

	hLayout->setAlignment(Qt::AlignTop);
	hLayout->addWidget(filesWidget);
	hLayout->addWidget(folderWidget);
	hLayout->addStretch();

	setCustomStyle();
}

void DkRecentFilesWidget::setCustomStyle(bool imgLoadedStyle) {

	setProperty("imageLoaded", imgLoadedStyle);
	style()->unpolish(bgLabel);
	style()->unpolish(filesTitle);	// don't know why I need to unpolish every widget on it's own
	style()->unpolish(folderTitle);
	style()->unpolish(this);
	ensurePolished();
}

void DkRecentFilesWidget::setVisible(bool visible, bool saveSettings) {
	
	if (visible && !isVisible()) {
		updateFileList();
		qDebug() << "mShowing recent files...";
	}

	DkWidget::setVisible(saveSettings);
}

void DkRecentFilesWidget::hide(bool saveSettings) {
	
	QWidget::hide();	// no animation effect on hide
	DkWidget::hide(saveSettings);
}

void DkRecentFilesWidget::updateFileList() {

	delete folderLayout;
	delete filesLayout;
	rFileIdx = 0;
	numActiveLabels = 0;

	filesLayout = new QGridLayout(filesWidget);
	folderLayout = new QVBoxLayout(folderWidget);

	filesWidget->setFixedHeight(1080);
	folderWidget->setFixedHeight(1080);

	//filesLayout->setSpacing(10);
	folderLayout->setSpacing(0);

	for (int idx = 0; idx < fileLabels.size(); idx++) {
		delete fileLabels.at(idx);
	}

	for (int idx = 0; idx < folderLabels.size(); idx++) {
		delete folderLabels.at(idx);
	}

	filesTitle->hide();
	folderTitle->hide();

	fileLabels.clear();
	folderLabels.clear();
	recentFiles.clear();
	recentFolders.clear();

	for (int idx = 0; idx < DkSettings::global.recentFiles.size(); idx++)
		recentFiles.append(QFileInfo(DkSettings::global.recentFiles.at(idx)));
	for (int idx = 0; idx < DkSettings::global.recentFolders.size(); idx++)
		recentFolders.append(QFileInfo(DkSettings::global.recentFolders.at(idx)));

	updateFiles();
	updateFolders();
}

void DkRecentFilesWidget::updateFiles() {

	int columns = 3;

	if (fileLabels.empty()) {
		filesTitle->show();
		filesLayout->setRowStretch(recentFiles.size()+2, 100);
		filesLayout->addWidget(filesTitle, 0, 0, 1, columns, Qt::AlignRight);
	}

	// show current
	if (rFileIdx < fileLabels.size() && fileLabels.at(rFileIdx)->hasFile()) {
		fileLabels.at(rFileIdx)->show();
		filesLayout->addWidget(fileLabels.at(rFileIdx), qFloor((float)numActiveLabels/columns)+2, numActiveLabels%columns);
		numActiveLabels++;
	}
	else if (rFileIdx < fileLabels.size()) {
		fileLabels.at(rFileIdx)->hide();

		// remove files which we can't load to keep the list clean...
		DkSettings::global.recentFiles.removeAll(fileLabels.at(rFileIdx)->getThumb()->getFilePath());		// remove recent files which we could not load...
	}

	if (!fileLabels.empty())
		rFileIdx++;

	// load next
	if ((rFileIdx/(float)columns*DkSettings::display.thumbSize < filesWidget->height()-200 || !(rFileIdx+1 % columns)) && rFileIdx < recentFiles.size()) {
		DkImageLabel* cLabel = new DkImageLabel(recentFiles.at(rFileIdx).absoluteFilePath(), this);
		cLabel->hide();
		cLabel->setStyleSheet(QString("QLabel{background-color: rgba(0,0,0,0), border: solid 1px black;}"));
		
		fileLabels.append(cLabel);
		connect(cLabel, SIGNAL(labelLoaded()), this, SLOT(updateFiles()));
		connect(cLabel, SIGNAL(loadFileSignal(const QString&)), this, SIGNAL(loadFileSignal(const QString&)));
		cLabel->getThumb()->fetchThumb(DkThumbNailT::force_exif_thumb);
	}
	else {
		qDebug() << "LOADING stopped at " << rFileIdx << " num files: " << recentFiles.size();
	}

	update();
}

void DkRecentFilesWidget::updateFolders() {

	folderTitle->show();
	folderLayout->addWidget(folderTitle);
	folderLayout->addSpacerItem(new QSpacerItem(10, 10));

	int cHeight = 0;

	for (DkFileInfo& fi : recentFolders) {

		DkFolderLabel* fLabel = new DkFolderLabel(fi, this);
		connect(fLabel, SIGNAL(loadFileSignal(const QString&)), this, SIGNAL(loadFileSignal(const QString&)));
		folderLayout->addWidget(fLabel);
		folderLabels.append(fLabel);

		cHeight += fLabel->height();
		if (cHeight > folderWidget->height())
			break;
	}

	folderLayout->addStretch();
}

// DkDirectoryEdit --------------------------------------------------------------------
DkDirectoryEdit::DkDirectoryEdit(QWidget* parent /* = 0 */) : QLineEdit(parent) {
	setObjectName("DkWarningEdit");
	connect(this, SIGNAL(textChanged(QString)), this, SLOT(lineEditChanged(QString)));

	QCompleter *completer = new QCompleter(this);
	QDirModel* model = new QDirModel(completer);
	model->setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
	completer->setModel(model);
	setCompleter(completer);
}

DkDirectoryEdit::DkDirectoryEdit(const QString& content, QWidget* parent /* = 0 */) : QLineEdit(parent) {
	
	setObjectName("DkWarningEdit");
	connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(lineEditChanged(const QString&)));
	setText(content);

	QCompleter *completer = new QCompleter(this);
	QDirModel* model = new QDirModel(completer);
	model->setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
	completer->setModel(model);
	setCompleter(completer);
}

void DkDirectoryEdit::lineEditChanged(const QString& path) {
	
	setProperty("error", !existsDirectory(path));
	style()->unpolish(this);
	style()->polish(this);
	update();
	
	// converting to QDir since D:/img == D:/img/ then
	if (QDir(path).absolutePath() != QDir(mOldPath).absolutePath() && existsDirectory(path)) {
		mOldPath = path;
		emit directoryChanged(path);
	}
}

bool DkDirectoryEdit::existsDirectory(const QString& path) {
	
	return QDir(path).exists();
}

// DkGmmListWidget --------------------------------------------------------------------
DkListWidget::DkListWidget(QWidget* parent) : QListWidget(parent) {

	setAcceptDrops(true);
	setDragEnabled(true);
	setMinimumHeight(100);
	setDropIndicatorShown(true);

	setSelectionMode(QAbstractItemView::ExtendedSelection);

}

void DkListWidget::startDrag(Qt::DropActions) {

	QListWidget::startDrag(Qt::MoveAction);
}

bool DkListWidget::isEmpty() const {
	return count() == 0;
}

void DkListWidget::setEmptyText(const QString & text) {
	mEmptyText = text;
}

void DkListWidget::dropEvent(QDropEvent *event) {

	if (event->source() != this)
		QListWidget::dropEvent(event);

	// work arround for the empty rows
	emit dataDroppedSignal();
}

void DkListWidget::paintEvent(QPaintEvent *event) {

	QListView::paintEvent(event);

	if (model() && model()->rowCount(rootIndex())) 
		return;

	// The view is empty.
	QPainter p(viewport());
	p.setPen(Qt::NoPen);
	p.setBrush(QBrush(QColor(200,200,200), Qt::BDiagPattern));
	p.drawRect(QRect(QPoint(), size()));

	p.setPen(QPen(QColor(100,100,100)));
	p.drawText(rect(), Qt::AlignCenter, mEmptyText);
}


}


