/*******************************************************************************************************
 DkMetaDataWidgets.cpp
 Created on:	17.08.2014
 
 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
 
 Copyright (C) 2011-2014 Markus Diem <markus@nomacs.org>
 Copyright (C) 2011-2014 Stefan Fiel <stefan@nomacs.org>
 Copyright (C) 2011-2014 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 "DkMetaDataWidgets.h"
#include "DkBasicLoader.h"
#include "DkImageContainer.h"
#include "DkMetaData.h"
#include "DkUtils.h"
#include "DkTimer.h"
#include "DkImageStorage.h"
#include "DkSettings.h"

#pragma warning(push, 0)	// no warnings from includes - begin
#include <QDockWidget>
#include <QTreeView>
#include <QLabel>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QResizeEvent>
#include <QPushButton>
#include <QPainter>
#include <QSettings>
#include <QScrollArea>
#include <QAction>
#include <QMenu>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QInputDialog>
#pragma warning(pop)		// no warnings from includes - end

namespace nmc {

// DkMetaDataModel --------------------------------------------------------------------
DkMetaDataModel::DkMetaDataModel(QObject* parent /* = 0 */) : QAbstractItemModel(parent) {

	// create root
	QVector<QVariant> rootData;
	rootData << tr("Key") << tr("Value");

	rootItem = new TreeItem(rootData);
}

DkMetaDataModel::~DkMetaDataModel() {

	delete rootItem;
}

void DkMetaDataModel::clear() {

	beginResetModel();
	rootItem->clear();
	endResetModel();
}

void DkMetaDataModel::addMetaData(QSharedPointer<DkMetaDataT> metaData) {

	//metaData->printMetaData();

	if (!metaData)
		return;

	DkTimer dt;
	QStringList fileKeys, fileValues;
	metaData->getFileMetaData(fileKeys, fileValues);

	for (int idx = 0; idx < fileKeys.size(); idx++) {

		QString lastKey = fileKeys.at(idx).split(".").last();
		createItem(fileKeys.at(idx), lastKey, fileValues.at(idx));
	}

	QStringList exifKeys = metaData->getExifKeys();

	for (int idx = 0; idx < exifKeys.size(); idx++) {
		
		QString lastKey = exifKeys.at(idx).split(".").last();
		QString translatedKey = DkMetaDataHelper::getInstance().translateKey(lastKey);
		QString exifValue = metaData->getNativeExifValue(exifKeys.at(idx));
		exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

		createItem(exifKeys.at(idx), translatedKey, exifValue);
	}

	QStringList iptcKeys = metaData->getIptcKeys();

	for (int idx = 0; idx < iptcKeys.size(); idx++) {

		QString lastKey = iptcKeys.at(idx).split(".").last();
		QString translatedKey = DkMetaDataHelper::getInstance().translateKey(lastKey);
		QString exifValue = metaData->getIptcValue(iptcKeys.at(idx));
		exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

		createItem(iptcKeys.at(idx), translatedKey, exifValue);
	}

	QStringList xmpKeys = metaData->getXmpKeys();

	for (int idx = 0; idx < xmpKeys.size(); idx++) {

		QString lastKey = xmpKeys.at(idx).split(".").last();
		QString translatedKey = DkMetaDataHelper::getInstance().translateKey(lastKey);
		QString exifValue = metaData->getXmpValue(xmpKeys.at(idx));
		exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

		createItem(xmpKeys.at(idx), translatedKey, exifValue);
	}

	QStringList qtKeys = metaData->getQtKeys();

	for (QString cKey : qtKeys) {

		QString lastKey = cKey.split(".").last();
		QString translatedKey = DkMetaDataHelper::getInstance().translateKey(lastKey);
		QString exifValue = metaData->getQtValue(cKey);
		exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

		createItem(tr("Data.") + cKey, translatedKey, exifValue);
	}

	qDebug() << "model refreshed in: " << dt.getTotal();
}

void DkMetaDataModel::createItem(const QString& key, const QString& keyName, const QString& value) {

	// Split key first
	QStringList keyHierarchy = key.split('.');

	if (keyHierarchy.empty()) {
		qDebug() << "no key hierarchy... skipping: " << key;
		return;
	}

	TreeItem* item = rootItem;

	for (int idx = 0; idx < keyHierarchy.size()-1; idx++) {

		QString cKey = keyHierarchy.at(idx);
		TreeItem* cHierarchyItem = item->find(cKey, 0);

		if (!cHierarchyItem) {
			QVector<QVariant> keyData;
			keyData << cKey;
			cHierarchyItem = new TreeItem(keyData, item);
			item->appendChild(cHierarchyItem);
		}

		item = cHierarchyItem;	// switch to next hierarchy level
	}

	QString cleanValue = DkUtils::cleanFraction(value);
	
	QVector<QVariant> metaDataEntry;
	metaDataEntry << keyName;

	QDateTime pd = DkUtils::getConvertableDate(cleanValue);

	if (!pd.isNull())
		metaDataEntry << pd;
	else
		metaDataEntry << cleanValue;

	TreeItem* dataItem = new TreeItem(metaDataEntry, item);
	item->appendChild(dataItem);

}

QModelIndex DkMetaDataModel::index(int row, int column, const QModelIndex &parent) const {

	if (!hasIndex(row, column, parent))
		return QModelIndex();

	TreeItem *parentItem;

	if (!parent.isValid())
		parentItem = rootItem;
	else
		parentItem = static_cast<TreeItem*>(parent.internalPointer());

	TreeItem *childItem = parentItem->child(row);

	//qDebug() << " creating index for: " << childItem->data(0) << " row: " << row;
	if (childItem)
		return createIndex(row, column, childItem);
	else
		return QModelIndex();
}

QModelIndex DkMetaDataModel::parent(const QModelIndex &index) const {

	if (!index.isValid())
		return QModelIndex();

	TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
	TreeItem *parentItem = childItem->parent();

	if (!parentItem || parentItem == rootItem)
		return QModelIndex();

	//qDebug() << "parent is: " << childItem->data(0);

	return createIndex(parentItem->row(), 0, parentItem);
}

int DkMetaDataModel::rowCount(const QModelIndex& parent) const {

	TreeItem *parentItem;
	if (parent.column() > 0)
		return 0;

	if (!parent.isValid())
		parentItem = rootItem;
	else
		parentItem = static_cast<TreeItem*>(parent.internalPointer());

	return parentItem->childCount();
}

int DkMetaDataModel::columnCount(const QModelIndex& parent) const {

	if (parent.isValid())
		return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
	else
		return rootItem->columnCount();
	//return 2;
}

QVariant DkMetaDataModel::data(const QModelIndex& index, int role) const {

	if (!index.isValid()) {
		qDebug() << "invalid row: " << index.row();
		return QVariant();
	}

	//if (index.row() > rowCount())
	//	return QVariant();

	//if (index.column() > columnCount())
	//	return QVariant();

	if (role == Qt::DisplayRole || role == Qt::EditRole) {

		TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
		//qDebug() << "returning: " << item->data(0) << "row: " << index.row();

		return item->data(index.column());
	}

	return QVariant();
}


QVariant DkMetaDataModel::headerData(int section, Qt::Orientation orientation, int role) const {

	if (orientation != Qt::Horizontal || role != Qt::DisplayRole) 
		return QVariant();

	return rootItem->data(section);
} 

//bool DkMetaDataModel::setData(const QModelIndex& index, const QVariant& value, int role) {
//
//	if (!index.isValid() || role != Qt::EditRole)
//		return false;
//
//	//if (index.column() == 1) {
//
//	//	TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
//	//	item->setData(ks, index.column());
//
//	//}
//	//else {
//		TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
//		item->setData(value, index.column());
//	//}
//
//	emit dataChanged(index, index);
//	return true;
//}
//
Qt::ItemFlags DkMetaDataModel::flags(const QModelIndex& index) const {

	if (!index.isValid())
		return Qt::ItemIsEditable;

	//TreeItem *item = static_cast<TreeItem*>(index.internalPointer());

	Qt::ItemFlags flags;

	if (index.column() == 0)
		flags = QAbstractItemModel::flags(index);
	if (index.column() == 1)
		flags = QAbstractItemModel::flags(index) | Qt::ItemIsEditable;

	return flags;
}


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

		setObjectName("DkMetaDataDock");

		createLayout();
		readSettings();
}

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

void DkMetaDataDock::writeSettings() {

	QSettings& settings = Settings::instance().getSettings();
	settings.beginGroup(objectName());
	
	for (int idx = 0; idx < mModel->columnCount(QModelIndex()); idx++) {

		QString headerVal = mModel->headerData(idx, Qt::Horizontal).toString();
		settings.setValue(headerVal + "Size", mTreeView->columnWidth(idx));
	}

	settings.setValue("expandedNames", mExpandedNames);
	qDebug() << "settings write expanded names: " << mExpandedNames;

	settings.endGroup();
}

void DkMetaDataDock::readSettings() {
	
	QSettings& settings = Settings::instance().getSettings();
	settings.beginGroup(objectName());

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

		QString headerVal = mModel->headerData(idx, Qt::Horizontal).toString();

		int colWidth = settings.value(headerVal + "Size", -1).toInt();
		if (colWidth != -1) 
			mTreeView->setColumnWidth(idx, colWidth);
	}
	mExpandedNames = settings.value("expandedNames", QStringList()).toStringList();
	qDebug() << "settings expanded names: " << mExpandedNames;

	settings.endGroup();
}

void DkMetaDataDock::createLayout() {

	QWidget* widget = new QWidget(this);
	QVBoxLayout* layout = new QVBoxLayout(widget);

	// create our beautiful shortcut view
	mModel = new DkMetaDataModel(this);
	mTreeView = new QTreeView(this);
	mTreeView->setModel(mModel);
	mTreeView->setAlternatingRowColors(true);
	mTreeView->setIndentation(8);
	//treeView->setStyleSheet("QTreeView{border-color: #C3C3C4; alternate-background-color: blue; background: #AAAAAA;}");

	mThumbNailLabel = new QLabel(tr("Thumbnail"), this);
	mThumbNailLabel->hide();

	// thumb layout
	QWidget* thumbWidget = new QWidget(this);
	QHBoxLayout* thumbLayout = new QHBoxLayout(thumbWidget);
	thumbLayout->setContentsMargins(0, 0, 0, 0);
	thumbLayout->addStretch();
	thumbLayout->addWidget(mThumbNailLabel);
	thumbLayout->addStretch();

	layout->addWidget(mTreeView);
	layout->addWidget(thumbWidget);
	setWidget(widget);
}

void DkMetaDataDock::updateEntries() {

	int numRows = mModel->rowCount(QModelIndex());

	for (int idx = 0; idx < numRows; idx++)
		getExpandedItemNames(mModel->index(idx,0,QModelIndex()), mExpandedNames);

	mModel->clear();

	if (!mImgC)
		return;

	mModel->addMetaData(mImgC->getMetaData());
	
	mTreeView->setUpdatesEnabled(false);
	numRows = mModel->rowCount(QModelIndex());
	for (int idx = 0; idx < numRows; idx++)
		expandRows(mModel->index(idx, 0, QModelIndex()), mExpandedNames);
	mTreeView->setUpdatesEnabled(true);

	// for values we should adjust the size at least to the currently visible rows...
	mTreeView->resizeColumnToContents(1);
	//if (treeView->columnWidth(1) > 1000)
	//	treeView->setColumnWidth(1, 1000);

}

void DkMetaDataDock::setImage(QSharedPointer<DkImageContainerT> imgC) {

	mImgC = imgC;

	if (isVisible())
		updateEntries();

	if (imgC) {

		// we need to load the thumbnail fresh to guarantee, that we just consider the exif thumb
		// the imgC thumbnail might be created from the image
		mThumb = QSharedPointer<DkThumbNailT>(new DkThumbNailT(imgC->filePath()));
		connect(mThumb.data(), SIGNAL(thumbLoadedSignal(bool)), this, SLOT(thumbLoaded(bool)));
		mThumb->fetchThumb(DkThumbNailT::force_exif_thumb);
	}
}

void DkMetaDataDock::thumbLoaded(bool loaded) {

	if (loaded) {
		QImage thumbImg = mThumb->getImage();
		
		//if (thumbImg.width() > width()) {
		//	mThumbNailLabel->setFixedWidth(width()-20);
		//	thumbImg = thumbImg.scaled(QSize(width(), thumbImg.height()), Qt::KeepAspectRatio);
		//}
		//else
		//	mThumbNailLabel->setFixedHeight(thumbImg.height());

		QSize tSize = thumbImg.size();
		thumbImg = thumbImg.scaled(tSize.boundedTo(QSize(mTreeView->width(), mTreeView->width())), Qt::KeepAspectRatio);

		mThumbNailLabel->setScaledContents(true);
		mThumbNailLabel->setPixmap(QPixmap::fromImage(thumbImg));
		mThumbNailLabel->show();
	}
	else
		mThumbNailLabel->hide();

	qDebug() << "has thumbnail: " << loaded;

}

void DkMetaDataDock::getExpandedItemNames(const QModelIndex& index, QStringList& expandedNames) {

	if (!mTreeView || !index.isValid())
		return;

	QString entryName = mModel->data(index,Qt::DisplayRole).toString();

	if (mTreeView->isExpanded(index) && !expandedNames.contains(entryName))
		expandedNames.append(entryName);
	else if (!mTreeView->isExpanded(index))
		expandedNames.removeAll(mModel->data(index,Qt::DisplayRole).toString());

	int rows = mModel->rowCount(index);

	for (int idx = 0; idx < rows; idx++)
		getExpandedItemNames(mModel->index(idx, 0, index), expandedNames);

}

void DkMetaDataDock::expandRows(const QModelIndex& index, const QStringList& expandedNames) {

	if (!index.isValid())
		return;

	if (expandedNames.contains(mModel->data(index).toString())) {
		mTreeView->setExpanded(index, true);
	}

	for (int idx = 0; idx < mModel->rowCount(index); idx++) {

		QModelIndex cIndex = index.child(idx, 0);

		if (expandedNames.contains(mModel->data(cIndex).toString())) {
			mTreeView->setExpanded(cIndex, true);
			expandRows(cIndex, expandedNames);
		}
	}
}

//void DkMetaDataDock::setVisible(bool visible) {
//
//	if (visible)
//		updateEntries();
//
//	QDockWidget::setVisible(visible);
//}

// DkMetaDataSelection --------------------------------------------------------------------
DkMetaDataSelection::DkMetaDataSelection(const QSharedPointer<DkMetaDataT> metaData, QWidget* parent) : QWidget(parent) {

	setObjectName("DkMetaDataSelection");
	mMetaData = metaData;
	createLayout();
	selectionChanged();
}

void DkMetaDataSelection::createLayout() {

	createEntries(mMetaData, mKeys, mValues);

	QWidget* lWidget = new QWidget(this);
	mLayout = new QGridLayout(lWidget);

	for (int idx = 0; idx < mKeys.size(); idx++) {
		appendGUIEntry(mKeys.at(idx), mValues.at(idx), idx);
	}

	mLayout->setColumnStretch(2, 10);

	QScrollArea* scrollArea = new QScrollArea(this);
	scrollArea->setWidgetResizable(true);
	scrollArea->setMinimumSize(QSize(200, 200));
	scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
	scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	scrollArea->setBackgroundRole(QPalette::Light);
	scrollArea->setWidget(lWidget);

	mCbCheckAll = new QCheckBox(tr("Check All"), this);
	mCbCheckAll->setTristate(true);
	connect(mCbCheckAll, SIGNAL(clicked(bool)), this, SLOT(checkAll(bool)));

	QVBoxLayout* l = new QVBoxLayout(this);
	l->addWidget(scrollArea);
	l->addWidget(mCbCheckAll);
}

void DkMetaDataSelection::appendGUIEntry(const QString& key, const QString& value, int idx) {

	QString cleanKey = key;
	cleanKey = cleanKey.replace(".", " > ");

	QCheckBox* cb = new QCheckBox(cleanKey, this);
	connect(cb, SIGNAL(clicked()), this, SLOT(selectionChanged()));
	mSelection.append(cb);

	QString cleanValue = DkUtils::cleanFraction(value);
	QDateTime pd = DkUtils::getConvertableDate(cleanValue);

	if (!pd.isNull())
		cleanValue = pd.toString(Qt::SystemLocaleShortDate);

	QLabel* label = new QLabel(cleanValue, this);
	label->setObjectName("DkMetadataValueLabel");

	if (idx == -1)
		idx = mKeys.size();

	mLayout->addWidget(cb, idx, 1);
	mLayout->addWidget(label, idx, 2);
}

void DkMetaDataSelection::checkAll(bool checked) {

	for (QCheckBox* cb : mSelection)
		cb->setChecked(checked);

}

void DkMetaDataSelection::selectionChanged() {

	bool sel = false;
	bool partial = false;
	mCbCheckAll->setTristate(false);

	for (int idx = 0; idx < mSelection.size(); idx++) {

		if (idx > 0 && sel != mSelection.at(idx)->isChecked()) {
			mCbCheckAll->setCheckState(Qt::PartiallyChecked);
			partial = true;
			break;
		}

		sel = mSelection.at(idx)->isChecked();
	}

	if (!partial)
		mCbCheckAll->setChecked(sel);

	qDebug() << "selection changed...";
}

void DkMetaDataSelection::setSelectedKeys(const QStringList& selKeys) {

	for (QString key : selKeys) {

		int idx = mKeys.indexOf(key);

		if (idx != -1) {
			mSelection.at(idx)->setChecked(true);
		}
		else {
			
			// append entries that are not available in the current image
			mKeys.append(key);
			appendGUIEntry(key, "");
			mSelection.last()->setChecked(true);
		}
	}

	selectionChanged();
}

QStringList DkMetaDataSelection::getSelectedKeys() const {

	QStringList selKeys;

	for (int idx = 0; idx < mSelection.size(); idx++) {
		
		if (mSelection.at(idx)->isChecked())
			selKeys.append(mKeys.at(idx));
	}

	return selKeys;
}

void DkMetaDataSelection::createEntries(QSharedPointer<DkMetaDataT> metaData, QStringList& outKeys, QStringList& outValues) const {

	if (!metaData)
		return;

	metaData->getFileMetaData(outKeys, outValues);
	metaData->getAllMetaData(outKeys, outValues);
}

// DkMetaDataHUD --------------------------------------------------------------------
DkMetaDataHUD::DkMetaDataHUD(QWidget* parent) : DkWidget(parent) {

	setObjectName("DkMetaDataHUD");

	// some inits
	mKeyValues = getDefaultKeys();
	loadSettings();

	if (mWindowPosition == pos_west || mWindowPosition == pos_east)
		mOrientation = Qt::Vertical;

	createLayout();
	createActions();
}

DkMetaDataHUD::~DkMetaDataHUD() {

	saveSettings();
}

void DkMetaDataHUD::createLayout() {


	QLabel* titleLabel = new QLabel(tr("Image Information"), this);
	titleLabel->setObjectName("DkMetaDataHUDTitle");

	QLabel* titleSeparator = new QLabel("", this);
	titleSeparator->setObjectName("DkSeparator");

	mTitleWidget = new QWidget(this);
	QVBoxLayout* titleLayout = new QVBoxLayout(mTitleWidget);
	titleLayout->addWidget(titleLabel);
	titleLayout->addWidget(titleSeparator);

	QString scrollbarStyle = 
		QString("QScrollBar:vertical {border: 1px solid #FFF; background: rgba(0,0,0,0); width: 7px; margin: 0 0 0 0;}")
		+ QString("QScrollBar::handle:vertical {background: #FFF; min-height: 0px;}")
		+ QString("QScrollBar::add-line:vertical {height: 0px;}")
		+ QString("QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: rgba(0,0,0,0); width: 1px;}")
		+ QString("QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {height: 0;}")
	+ QString("QScrollBar:horizontal {border: 1px solid #FFF; background: rgba(0,0,0,0); height: 7px; margin: 0 0 0 0;}")	// horizontal
		+ QString("QScrollBar::handle:horizontal {background: #FFF; min-width: 0px;}")
		+ QString("QScrollBar::add-line:horizontal {width: 0px;}")
		+ QString("QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {background: rgba(0,0,0,0); height: 1px;}")
		+ QString("QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {width: 0;}");

	mScrollArea = new DkResizableScrollArea(this);
	mScrollArea->setObjectName("DkScrollAreaMetaData");
	mScrollArea->setWidgetResizable(true);
	mScrollArea->setStyleSheet(scrollbarStyle + mScrollArea->styleSheet());
	mScrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

	mContentWidget = new QWidget(this);
	mContentWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);

	mContentLayout = new QGridLayout(mContentWidget);
	updateLabels();

	mScrollArea->setWidget(mContentWidget);

	QVBoxLayout* l = new QVBoxLayout(this);
	l->setSpacing(0);
	l->setContentsMargins(3,3,3,3);
	l->addWidget(mScrollArea);
} 

void DkMetaDataHUD::createActions() {

	mActions.resize(action_end);

	mActions[action_change_keys] = new QAction(tr("Change Entries"), this);
	mActions[action_change_keys]->setStatusTip(tr("You can customize the entries displayed here."));
	connect(mActions[action_change_keys], SIGNAL(triggered()), this, SLOT(changeKeys()));

	mActions[action_num_columns] = new QAction(tr("Number of Columns"), this);
	mActions[action_num_columns]->setStatusTip(tr("Select the desired number of columns."));
	connect(mActions[action_num_columns], SIGNAL(triggered()), this, SLOT(changeNumColumns()));

	mActions[action_set_to_default] = new QAction(tr("Set to Default"), this);
	mActions[action_set_to_default]->setStatusTip(tr("Reset the metadata panel."));
	connect(mActions[action_set_to_default], SIGNAL(triggered()), this, SLOT(setToDefault()));

	// orientations
	mActions[action_pos_west] = new QAction(tr("Show Left"), this);
	mActions[action_pos_west]->setStatusTip(tr("Shows the Metadata on the Left"));
	connect(mActions[action_pos_west], SIGNAL(triggered()), this, SLOT(newPosition()));

	mActions[action_pos_north] = new QAction(tr("Show Top"), this);
	mActions[action_pos_north]->setStatusTip(tr("Shows the Metadata at the Top"));
	connect(mActions[action_pos_north], SIGNAL(triggered()), this, SLOT(newPosition()));

	mActions[action_pos_east] = new QAction(tr("Show Right"), this);
	mActions[action_pos_east]->setStatusTip(tr("Shows the Metadata on the Right"));
	connect(mActions[action_pos_east], SIGNAL(triggered()), this, SLOT(newPosition()));

	mActions[action_pos_south] = new QAction(tr("Show Bottom"), this);
	mActions[action_pos_south]->setStatusTip(tr("Shows the Metadata at the Bottom"));
	connect(mActions[action_pos_south], SIGNAL(triggered()), this, SLOT(newPosition()));

}

void DkMetaDataHUD::loadSettings() {

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

	settings.beginGroup(objectName());
	QStringList keyVals = settings.value("keyValues", QStringList()).toStringList();
	mNumColumns = settings.value("numColumns", mNumColumns).toInt();
	mWindowPosition = settings.value("windowPosition", mWindowPosition).toInt();
	settings.endGroup();

	if (!keyVals.isEmpty())
		mKeyValues = keyVals;
}

void DkMetaDataHUD::saveSettings() const {

	if (mKeyValues.isEmpty())
		return;

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

	settings.beginGroup(objectName());
	settings.setValue("keyValues", mKeyValues);
	settings.setValue("numColumns", mNumColumns);
	settings.setValue("windowPosition", mWindowPosition);
	settings.endGroup();
}

int DkMetaDataHUD::getWindowPosition() const {
	
	return mWindowPosition;
}

QStringList DkMetaDataHUD::getDefaultKeys() const {

	QStringList keyValues;

	keyValues.append("File." + QObject::tr("Filename"));
	keyValues.append("File." + QObject::tr("Path"));
	keyValues.append("File." + QObject::tr("Size"));
	keyValues.append("Exif.Image.Make");
	keyValues.append("Exif.Image.Model");
	keyValues.append("Exif.Image.DateTime");
	keyValues.append("Exif.Image.ImageDescription");

	keyValues.append("Exif.Photo.ISO");
	keyValues.append("Exif.Photo.FocalLength");
	keyValues.append("Exif.Photo.ExposureTime");
	keyValues.append("Exif.Photo.Flash");
	keyValues.append("Exif.Photo.FNumber");

	return keyValues;
}

void DkMetaDataHUD::updateMetaData(const QSharedPointer<DkImageContainerT> cImg) {

	if (cImg) {
		mMetaData = cImg->getMetaData();
		
		// only update if I am visible
		if (isVisible())
			updateMetaData(mMetaData);
	}
	else
		mMetaData = QSharedPointer<DkMetaDataT>();
}

void DkMetaDataHUD::updateMetaData(const QSharedPointer<DkMetaDataT> metaData) {

	// clean up
	for (QLabel* cLabel : mEntryKeyLabels)
		delete cLabel;
	for (QLabel* cLabel : mEntryValueLabels)
		delete cLabel;

	mEntryKeyLabels.clear();
	mEntryValueLabels.clear();

	if (!metaData) {

		// create dummy entries
		for (QString cKey : mKeyValues) {
			mEntryKeyLabels.append(createKeyLabel(cKey));
		}
		return;
	}

	DkTimer dt;

	QStringList fileKeys, fileValues;
	metaData->getFileMetaData(fileKeys, fileValues);

	for (int idx = 0; idx < fileKeys.size(); idx++) {

		QString cKey = fileKeys.at(idx);
		if (mKeyValues.contains(cKey)) {
			mEntryKeyLabels.append(createKeyLabel(cKey));
			mEntryValueLabels.append(createValueLabel(fileValues.at(idx)));
		}
	}

	QStringList exifKeys = metaData->getExifKeys();

	for (int idx = 0; idx < exifKeys.size(); idx++) {

		QString cKey = exifKeys.at(idx);

		if (mKeyValues.contains(cKey)) {
			QString lastKey = cKey.split(".").last();
			QString exifValue = metaData->getNativeExifValue(exifKeys.at(idx));
			exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

			mEntryKeyLabels.append(createKeyLabel(cKey));
			mEntryValueLabels.append(createValueLabel(exifValue));
		}
	}

	QStringList iptcKeys = metaData->getIptcKeys();

	for (int idx = 0; idx < iptcKeys.size(); idx++) {

		QString cKey = iptcKeys.at(idx);

		if (mKeyValues.contains(cKey)) {

			QString lastKey = iptcKeys.at(idx).split(".").last();
			QString exifValue = metaData->getIptcValue(iptcKeys.at(idx));
			exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

			mEntryKeyLabels.append(createKeyLabel(cKey));
			mEntryValueLabels.append(createValueLabel(exifValue));
		}
	}

	QStringList xmpKeys = metaData->getXmpKeys();

	for (int idx = 0; idx < xmpKeys.size(); idx++) {

		QString cKey = xmpKeys.at(idx);

		if (mKeyValues.contains(cKey)) {

			QString lastKey = xmpKeys.at(idx).split(".").last();
			QString exifValue = metaData->getXmpValue(xmpKeys.at(idx));
			exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

			mEntryKeyLabels.append(createKeyLabel(cKey));
			mEntryValueLabels.append(createValueLabel(exifValue));
		}
	}

	QStringList qtKeys = metaData->getQtKeys();

	for (int idx = 0; idx < qtKeys.size(); idx++) {

		QString cKey = qtKeys.at(idx);

		if (mKeyValues.contains(cKey)) {

			QString lastKey = cKey.split(".").last();
			QString exifValue = metaData->getQtValue(cKey);
			exifValue = DkMetaDataHelper::getInstance().resolveSpecialValue(metaData, lastKey, exifValue);

			mEntryKeyLabels.append(createKeyLabel(cKey));
			mEntryValueLabels.append(createValueLabel(exifValue));
		}
	}


	updateLabels();
}

void DkMetaDataHUD::updateLabels(int numColumns /* = -1 */) {

	if (numColumns == -1 && mNumColumns == -1) {
		int numLines = 6;
		numColumns = ((float)mEntryKeyLabels.size()+numLines-1)/numLines > 2 ? qRound(((float)mEntryKeyLabels.size()+numLines-1)/numLines) : 2;
	}
	else if (numColumns == -1) {
		numColumns = mNumColumns;
	}

	if (mOrientation == Qt::Vertical)
		numColumns = 1;

	int cIdx = 0;
	int rIdx = 0;
	int nRows = cvCeil((float)(mEntryKeyLabels.size())/numColumns);

	// stretch between labels
	// we need this for correct context menu handling
	int cS = (mOrientation == Qt::Horizontal) ? 10 : 0;
	mContentLayout->setColumnStretch(cIdx, cS); cIdx++;
	mContentLayout->setRowStretch(rIdx, cS);

	mTitleWidget->setVisible(mOrientation == Qt::Vertical);
	if (mOrientation == Qt::Vertical)
		mContentLayout->addWidget(mTitleWidget, 0, 0, 1, 4);

	for (int idx = 0; idx < mEntryKeyLabels.size(); idx++) {

		if (idx && idx % nRows == 0) {
			rIdx = 0;
			cIdx += 3;
			mContentLayout->setColumnStretch(cIdx-1, cS);
		}
		 
		mContentLayout->addWidget(mEntryKeyLabels.at(idx), rIdx+1, cIdx, 1, 1, Qt::AlignTop);
		mContentLayout->addWidget(mEntryValueLabels.at(idx), rIdx+1, cIdx+1, 1, 1, Qt::AlignTop);
		rIdx++;
	}
	
	mContentLayout->setColumnStretch(cIdx+1, cS);
	mContentLayout->setRowStretch(1000, 10);	// stretch a reasonably high row (we assume to have less than 1000 entries)

	// remove old columnStretches
	for (int idx = cIdx+2; idx < 40; idx++)
		mContentLayout->setColumnStretch(idx, 0);

	if (mOrientation == Qt::Vertical) {
		// some scroll area settings need to be adopted to the orientation
		mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
		mScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	}
	else {
		// some scroll area settings need to be adopted to the orientation
		mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		mScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
	}

	// TODO: I give now up on this:
	// I do not understand why Qt does not simply resize according to the
	// child widget's constraints if Qt::ScrollBarAlwaysOff is set
	// to me, this would be intended behavior
	// resizing itself is fixed, however, on layout changes it won't
	// decrease it's size
}

QLabel* DkMetaDataHUD::createKeyLabel(const QString& key) {

	QString labelString = key.split(".").last();
	labelString = DkMetaDataHelper::getInstance().translateKey(labelString);
	QLabel* keyLabel = new QLabel(labelString, this);
	keyLabel->setObjectName("DkMetaDataKeyLabel");
	keyLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	keyLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);

	return keyLabel;
}

QLabel* DkMetaDataHUD::createValueLabel(const QString& val) {

	QString cleanValue = DkUtils::cleanFraction(val);
	QDateTime pd = DkUtils::getConvertableDate(cleanValue);

	if (!pd.isNull())
		cleanValue = pd.toString(Qt::SystemLocaleShortDate);

	QLabel* valLabel = new QLabel(cleanValue.trimmed(), this);
	valLabel->setObjectName("DkMetaDataLabel");
	valLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	valLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);

	return valLabel;
}

// events
void DkMetaDataHUD::contextMenuEvent(QContextMenuEvent *event) {

	if (!mContextMenu) {
		mContextMenu = new QMenu(tr("Metadata Menu"), this);
		mContextMenu->addActions(mActions.toList());
	}

	mContextMenu->exec(event->globalPos());
	event->accept();

	//DkWidget::contextMenuEvent(event);
}

// public slots...
void DkMetaDataHUD::setVisible(bool visible, bool saveSetting /* = true */) {

	DkWidget::setVisible(visible, saveSetting);

	updateMetaData(mMetaData);
}

void DkMetaDataHUD::newPosition() {

	QAction* sender = static_cast<QAction*>(QObject::sender());

	if (!sender)
		return;

	int pos = 0;
	Qt::Orientation orient = Qt::Horizontal;

	if (sender == mActions[action_pos_west]) {
		pos = pos_west;
		orient = Qt::Vertical;
	}
	else if (sender == mActions[action_pos_east]) {
		pos = pos_east;
		orient = Qt::Vertical;
	}
	else if (sender == mActions[action_pos_north]) {
		pos = pos_north;
		orient = Qt::Horizontal;
	}
	else {
		pos = pos_south;
		orient = Qt::Horizontal;
	}

	mWindowPosition = pos;
	mOrientation = orient;
	emit positionChangeSignal(mWindowPosition);

	updateLabels();
}

void DkMetaDataHUD::changeKeys() {

	QDialog* dialog = new QDialog(this);
	QVBoxLayout* layout = new QVBoxLayout(dialog);
		
	DkMetaDataSelection* selWidget = new DkMetaDataSelection(mMetaData, this);
	selWidget->setSelectedKeys(mKeyValues);

	// mButtons
	QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
	buttons->button(QDialogButtonBox::Ok)->setText(tr("&OK"));
	buttons->button(QDialogButtonBox::Cancel)->setText(tr("&Cancel"));
	connect(buttons, SIGNAL(accepted()), dialog, SLOT(accept()));
	connect(buttons, SIGNAL(rejected()), dialog, SLOT(reject()));

	layout->addWidget(selWidget);
	layout->addWidget(buttons);

	int res = dialog->exec();

	if (res == QDialog::Accepted) {
		mKeyValues = selWidget->getSelectedKeys();
		updateMetaData(mMetaData);
	}

	dialog->deleteLater();
}

void DkMetaDataHUD::changeNumColumns() {

	bool ok;
	int val = QInputDialog::getInt(this, tr("Number of Columns"), tr("Number of columns (-1 is default)"), mNumColumns, -1, 20, 1, &ok);

	if (ok) {
		mNumColumns = val;
		updateLabels(mNumColumns);
	}
}

void DkMetaDataHUD::setToDefault() {

	mNumColumns = -1;
	mKeyValues = getDefaultKeys();
	updateMetaData(mMetaData);
}

// DkCommentTextEdit --------------------------------------------------------------------
DkCommentTextEdit::DkCommentTextEdit(QWidget* parent /* = 0 */) : QTextEdit(parent) {

}

void DkCommentTextEdit::focusOutEvent(QFocusEvent *focusEvent) {
	emit focusLost();
	QTextEdit::focusOutEvent(focusEvent);
}

void DkCommentTextEdit::paintEvent(QPaintEvent* e) {

	if (toPlainText().isEmpty() && !viewport()->hasFocus()) {
		QPainter p(viewport());
		p.setOpacity(0.5);
		p.drawText(QRect(QPoint(), viewport()->size()), Qt::AlignHCenter | Qt::AlignVCenter, tr("Click here to add notes"));
		//qDebug() << "painting placeholder...";
	}
	
	QTextEdit::paintEvent(e);
}

// DkCommentWidget --------------------------------------------------------------------
DkCommentWidget::DkCommentWidget(QWidget* parent /* = 0 */, Qt::WindowFlags /* = 0 */) : DkFadeLabel(parent) {

	setMaximumSize(220, 150);
	createLayout();
	QMetaObject::connectSlotsByName(this);
}

void DkCommentWidget::createLayout() {

	setObjectName("DkCommentWidget");

	QLabel* titleLabel = new QLabel(tr("NOTES"), this);
	titleLabel->setObjectName("commentTitleLabel");

	QString scrollbarStyle = 
		QString("QScrollBar:vertical {border: 1px solid #FFF; background: rgba(0,0,0,0); width: 7px; margin: 0 0 0 0;}")
		+ QString("QScrollBar::handle:vertical {background: #FFF; min-height: 0px;}")
		+ QString("QScrollBar::add-line:vertical {height: 0px;}")
		+ QString("QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: rgba(0,0,0,0); width: 1px;}")
		+ QString("QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {height: 0;}");

	mCommentLabel = new DkCommentTextEdit(this);
	mCommentLabel->setObjectName("CommentLabel");
	mCommentLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	mCommentLabel->setStyleSheet(scrollbarStyle + mCommentLabel->styleSheet());
	mCommentLabel->setToolTip(tr("Enter your notes here. They will be saved to the image metadata."));

	QPushButton* saveButton = new QPushButton(this);
	saveButton->setObjectName("saveButton");
	saveButton->setFlat(true);
	saveButton->setIcon(QIcon(DkImage::colorizePixmap(QPixmap(":/nomacs/img/save.png"), QColor(255,255,255,255), 1.0f)));
	saveButton->setToolTip(tr("Save Note (CTRL + ENTER)"));
	saveButton->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));

	QPushButton* cancelButton = new QPushButton(this);
	cancelButton->setObjectName("cancelButton");
	cancelButton->setFlat(true);
	cancelButton->setIcon(QIcon(DkImage::colorizePixmap(QPixmap(":/nomacs/img/trash.png"), QColor(255,255,255,255), 1.0f)));
	cancelButton->setToolTip(tr("Discard Changes (ESC)"));
	cancelButton->setShortcut(QKeySequence(Qt::Key_Escape));

	QWidget* titleWidget = new QWidget(this);
	QHBoxLayout* titleLayout = new QHBoxLayout(titleWidget);
	titleLayout->setAlignment(Qt::AlignLeft);
	titleLayout->setContentsMargins(0, 0, 0, 0);
	titleLayout->setSpacing(0);
	titleLayout->addWidget(titleLabel);
	titleLayout->addStretch();
	titleLayout->addWidget(cancelButton, 0, Qt::AlignVCenter);
	titleLayout->addWidget(saveButton, 0, Qt::AlignVCenter);

	QVBoxLayout* layout = new QVBoxLayout(this);
	//layout->setContentsMargins(0,0,0,0);
	layout->addWidget(titleWidget);
	layout->addWidget(mCommentLabel);

	setLayout(layout);
	setCursor(Qt::ArrowCursor);
}

void DkCommentWidget::setMetaData(QSharedPointer<DkMetaDataT> metaData) {

	mMetaData = metaData;
	setComment(metaData->getDescription());
}

void DkCommentWidget::setComment(const QString& description) {
	
	mCommentLabel->setText(description);

	mOldText = description;
	mDirty = false;
}

void DkCommentWidget::saveComment() {

	if (mTextChanged && mCommentLabel->toPlainText() != mMetaData->getDescription() && mMetaData) {
		
		if (!mMetaData->setDescription(mCommentLabel->toPlainText()) && !mCommentLabel->toPlainText().isEmpty()) {
			emit showInfoSignal(tr("Sorry, I cannot save comments for this image format."));
		}
		else
			mDirty = true;
	}
}

void DkCommentWidget::on_CommentLabel_textChanged() {

	mTextChanged = true;
}

void DkCommentWidget::on_CommentLabel_focusLost() {

	saveComment();
}

void DkCommentWidget::on_saveButton_clicked() {

	mCommentLabel->clearFocus();
}

void DkCommentWidget::on_cancelButton_clicked() {

	mTextChanged = false;
	mCommentLabel->clearFocus();
	mCommentLabel->setText("");

	saveComment();
}

}