/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2020 mjbudd77
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
// FamilyKeyboard.cpp
#include <SDL.h>
#include <QHeaderView>
#include <QCloseEvent>
#include <QFileDialog>
#include <QGroupBox>
#include <QPainter>
#include <QFontMetrics>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFontDialog>
#include <QSettings>
#include <QDir>

#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/config.h"
#include "Qt/keyscan.h"
#include "Qt/fceuWrapper.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/FamilyKeyboard.h"

static const char *keyNames[] =
{
    "F1", "F1",
    "F2", "F2",
    "F3", "F3",
    "F4", "F4",
    "F5", "F5",
    "F6", "F6",
    "F7", "F7",
    "F8", "F8",
    "1", "!",
    "2", "\"",
    "3", "#",
    "4", "$",
    "5", "%",
    "6", "&",
    "7", "'",
    "8", "(",
    "9", ")",
    "0", "0",
    "-", "=",
    "^", "^",
    "¥", "¥",
    "STOP", "STOP",
    "ESC", "ESC",
    "Q", "Q",
    "W", "W",
    "E", "E",
    "R", "R",
    "T", "T",
    "Y", "Y",
    "U", "U",
    "I", "I",
    "O", "O",
    "P", "P",
    "@", "@",
    "[", "[",
    "RETURN", "RETURN",
    "CTR", "CTR",
    "A", "A",
    "S", "S",
    "D", "D",
    "F", "F",
    "G", "G",
    "H", "H",
    "J", "J",
    "K", "K",
    "L", "L",
    ";", "+",
    ":", "*",
    "]", "]",
    "KANA", "KANA",
    "SHIFT", "SHIFT",
    "Z", "Z",
    "X", "X",
    "C", "C",
    "V", "V",
    "B", "B",
    "N", "N",
    "M", "M",
    ",", "<",
    ".", ">",
    "/", "?",
    "_", "_",
    "SHIFT", "SHIFT",
    "GRPH", "GRPH",
    "SPACE", "SPACE",
    "HOM", "CLR",
    "INS", "INS",
    "DEL", "DEL",
    "UP", "UP",
    "LEFT", "LEFT",
    "RIGHT", "RIGHT",
    "DOWN", "DOWN",
     NULL, NULL
};

static FKBConfigDialog *fkbWin = NULL;
static bool fkbActv = false;
//*********************************************************************************
int openFamilyKeyboardDialog(QWidget *parent)
{
	if (fkbWin != NULL)
	{
		fkbWin->activateWindow();
		fkbWin->raise();
	}
	else
	{
		fkbWin = new FKBConfigDialog(parent);
		fkbWin->show();
	}
	return 0;
}
//*********************************************************************************
char getFamilyKeyboardVirtualKey( int idx )
{
	char state = 0;

	if (fkbWin != NULL)
	{
		state = fkbWin->keyboard->key[idx].vState;
	}
	return state;
}
//*********************************************************************************
FamilyKeyboardWidget::FamilyKeyboardWidget( QWidget *parent )
{
	QFont font;
	std::string fontString;

	setMouseTracking(true);
	setFocusPolicy(Qt::StrongFocus);

	g_config->getOption("SDL.FamilyKeyboardFont", &fontString);

	if ( fontString.size() > 0 )
	{
		font.fromString( QString::fromStdString( fontString ) );
	}
	else
	{
		font.setFamily("Courier New");
		font.setStyle( QFont::StyleNormal );
		font.setStyleHint( QFont::Monospace );
	}
	setFont( font );
		
	setMinimumSize( 512, 256 );

	calcFontData();

	keyPressed = -1;
	keyUnderMouse = -1;
	ctxMenuKey = -1;

	// Set Shift Keys to Toggle State On Press
	key[50].toggleOnPress = true;
	key[62].toggleOnPress = true;

	updateTimer = new QTimer(this);

	connect(updateTimer, &QTimer::timeout, this, &FamilyKeyboardWidget::updatePeriodic);

	updateTimer->start(50); // 20hz
}
//*********************************************************************************
FamilyKeyboardWidget::~FamilyKeyboardWidget(void)
{
	updateTimer->stop();
	
}
//*********************************************************************************
void FamilyKeyboardWidget::setFont( const QFont &newFont )
{
	QWidget::setFont(newFont);

	calcFontData();
}
//*********************************************************************************
void FamilyKeyboardWidget::calcFontData(void)
{
	QWidget::setFont(font());
	QFontMetrics metrics(font());
#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0)
	pxCharWidth = metrics.horizontalAdvance(QLatin1Char('2'));
#else
	pxCharWidth = metrics.width(QLatin1Char('2'));
#endif
	pxCharHeight   = metrics.capHeight();
	//pxLineSpacing  = metrics.lineSpacing() * 1.25;
	//pxCursorHeight = metrics.height();
	
	pxBtnGridX = (3 * pxCharWidth) + 2;
	pxBtnGridY = (2 * pxCharHeight);

	if ( pxBtnGridX % 2 )
	{
		pxBtnGridX++;
	}
	if ( pxBtnGridY % 2 )
	{
		pxBtnGridY++;
	}
	setMinimumWidth( pxBtnGridX * 26 );
	setMinimumHeight( pxBtnGridY * 8 );
}
//*********************************************************************************
void FamilyKeyboardWidget::updatePeriodic(void)
{
	updateHardwareStatus();

	update();
}
//*********************************************************************************
void FamilyKeyboardWidget::updateHardwareStatus(void)
{
	const uint8 *hwKeyState = getFamilyKeyboardState();

	for (int i=0; i<FAMILYKEYBOARD_NUM_BUTTONS; i++)
	{
		key[i].hwState = hwKeyState[i];
	}
}
//*********************************************************************************
int FamilyKeyboardWidget::getKeyAtPoint( QPoint p )
{
	for (int i=0; i < static_cast<int>(NUM_KEYS); i++)
	{
		if ( key[i].rect.contains(p) )
		{
			return i;
		}
	}
	return -1;
}
//*********************************************************************************
void FamilyKeyboardWidget::mousePressEvent(QMouseEvent * event)
{
	keyPressed = keyUnderMouse = getKeyAtPoint(event->pos());

	if ( (keyPressed >= 0) && (event->button() == Qt::LeftButton) )
	{
		key[ keyPressed ].pressed();
	}
	update();
}
//*********************************************************************************
void FamilyKeyboardWidget::mouseReleaseEvent(QMouseEvent * event)
{
	keyUnderMouse = getKeyAtPoint(event->pos());

	if ( keyPressed >= 0 )
	{
		key[ keyPressed ].released();

		keyPressed = -1;
	}
	update();
}
//*********************************************************************************
void FamilyKeyboardWidget::mouseMoveEvent(QMouseEvent * event)
{
	int tmpKeyUnderMouse = getKeyAtPoint(event->pos());

	//printf("Mouse Move: Key:%i \n", keyUnderMouse );

	if ( tmpKeyUnderMouse != keyUnderMouse )
	{
		keyUnderMouse = tmpKeyUnderMouse;
		update();
	}
}
//*********************************************************************************
void FamilyKeyboardWidget::mouseDoubleClickEvent(QMouseEvent * event)
{
	keyUnderMouse = getKeyAtPoint(event->pos());
}
//*********************************************************************************
void FamilyKeyboardWidget::leaveEvent(QEvent *event)
{
	keyUnderMouse = -1;
	update();
}
//*********************************************************************************
void FamilyKeyboardWidget::contextMenuEvent(QContextMenuEvent *event)
{
	ctxMenuKey = keyUnderMouse = getKeyAtPoint(event->pos());

	if ( keyUnderMouse < 0 )
	{
		return;
	}

	QAction *act;
	QMenu menu(this);

	act = new QAction(tr("Map Key Binding..."), &menu);
	//act->setShortcut( QKeySequence(tr("E")));
	connect( act, SIGNAL(triggered(void)), this, SLOT(ctxMapPhysicalKey(void)) );
	menu.addAction( act );

	act = new QAction(tr("Toggle State on Press"), &menu);
	act->setCheckable(true);
	act->setChecked( key[ ctxMenuKey ].toggleOnPress );
	//act->setShortcut( QKeySequence(tr("E")));
	connect( act, SIGNAL(triggered(void)), this, SLOT(ctxChangeToggleOnPress(void)) );
	menu.addAction( act );

	menu.exec(event->globalPos());

	event->accept();
}
//*********************************************************************************
void FamilyKeyboardWidget::ctxMapPhysicalKey(void)
{
	FKBKeyMapDialog *mapDialog = new FKBKeyMapDialog( ctxMenuKey, this );

	mapDialog->show();
	mapDialog->enterButtonLoop();

	if ( fkbWin )
	{
		fkbWin->updateBindingList();
		fkbWin->show();
		fkbWin->activateWindow();
		fkbWin->raise();
	}
}
//*********************************************************************************
void FamilyKeyboardWidget::ctxChangeToggleOnPress(void)
{
	key[ ctxMenuKey ].toggleOnPress = !key[ ctxMenuKey ].toggleOnPress;
}
//*********************************************************************************
void FamilyKeyboardWidget::keyPressEvent(QKeyEvent *event)
{
	//printf("Key Press: 0x%x \n", event->key() );
	pushKeyEvent( event, 1 );

	event->accept();
}
//*********************************************************************************
void FamilyKeyboardWidget::keyReleaseEvent(QKeyEvent *event)
{
	//printf("Key Release: 0x%x \n", event->key() );
	pushKeyEvent( event, 0 );

	event->accept();
}
//*********************************************************************************
void FamilyKeyboardWidget::drawButton( QPainter &painter, int idx, int x, int y, int w, int h )
{
	int i = idx, j;
	QColor bgColor;

	key[i].rect = QRect( x, y, w, h );

	if ( key[i].isDown() )
	{
		if ( keyUnderMouse == idx )
		{
			bgColor = QColor( Qt::darkGreen );
		}
		else
		{
			bgColor = QColor( Qt::green );
		}
	}
	else
	{
		if ( keyUnderMouse == idx )
		{
			bgColor = QColor( Qt::gray );
		}
		else
		{
			bgColor = QColor( Qt::lightGray );
		}
	}

	j = i*2;

	if ( key[50].isDown() || key[62].isDown() )
	{
		j++;
	}

	painter.fillRect( key[i].rect, bgColor );
	painter.drawText( key[i].rect, Qt::AlignCenter, tr(keyNames[j]) );
	painter.drawRect( key[i].rect );

}
//*********************************************************************************
void FamilyKeyboardWidget::paintEvent(QPaintEvent *event)
{
	int i, x, y, w, h, xs, ys;
	QPainter painter(this);

	// Row 1
	x = pxBtnGridX / 2;
	y = pxBtnGridY / 2;

	w = pxBtnGridX * 2;
	h = pxBtnGridY;

	xs = w / 4;
	ys = h / 4;

	for (i=0; i<8; i++)
	{
		drawButton( painter, i, x, y, w, h );
		
		x += (w + xs);
	}

	// Row 2
	x  = pxBtnGridX;
	y += pxBtnGridY + ys;

	w = pxBtnGridX;

	xs = w / 4;

	for (i=8; i<21; i++)
	{
		drawButton( painter, i, x, y, w, h );

		x += (w + xs);
	}
	
	drawButton( painter, 21, x, y, w*2, h );

	// Row 3
	x  = pxBtnGridX / 2;
	y += pxBtnGridY + ys;

	w = pxBtnGridX;

	xs = w / 4;

	for (i=22; i<35; i++)
	{
		drawButton( painter, i, x, y, w, h );
		
		x += (w + xs);
	}

	x += (xs);

	drawButton( painter, 35, x, y, w*3, h );

	// Row 4
	x  = pxBtnGridX;
	y += pxBtnGridY + ys;

	w = pxBtnGridX;

	xs = w / 4;
	x -= xs;

	for (i=36; i<49; i++)
	{
		drawButton( painter, i, x, y, w, h );
		
		x += (w + xs);
	}

	drawButton( painter, 49, x, y, w*2, h );

	// Row 5
	x  = pxBtnGridX / 2;
	y += pxBtnGridY + ys;

	w = pxBtnGridX;

	xs = w / 4;
	//x -= xs;

	drawButton( painter, 50, x, y, w*2, h );

	x += ((w*2) + xs);

	for (i=51; i<62; i++)
	{
		drawButton( painter, i, x, y, w, h );
		
		x += (w + xs);
	}

	drawButton( painter, 62, x, y, w*2, h );

	// Row 6
	x  = (pxBtnGridX * 5) / 2;
	y += pxBtnGridY + ys;

	w = pxBtnGridX;

	xs = w / 4;
	x += (2*xs);

	drawButton( painter, 63, x, y, w*2, h );

	x += ((w*2) + xs);

	drawButton( painter, 64, x, y, (w*8) + (xs*7), h );

	// Row 7
	xs =  pxBtnGridX / 4;
	x  = (pxBtnGridX+xs) * 17;
	y  = (pxBtnGridY * 3) / 2;

	w = pxBtnGridX;

	xs = w / 4;
	//x += (2*xs);
	y += (2*ys);

	for (i=65; i<68; i++)
	{
		drawButton( painter, i, x, y, w, h );
		
		x += (w + xs);
	}

	// Arrow Keys
	x  = (pxBtnGridX+xs) * 17 + (pxBtnGridX)/2 + xs;
	y += pxBtnGridY + ys;

	drawButton( painter, 68, x, y, w*2, h );

	x  = (pxBtnGridX+xs) * 17 - (pxBtnGridX)/2 + (xs/2);
	y += pxBtnGridY + ys;

	drawButton( painter, 69, x, y, w*2, h );

	x += ((w*2) + xs);

	drawButton( painter, 70, x, y, w*2, h );

	x  = (pxBtnGridX+xs) * 17 + (pxBtnGridX)/2 + xs;
	y += pxBtnGridY + ys;

	drawButton( painter, 71, x, y, w*2, h );
}
//*********************************************************************************
//----------------------------------------------------------------------------
//--- Family Keyboard Config Dialog
//----------------------------------------------------------------------------
FKBConfigDialog::FKBConfigDialog(QWidget *parent)
	: QDialog(parent)
{
	QVBoxLayout *mainVbox;
	QHBoxLayout *hbox;
	QPushButton *closeButton;
	QTreeWidgetItem *item;
	QMenuBar    *menuBar;
	QSettings    settings;
	char stmp[64];

	setWindowTitle( "Family Keyboard" );

	mainVbox = new QVBoxLayout();
	setLayout( mainVbox );

	menuBar  = buildMenuBar();
	mainVbox->setMenuBar( menuBar );

	keyboard = new FamilyKeyboardWidget();

	mainVbox->addWidget( keyboard );

	keyTree = new QTreeWidget();

	keyTree->setColumnCount(2);
	keyTree->setSelectionMode( QAbstractItemView::SingleSelection );
	keyTree->setAlternatingRowColors(true);

	item = new QTreeWidgetItem();
	item->setText(0, QString::fromStdString("FKB Key"));
	item->setText(1, QString::fromStdString("SDL Binding"));
	item->setTextAlignment(0, Qt::AlignLeft);
	item->setTextAlignment(1, Qt::AlignLeft);

	keyTree->setHeaderItem(item);

	keyTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

	for (int i=0; i<FAMILYKEYBOARD_NUM_BUTTONS; i++)
	{
		int j = i*2;

		item = new QTreeWidgetItem();

		if ( strcmp( keyNames[j], keyNames[j+1] ) == 0 )
		{
			snprintf( stmp, sizeof(stmp), " %s     ", keyNames[j] );
		}
		else
		{
			snprintf( stmp, sizeof(stmp), " %s  -  %s   ", keyNames[j], keyNames[j+1] );
		}

		item->setText(0, tr(stmp) );

		item->setTextAlignment(0, Qt::AlignLeft);
		item->setTextAlignment(1, Qt::AlignLeft);

		keyTree->addTopLevelItem(item);
	}
	updateBindingList();

	mainVbox->addWidget( keyTree );

	hbox = new QHBoxLayout();

	mainVbox->addLayout( hbox );

	statLbl = new QLabel();
	fkbEnaBtn = new QPushButton( tr("Enable") );
	closeButton = new QPushButton( tr("Close") );
	closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
	closeButton->setAutoDefault(false);

	hbox->addWidget(fkbEnaBtn, 1);
	hbox->addWidget(statLbl, 3);
	hbox->addStretch(5);
	hbox->addWidget( closeButton, 1);

	connect( keyTree, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(keyTreeItemActivated(QTreeWidgetItem*,int) ) );

	keyTree->setMinimumHeight(0);
	keyTree->setMaximumHeight(0);

	keyTreeHgtAnimation = new QPropertyAnimation( keyTree, "maximumHeight", this);
	keyTreeHgtAnimation->setDuration(500);
	keyTreeHgtAnimation->setStartValue(0);
	keyTreeHgtAnimation->setEndValue(512);
	keyTreeHgtAnimation->setEasingCurve( QEasingCurve::InOutCirc );

	connect( keyTreeHgtAnimation, SIGNAL(valueChanged(const QVariant &)), this, SLOT(keyTreeHeightChange(const QVariant &)));
	connect( keyTreeHgtAnimation, SIGNAL(finished(void)), this, SLOT(keyTreeResizeDone(void)) );

	connect(closeButton  , SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
	connect(fkbEnaBtn    , SIGNAL(clicked(void)), this, SLOT(toggleFamilyKeyboardEnable(void)) );

	updateTimer = new QTimer(this);

	connect(updateTimer, &QTimer::timeout, this, &FKBConfigDialog::updatePeriodic);

	updateTimer->start(500); // 2hz

	updateStatusLabel();

	// Restore Window Geometry
	restoreGeometry(settings.value("familyKeyboard/geometry").toByteArray());
}
//----------------------------------------------------------------------------
FKBConfigDialog::~FKBConfigDialog(void)
{
	QSettings  settings;

	fkbWin = NULL;

	updateTimer->stop();

	// Save Window Geometry
	settings.setValue("familyKeyboard/geometry", saveGeometry());

	SaveCurrentMapping();
}
//----------------------------------------------------------------------------
void FKBConfigDialog::SaveCurrentMapping(void)
{
	std::string prefix;

	// FamilyKeyBoard
	prefix = "SDL.Input.FamilyKeyBoard.";
	g_config->setOption(prefix + "DeviceType", DefaultFamilyKeyBoardDevice);
	g_config->setOption(prefix + "DeviceNum", 0);
	for(unsigned int j = 0; j < FAMILYKEYBOARD_NUM_BUTTONS; j++)
	{
		g_config->setOption(prefix + FamilyKeyBoardNames[j], fkbmap[j].ButtonNum );
	}
	g_config->save();
}
//----------------------------------------------------------------------------
void FKBConfigDialog::resetDefaultMapping(void)
{
	std::string prefix, device;
	int type = 0, devnum = 0;

	// FamilyKeyBoard
	prefix = "SDL.Input.FamilyKeyBoard.";
	g_config->setOption(prefix + "DeviceType", DefaultFamilyKeyBoardDevice);
	g_config->setOption(prefix + "DeviceNum", 0);

	g_config->getOption(prefix + "DeviceType", &device);

	if (device.find("Keyboard") != std::string::npos)
	{
		type = BUTTC_KEYBOARD;
	}
	else if (device.find("Joystick") != std::string::npos)
	{
		type = BUTTC_JOYSTICK;
	}
	else
	{
		type = 0;
	}
	g_config->getOption(prefix + "DeviceNum", &devnum);

	for(unsigned int j = 0; j < FAMILYKEYBOARD_NUM_BUTTONS; j++)
	{
		g_config->setOption(prefix + FamilyKeyBoardNames[j], DefaultFamilyKeyBoard[j] );

		fkbmap[j].ButtType = type;
		fkbmap[j].DeviceNum = devnum;
		fkbmap[j].ButtonNum = DefaultFamilyKeyBoard[j];
	}
	g_config->save();
}
//----------------------------------------------------------------------------
QMenuBar *FKBConfigDialog::buildMenuBar(void)
{
	QMenu       *fileMenu, *confMenu, *viewMenu;
	//QActionGroup *actGroup;
	QAction     *act;
	int useNativeMenuBar=0;

	QMenuBar *menuBar = new consoleMenuBar(this);

	// This is needed for menu bar to show up on MacOS
	g_config->getOption( "SDL.UseNativeMenuBar", &useNativeMenuBar );

	menuBar->setNativeMenuBar( useNativeMenuBar ? true : false );

	//-----------------------------------------------------------------------
	// Menu Start
	//-----------------------------------------------------------------------
	// File
	fileMenu = menuBar->addMenu(tr("File"));

	// File -> Load
	act = new QAction(tr("Load"), this);
	//act->setShortcut(QKeySequence::Open);
	act->setStatusTip(tr("Load Mapping"));
	connect(act, SIGNAL(triggered()), this, SLOT(mappingLoad(void)) );

	fileMenu->addAction(act);

	// File -> Save
	act = new QAction(tr("Save"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Save Mapping"));
	connect(act, SIGNAL(triggered()), this, SLOT(mappingSave(void)) );

	fileMenu->addAction(act);

	// File -> Save As
	act = new QAction(tr("Save As"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Save Mapping As"));
	connect(act, SIGNAL(triggered()), this, SLOT(mappingSaveAs(void)) );

	fileMenu->addAction(act);

	fileMenu->addSeparator();

	// File -> Close
	act = new QAction(tr("Close"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Close Window"));
	connect(act, SIGNAL(triggered()), this, SLOT(closeWindow(void)) );

	fileMenu->addAction(act);

	// Config
	confMenu = menuBar->addMenu(tr("Config"));

	// Config -> Font
	act = new QAction(tr("Font"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Choose Font"));
	connect(act, SIGNAL(triggered()), this, SLOT(openFontDialog(void)) );

	confMenu->addAction(act);

	confMenu->addSeparator();

	// Config -> Reset to Defaults
	act = new QAction(tr("Reset to Defaults"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Reset to Defaults Mappings"));
	connect(act, SIGNAL(triggered()), this, SLOT(resetDefaultMapping(void)) );

	confMenu->addAction(act);

	// View
	viewMenu = menuBar->addMenu(tr("View"));

	// View -> Show Key Binding Tree
	act = new QAction(tr("Show Key Binding Tree"), this);
	act->setCheckable(true);
	act->setChecked(false);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Show Key Binding Tree"));
	connect(act, SIGNAL(triggered(bool)), this, SLOT(toggleTreeView(bool)) );

	viewMenu->addAction(act);

	// View -> Shrink Window to Minimum Size
	act = new QAction(tr("Shrink Window to Minimum Size"), this);
	//act->setShortcut(QKeySequence::Close);
	act->setStatusTip(tr("Shrink Window to Minimum Size"));
	connect(act, SIGNAL(triggered(void)), this, SLOT(keyTreeResizeDone(void)) );

	viewMenu->addAction(act);

	return menuBar;
}
//*********************************************************************************
void FKBConfigDialog::openFontDialog(void)
{
	bool ok = false;

	QFont selFont = QFontDialog::getFont( &ok, keyboard->QWidget::font(), this, tr("Select Font"), QFontDialog::MonospacedFonts );

	if ( ok )
	{
		if ( selFont.pointSize() > 72 )
		{
			selFont.setPointSize(72);
		}
		keyboard->setFont( selFont );
		keyboard->update();

		//printf("Font Changed to: '%s'\n", font.toString().toLocal8Bit().constData() );

		g_config->setOption("SDL.FamilyKeyboardFont", selFont.toString().toLocal8Bit().constData() );

		QTimer::singleShot( 100, this, SLOT(keyTreeResizeDone(void)) );
	}
}
//----------------------------------------------------------------------------
void FKBConfigDialog::toggleTreeView(bool state)
{
	if ( state )
	{
		keyTreeHgtAnimation->setStartValue(0);
		keyTreeHgtAnimation->setEndValue(512);
		keyTreeHgtAnimation->start();
	}
	else
	{
		keyTree->setMinimumHeight( 0 );
		keyTreeHgtAnimation->setStartValue( keyTree->height() );
		keyTreeHgtAnimation->setEndValue(0);
		keyTreeHgtAnimation->start();
	}
}
//----------------------------------------------------
void FKBConfigDialog::keyTreeHeightChange(const QVariant &value)
{
	int val = value.toInt();

	if ( val > 256 )
	{
		keyTree->setMinimumHeight( 256 );
	}
	else
	{
		keyTree->setMinimumHeight( val );
	}

	resize( minimumSizeHint() );
}
//----------------------------------------------------
void FKBConfigDialog::keyTreeResizeDone(void)
{
	resize( minimumSizeHint() );
}
//----------------------------------------------------------------------------
void FKBConfigDialog::updateBindingList(void)
{
	char keyNameStr[128];

	for (int i=0; i<FAMILYKEYBOARD_NUM_BUTTONS; i++)
	{
		QTreeWidgetItem *item = keyTree->topLevelItem(i);

		//item->setText(0, tr(keyNames[i*2]));

		if (fkbmap[i].ButtType == BUTTC_KEYBOARD)
		{
			snprintf(keyNameStr, sizeof(keyNameStr), "%s",
					 SDL_GetKeyName(fkbmap[i].ButtonNum));
		}
		else
		{
			strcpy(keyNameStr, ButtonName(&fkbmap[i]));
		}
		item->setText(1, tr(keyNameStr));
	}
}
//----------------------------------------------------------------------------
void FKBConfigDialog::updatePeriodic(void)
{
	bool tmpFkbActv = isFamilyKeyboardActv();

	if ( tmpFkbActv != fkbActv )
	{
		fkbActv = tmpFkbActv;

		updateStatusLabel();
	}
}
//----------------------------------------------------------------------------
void FKBConfigDialog::updateStatusLabel(void)
{
	if ( fkbActv )
	{
		statLbl->setText( tr("Family Keyboard is Enabled") );
		fkbEnaBtn->setText( tr("Disable") );
	}
	else
	{
		statLbl->setText( tr("Family Keyboard is Disabled") );
		fkbEnaBtn->setText( tr("Enable") );
	}
}
//----------------------------------------------------------------------------
void FKBConfigDialog::toggleFamilyKeyboardEnable(void)
{
	int curNesInput[3], usrNesInput[3];

	getInputSelection(0, &curNesInput[0], &usrNesInput[0]);
	getInputSelection(1, &curNesInput[1], &usrNesInput[1]);
	getInputSelection(2, &curNesInput[2], &usrNesInput[2]);

	if ( curNesInput[2] != SIFC_FKB )
	{
		ESI port[2];
		ESIFC fcexp;
		int fourscore = false, microphone = false;

		curNesInput[2] = SIFC_FKB;

		g_config->getOption("SDL.FourScore", &fourscore);

		microphone = replaceP2StartWithMicrophone;

		port[0] = (ESI)curNesInput[0];
		port[1] = (ESI)curNesInput[1];
		fcexp = (ESIFC)curNesInput[2];

		FCEUD_SetInput(fourscore, microphone, port[0], port[1], fcexp);

		if ( !isFamilyKeyboardActv() )
		{
			toggleFamilyKeyboardFunc();
		}
	}
	else
	{
		toggleFamilyKeyboardFunc();
	}
	updateStatusLabel();
}
//----------------------------------------------------------------------------
void FKBConfigDialog::closeEvent(QCloseEvent *event)
{
	//printf("FKB Config Close Window Event\n");
	done(0);
	deleteLater();
	event->accept();
}
//----------------------------------------------------------------------------
void FKBConfigDialog::closeWindow(void)
{
	//printf("Close Window\n");
	done(0);
	deleteLater();
}
//----------------------------------------------------------------------------
void FKBConfigDialog::keyTreeItemActivated(QTreeWidgetItem *item, int column)
{
	int itemIndex = keyTree->indexOfTopLevelItem( item );

	if ( itemIndex < 0 )
	{
		return;
	}
	FKBKeyMapDialog *mapDialog = new FKBKeyMapDialog( itemIndex, this );

	mapDialog->show();
	mapDialog->enterButtonLoop();

	updateBindingList();

	show();
	activateWindow();
	raise();
}
//----------------------------------------------------------------------------
int FKBConfigDialog::getButtonIndexFromName( const char *buttonName )
{
	for (int j=0; j < static_cast<int>(FamilyKeyboardWidget::NUM_KEYS); j++)
	{
		if ( strcmp( buttonName, FamilyKeyBoardNames[j] ) == 0 )
		{
			return j;
		}
	}
	return -1;
}
//----------------------------------------------------------------------------
int FKBConfigDialog::convText2ButtConfig( const char *txt, ButtConfig *bmap )
{
	int devIdx = -1;

	bmap->ButtType  = -1;
	bmap->DeviceNum = -1;
	bmap->ButtonNum = -1;

	if (txt[0] == 0 )
	{
		return 0;
	}

	if (txt[0] == 'k')
	{
		SDL_Keycode key;

		bmap->ButtType = BUTTC_KEYBOARD;
		bmap->DeviceNum = -1;

		key = SDL_GetKeyFromName(&txt[1]);

		if (key != SDLK_UNKNOWN)
		{
			bmap->ButtonNum = key;
		}
		else
		{
			bmap->ButtonNum = -1;
		}
	}
	else if ((txt[0] == 'b') && isdigit(txt[1]))
	{
		bmap->ButtType = BUTTC_JOYSTICK;
		bmap->DeviceNum = devIdx;
		bmap->ButtonNum = atoi(&txt[1]);
	}
	else if ((txt[0] == 'h') && isdigit(txt[1]) &&
			 (txt[2] == '.') && isdigit(txt[3]))
	{
		int hatIdx, hatVal;

		hatIdx = txt[1] - '0';
		hatVal = atoi(&txt[3]);

		bmap->ButtType = BUTTC_JOYSTICK;
		bmap->DeviceNum = devIdx;
		bmap->ButtonNum = 0x2000 | ((hatIdx & 0x1F) << 8) | (hatVal & 0xFF);
	}
	else if ((txt[0] == 'a') || (txt[1] == 'a'))
	{
		int l = 0, axisIdx = 0, axisSign = 0;

		l = 0;
		if (txt[l] == '-')
		{
			axisSign = 1;
			l++;
		}
		else if (txt[l] == '+')
		{
			axisSign = 0;
			l++;
		}

		if (txt[l] == 'a')
		{
			l++;
		}
		if (isdigit(txt[l]))
		{
			axisIdx = atoi(&txt[l]);

			while (isdigit(txt[l]))
				l++;
		}
		if (txt[l] == '-')
		{
			axisSign = 1;
			l++;
		}
		else if (txt[l] == '+')
		{
			axisSign = 0;
			l++;
		}
		bmap->ButtType = BUTTC_JOYSTICK;
		bmap->DeviceNum = devIdx;
		bmap->ButtonNum = 0x8000 | (axisSign ? 0x4000 : 0) | (axisIdx & 0xFF);
	}

	return 0;
}
//----------------------------------------------------------------------------
void FKBConfigDialog::mappingLoad(void)
{
	int ret, useNativeFileDialogVal;
	QString filename;
	std::string last;
	char dir[512];
	QFileDialog  dialog(this, tr("Load Family Keyboard Mapping File") );
	QList<QUrl> urls;
	QDir d;

	const QStringList filters(
	{ "FKB Config Files (*.txt *.TXT)",
           "Any files (*)"
         });

	urls << QUrl::fromLocalFile( QDir::rootPath() );
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first());
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first());
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first());
	urls << QUrl::fromLocalFile( QDir( FCEUI_GetBaseDirectory() ).absolutePath() );

	dialog.setFileMode(QFileDialog::ExistingFile);

	dialog.setNameFilters( filters );

	dialog.setViewMode(QFileDialog::List);
	dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden );
	dialog.setLabelText( QFileDialog::Accept, tr("Load") );

	strcpy( dir, FCEUI_GetBaseDirectory() );
	strcat( dir, "/input/FamilyKeyboard");

	dialog.setDirectory( tr(dir) );

	// Check config option to use native file dialog or not
	g_config->getOption ("SDL.UseNativeFileDialog", &useNativeFileDialogVal);

	dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal);
	dialog.setSidebarUrls(urls);

	ret = dialog.exec();

	if ( ret )
	{
		QStringList fileList;
		fileList = dialog.selectedFiles();

		if ( fileList.size() > 0 )
		{
			filename = fileList[0];
		}
	}

	if ( filename.isNull() )
	{
	   return;
	}
	//qDebug() << "selected file path : " << filename.toLocal8Bit();

	mappingLoad( filename.toLocal8Bit().constData() );

	return;
}
//----------------------------------------------------------------------------
void FKBConfigDialog::mappingLoad(const char *filepath)
{
	int i=0,j=0, idx;
	FILE *fp;
	char line[256];
	char id[128];
	char val[128];

	fp = fopen( filepath, "r" );

	if ( fp == NULL )
	{
		printf("Error: Failed to open file '%s' for reading\n", filepath );
		return;
	}

	while (fgets(line, sizeof(line), fp) != 0)
	{
		i = 0;
		while (line[i] != 0)
		{
			if (line[i] == '#')
			{
				line[i] = 0;
				break;
			}
			i++;
		}

		i = 0;
		while (isspace(line[i])) i++;

		if ( line[i] == 0 )
		{
			continue;
		}

		j=0;
		while ((line[i] != 0) && (line[i] != '\n') && (line[i] != '=') && !isspace(line[i]) )
		{
			id[j] = line[i];
			i++;
			j++;
		}
		id[j] = 0;

		while (isspace(line[i])) i++;

		if ( line[i] != '=' )
		{
			printf("Error: Invalid Line\n");
			continue;
		}
		i++;

		while (isspace(line[i])) i++;

		j=0;
		while ((line[i] != 0) && (line[i] != '\n') && !isspace(line[i]) )
		{
			if ( line[i] == '\\' )
			{  // next character should be interpretted literally
				i++;
			}
			val[j] = line[i];
			i++;
			j++;
		}
		val[j] = 0;

		idx = getButtonIndexFromName( id );

		if ( idx < 0 )
		{
			continue;
		}

		convText2ButtConfig( val, &fkbmap[idx] );
	}

	fclose(fp);
}
//----------------------------------------------------------------------------
void FKBConfigDialog::mappingSave(void)
{
	const char *guid = "keyboard";
	const char *baseDir = FCEUI_GetBaseDirectory();
	std::string path;
	FILE *fp;
	char stmp[64];
	QDir dir;

	path = std::string(baseDir) + std::string("/input/FamilyKeyboard/");

	dir.mkpath(QString::fromStdString(path));

	if ( saveFileName.size() == 0 )
	{
		mappingSaveAs();
		return;
	}
	path = saveFileName;

	fp = fopen( path.c_str(), "w" );

	if ( fp == NULL )
	{
		printf("Error: Failed to open file '%s' for writing\n", path.c_str() );
		return;
	}

	fprintf( fp, "# Family Keyboard Button Mapping File for Fceux (Qt version)\n");
	fprintf( fp, "GUID=%s\n", guid );

	for (unsigned int i=0; i<FamilyKeyboardWidget::NUM_KEYS; i++)
	{
		if ( fkbmap[i].ButtType == BUTTC_KEYBOARD)
		{
			int j=0,k=0; const char *keyName;

			keyName = SDL_GetKeyName(fkbmap[i].ButtonNum);

			stmp[k] = 'k'; k++;

			// Write keyname in with necessary escape characters.
			while ( keyName[j] != 0 )
			{
				if ( (keyName[j] == '\\') || (keyName[j] == ',') )
				{
					stmp[k] = '\\'; k++;
				}
				stmp[k] = keyName[j]; k++; j++;
			}
			stmp[k] = 0;

			//snprintf(stmp, sizeof(stmp), "k%s", SDL_GetKeyName(bmap[c][i].ButtonNum));
		}
		else
		{
			if (fkbmap[i].ButtonNum & 0x2000)
			{
				/* Hat "button" */
				snprintf(stmp, sizeof(stmp), "h%i.%i",
						(fkbmap[i].ButtonNum >> 8) & 0x1F, fkbmap[i].ButtonNum & 0xFF);
			}
			else if (fkbmap[i].ButtonNum & 0x8000)
			{
				/* Axis "button" */
				snprintf(stmp, sizeof(stmp), "%ca%i",
						(fkbmap[i].ButtonNum & 0x4000) ? '-' : '+', fkbmap[i].ButtonNum & 0x3FFF);
			}
			else
			{
				/* Button */
				snprintf(stmp, sizeof(stmp), "b%i", fkbmap[i].ButtonNum);
			}
		}
		fprintf( fp, "%s=%s\n", FamilyKeyBoardNames[i], stmp );

	}

	fclose(fp);
}
//----------------------------------------------------------------------------
void FKBConfigDialog::mappingSaveAs(void)
{
	int ret, useNativeFileDialogVal;
	QString filename;
	std::string last;
	const char *base;
	QFileDialog  dialog(this, tr("Save Mapping To File") );
	QList<QUrl> urls;
	QDir d;

	base = FCEUI_GetBaseDirectory();

	urls << QUrl::fromLocalFile( QDir::rootPath() );
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first());
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first());
	urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first());

	if ( base )
	{
		urls << QUrl::fromLocalFile( QDir( base ).absolutePath() );

		d.setPath( QString(base) + "/input/FamilyKeyboard");
		d.mkpath( QString(base) + "/input/FamilyKeyboard");

		if ( d.exists() )
		{
			urls << QUrl::fromLocalFile( d.absolutePath() );
		}
	}

	dialog.setFileMode(QFileDialog::AnyFile);

	dialog.setNameFilter(tr("FKB Config Files (*.txt *.TXT) ;; All files (*)"));

	dialog.setViewMode(QFileDialog::List);
	dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden );
	dialog.setLabelText( QFileDialog::Accept, tr("Save") );
	dialog.setDefaultSuffix( tr(".txt") );

	if ( last.size() == 0 )
	{
		if ( base )
		{
			last = std::string(base) + "/input/FamilyKeyboard";
			dialog.setDirectory( last.c_str() );
		}
	}

	// Check config option to use native file dialog or not
	g_config->getOption ("SDL.UseNativeFileDialog", &useNativeFileDialogVal);

	dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal);
	dialog.setSidebarUrls(urls);

	ret = dialog.exec();

	if ( ret )
	{
		QStringList fileList;
		fileList = dialog.selectedFiles();

		if ( fileList.size() > 0 )
		{
			filename = fileList[0];
		}
	}

	if ( filename.isNull() )
	{
	   return;
	}

	saveFileName = filename.toLocal8Bit().constData();

	mappingSave();
}
//----------------------------------------------------------------------------
//*********************************************************************************
//******  Key Mapping Window
//*********************************************************************************
FKBKeyMapDialog::FKBKeyMapDialog( int idx, QWidget *parent )
	: QDialog( parent )
{
	QVBoxLayout *mainLayout;
	QHBoxLayout *hbox;
	QPushButton *cancelButton;
	QLabel      *lbl;
	char keyMapName[64];
	char stmp[128];

	setWindowTitle( tr("Family Keyboard Key Mapping") );
	setModal(true);

	keyIdx = idx;
	buttonConfigStatus = 0;
	waitingForButton = false;

	mainLayout = new QVBoxLayout();
	hbox       = new QHBoxLayout();

	setLayout( mainLayout );

	snprintf( stmp, sizeof(stmp), "Press a key to set new physical mapping for the '%s' Key", keyNames[idx*2] );

	msgLbl = new QLabel( tr(stmp) );

	if (fkbmap[ keyIdx ].ButtType == BUTTC_KEYBOARD)
	{
		snprintf(keyMapName, sizeof(keyMapName), "%s",
				 SDL_GetKeyName(fkbmap[ keyIdx ].ButtonNum));
	}
	else
	{
		strcpy(keyMapName, ButtonName(&fkbmap[ keyIdx ]));
	}

	lbl       = new QLabel( tr("Current Mapping is:") );
	curMapLbl = new QLabel( tr(keyMapName) );

	hbox->addWidget( lbl );
	hbox->addWidget( curMapLbl );

	cancelButton = new QPushButton( tr("Cancel") );
	cancelButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
	cancelButton->setAutoDefault(false);

	mainLayout->addWidget( msgLbl );
	mainLayout->addLayout( hbox );

	hbox       = new QHBoxLayout();
	mainLayout->addLayout( hbox );
	hbox->addWidget( cancelButton, 1 );
	hbox->addStretch( 4 );

	connect( cancelButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)) );
}
//*********************************************************************************
FKBKeyMapDialog::~FKBKeyMapDialog(void)
{
	buttonConfigStatus = 0;
}
//*********************************************************************************
void FKBKeyMapDialog::enterButtonLoop(void)
{
	buttonConfigStatus = 2;

	waitingForButton = true;

	ButtonConfigBegin();

	DWaitButton(NULL, &fkbmap[ keyIdx ], &buttonConfigStatus);

	ButtonConfigEnd();

	buttonConfigStatus = 1;

	waitingForButton = false;

	done(0);
	deleteLater();
}
//*********************************************************************************
void FKBKeyMapDialog::closeEvent(QCloseEvent *event)
{
	//printf("FKB Key Map Close Window Event\n");
	buttonConfigStatus = 0;

	if ( !waitingForButton )
	{
		done(0);
		deleteLater();
	}
	event->accept();
}
//*********************************************************************************
void FKBKeyMapDialog::closeWindow(void)
{
	//printf("Close Window\n");
	buttonConfigStatus = 0;

	if ( !waitingForButton )
	{
		done(0);
		deleteLater();
	}
}
//*********************************************************************************
void FKBKeyMapDialog::keyPressEvent(QKeyEvent *event)
{
	//printf("Key Press: 0x%x \n", event->key() );
	pushKeyEvent( event, 1 );

	event->accept();
}
//*********************************************************************************
void FKBKeyMapDialog::keyReleaseEvent(QKeyEvent *event)
{
	//printf("Key Release: 0x%x \n", event->key() );
	pushKeyEvent( event, 0 );

	event->accept();
}
//*********************************************************************************
