/*-*-c++-*-
 * $Id: chotkey.cpp,v 1.1 2002/01/28 15:38:32 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (let@users.sourceforge.net)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#include "chotkey.h"
#include "caction.h"
#include "qlcrash.h"
#include "cconfigmanager.h"
#include "crashtypes.h"

#include <qaccel.h>
#include <qlistview.h>
#include <qpushbutton.h>
#include <qbuttongroup.h>
#include <qradiobutton.h>
#include <qlabel.h>
#include <qdict.h>
#include <qintdict.h>

#include <assert.h>

#include "resourceadapter.h"

// identifiers of radio buttons
#define KEY_NO		0
#define KEY_DEFAULT	1
#define KEY_CUSTOM	2

#define CFG_GRP_HOTKEYS "Hotkeys"

class HKListViewItem : public QListViewItem {
public:
	HKListViewItem(QListViewItem* parent) : QListViewItem(parent) { }
	
	inline int id() const {
		return oId;
	}
	inline void setId(int i) {
		oId = i;
	}
	
private:
	int oId;
};

// buildin actions
static const struct HKActions {
	CHotKeyType id;
	const char* group;
	const char* text;
	const char* menuText;
	int defCode;
} sActions[] = {
	{
		HK_newConsole,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Display a new console"),
		QT_TRANSLATE_NOOP("CHotKey", "New &console"),
		Qt::Key_F2
	},
	{
		HK_taskList,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Display or update the task list"),
		QT_TRANSLATE_NOOP("CHotKey", "&Task list"),
		Qt::Key_F4
	},
	{
		HK_graphPrint,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Show `graph print' dialog"),
		QT_TRANSLATE_NOOP("CHotKey", "Graph &print"),
		Qt::Key_F3
	},
	{
		HK_graphWalk,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Show `graph walk' dialog"),
		QT_TRANSLATE_NOOP("CHotKey", "Graph &walk"),
		0
	},
	{
		HK_graphTrace,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "New trace view"),
		QT_TRANSLATE_NOOP("CHotKey", "New &trace view"),
		0
	},
	{
		HK_newDumpView,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "New dump view"),
		QT_TRANSLATE_NOOP("CHotKey", "New &dump view"),
		0
	},
	{
		HK_symbolList,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Display a list with global symbols"),
		QT_TRANSLATE_NOOP("CHotKey", "S&ymbol list"),
		0
	},
	{
		HK_symbolListUpdate,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Update global symbols"),
		QT_TRANSLATE_NOOP("CHotKey", "&Update symbol list"),
		0
	},
	{
		HK_restart,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Restart lcrash"),
		QT_TRANSLATE_NOOP("CHotKey", "&Restart lcrash"),
		0
	},
	{
		HK_exit,
		QT_TRANSLATE_NOOP("CHotKey", "File"),
		QT_TRANSLATE_NOOP("CHotKey", "Exit qlcrash"),
		QT_TRANSLATE_NOOP("CHotKey", "E&xit"),
		Qt::CTRL + Qt::Key_Q
	},
	{
		HK_layout,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Layout objects"),
		QT_TRANSLATE_NOOP("CHotKey", "&Layout objects"),
		Qt::Key_F5
	},
	{
		HK_switchMDIrev,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Switch to previous window"),
		QT_TRANSLATE_NOOP("CHotKey", "&Previous window"),
		Qt::ALT + Qt::Key_Left
	},
	{
		HK_switchMDI,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Switch to next window"),
		QT_TRANSLATE_NOOP("CHotKey", "&Next window"),
		Qt::ALT + Qt::Key_Right
	},
	{
		HK_toggleToolbar,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Enable/Disable toolbar"),
		QT_TRANSLATE_NOOP("CHotKey", "Toggle &toolbar"),
		0
	},
	{
		HK_toggleStatusbar,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Enable/Disable statusbar"),
		QT_TRANSLATE_NOOP("CHotKey", "Toggle &statusbar"),
		0
	},
	{
		HK_toggleTaskbar,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Enable/Disable childbar"),
		QT_TRANSLATE_NOOP("CHotKey", "Toggle &childbar"),
		0
	},
	{
		HK_findItem,
		QT_TRANSLATE_NOOP("CHotKey", "View"),
		QT_TRANSLATE_NOOP("CHotKey", "Find item"),
		QT_TRANSLATE_NOOP("CHotKey", "&Find item"),
		Qt::CTRL + Qt::Key_S
	},
	{
		HK_options,
		QT_TRANSLATE_NOOP("CHotKey", "Option"),
		QT_TRANSLATE_NOOP("CHotKey", "Display configuration menu"),
		QT_TRANSLATE_NOOP("CHotKey", "&Options"),
		0
	},
	{
		HK_keyBindings,
		QT_TRANSLATE_NOOP("CHotKey", "Option"),
		QT_TRANSLATE_NOOP("CHotKey", "Configure key bindings"),
		QT_TRANSLATE_NOOP("CHotKey", "&Key bindings"),
		0
	},
	{
		HK_windowTile,
		QT_TRANSLATE_NOOP("CHotKey", "Window"),
		QT_TRANSLATE_NOOP("CHotKey", "Tile windows"),
		QT_TRANSLATE_NOOP("CHotKey", "&Tile windows"),
		0
	},
	{
		HK_windowCascade,
		QT_TRANSLATE_NOOP("CHotKey", "Window"),
		QT_TRANSLATE_NOOP("CHotKey", "Cascade windows"),
		QT_TRANSLATE_NOOP("CHotKey", "&Cascade windows"),
		0
	},
	{
		HK_windowLower,
		QT_TRANSLATE_NOOP("CHotKey", "Window"),
		QT_TRANSLATE_NOOP("CHotKey", "Lower current window"),
		QT_TRANSLATE_NOOP("CHotKey", "&Lower window"),
		Qt::ALT + Qt::Key_F2
	},
	{
		HK_helpAbout,
		QT_TRANSLATE_NOOP("CHotKey", "Help"),
		QT_TRANSLATE_NOOP("CHotKey", "About"),
		QT_TRANSLATE_NOOP("CHotKey", "&About"),
		0
	},
	{
		HK_helpManual,
		QT_TRANSLATE_NOOP("CHotKey", "Help"),
		QT_TRANSLATE_NOOP("CHotKey", "Manual"),
		QT_TRANSLATE_NOOP("CHotKey", "&Manual"),
		Qt::Key_F1
	}
};

CHotKey::CHotKey(QLcrashApp* app, const char* name)
	: QDialog(app, name, true)
	, oRecording(false)
{
	setCaption(stdCaption(tr("Key bindings")));
	setIcon(Resource::getBitmap(IDR_PNG7));
	oApp = app;
	oActionMap = new QIntDict<CAction>;
	oActionMap->setAutoDelete(true);
	
	// create widgets
	
	oActionList = new QListView(this);
	oActionList->addColumn(tr("Action"));
	oActionList->addColumn(tr("Hotkey"));
	oActionList->setSorting(-1);
	oActionList->setRootIsDecorated(true);
	
	oBGroup = new QButtonGroup(tr("Hotkey for selected Action"), this);
	oBGroup->setRadioButtonExclusive(true);
	
	oKeyNo = new QRadioButton(tr("No"), oBGroup);
	oKeyNo->adjustSize();
	oKeyDefault = new QRadioButton(tr("Default"), oBGroup);
	oKeyDefault->adjustSize();
	oKeyCustom = new QRadioButton(tr("Custom"), oBGroup);
	oKeyCustom->adjustSize();
	
	oHotKey = new QLabel(oBGroup);
	oHotKey->setFocusPolicy(StrongFocus);
	oHotKey->setFixedHeight(25);
	oHotKey->setFrameStyle(QFrame::Box | QFrame::Plain);
		
	oOk = new QPushButton(tr("Ok"), this);
	oOk->setDefault(true);
	oCancel = new QPushButton(tr("Cancel"), this);
	
	// handle signals
	
	connect(oActionList, SIGNAL(currentChanged(QListViewItem*)), SLOT(slotActionChanged(QListViewItem*)));
	connect(oBGroup, SIGNAL(clicked(int)), SLOT(slotKeyClicked(int)));
	connect(oOk, SIGNAL(clicked()), SLOT(slotOk()));
	connect(oCancel, SIGNAL(clicked()), SLOT(slotCancel()));
	
	// we need key events from QLabel, so let's install an event filter
	oHotKey->installEventFilter(this);
	
	// at the beginning there is now selected item
	oBGroup->setEnabled(false);
	
	setMinimumSize(300, 300);
	
	initListView();
}

CHotKey::~CHotKey()
{
}

void
CHotKey::resizeEvent(QResizeEvent*)
{
	oBGroup->resize(width() - 5 - 5, oKeyNo->height() + oHotKey->height() + 5 + 25);
	
	oActionList->setGeometry(
		5,
		5,
		width() - 5 - 5,
		height() - 5 - 5 - oBGroup->height() - 5 - oOk->height() - 5
	);
	oBGroup->move(5, oActionList->x() + oActionList->height());
	oKeyNo->move(5, 20);
	oKeyDefault->move(oKeyNo->x() + oKeyNo->width() + 5, 20);
	oKeyCustom->move(oKeyDefault->x() + oKeyDefault->width() + 5, 20);
	oHotKey->setGeometry(
		5,
		oKeyNo->y() + oKeyNo->height() + 5,
		oBGroup->width() - 5 - 5,
		1
	);
		
	oCancel->move(width() - 5 - oCancel->width(), height() - 5 - oCancel->height());
	oOk->move(oCancel->x() - 5 - oOk->width(), oCancel->y());
}

bool
CHotKey::eventFilter(QObject*, QEvent* e)
{
	if (oRecording && e->type() == QEvent::KeyPress) {
#ifdef WIN32
		QKeyEvent* ke = (QKeyEvent*)e;
#else
		QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
#endif
		if (ke->ascii() == '\r') {
			recordingEnd();
			return true;
		}
		else {
			int s = ke->state();
			int mod = 0;
			
			if (s > 0) {
				if (s & ShiftButton) {
					mod += SHIFT;
				}
				if (s & ControlButton) {
					mod += CTRL;
				}
				if (s & AltButton) {
					mod += ALT;
				}
			}
			
			if (ke->key() != Key_Backspace) {
				QString str = QAccel::keyToString(ke->key() + mod);
				if (str.at(0) == '<' && str.right(1) == ">") {
					oHotKey->setText("");
				}
				else {
					oHotKey->setText(str);
				}
			}
			else {
				oHotKey->setText("");
			}
		}
	}
	// start recording on left clicking the label
	else if (!oRecording && e->type() == QEvent::MouseButtonPress) {
#ifdef WIN32
		QMouseEvent* me = (QMouseEvent*)e;
#else
		QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
#endif
		if (me->button() == LeftButton) {
			oKeyCustom->setChecked(true);
			slotKeyClicked(KEY_CUSTOM);
		}
	}
	else if (oRecording && e->type() == QEvent::FocusOut) {
		oHotKey->setFocus();
	}
	
	return false;
}

void
CHotKey::initListView()
{
	const int size = sizeof(sActions) / sizeof(HKActions);
	QDict<QListViewItem> dict;
	
	for (int i = size-1; i >= 0; i--) {
		// find listview item for group
		QListViewItem* parent = dict.find(sActions[i].group);
		if (parent == 0) { // no such group -> create one
			parent = new QListViewItem(oActionList);
			parent->setText(0, tr(sActions[i].group));
			dict.insert(sActions[i].group, parent);
		}
		
		HKListViewItem* item = new HKListViewItem(parent);
		int key = sActions[i].defCode;
		item->setText(0, tr(sActions[i].text));
		if (key > ' ') {
			item->setText(1, QAccel::keyToString(key));
		}
		item->setId(sActions[i].id);
		
		CAction* a = new CAction(oApp);
		a->setText(tr(item->text(0)));
		a->setMenuText(tr(sActions[i].menuText));
		a->setAccel(key);
		a->setDefCode(key);
		a->setId(sActions[i].id);
		oActionMap->insert(sActions[i].id, a);
		connect(a, SIGNAL(sigAction(CAction*)), SLOT(slotAction(CAction*)));
	}
}

void
CHotKey::slotActionChanged(QListViewItem* item)
{
	recordingBreak();
	
	if (item != 0 && item->parent() != 0) {
		HKListViewItem* hk = dynamic_cast<HKListViewItem*>(item);
		CAction* a = oActionMap->find(hk->id());
		assert(a != 0);
		
		setCustomField(a);
		oBGroup->setEnabled(true);
	}
	else {
		oBGroup->setEnabled(false);
	}
}

void
CHotKey::slotOk()
{
	recordingEnd();
	accept();
}

void
CHotKey::slotCancel()
{
	recordingBreak();
	reject();
}

void
CHotKey::slotKeyClicked(int id)
{
	CAction* a;
	
	if (oActionList->selectedItem()) {
		HKListViewItem* hk;
		
		switch (id) {
			case KEY_NO:
				oHotKey->setText("");
				oRecording = true;
				recordingEnd();
				break;
			case KEY_DEFAULT:
				hk = dynamic_cast<HKListViewItem*>(oActionList->currentItem());
				a = oActionMap->find(hk->id());
				assert(a != 0);
				if (a->defCode() > 0) {
					oHotKey->setText(QAccel::keyToString(a->defCode()));
				}
				else {
					oHotKey->setText("");
					oKeyNo->setChecked(true);
				}
				oRecording = true;
				recordingEnd();
				break;
			case KEY_CUSTOM:
				recordingStart();
				break;
		}
	}
}

void
CHotKey::slotAction(CAction* a)
{
	assert(a != 0);
	
	emit sigAction(a->id());
}

void
CHotKey::recordingBreak()
{
	if (oRecording) {
		oHotKey->setBackgroundColor(backgroundColor());
		oRecording = false;
	}
}

void
CHotKey::recordingStart()
{
	if (!oRecording) {
		oHotKey->setBackgroundColor(white);
		oHotKey->setFocus();
		oRecording = true;
	}
}

void
CHotKey::recordingEnd()
{
	if (oRecording) {
		oHotKey->setBackgroundColor(backgroundColor());
		
		QListViewItem* item = oActionList->selectedItem();
		if (item != 0) {
			HKListViewItem* hk = dynamic_cast<HKListViewItem*>(item);
			CAction* a = oActionMap->find(hk->id());
			assert(a != 0);
			
			int code = QAccel::stringToKey(oHotKey->text());
			
			// check if the accelerator contains a modifier
			int cmp = ALT|SHIFT|CTRL;
			if (!(cmp & code) && code < Key_F1 && code > Key_F35) {
				QMessageBox::warning(
					this,
					stdCaption(tr("Error")),
					tr("Accelerators must contain a modifier key")
				);
				oRecording = false;
				setCustomField(a);
				return;
			}
			
			// check if the hotkey is already assigned
			HKListViewItem* exist = findHotKey(code);
			if (exist != 0 && exist != hk) {
				int ret = QMessageBox::warning(
					this,
					stdCaption(tr("Warning")),
					tr("The key `%1' is already assigned to action\n\"%2\"")
						.arg(oHotKey->text()).arg(exist->text(0)),
					tr("Overwrite"),
					tr("Cancel")
				);
				
				if (ret == 0) { // overwrite
					CAction* ea = oActionMap->find(exist->id());
					assert(ea != 0);
					exist->setText(1, "");
					ea->setAccel(0);
				}
				else {
					oRecording = false;
					setCustomField(a);
					return;
				}
			}
			
			a->setAccel(code);
			setCustomField(a);
			hk->setText(1, oHotKey->text());
		}
		
		oRecording = false;
	}
}

HKListViewItem*
CHotKey::findHotKey(int code)
{
	if (code > 0) {
		QListViewItem* walker = oActionList->firstChild();
		while (walker != 0) {
			QListViewItem* it = walker->firstChild();
			
			while (it != 0) {
				HKListViewItem* hk = dynamic_cast<HKListViewItem*>(it);
				CAction* a = oActionMap->find(hk->id());
				assert(a != 0);
				if (int(a->accel()) == code) {
					return hk;
				}
				
				it = it->nextSibling();
			}
			
			walker = walker->nextSibling();
		}
	}
	
	return 0;
}

void
CHotKey::setCustomField(CAction* a)
{
	if (int(a->accel()) == 0) {
		oHotKey->setText("");
		oKeyNo->setChecked(true);
	}
	else {
		oHotKey->setText(QAccel::keyToString(a->accel()));
		if (int(a->accel()) == a->defCode()) {
			oKeyDefault->setChecked(true);
		}
		else {
			oKeyCustom->setChecked(true);
		}
	}
}

QAction*
CHotKey::action(int id)
{
	QAction* a = oActionMap->find(id);
	assert(a != 0);
	return a;
}

void
CHotKey::storeConfig(CConfigManager* mgr)
{
	QIntDictIterator<CAction> it(*oActionMap);
	QString code;
	CAction* cur;
	while ((cur = it.current()) != 0) {
		if (int(cur->accel()) != cur->defCode()) {
			code.setNum(cur->id());
			mgr->setItem(CFG_GRP_HOTKEYS, code, int(cur->accel()));
		}
		
		++it;
	}
}

void
CHotKey::loadConfig(CConfigManager* mgr)
{
	QListViewItem* walker = oActionList->firstChild();
	QString code;
	
	// traverse over all entries and check if there is another key for the action
	while (walker != 0) {
		QListViewItem* it = walker->firstChild();
		
		while (it != 0) {
			HKListViewItem* hk = dynamic_cast<HKListViewItem*>(it);
			code.setNum(hk->id());
			CAction* a = oActionMap->find(hk->id());
			assert(a != 0);
			int nc = mgr->item(CFG_GRP_HOTKEYS, code, a->defCode());
			if (nc != a->defCode()) {
				a->setAccel(nc);
				hk->setText(1, QAccel::keyToString(nc));
			}
			
			it = it->nextSibling();
		}
		
		walker = walker->nextSibling();
	}
}
