diff options
44 files changed, 5342 insertions, 50 deletions
diff --git a/Makefile.common b/Makefile.common index c2704f2219..e365ce79c4 100644 --- a/Makefile.common +++ b/Makefile.common @@ -27,7 +27,12 @@ MODULES += \  	sound \  	common \  	engines \ -	backends +	backends \ +	graphics \ +	common      # HACK/FIXME: the extra 'common' and 'graphics' were added because of circular  +			# dependencies (the newly added Virtual Keyboard stuff depends on things from  +			# common and graphics). This should be resolved in one way or another, perhaps  +			# by moving the VK code out of backends?  ifdef USE_MT32EMU  MODULES += sound/softsynth/mt32 diff --git a/backends/events/default/default-events.cpp b/backends/events/default/default-events.cpp index c503e6a536..a2616bc58a 100644 --- a/backends/events/default/default-events.cpp +++ b/backends/events/default/default-events.cpp @@ -28,6 +28,9 @@  #include "common/system.h"  #include "common/config-manager.h"  #include "backends/events/default/default-events.h" +#include "backends/keymapper/keymapper.h" +#include "backends/keymapper/remap-dialog.h" +#include "backends/vkeybd/virtual-keyboard.h"  #include "engines/engine.h"  #include "gui/message.h" @@ -192,9 +195,17 @@ DefaultEventManager::DefaultEventManager(OSystem *boss) :  		_hasPlaybackEvent = false;  	} + +	_vk = new Common::VirtualKeyboard(); +	_keymapper = new Common::Keymapper(this); +	_remap = false; + +	//init();  }  DefaultEventManager::~DefaultEventManager() { +	delete _keymapper; +	delete _vk;  	_boss->lockMutex(_timeMutex);  	_boss->lockMutex(_recorderMutex);  	_recordMode = kPassthrough; @@ -253,6 +264,14 @@ DefaultEventManager::~DefaultEventManager() {  	_boss->deleteMutex(_recorderMutex);  } +void DefaultEventManager::init() { +	if (ConfMan.hasKey("vkeybd_pack_name")) { +		_vk->loadKeyboardPack(ConfMan.get("vkeybd_pack_name")); +	} else { +		_vk->loadKeyboardPack("vkeybd"); +	} +} +  bool DefaultEventManager::playback(Common::Event &event) {  	if (!_hasPlaybackEvent) { @@ -351,13 +370,28 @@ void DefaultEventManager::processMillis(uint32 &millis) {  bool DefaultEventManager::pollEvent(Common::Event &event) {  	uint32 time = _boss->getMillis(); -	bool result; - -	if (!artificialEventQueue.empty()) { -		event = artificialEventQueue.pop(); +	bool result = false; +	 +	// poll for pushed events +	if (!_artificialEventQueue.empty()) { +		event = _artificialEventQueue.pop();  		result = true; -	} else 	 +	} else { +		// poll for event from backend  		result = _boss->pollEvent(event); +		if (result) { +			// send key press events to keymapper +			if (event.type == Common::EVENT_KEYDOWN) { +				if (_keymapper->mapKeyDown(event.kbd)) { +					result = false; +				} +			} else if (event.type == Common::EVENT_KEYUP) { +				if (_keymapper->mapKeyUp(event.kbd)) { +					result = false; +				} +			} +		} +	}  	if (_recordMode != kPassthrough)  { @@ -423,6 +457,26 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {  					else if (_shouldRTL)  						event.type = Common::EVENT_RTL;  				} +			} else if (event.kbd.keycode == Common::KEYCODE_F7 && event.kbd.flags == 0) { +				if (_vk->isDisplaying()) { +					_vk->close(true); +				} else { +					bool isPaused = (g_engine) ? g_engine->isPaused() : true; +					if (!isPaused) g_engine->pauseEngine(true); +					_vk->show(); +					if (!isPaused) g_engine->pauseEngine(false); +					result = false; +				} +			} else if (event.kbd.keycode == Common::KEYCODE_F8 && event.kbd.flags == 0) { +				if (!_remap) { +					_remap = true; +					Common::RemapDialog _remapDialog; +					bool isPaused = (g_engine) ? g_engine->isPaused() : true; +					if (!isPaused) g_engine->pauseEngine(true); +					_remapDialog.runModal(); +					if (!isPaused) g_engine->pauseEngine(false); +					_remap = false; +				}  			}  			break; @@ -505,7 +559,7 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {  	return result;  } -void DefaultEventManager::pushEvent(Common::Event event) { +void DefaultEventManager::pushEvent(const Common::Event &event) {  	// If already received an EVENT_QUIT, don't add another one  	if (event.type == Common::EVENT_QUIT) { diff --git a/backends/events/default/default-events.h b/backends/events/default/default-events.h index b2cd1354cc..7bd316475a 100644 --- a/backends/events/default/default-events.h +++ b/backends/events/default/default-events.h @@ -27,8 +27,14 @@  #define BACKEND_EVENTS_DEFAULT_H  #include "common/events.h" +#include "common/queue.h"  #include "common/savefile.h" +namespace Common { +	class Keymapper; +	class VirtualKeyboard; +} +  /*  At some point we will remove pollEvent from OSystem and change  DefaultEventManager to use a "boss" derived from this class: @@ -44,6 +50,12 @@ use a subclass of EventProvider.  class DefaultEventManager : public Common::EventManager {  	OSystem *_boss; +	Common::VirtualKeyboard *_vk; +	Common::Keymapper *_keymapper; +	bool _remap; + +	Common::Queue<Common::Event> _artificialEventQueue; +  	Common::Point _mousePos;  	int _buttonState;  	int _modifierState; @@ -107,8 +119,9 @@ public:  	DefaultEventManager(OSystem *boss);  	~DefaultEventManager(); +	virtual void init();  	virtual bool pollEvent(Common::Event &event); -	virtual void pushEvent(Common::Event event); +	virtual void pushEvent(const Common::Event &event);  	virtual void registerRandomSource(Common::RandomSource &rnd, const char *name);  	virtual void processMillis(uint32 &millis); @@ -118,6 +131,8 @@ public:  	virtual int shouldQuit() const { return _shouldQuit; }  	virtual int shouldRTL() const { return _shouldRTL; }  	virtual void resetRTL() { _shouldRTL = false; } +	 +	virtual Common::Keymapper *getKeymapper() { return _keymapper; }  };  #endif diff --git a/backends/keymapper/action.cpp b/backends/keymapper/action.cpp new file mode 100644 index 0000000000..8b2490861e --- /dev/null +++ b/backends/keymapper/action.cpp @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#include "backends/keymapper/action.h" +#include "backends/keymapper/keymap.h" + +namespace Common { + +Action::Action(Keymap *boss, const char *i,	String des, ActionType typ,  +			   KeyType prefKey, int pri, int flg) +	: _boss(boss), description(des), type(typ), preferredKey(prefKey), +	priority(pri), flags(flg), _hwKey(0) { +	assert(i); +	assert(_boss); + +	strncpy(id, i, ACTION_ID_SIZE); + +	_boss->addAction(this); +} + +void Action::mapKey(const HardwareKey *key) { +	if (_hwKey) _boss->unregisterMapping(this); +	_hwKey = key; +	if (_hwKey) _boss->registerMapping(this, _hwKey); +} + +const HardwareKey *Action::getMappedKey() const { +	return _hwKey; +} + +} // end of namespace Common diff --git a/backends/keymapper/action.h b/backends/keymapper/action.h new file mode 100644 index 0000000000..ef47930a71 --- /dev/null +++ b/backends/keymapper/action.h @@ -0,0 +1,116 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_ACTION +#define COMMON_ACTION + +#include "backends/keymapper/types.h" +#include "common/events.h" +#include "common/func.h" +#include "common/list.h" +#include "common/str.h" + +namespace Common { + +struct HardwareKey; +class Keymap; + +#define ACTION_ID_SIZE (4) + +struct Action { +	/** unique id used for saving/loading to config */ +	char id[ACTION_ID_SIZE]; +	/** Human readable description */ +	String description; + +	/** Events to be sent when mapped key is pressed */ +	List<Event> events; +	ActionType type; +	KeyType preferredKey; +	int priority; +	int group; +	int flags; + +private: +	/** Hardware key that is mapped to this Action */ +	const HardwareKey *_hwKey; +	Keymap *_boss; + +public: +	Action(Keymap *boss, const char *id, String des = "",  +		   ActionType typ = kGenericActionType, +		   KeyType prefKey = kGenericKeyType, +		   int pri = 0, int flg = 0 ); + +	void addEvent(const Event &evt) {  +		events.push_back(evt); +	} + +	void addKeyEvent(const KeyState &ks) { +		Event evt; +		evt.type = EVENT_KEYDOWN; +		evt.kbd = ks; +		addEvent(evt); +	} + +	void addLeftClickEvent() { +		Event evt; +		evt.type = EVENT_LBUTTONDOWN; +		addEvent(evt); +	} + +	void addMiddleClickEvent() { +		Event evt; +		evt.type = EVENT_MBUTTONDOWN; +		addEvent(evt); +	} + +	void addRightClickEvent() { +		Event evt; +		evt.type = EVENT_RBUTTONDOWN; +		addEvent(evt); +	} + +	Keymap *getBoss() {  +		return _boss; +	} + +	void mapKey(const HardwareKey *key); +	const HardwareKey *getMappedKey() const; + +}; + +struct ActionPriorityComp : public BinaryFunction<Action, Action, bool> { +	bool operator()(const Action *x, const Action *y) const {  +		return x->priority > y->priority; +	} +	bool operator()(const Action &x, const Action &y) const {  +		return x.priority > y.priority; +	} +}; + +} // end of namespace Common + +#endif diff --git a/backends/keymapper/hardware-key.h b/backends/keymapper/hardware-key.h new file mode 100644 index 0000000000..082ecfc944 --- /dev/null +++ b/backends/keymapper/hardware-key.h @@ -0,0 +1,128 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_HARDWAREKEY +#define COMMON_HARDWAREKEY + +#include "backends/keymapper/types.h" + +namespace Common { + + +#define HWKEY_ID_SIZE (4) +/** +* Describes an available hardware key  +*/ +struct HardwareKey { +	/** unique id used for saving/loading to config */ +	char id[HWKEY_ID_SIZE]; +	/** Human readable description */ +	String description;  +	/**  +	* The KeyState that is generated by the back-end  +	* when this hardware key is pressed. +	*/ +	KeyState key; + +	KeyType type; +	ActionType preferredAction; + +	HardwareKey(const char *i, KeyState ky = KeyState(), String desc = "", +				KeyType typ = kGenericKeyType, ActionType prefAct = kGenericActionType) +		: key(ky), description(desc), type(typ), preferredAction(prefAct) { +		assert(i); +		strncpy(id, i, HWKEY_ID_SIZE); +	} +}; + + +/** + * Simple class to encapsulate a device's set of HardwareKeys. + * Each device should extend this and call addHardwareKey a number of times + * in its constructor to define the device's available keys. + */  +class HardwareKeySet { +public: + +	HardwareKeySet() : _count(0) {} +	virtual ~HardwareKeySet() { +		List<const HardwareKey*>::iterator it; +		for (it = _keys.begin(); it != _keys.end(); it++) +			delete *it; +	} + +	void addHardwareKey(HardwareKey *key) { +		checkForKey(key); +		_keys.push_back(key); +		++_count; +	} + +	const HardwareKey *findHardwareKey(const char *id) const { +		List<const HardwareKey*>::iterator it; +		for (it = _keys.begin(); it != _keys.end(); it++) { +			if (strncmp((*it)->id, id, HWKEY_ID_SIZE) == 0) +				return (*it); +		} +		return 0; +	} + +	const HardwareKey *findHardwareKey(const KeyState& keystate) const { +		List<const HardwareKey*>::iterator it; +		for (it = _keys.begin(); it != _keys.end(); it++) { +			if ((*it)->key == keystate) +				return (*it); +		} +		return 0; +	} + +	List<const HardwareKey*> getHardwareKeys() const { +		return _keys; +	} + +	uint count() const { +		return _count; +	} + + +private: + +	void checkForKey(HardwareKey *key) { +		List<const HardwareKey*>::iterator it; +		for (it = _keys.begin(); it != _keys.end(); it++) { +			if (strncmp((*it)->id, key->id, HWKEY_ID_SIZE) == 0) +				error("Error adding HardwareKey '%s' - id of %s already in use!", key->description.c_str(), key->id); +			else if ((*it)->key == key->key) +				error("Error adding HardwareKey '%s' - key already in use!", key->description.c_str()); +		} +	} + +	List<const HardwareKey*> _keys; +	uint _count; +}; + + +} // end of namespace Common + +#endif diff --git a/backends/keymapper/keymap.cpp b/backends/keymapper/keymap.cpp new file mode 100644 index 0000000000..ee07e36485 --- /dev/null +++ b/backends/keymapper/keymap.cpp @@ -0,0 +1,299 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#include "backends/keymapper/keymap.h" +#include "backends/keymapper/hardware-key.h" + +#define KEYMAP_KEY_PREFIX "keymap_" + +namespace Common { + +Keymap::Keymap(const Keymap& km) : _actions(km._actions), _keymap(), _configDomain(0) { +	List<Action*>::iterator it; +	for (it = _actions.begin(); it != _actions.end(); it++) { +		const HardwareKey *hwKey = (*it)->getMappedKey(); +		if (hwKey) { +			_keymap[hwKey->key] = *it; +		} +	} +} + +Keymap::~Keymap() { +	List<Action*>::iterator it; +	for (it = _actions.begin(); it != _actions.end(); it++) +		delete *it; +} + +void Keymap::addAction(Action *action) { +	if (findAction(action->id)) +		error("Action with id %s already in KeyMap!", action->id); +	_actions.push_back(action); +} + +void Keymap::registerMapping(Action *action, const HardwareKey *hwKey) { +	HashMap<KeyState, Action*>::iterator it; +	it = _keymap.find(hwKey->key); +	// if key is already mapped to a different action then un-map it +	if (it != _keymap.end() && action != it->_value) { +		it->_value->mapKey(0); +	} + +	_keymap[hwKey->key] = action; +} + +void Keymap::unregisterMapping(Action *action) { +	const HardwareKey *hwKey = action->getMappedKey(); +	if (hwKey) { +		_keymap.erase(hwKey->key); +	} +} + +Action *Keymap::getAction(const char *id) { +	return findAction(id); +} + +Action *Keymap::findAction(const char *id) { +	List<Action*>::iterator it; +	for (it = _actions.begin(); it != _actions.end(); it++) { +		if (strncmp((*it)->id, id, ACTION_ID_SIZE) == 0) +			return *it; +	} +	return 0; +} + +const Action *Keymap::findAction(const char *id) const { +	List<Action*>::const_iterator it; +	for (it = _actions.begin(); it != _actions.end(); it++) { +		if (strncmp((*it)->id, id, ACTION_ID_SIZE) == 0) +			return *it; +	} +	return 0; +} + +Action *Keymap::getMappedAction(const KeyState& ks) const { +	HashMap<KeyState, Action*>::iterator it; +	it = _keymap.find(ks); +	if (it == _keymap.end()) +		return 0; +	else +		return it->_value; +} + +void Keymap::setConfigDomain(ConfigManager::Domain *dom) { +	_configDomain = dom; +} + +void Keymap::loadMappings(const HardwareKeySet *hwKeys) { +	if (!_configDomain) return; +	ConfigManager::Domain::iterator it;	 +	String prefix = KEYMAP_KEY_PREFIX + _name + "_"; +	for (it = _configDomain->begin(); it != _configDomain->end(); it++) { +		const String& key = it->_key; +		if (!key.hasPrefix(prefix.c_str())) +			continue; + +		// parse Action ID +		const char *actionId = key.c_str() + prefix.size(); +		Action *ua = getAction(actionId); +		if (!ua) { +			warning("'%s' keymap does not contain Action with ID %s",  +				_name.c_str(), actionId); +			_configDomain->erase(key); +			continue; +		} + +		const HardwareKey *hwKey = hwKeys->findHardwareKey(it->_value.c_str()); +		if (!hwKey) { +			warning("HardwareKey with ID %s not known", it->_value.c_str()); +			_configDomain->erase(key); +			continue; +		} + +		ua->mapKey(hwKey); +	} +} + +void Keymap::saveMappings() { +	if (!_configDomain) return; +	List<Action*>::const_iterator it; +	String prefix = KEYMAP_KEY_PREFIX + _name + "_"; +	for (it = _actions.begin(); it != _actions.end(); it++) { +		uint actIdLen = strlen((*it)->id); +		actIdLen = (actIdLen > ACTION_ID_SIZE) ? ACTION_ID_SIZE : actIdLen; +		String actId((*it)->id, (*it)->id + actIdLen); +		if ((*it)->getMappedKey()) { +			uint hwIdLen = strlen((*it)->getMappedKey()->id); +			hwIdLen = (hwIdLen > HWKEY_ID_SIZE) ? HWKEY_ID_SIZE : hwIdLen; +			String hwId((*it)->getMappedKey()->id, (*it)->getMappedKey()->id + hwIdLen); +			_configDomain->setVal(prefix + actId, hwId); +		} else { +			_configDomain->setVal(prefix + actId, ""); +		} +	} +} + +bool Keymap::isComplete(const HardwareKeySet *hwKeys) { +	List<Action*>::iterator it; +	bool allMapped = true; +	uint numberMapped = 0; +	for (it = _actions.begin(); it != _actions.end(); it++) { +		if ((*it)->getMappedKey()) { +			numberMapped++; +		} else { +			allMapped = false; +		} +	} +	return allMapped || (numberMapped == hwKeys->count()); +} + +// TODO: +// - current weakness: +//     - if an action finds a key with required type but a parent action with  +//       higher priority is using it, that key is never used +void Keymap::automaticMapping(HardwareKeySet *hwKeys) { +	// Create copies of action and key lists. +	List<Action*> actions(_actions); +	List<const HardwareKey*> keys(hwKeys->getHardwareKeys()); + +	List<Action*>::iterator actIt; +	List<const HardwareKey*>::iterator keyIt, selectedKey; +	 +	// Remove actions and keys from local lists that have already been mapped. +	actIt = actions.begin(); +	while (actIt != actions.end()) { +		Action *act = *actIt; +		const HardwareKey *key = act->getMappedKey(); +		if (key) { +			keys.remove(key); +			actIt = actions.erase(actIt); +		} else { +			++actIt; +		} +	} + +	// Sort remaining actions by priority. +	ActionPriorityComp priorityComp; +	sort(actions.begin(), actions.end(), priorityComp); +	 +	// First mapping pass: +	// - Match if a key's preferred action type is the same as the action's  +	// type, or vice versa. +	// - Priority is given to: +	//     - keys that match action types over key types. +	//     - keys that have not been used by parent maps. +	// - If a key has been used by a parent map the new action must have a +	//   higher priority than the parent action. +	// - As soon as the number of skipped actions equals the number of keys  +	//   remaining we stop matching. This means that the second pass will assign keys +	//   to these higher priority skipped actions. +	uint skipped = 0; +	actIt = actions.begin(); +	while (actIt != actions.end() && skipped < keys.size()) { +		selectedKey = keys.end(); +		int matchRank = 0; +		Action *act = *actIt; +		for (keyIt = keys.begin(); keyIt != keys.end(); ++keyIt) { +			if ((*keyIt)->preferredAction == act->type && act->type != kGenericActionType) { +				Action *parentAct = getParentMappedAction((*keyIt)->key); +				if (!parentAct) { +					selectedKey = keyIt; +					break; +				} else if (parentAct->priority <= act->priority && matchRank < 3) { +					selectedKey = keyIt; +					matchRank = 3; +				} +			} else if ((*keyIt)->type == act->preferredKey && act->preferredKey != kGenericKeyType && matchRank < 2) { +				Action *parentAct = getParentMappedAction((*keyIt)->key); +				if (!parentAct) { +					selectedKey = keyIt; +					matchRank = 2; +				} else if (parentAct->priority <= act->priority && matchRank < 1) { +					selectedKey = keyIt; +					matchRank = 1; +				} +			} +		} +		if (selectedKey != keys.end()) { +			// Map action and delete action & key from local lists. +			act->mapKey(*selectedKey); +			keys.erase(selectedKey); +			actIt = actions.erase(actIt); +		} else { +			// Skip action (will be mapped in next pass). +			++actIt; +			++skipped; +		} +	} + +	// Second mapping pass: +	// - Maps any remaining actions to keys +	// - priority given to: +	//     - keys that have no parent action +	//     - keys whose parent action has lower priority than the new action +	//     - keys whose parent action has the lowest priority +	// - is guaranteed to match a key if they are not all used up +	for (actIt = actions.begin(); actIt != actions.end(); ++actIt) { +		selectedKey = keys.end(); +		int matchRank = 0; +		int lowestPriority = 0; +		Action *act = *actIt; +		for (keyIt = keys.begin(); keyIt != keys.end(); ++keyIt) { +			Action *parentAct = getParentMappedAction((*keyIt)->key); +			if (!parentAct) { +				selectedKey = keyIt; +				break; +			} else if (matchRank < 2) { +				if (parentAct->priority <= act->priority) { +					matchRank = 2; +					selectedKey = keyIt; +				} else if (parentAct->priority < lowestPriority || matchRank == 0) { +					matchRank = 1; +					lowestPriority = parentAct->priority; +					selectedKey = keyIt; +				} +			} +		} +		if (selectedKey != keys.end()) { +			act->mapKey(*selectedKey); +			keys.erase(selectedKey); +		} else {// no match = no keys left  +			break; +		} +	} +} + +Action *Keymap::getParentMappedAction(KeyState key) { +	if (_parent) { +		Action *act = _parent->getMappedAction(key); +		if (act) +			return act; +		else +			return _parent->getParentMappedAction(key); +	} else { +		return 0; +	} +} + +} // end of namespace Common diff --git a/backends/keymapper/keymap.h b/backends/keymapper/keymap.h new file mode 100644 index 0000000000..438660fd4b --- /dev/null +++ b/backends/keymapper/keymap.h @@ -0,0 +1,148 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_KEYMAP +#define COMMON_KEYMAP + +#include "common/config-manager.h" +#include "common/func.h" +#include "common/hashmap.h" +#include "common/keyboard.h" +#include "common/list.h" +#include "backends/keymapper/action.h" + +namespace Common { + +struct HardwareKey; +class HardwareKeySet; + +/** + * Hash function for KeyState + */ +template<> struct Hash<KeyState> +	: public UnaryFunction<KeyState, uint> { + +	uint operator()(const KeyState &val) const {  +		return (uint)(val.keycode * (val.flags << 1)); +	} +}; + +class Keymap { +public: +	Keymap(const String& name, Keymap *parent = 0) : _name(name), _parent(parent) {} +	Keymap(const Keymap& km); +	~Keymap(); + +public: +	/** +	 * Retrieves the Action with the given id +	 * @param id id of Action to retrieve +	 * @return Pointer to the Action or 0 if not found +	 */ +	Action *getAction(const char *id); + +	/** +	 * Get the list of all the Actions contained in this Keymap +	 */ +	List<Action*>& getActions() { return _actions; } + +	/** +	 * Find the Action that a key is mapped to +	 * @param key	the key that is mapped to the required Action +	 * @return		a pointer to the Action or 0 if no +	 */ +	Action *getMappedAction(const KeyState& ks) const; +	 +	void setConfigDomain(ConfigManager::Domain *dom); + +	/** +	 * Load this keymap's mappings from the config manager. +	 * @param hwKeys	the set to retrieve hardware key pointers from +	 */ +	void loadMappings(const HardwareKeySet *hwKeys); + +	/** +	 * Save this keymap's mappings to the config manager +	 * @note Changes are *not* flushed to disk, to do so call ConfMan.flushToDisk() +	 * @note Changes are *not* flushed to disk, to do so call ConfMan.flushToDisk() +	 */ +	void saveMappings(); + +	 +	void automaticMapping(HardwareKeySet *hwKeys); + +	/** +	 * Returns true if all UserAction's in Keymap are mapped, or, +	 * all HardwareKey's from the given set have been used up. +	 */ +	bool isComplete(const HardwareKeySet *hwKeys); + +	const String& getName() { return _name; } +	Keymap *getParent() { return _parent; } + +private: +	friend struct Action; + +	/** +	 * Adds a new Action to this Map,  +	 * adding it at the back of the internal array +	 * @param action the Action to add +	 */ +	void addAction(Action *action); + +	/** +	* Registers a HardwareKey to the given Action +	* @param action Action in this Keymap +	* @param key pointer to HardwareKey to map +	* @see Action::mapKey +	*/ +	void registerMapping(Action *action, const HardwareKey *key); + +	/** +	* Unregisters a HardwareKey from the given Action (if one is mapped) +	* @param action Action in this Keymap +	* @see Action::mapKey +	*/ +	void unregisterMapping(Action *action); + +	Action *findAction(const char *id); +	const Action *findAction(const char *id) const; + +	void internalMapKey(Action *action, HardwareKey *hwKey); + +	Action *getParentMappedAction(KeyState key); + +	String _name; +	Keymap *_parent; +	List<Action*> _actions; +	HashMap<KeyState, Action*> _keymap;  +	ConfigManager::Domain *_configDomain; + +}; + + +} // end of namespace Common + +#endif diff --git a/backends/keymapper/keymapper.cpp b/backends/keymapper/keymapper.cpp new file mode 100644 index 0000000000..5c79d445cb --- /dev/null +++ b/backends/keymapper/keymapper.cpp @@ -0,0 +1,219 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#include "backends/keymapper/keymapper.h" +#include "common/config-manager.h" +namespace Common { + +void Keymapper::Domain::addKeymap(Keymap *map) { +	KeymapMap::iterator it = _keymaps.find(map->getName()); +	if (it != _keymaps.end()) +		delete _keymaps[map->getName()]; +	_keymaps[map->getName()] = map; +} + +void Keymapper::Domain::deleteAllKeyMaps() { +	KeymapMap::iterator it; +	for (it = _keymaps.begin(); it != _keymaps.end(); it++) +		delete it->_value; +	_keymaps.clear(); +} + +Keymap *Keymapper::Domain::getKeymap(const String& name) { +	KeymapMap::iterator it = _keymaps.find(name); +	if (it != _keymaps.end()) +		return it->_value; +	else +		return 0; +} + +Keymapper::Keymapper(EventManager *evtMgr) +	: _eventMan(evtMgr), _enabled(true), _hardwareKeys(0) { +	_globalDomain.setConfigDomain(ConfMan.getDomain(ConfigManager::kApplicationDomain)); +} + +Keymapper::~Keymapper() { +	delete _hardwareKeys; +} + +void Keymapper::registerHardwareKeySet(HardwareKeySet *keys) { +	if (_hardwareKeys) +		error("Hardware key set already registered!"); +	_hardwareKeys = keys; +} + +void Keymapper::addGlobalKeymap(Keymap *keymap) { +	initKeymap(_globalDomain.getConfigDomain(), keymap); +	_globalDomain.addKeymap(keymap); +} + +void Keymapper::refreshGameDomain() { +	if (_gameDomain.getConfigDomain() != ConfMan.getActiveDomain()) { +		cleanupGameKeymaps(); +		_gameDomain.setConfigDomain(ConfMan.getActiveDomain()); +	} +} + +void Keymapper::addGameKeymap(Keymap *keymap) { +	if (ConfMan.getActiveDomain() == 0) +		error("Call to Keymapper::addGameKeymap when no game loaded"); +		 +	refreshGameDomain(); +	initKeymap(_gameDomain.getConfigDomain(), keymap); +	_gameDomain.addKeymap(keymap); +} + +void Keymapper::initKeymap(ConfigManager::Domain *domain, Keymap *map) { +	map->setConfigDomain(domain); +	map->loadMappings(_hardwareKeys); +	if (map->isComplete(_hardwareKeys) == false) { +		map->automaticMapping(_hardwareKeys); +		map->saveMappings(); +		ConfMan.flushToDisk(); +	} +} + +void Keymapper::cleanupGameKeymaps() { +	_gameDomain.deleteAllKeyMaps(); +	Stack<MapRecord> newStack; +	for (int i = 0; i < _activeMaps.size(); i++) { +		if (!_activeMaps[i].global) +			newStack.push(_activeMaps[i]); +	} +	_activeMaps = newStack; +} + +Keymap *Keymapper::getKeymap(const String& name, bool &global) { +	Keymap *keymap = _gameDomain.getKeymap(name); +	global = false; +	if (!keymap) { +		keymap = _globalDomain.getKeymap(name); +		global = true; +	} +	return keymap; +} + +bool Keymapper::pushKeymap(const String& name, bool inherit) { +	bool global; +	Keymap *newMap = getKeymap(name, global); +	if (!newMap) { +		warning("Keymap '%s' not registered", name.c_str()); +		return false; +	} +	pushKeymap(newMap, inherit, global); +	return true; +} + +void Keymapper::pushKeymap(Keymap *newMap, bool inherit, bool global) { +	MapRecord mr = {newMap, inherit, global}; +	_activeMaps.push(mr); +} + +void Keymapper::popKeymap() { +	if (!_activeMaps.empty()) +		_activeMaps.pop(); +} + +bool Keymapper::mapKeyDown(const KeyState& key) { +	return mapKey(key, true); +} + +bool Keymapper::mapKeyUp(const KeyState& key) { +	return mapKey(key, false); +} + +bool Keymapper::mapKey(const KeyState& key, bool keyDown) { +	if (!_enabled) return false; +	if (_activeMaps.empty()) return false; + +	Action *action = 0; +	if (keyDown) { +		// Search for key in active keymap stack +		for (int i = _activeMaps.size() - 1; i >= 0; --i) { +			MapRecord mr = _activeMaps[i]; +			action = mr.keymap->getMappedAction(key); +			if (action || mr.inherit == false) break; +		} +		if (action) _keysDown[key] = action; +	} else { +		HashMap<KeyState, Action*>::iterator it = _keysDown.find(key); +		if (it != _keysDown.end()) { +			action = it->_value; +			_keysDown.erase(key); +		} +	} +	if (!action) return false; +	executeAction(action, keyDown); +	return true; +} + +Action *Keymapper::getAction(const KeyState& key) { +	Action *action = 0; +	return action; +} + +void Keymapper::executeAction(const Action *action, bool keyDown) { +	List<Event>::iterator it; +	for (it = action->events.begin(); it != action->events.end(); ++it) { +		Event evt = *it; +		switch (evt.type) { +		case EVENT_KEYDOWN: +			if (!keyDown) evt.type = EVENT_KEYUP; +			break; +		case EVENT_KEYUP: +			if (keyDown) evt.type = EVENT_KEYDOWN; +			break; +		case EVENT_LBUTTONDOWN: +			if (!keyDown) evt.type = EVENT_LBUTTONUP; +			break; +		case EVENT_LBUTTONUP: +			if (keyDown) evt.type = EVENT_LBUTTONDOWN; +			break; +		case EVENT_RBUTTONDOWN: +			if (!keyDown) evt.type = EVENT_RBUTTONUP; +			break; +		case EVENT_RBUTTONUP: +			if (keyDown) evt.type = EVENT_RBUTTONDOWN; +			break; +		case EVENT_MBUTTONDOWN: +			if (!keyDown) evt.type = EVENT_MBUTTONUP; +			break; +		case EVENT_MBUTTONUP: +			if (keyDown) evt.type = EVENT_MBUTTONDOWN; +			break; +		default: +			// don't deliver other events on key up +			if (!keyDown) continue; +		} +		evt.mouse = _eventMan->getMousePos(); +		_eventMan->pushEvent(evt); +	} +} + +const HardwareKey *Keymapper::getHardwareKey(const KeyState& key) { +	return (_hardwareKeys) ? _hardwareKeys->findHardwareKey(key) : 0; +} + +} // end of namespace Common diff --git a/backends/keymapper/keymapper.h b/backends/keymapper/keymapper.h new file mode 100644 index 0000000000..a13cebe39a --- /dev/null +++ b/backends/keymapper/keymapper.h @@ -0,0 +1,204 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_KEYMAPPER +#define COMMON_KEYMAPPER + +#include "common/events.h" +#include "common/list.h" +#include "common/hashmap.h" +#include "common/stack.h" +#include "backends/keymapper/hardware-key.h" +#include "backends/keymapper/keymap.h" + +namespace Common { + +class Keymapper { +public: +	 +	struct MapRecord { +		Keymap* keymap; +		bool inherit; +		bool global; +	}; + +	/* Nested class that represents a set of keymaps */ +	class Domain { +		typedef HashMap<String, Keymap*,  +			IgnoreCase_Hash, IgnoreCase_EqualTo> KeymapMap; + +	public: +		Domain() : _configDomain(0) {} +		~Domain() {  +			deleteAllKeyMaps(); +		} + +		void setConfigDomain(ConfigManager::Domain *confDom) {  +			_configDomain = confDom; +		} +		ConfigManager::Domain *getConfigDomain() { +			return _configDomain; +		} + +		void addKeymap(Keymap *map); + +		void deleteAllKeyMaps(); + +		Keymap *getKeymap(const String& name); +		 +		typedef KeymapMap::iterator iterator; +		typedef KeymapMap::const_iterator const_iterator; +		iterator begin() { return _keymaps.begin(); } +		const_iterator begin() const { return _keymaps.begin(); } +		iterator end() { return _keymaps.end(); } +		const_iterator end() const { return _keymaps.end(); } +		uint32 count() { return _keymaps.size(); } +	private: +		ConfigManager::Domain *_configDomain; +		KeymapMap _keymaps; +	}; + +	Keymapper(EventManager *eventMan); +	~Keymapper(); + +	/** +	 * Registers a HardwareKeySet with the Keymapper +	 * @note should only be called once (during backend initialisation) +	 */ +	void registerHardwareKeySet(HardwareKeySet *keys); + +	/** +	 * Get the HardwareKeySet that is registered with the Keymapper +	 */ +	HardwareKeySet *getHardwareKeySet() { return _hardwareKeys; } + +	/** +	 * Add a keymap to the global domain. +	 * If a saved key setup exists for it in the ini file it will be used. +	 * Else, the key setup will be automatically mapped. +	 */ +	void addGlobalKeymap(Keymap *keymap); + +	/** +	 * Add a keymap to the game domain. +	 * @see addGlobalKeyMap +	 * @note initGame() should be called before any game keymaps are added. +	 */ +	void addGameKeymap(Keymap *keymap); + +	/** +	 * Should be called at end of game to tell Keymapper to deactivate and free +	 * any game keymaps that are loaded. +	 */ +	void cleanupGameKeymaps(); + +	/** +	 * Obtain a keymap of the given name from the keymapper. +	 * Game keymaps have priority over global keymaps +	 * @param name		name of the keymap to return +	 * @param global	set to true if returned keymap is global, false if game +	 */ +	Keymap *getKeymap(const String& name, bool &global); + +	/** +	 * Push a new keymap to the top of the active stack, activating  +	 * it for use. +	 * @param name		name of the keymap to push +	 * @param inherit	if true keymapper will iterate down the  +	 *					stack it cannot find a key in the new map +	 * @return			true if succesful +	 */ +	bool pushKeymap(const String& name, bool inherit = false); + +	/** +	 * Pop the top keymap off the active stack. +	 */ +	void popKeymap(); + +	/** +	 * @brief Map a key press event. +	 * If the active keymap contains a Action mapped to the given key, then  +	 * the Action's events are pushed into the EventManager's event queue. +	 * @param key		key that was pressed +	 * @param keyDown	true for key down, false for key up +	 * @return			true if key was mapped +	 */ +	bool mapKey(const KeyState& key, bool keyDown); + +	/** +	 * @brief Map a key down event. +	 * @see mapKey +	 */ +	bool mapKeyDown(const KeyState& key); + +	/** +	 * @brief Map a key up event. +	 * @see mapKey +	 */ +	bool mapKeyUp(const KeyState& key); + +	/** +	 * Enable/disable the keymapper +	 */ +	void setEnabled(bool enabled) { _enabled = enabled; } + +	/** +	 * Return a HardwareKey pointer for the given key state +	 */ +	const HardwareKey *getHardwareKey(const KeyState& key); + +	Domain& getGlobalDomain() { return _globalDomain; } +	Domain& getGameDomain() { return _gameDomain; } +	Stack<MapRecord>& getActiveStack() { return _activeMaps; } + +private: + +	void initKeymap(ConfigManager::Domain *domain, Keymap *keymap); +	void refreshGameDomain(); + +	Domain _globalDomain; +	Domain _gameDomain; + +	HardwareKeySet *_hardwareKeys; + +	void pushKeymap(Keymap *newMap, bool inherit, bool global); +	 +	Action *getAction(const KeyState& key); +	void executeAction(const Action *act, bool keyDown); + +	typedef List<HardwareKey*>::iterator Iterator; + +	EventManager *_eventMan; + +	bool _enabled; + +	Stack<MapRecord> _activeMaps; +	HashMap<KeyState, Action*> _keysDown; + +}; + +} // end of namespace Common + +#endif diff --git a/backends/keymapper/remap-dialog.cpp b/backends/keymapper/remap-dialog.cpp new file mode 100644 index 0000000000..1998fa813e --- /dev/null +++ b/backends/keymapper/remap-dialog.cpp @@ -0,0 +1,329 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + */ + +#include "backends/keymapper/remap-dialog.h" +#include "gui/eval.h" +#include "gui/newgui.h" +#include "gui/PopUpWidget.h" +#include "gui/ScrollBarWidget.h" + +namespace Common { + +enum { +	kRemapCmd = 'REMP', +	kCloseCmd = 'CLOS' +}; + +RemapDialog::RemapDialog() +	: Dialog("remap"), _keymapTable(0), _activeRemapAction(0), _topAction(0), _remapTimeout(0) { + +	_keymapper = g_system->getEventManager()->getKeymapper(); +	assert(_keymapper); + +	int labelWidth = g_gui.evaluator()->getVar("remap_popup_labelW"); +	_kmPopUp = new GUI::PopUpWidget(this, "remap_popup", "Keymap: ", labelWidth); + +	_scrollBar = new GUI::ScrollBarWidget(this, 0, 0, 0, 0); + +	new GUI::ButtonWidget(this, "remap_close_button", "Close", kCloseCmd); +} + +RemapDialog::~RemapDialog() { +	if (_keymapTable) free(_keymapTable); +} + +void RemapDialog::open() { +	bool divider = false; +	_activeKeymaps = &_keymapper->getActiveStack(); +	if (_activeKeymaps->size() > 0) { +		_kmPopUp->appendEntry(_activeKeymaps->top().keymap->getName() + " (Active)"); +		divider = true; +	} + +	Keymapper::Domain *_globalKeymaps = &_keymapper->getGlobalDomain(); +	Keymapper::Domain *_gameKeymaps = 0; + +	int keymapCount = 0; +	if (_globalKeymaps->count() == 0) +		_globalKeymaps = 0; +	else +		keymapCount += _globalKeymaps->count(); + +	if (ConfMan.getActiveDomain() != 0) { +		_gameKeymaps = &_keymapper->getGameDomain(); +		if (_gameKeymaps->count() == 0) +			_gameKeymaps = 0; +		else +			keymapCount += _gameKeymaps->count(); +	} + +	_keymapTable = (Keymap**)malloc(sizeof(Keymap*) * keymapCount); + +	Keymapper::Domain::iterator it; +	uint32 idx = 0; +	if (_globalKeymaps) { +		if (divider) _kmPopUp->appendEntry(""); +		for (it = _globalKeymaps->begin(); it != _globalKeymaps->end(); it++) { +			_kmPopUp->appendEntry(it->_value->getName() + " (Global)", idx); +			_keymapTable[idx++] = it->_value; +		} +		divider = true; +	} +	if (_gameKeymaps) { +		if (divider) _kmPopUp->appendEntry(""); +		for (it = _gameKeymaps->begin(); it != _gameKeymaps->end(); it++) { +			_kmPopUp->appendEntry(it->_value->getName() + " (Game)", idx); +			_keymapTable[idx++] = it->_value; +		} +	} + +	_changes = false; + +	Dialog::open(); + +	_kmPopUp->setSelected(0); +	loadKeymap(); +} + +void RemapDialog::close() { +	_kmPopUp->clearEntries(); +	if (_keymapTable) { +		free(_keymapTable); +		_keymapTable = 0; +	} +	if (_changes)  +		ConfMan.flushToDisk(); +	Dialog::close(); +} + +void RemapDialog::reflowLayout() { +	int scrollbarWidth, buttonHeight; +	if (g_gui.getWidgetSize() == GUI::kBigWidgetSize) { +		buttonHeight = GUI::kBigButtonHeight; +		scrollbarWidth = GUI::kBigScrollBarWidth; +	} else { +		buttonHeight = GUI::kButtonHeight; +		scrollbarWidth = GUI::kNormalScrollBarWidth; +	} +	int areaX = g_gui.evaluator()->getVar("remap_keymap_area.x"); +	int areaY = g_gui.evaluator()->getVar("remap_keymap_area.y"); +	int areaW = g_gui.evaluator()->getVar("remap_keymap_area.w"); +	int areaH = g_gui.evaluator()->getVar("remap_keymap_area.h"); +	int spacing = g_gui.evaluator()->getVar("remap_spacing"); +	int labelWidth = g_gui.evaluator()->getVar("remap_label_width"); +	int buttonWidth = g_gui.evaluator()->getVar("remap_button_width"); +	int colWidth = labelWidth + buttonWidth + spacing; +	_colCount = (areaW - scrollbarWidth) / colWidth; +	_rowCount = (areaH + spacing) / (buttonHeight + spacing); +	if (_colCount <= 0 || _rowCount <= 0)  +		error("Remap dialog too small to display any keymaps!"); + +	_kmPopUp->changeLabelWidth(labelWidth); + +	_scrollBar->resize(areaX + areaW - scrollbarWidth, areaY, scrollbarWidth, areaH); +	_scrollBar->_entriesPerPage = _rowCount; +	_scrollBar->_numEntries = 1; +	_scrollBar->recalc(); + +	uint textYOff = (buttonHeight - kLineHeight) / 2; +	uint oldSize = _keymapWidgets.size(); +	uint newSize = _rowCount * _colCount; +	_keymapWidgets.reserve(newSize); +	for (uint i = 0; i < newSize; i++) { +		ActionWidgets widg; +		if (i >= _keymapWidgets.size()) { +			widg.actionText =  +				new GUI::StaticTextWidget(this, 0, 0, 0, 0, "", Graphics::kTextAlignRight); +			widg.keyButton =  +				new GUI::ButtonWidget(this, 0, 0, 0, 0, "", kRemapCmd + i); +			_keymapWidgets.push_back(widg); +		} else { +			widg = _keymapWidgets[i]; +		} +		uint x = areaX + (i % _colCount) * colWidth; +		uint y = areaY + (i / _colCount) * (buttonHeight + spacing); +		widg.actionText->resize(x, y + textYOff, labelWidth, kLineHeight); +		widg.keyButton->resize(x + labelWidth, y, buttonWidth, buttonHeight); +	} +	while (oldSize > newSize) { +		ActionWidgets widg = _keymapWidgets.remove_at(--oldSize); +		removeWidget(widg.actionText); +		delete widg.actionText; +		removeWidget(widg.keyButton); +		delete widg.keyButton; +	} +	Dialog::reflowLayout(); +} + +void RemapDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { +	if (cmd >= kRemapCmd && cmd < kRemapCmd + _keymapWidgets.size()) { +		startRemapping(cmd - kRemapCmd); +	} else if (cmd == GUI::kPopUpItemSelectedCmd) { +		loadKeymap(); +	} else if (cmd == GUI::kSetPositionCmd) { +		refreshKeymap(); +	} else if (cmd == kCloseCmd) { +		close(); +	} else { +		GUI::Dialog::handleCommand(sender, cmd, data); +	} +} + +void RemapDialog::startRemapping(uint i) { +	if (_topAction + i >= _currentActions.size()) return;  +	_remapTimeout = getMillis() + kRemapTimeoutDelay; +	_activeRemapAction = _currentActions[_topAction + i].action; +	_keymapWidgets[i].keyButton->setLabel("..."); +	_keymapWidgets[i].keyButton->draw(); +	_keymapper->setEnabled(false); + +} + +void RemapDialog::stopRemapping() { +	_topAction = -1; +	refreshKeymap(); +	_activeRemapAction = 0; +	_keymapper->setEnabled(true); +} + +void RemapDialog::handleKeyUp(Common::KeyState state) { +	if (_activeRemapAction) { +		const HardwareKey *hwkey = _keymapper->getHardwareKey(state); +		if (hwkey) { +			_activeRemapAction->mapKey(hwkey); +			// TODO:   _activeRemapAction->getParent()->saveMappings(); +			_changes = true; +			stopRemapping(); +		} +	} else { +		GUI::Dialog::handleKeyDown(state); +	} +} + +void RemapDialog::handleMouseDown(int x, int y, int button, int clickCount) { +	if (_activeRemapAction) +		stopRemapping(); +	else +		Dialog::handleMouseDown(x, y, button, clickCount); +} + +void RemapDialog::handleTickle() { +	if (_activeRemapAction && getMillis() > _remapTimeout) +		stopRemapping(); +	Dialog::handleTickle(); +} + +void RemapDialog::loadKeymap() { +	_currentActions.clear(); +	if (_activeKeymaps->size() > 0 && _kmPopUp->getSelected() == 0) { +		// load active keymaps + +		List<const HardwareKey*> freeKeys (_keymapper->getHardwareKeySet()->getHardwareKeys()); + +		// add most active keymap's keys +		Keymapper::MapRecord top = _activeKeymaps->top(); +		List<Action*>::iterator actIt; +		for (actIt = top.keymap->getActions().begin(); actIt != top.keymap->getActions().end(); ++actIt) { +			Action *act = *actIt; +			ActionInfo info = {act, false, act->description}; +			_currentActions.push_back(info); +			if (act->getMappedKey()) +				freeKeys.remove(act->getMappedKey()); +		} +		 +		// loop through remaining finding mappings for unmapped keys +		if (top.inherit) { +			for (int i = _activeKeymaps->size() - 2; i >= 0; --i) { +				Keymapper::MapRecord mr = (*_activeKeymaps)[i]; +				List<const HardwareKey*>::iterator keyIt = freeKeys.begin(); +				while (keyIt != freeKeys.end()) { +					Action *act = mr.keymap->getMappedAction((*keyIt)->key); +					if (act) { +						ActionInfo info = {act, true, act->description + " (" + mr.keymap->getName() + ")"}; +						_currentActions.push_back(info); +						freeKeys.erase(keyIt++); +					} else { +						++keyIt; +					} +				} +				if (mr.inherit == false || freeKeys.empty()) break; +			} +		} + +	} else if (_kmPopUp->getSelected() != -1) { +		Keymap *km = _keymapTable[_kmPopUp->getSelectedTag()]; + +		List<Action*>::iterator it; +		for (it = km->getActions().begin(); it != km->getActions().end(); it++) { +			ActionInfo info = {*it, false, (*it)->description}; +			_currentActions.push_back(info); +		} +	} + +	// refresh scroll bar +	_scrollBar->_currentPos = 0; +	_scrollBar->_numEntries = (_currentActions.size() + _colCount - 1) / _colCount; +	_scrollBar->recalc(); +	// force refresh +	_topAction = -1; +	refreshKeymap(); +} + +void RemapDialog::refreshKeymap() { +	int newTopAction = _scrollBar->_currentPos * _colCount; +	if (newTopAction == _topAction) return; +	_topAction = newTopAction; + +	//_container->draw(); +	_scrollBar->draw(); + +	uint actionI = _topAction; +	for (uint widgetI = 0; widgetI < _keymapWidgets.size(); widgetI++) { +		ActionWidgets& widg = _keymapWidgets[widgetI]; +		if (actionI < _currentActions.size()) { +			ActionInfo&    info = _currentActions[actionI]; +			widg.actionText->setLabel(info.description + ": "); +			widg.actionText->setEnabled(!info.inherited); +			const HardwareKey *mappedKey = info.action->getMappedKey(); +			if (mappedKey) +				widg.keyButton->setLabel(mappedKey->description); +			else +				widg.keyButton->setLabel("-"); +			widg.actionText->clearFlags(GUI::WIDGET_INVISIBLE); +			widg.keyButton->clearFlags(GUI::WIDGET_INVISIBLE); +			actionI++;  +		} else { +			widg.actionText->setFlags(GUI::WIDGET_INVISIBLE); +			widg.keyButton->setFlags(GUI::WIDGET_INVISIBLE); +		} +		//widg.actionText->draw(); +		//widg.keyButton->draw(); +	} +	// need to redraw entire Dialog so that invisible  +	// widgets disappear +	draw(); +} + + +} // end of namespace Common diff --git a/backends/keymapper/remap-dialog.h b/backends/keymapper/remap-dialog.h new file mode 100644 index 0000000000..f9396e3b45 --- /dev/null +++ b/backends/keymapper/remap-dialog.h @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + */ + +#ifndef REMAP_DIALOG_H +#define REMAP_DIALOG_H + +#include "backends/keymapper/keymapper.h" +#include "gui/dialog.h" + +namespace GUI { +	class PopupWidget; +	class ScrollBarWidget; +} + +namespace Common { + +class RemapDialog : public GUI::Dialog { +public: +	RemapDialog(); +	virtual ~RemapDialog(); +	virtual void open(); +	virtual void close(); +	virtual void reflowLayout(); +	virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); +	virtual void handleKeyUp(Common::KeyState state); +	virtual void handleMouseDown(int x, int y, int button, int clickCount); +	virtual void handleTickle(); + +protected: +	struct ActionWidgets { +		GUI::StaticTextWidget *actionText; +		GUI::ButtonWidget *keyButton; +	}; +	struct ActionInfo { +		Action *action; +		bool inherited; +		String description; +	}; + +	void loadKeymap(); +	void refreshKeymap(); +	void startRemapping(uint i); +	void stopRemapping(); + +	Keymapper *_keymapper; +	Stack<Keymapper::MapRecord> *_activeKeymaps; +	Keymap** _keymapTable; + +	Array<ActionInfo> _currentActions; +	int _topAction; + +	Rect _keymapArea; + +	GUI::PopUpWidget *_kmPopUp; +	//GUI::ContainerWidget *_container; +	GUI::ScrollBarWidget *_scrollBar; + +	uint _colCount, _rowCount; + +	Array<ActionWidgets> _keymapWidgets; +	Action *_activeRemapAction; +	uint32 _remapTimeout; +	static const uint32 kRemapTimeoutDelay = 3000; + +	bool _changes; + +}; + +} // end of namespace Common + +#endif diff --git a/backends/keymapper/types.h b/backends/keymapper/types.h new file mode 100644 index 0000000000..8031ab5e38 --- /dev/null +++ b/backends/keymapper/types.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_TYPES +#define COMMON_TYPES + +namespace Common { + +enum KeyType { +	kGenericKeyType, +	kDirUpKeyType, +	kDirDownKeyType, +	kDirLeftKeyType, +	kDirRightKeyType, +	kActionKeyType, +	kTriggerLeftKeyType, +	kTriggerRightKeyType, +	kStartKeyType, +	kSelectKeyType, +	/*  ...  */ + +	kKeyTypeMax +}; + +enum ActionType { +	kGenericActionType, + +	// common actions +	kDirUpActionType, +	kDirDownActionType, +	kDirLeftActionType, +	kDirRightActionType, +	kLeftClickActionType, +	kRightClickActionType, +	kSaveActionType, +	kMenuActionType, +	kQuitActionType, +	kVirtualKeyboardActionType, +	kKeyRemapActionType, +	kVolumeUpActionType, +	kVolumeDownActionType, + + +	kActionTypeMax +}; + +} // end of namespace Common + +#endif diff --git a/backends/module.mk b/backends/module.mk index 8944e9a3db..ba4cefc3c0 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -30,7 +30,16 @@ MODULE_OBJS := \  	saves/savefile.o \  	saves/default/default-saves.o \  	saves/compressed/compressed-saves.o \ -	timer/default/default-timer.o +	timer/default/default-timer.o \ +	keymapper/action.o \ +	keymapper/remap-dialog.o \ +	keymapper/keymap.o \ +	keymapper/keymapper.o \ +	vkeybd/image-map.o \ +	vkeybd/polygon.o \ +	vkeybd/virtual-keyboard.o \ +	vkeybd/virtual-keyboard-gui.o \ +	vkeybd/virtual-keyboard-parser.o  # Include common rules  include $(srcdir)/rules.mk diff --git a/backends/platform/sdl/events.cpp b/backends/platform/sdl/events.cpp index a7a9251678..c2edcf78fb 100644 --- a/backends/platform/sdl/events.cpp +++ b/backends/platform/sdl/events.cpp @@ -24,6 +24,7 @@   */  #include "backends/platform/sdl/sdl.h" +#include "backends/keymapper/keymapper.h"  #include "common/util.h"  #include "common/events.h" @@ -522,3 +523,60 @@ bool OSystem_SDL::remapKey(SDL_Event &ev, Common::Event &event) {  	return false;  } +void OSystem_SDL::setupKeymapper() { +	using namespace Common; +	Keymapper *mapper = getEventManager()->getKeymapper(); + +	HardwareKeySet *keySet = new HardwareKeySet(); +	keySet->addHardwareKey(new HardwareKey( "a", KeyState(KEYCODE_a), "a", kActionKeyType )); +	keySet->addHardwareKey(new HardwareKey( "s", KeyState(KEYCODE_s), "s", kActionKeyType )); +	keySet->addHardwareKey(new HardwareKey( "d", KeyState(KEYCODE_d), "d", kActionKeyType )); +	keySet->addHardwareKey(new HardwareKey( "f", KeyState(KEYCODE_f), "f", kActionKeyType )); +	keySet->addHardwareKey(new HardwareKey( "n", KeyState(KEYCODE_n), "n (vk)", kTriggerLeftKeyType, kVirtualKeyboardActionType )); +	keySet->addHardwareKey(new HardwareKey( "m", KeyState(KEYCODE_m), "m (remap)", kTriggerRightKeyType, kKeyRemapActionType )); +	keySet->addHardwareKey(new HardwareKey( "[", KeyState(KEYCODE_LEFTBRACKET), "[ (select)", kSelectKeyType )); +	keySet->addHardwareKey(new HardwareKey( "]", KeyState(KEYCODE_RIGHTBRACKET), "] (start)", kStartKeyType )); +	mapper->registerHardwareKeySet(keySet); + +	Keymap *globalMap = new Keymap("global"); +	Keymap *guiMap = new Keymap("gui"); +	Action *act; +	Event evt ; + +	act = new Action(globalMap, "MENU", "Menu", kGenericActionType, kSelectKeyType); +	act->addKeyEvent(KeyState(KEYCODE_F5, ASCII_F5, 0)); +	 +	act = new Action(globalMap, "SKCT", "Skip", kGenericActionType, kActionKeyType); +	act->addKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE, 0)); + +	act = new Action(globalMap, "PAUS", "Pause", kGenericActionType, kStartKeyType); +	act->addKeyEvent(KeyState(KEYCODE_SPACE, ' ', 0)); +	 +	act = new Action(globalMap, "SKLI", "Skip line", kGenericActionType, kActionKeyType); +	act->addKeyEvent(KeyState(KEYCODE_PERIOD, '.', 0)); + +	act = new Action(globalMap, "VIRT", "Display keyboard", kVirtualKeyboardActionType); +	act->addKeyEvent(KeyState(KEYCODE_F6, ASCII_F6, 0)); + +	act = new Action(globalMap, "REMP", "Remap keys", kKeyRemapActionType); +	act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0)); + +	mapper->addGlobalKeymap(globalMap); + +	act = new Action(guiMap, "CLOS", "Close", kGenericActionType, kStartKeyType); +	act->addKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE, 0)); +	 +	act = new Action(guiMap, "CLIK", "Mouse click"); +	act->addLeftClickEvent(); + +	act = new Action(guiMap, "VIRT", "Display keyboard", kVirtualKeyboardActionType); +	act->addKeyEvent(KeyState(KEYCODE_F6, ASCII_F6, 0)); + +	act = new Action(guiMap, "REMP", "Remap keys", kKeyRemapActionType); +	act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0)); + +	mapper->addGlobalKeymap(guiMap); + +	mapper->pushKeymap("global"); +} + diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 9a6f294a55..fdc1ba97f6 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -159,6 +159,11 @@ void OSystem_SDL::initBackend() {  		setupMixer();  	} +	// Setup the keymapper with backend's set of keys +	// NOTE: must be done before creating TimerManager  +	// to avoid race conditions in creating EventManager +	setupKeymapper(); +  	// Create and hook up the timer manager, if none exists yet (we check for  	// this to allow subclasses to provide their own).  	if (_timer == 0) { diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 1cc0acbc29..3639d61c3a 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -142,6 +142,9 @@ public:  	// Returns true if an event was retrieved.  	virtual bool pollEvent(Common::Event &event); // overloaded by CE backend +	// Sets up the keymapper with the backends hardware key set +	virtual void setupKeymapper(); +  	// Set function that generates samples  	virtual void setupMixer();  	static void mixCallback(void *s, byte *samples, int len); diff --git a/backends/vkeybd/image-map.cpp b/backends/vkeybd/image-map.cpp new file mode 100644 index 0000000000..d97b662c7d --- /dev/null +++ b/backends/vkeybd/image-map.cpp @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/vkeybd/image-map.h" + +namespace Common { + +ImageMap::~ImageMap() { +	removeAllAreas(); +} + +Polygon *ImageMap::createArea(const String& id) { +	if (_areas.contains(id)) { +		warning("Image map already contains an area with target of '%s'", id.c_str()); +		return 0; +	} +	Polygon *p = new Polygon(); +	_areas[id] = p; +	return p; +} + +void ImageMap::removeArea(const String& id) { +	if (!_areas.contains(id)) +		return; +	delete _areas[id]; +	_areas.erase(id); +} + +void ImageMap::removeAllAreas() { +	HashMap<String, Polygon*>::iterator it; +	for (it = _areas.begin(); it != _areas.end(); it++) { +		delete it->_value; +	} +	_areas.clear(); +} + +String ImageMap::findMapArea(int16 x, int16 y) { +	HashMap<String, Polygon*>::iterator it; +	for (it = _areas.begin(); it != _areas.end(); it++) { +		if (it->_value->contains(x, y)) +			return it->_key; +	} +	return ""; +} + + +} // End of namespace Common diff --git a/backends/vkeybd/image-map.h b/backends/vkeybd/image-map.h new file mode 100644 index 0000000000..ed6feaa26e --- /dev/null +++ b/backends/vkeybd/image-map.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef COMMON_IMAGEMAP_H +#define COMMON_IMAGEMAP_H + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "backends/vkeybd/polygon.h" + +namespace Common { + +class ImageMap { + +public: + +	~ImageMap(); +	 +	Polygon *createArea(const String& id); +	void removeArea(const String& id); +	void removeAllAreas(); +	String findMapArea(int16 x, int16 y); + +protected: +	HashMap<String, Polygon*> _areas; +}; + + +} // End of namespace Common + +#endif diff --git a/backends/vkeybd/keycode-descriptions.h b/backends/vkeybd/keycode-descriptions.h new file mode 100644 index 0000000000..e31cc562be --- /dev/null +++ b/backends/vkeybd/keycode-descriptions.h @@ -0,0 +1,331 @@ +#ifndef KEYCODE_DESCRIPTIONS +#define KEYCODE_DESCRIPTIONS + +static const char *keycodeDescTable[] = { +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"Backspace", +	"Tab", +	"", +	"", +	"Clear", +	"Return", +	"", +	"", +	"", +	"", +	"", +	"Pause", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"Escape", +	"", +	"", +	"", +	"", +	" ", +	"!", +	"\"", +	"#", +	"$", +	"%", +	"&", +	"'", +	"(", +	")", +	"*", +	"+", +	",", +	"-", +	".", +	"/", +	"0", +	"1", +	"2", +	"3", +	"4", +	"5", +	"6", +	"7", +	"8", +	"9", +	":", +	";", +	"<", +	"=", +	">", +	"?", +	"@", +	"A", +	"B", +	"C", +	"D", +	"E", +	"F", +	"G", +	"H", +	"I", +	"J", +	"K", +	"L", +	"M", +	"N", +	"O", +	"P", +	"Q", +	"R", +	"S", +	"T", +	"U", +	"V", +	"W", +	"X", +	"Y", +	"Z", +	"[", +	"\\", +	"]", +	"^", +	"_", +	"`", +	"a", +	"b", +	"c", +	"d", +	"e", +	"f", +	"g", +	"h", +	"i", +	"j", +	"k", +	"l", +	"m", +	"n", +	"o", +	"p", +	"q", +	"r", +	"s", +	"t", +	"u", +	"v", +	"w", +	"x", +	"y", +	"z", +	"{", +	"|", +	"}", +	"~", +	"Delete", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"", +	"0", +	"1", +	"2", +	"3", +	"4", +	"5", +	"6", +	"7", +	"8", +	"9", +	".", +	"/", +	"*", +	"-", +	"+", +	"Enter", +	"=", +	"Up", +	"Down", +	"Right", +	"Left", +	"Ins", +	"Home", +	"End", +	"Page Up", +	"Page Down", +	"F1", +	"F2", +	"F3", +	"F4", +	"F5", +	"F6", +	"F7", +	"F8", +	"F9", +	"F10", +	"F11", +	"F12", +	"F13", +	"F14", +	"F15", +	"", +	"", +	"", +	"Num Lock", +	"Caps Lock", +	"Scroll Lock", +	"Shift", +	"Shift", +	"Ctrl", +	"Ctrl", +	"Alt", +	"Alt", +	"Meta", +	"Meta", +	"Super", +	"Super", +	"Mode", +	"Compose", +	"Help", +	"Print", +	"SysReq", +	"Break", +	"Menu", +	"Power", +	"€", +	"Undo" +}; +static const int keycodeDescTableSize = 322; + +#endif diff --git a/backends/vkeybd/polygon.cpp b/backends/vkeybd/polygon.cpp new file mode 100644 index 0000000000..77ef3f0f44 --- /dev/null +++ b/backends/vkeybd/polygon.cpp @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/vkeybd/polygon.h" + +namespace Common { + +bool Polygon::contains(int16 x, int16 y) const { +	int yflag0; +	int yflag1; +	bool inside_flag = false; +	unsigned int pt; + +	const Point *vtx0 = &_points[_points.size() - 1]; +	const Point *vtx1 = &_points[0]; + +	yflag0 = (vtx0->y >= y); +	for (pt = 0; pt < _points.size(); pt++, vtx1++) { +		yflag1 = (vtx1->y >= y); +		if (yflag0 != yflag1) { +			if (((vtx1->y - y) * (vtx0->x - vtx1->x) >= +				(vtx1->x - x) * (vtx0->y - vtx1->y)) == yflag1) { +				inside_flag = !inside_flag; +			} +		} +		yflag0 = yflag1; +		vtx0 = vtx1; +	} + +	return inside_flag; +} + +} // end of namespace Common diff --git a/backends/vkeybd/polygon.h b/backends/vkeybd/polygon.h new file mode 100644 index 0000000000..69df2c0ca3 --- /dev/null +++ b/backends/vkeybd/polygon.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef COMMON_POLYGON_H +#define COMMON_POLYGON_H + +#include "common/array.h" +#include "common/rect.h" + +namespace Common { + +struct Polygon { + + +	Polygon() {} +	Polygon(const Polygon& p) : _points(p._points), _bound(p._bound) {} +	Polygon(Array<Point> p) : _points(p) { +		if (p.empty()) return; +		_bound = Rect(p[0].x, p[0].y, p[0].x, p[0].y); +		for (uint i = 1; i < p.size(); i++) { +			_bound.extend(Rect(p[i].x, p[i].y, p[i].x, p[i].y)); +		} +	} +	Polygon(Point *p, int n) { +		for (int i = 0; i < n; i++) { +			addPoint(p[i]); +		} +	} +	virtual ~Polygon() {} + +	void addPoint(const Point& p) { +		_points.push_back(p); +		_bound.extend(Rect(p.x, p.y, p.x, p.y)); +	} + +	void addPoint(int16 x, int16 y) { +		addPoint(Point(x,y)); +	} + +	uint getPointCount() { +		return _points.size(); +	} + +	/*!	@brief check if given position is inside this polygon + +		@param x the horizontal position to check +		@param y the vertical position to check + +		@return true if the given position is inside this polygon, false otherwise +	*/ +	virtual bool contains(int16 x, int16 y) const; + +	/*!	@brief check if given point is inside this polygon + +		@param p the point to check + +		@return true if the given point is inside this polygon, false otherwise +	*/ +	virtual bool contains(const Point &p) const { +		return contains(p.x, p.y); +	} + +	virtual void moveTo(int16 x, int16 y) { +		int16 dx = x - ((_bound.right + _bound.left) / 2); +		int16 dy = y - ((_bound.bottom + _bound.top) / 2); +		translate(dx, dy); +	} + +	virtual void moveTo(const Point &p) { +		moveTo(p.x, p.y); +	} + +	virtual void translate(int16 dx, int16 dy) { +		Array<Point>::iterator it; +		for (it = _points.begin(); it != _points.end(); it++) { +			it->x += dx; +			it->y += dy; +		} +	} + +	virtual Rect getBoundingRect() const { +		return _bound; +	} + +private: +	Array<Point> _points; +	Rect _bound; +}; + +} // end of namespace Common + +#endif diff --git a/backends/vkeybd/virtual-keyboard-gui.cpp b/backends/vkeybd/virtual-keyboard-gui.cpp new file mode 100644 index 0000000000..a66e0721f8 --- /dev/null +++ b/backends/vkeybd/virtual-keyboard-gui.cpp @@ -0,0 +1,416 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#include "backends/vkeybd/virtual-keyboard-gui.h" +#include "graphics/cursorman.h" +#include "gui/newgui.h" + +namespace Common { + +VirtualKeyboardGUI::VirtualKeyboardGUI(VirtualKeyboard *kbd) +	: _kbd(kbd), _displaying(false), _drag(false),	 +	_drawCaret(false), 	_displayEnabled(false),	_firstRun(true),  +	_cursorAnimateTimer(0), _cursorAnimateCounter(0) { +	 +	assert(_kbd); +	assert(g_system); +	_system = g_system; +	 +	_lastScreenChanged = _system->getScreenChangeID(); +	_screenW = _system->getOverlayWidth(); +	_screenH = _system->getOverlayHeight(); + +	 +	memset(_cursor, 0xFF, sizeof(_cursor)); +} + +VirtualKeyboardGUI::~VirtualKeyboardGUI() { +	_overlayBackup.free(); +	_dispSurface.free(); +} + +void VirtualKeyboardGUI::initMode(VirtualKeyboard::Mode *mode) { +	_kbdSurface = mode->image; +	_kbdTransparentColor = mode->transparentColor; +	_kbdBound.setWidth(_kbdSurface->w); +	_kbdBound.setHeight(_kbdSurface->h); + +	if (mode->displayArea) +		setupDisplayArea(*(mode->displayArea), mode->displayFontColor); + +	if (_displaying) { +		extendDirtyRect(_kbdBound); +		redraw(); +	} +} + +void VirtualKeyboardGUI::setupDisplayArea(Rect& r, OverlayColor forecolor) { + +	_dispFont = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont); +	if (!fontIsSuitable(_dispFont, r)) { +		_dispFont = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont); +		if (!fontIsSuitable(_dispFont, r)) { +			_displayEnabled = false; +			return; +		} +	} +	_dispX = _kbdBound.left + r.left; +	_dispY = _kbdBound.top + r.top + (r.height() - _dispFont->getFontHeight()) / 2; +	_dispI = 0; +	_dispForeColor = forecolor; +	_dispBackColor = _dispForeColor + 0xFF; +	_dispSurface.create(r.width(), _dispFont->getFontHeight(), sizeof(OverlayColor)); +	_dispSurface.fillRect(Rect(_dispSurface.w, _dispSurface.h), _dispBackColor); +	_displayEnabled = true; +} + +bool VirtualKeyboardGUI::fontIsSuitable(const Graphics::Font *font, const Rect& rect) { +	return (font->getMaxCharWidth() < rect.width() && +			font->getFontHeight() < rect.height()); +} + +void VirtualKeyboardGUI::checkScreenChanged() { +	if (_lastScreenChanged != _system->getScreenChangeID()) +		screenChanged();  +} + +void VirtualKeyboardGUI::initSize(int16 w, int16 h) { +	_screenW = w; +	_screenH = h; +} + +void VirtualKeyboardGUI::run() { +	if (_firstRun) { +		_firstRun = false; +		moveToDefaultPosition(); +	} + +	if (!g_gui.isActive()) { +		_system->showOverlay(); +		_system->clearOverlay(); +	} +	_overlayBackup.create(_screenW, _screenH, sizeof(OverlayColor)); +	_system->grabOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w); + +	setupCursor(); + +	forceRedraw(); +	_displaying = true; +	mainLoop(); + +	removeCursor(); + +	_system->copyRectToOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w, 0, 0, _overlayBackup.w, _overlayBackup.h); +	if (!g_gui.isActive()) _system->hideOverlay(); + +	_overlayBackup.free(); +	_dispSurface.free(); +} + +void VirtualKeyboardGUI::close() { +	_displaying = false; +} + +void VirtualKeyboardGUI::reset() { +	_kbdBound.left = _kbdBound.top +		= _kbdBound.right = _kbdBound.bottom = 0; +	_displaying = _drag = false; +	_firstRun = true; +	_lastScreenChanged = _system->getScreenChangeID(); +	_kbdSurface = 0; +} + +void VirtualKeyboardGUI::moveToDefaultPosition() +{ +	int16 kbdW = _kbdBound.width(), kbdH = _kbdBound.height(); +	int16 x = 0, y = 0; +	if (_screenW != kbdW) { +		switch (_kbd->_hAlignment) { +		case VirtualKeyboard::kAlignLeft: +			x = 0; +			break; +		case VirtualKeyboard::kAlignCentre: +			x = (_screenW - kbdW) / 2; +			break; +		case VirtualKeyboard::kAlignRight: +			x = _screenW - kbdW; +			break; +		} +	} +	if (_screenH != kbdH) { +		switch (_kbd->_vAlignment) { +		case VirtualKeyboard::kAlignTop: +			y = 0; +			break; +		case VirtualKeyboard::kAlignMiddle: +			y = (_screenH - kbdH) / 2; +			break; +		case VirtualKeyboard::kAlignBottom: +			y = _screenH - kbdH; +			break; +		} +	} +	move(x, y); +} + +void VirtualKeyboardGUI::move(int16 x, int16 y) { +	// add old position to dirty area +	if (_displaying) extendDirtyRect(_kbdBound); + +	// snap to edge of screen +	if (ABS(x) < SNAP_WIDTH) +		x = 0; +	int16 x2 = _screenW - _kbdBound.width(); +	if (ABS(x - x2) < SNAP_WIDTH) +		x = x2; +	if (ABS(y) < SNAP_WIDTH) +		y = 0; +	int16 y2 = _screenH - _kbdBound.height(); +	if (ABS(y - y2) < SNAP_WIDTH) +		y = y2; + +	_dispX += x - _kbdBound.left; +	_dispY += y - _kbdBound.top; +	_kbdBound.moveTo(x, y); + +	if (_displaying) { +		// add new position to dirty area +		extendDirtyRect(_kbdBound); +		redraw(); +	} +} + +void VirtualKeyboardGUI::screenChanged() { +	_lastScreenChanged = _system->getScreenChangeID(); +	int16 newScreenW = _system->getOverlayWidth(); +	int16 newScreenH = _system->getOverlayHeight(); +	if (_screenW != newScreenW || _screenH != newScreenH) { +		_screenW = newScreenW; +		_screenH = newScreenH; +		if (!_kbd->checkModeResolutions()) { +			_displaying = false; +			return; +		} +		moveToDefaultPosition(); +	} +} + + +void VirtualKeyboardGUI::mainLoop() { +	Common::EventManager *eventMan = _system->getEventManager(); + +	while (_displaying) { +		if (_kbd->_keyQueue.hasStringChanged()) +			updateDisplay(); +		animateCaret(); +		animateCursor(); +		redraw(); +		_system->updateScreen(); +		Common::Event event; +		while (eventMan->pollEvent(event)) { +			switch (event.type) { +			case Common::EVENT_LBUTTONDOWN: +				if (_kbdBound.contains(event.mouse)) { +					_kbd->handleMouseDown(event.mouse.x - _kbdBound.left, +										  event.mouse.y - _kbdBound.top); +				} +				break; +			case Common::EVENT_LBUTTONUP: +				if (_kbdBound.contains(event.mouse)) { +					_kbd->handleMouseUp(event.mouse.x - _kbdBound.left, +										event.mouse.y - _kbdBound.top); +				} +				break; +			case Common::EVENT_MOUSEMOVE: +				if (_drag) +					move(event.mouse.x - _dragPoint.x,  +						event.mouse.y - _dragPoint.y); +				break; +			case Common::EVENT_SCREEN_CHANGED: +				screenChanged(); +				break; +			case Common::EVENT_QUIT: +				_system->quit(); +				return; +			default: +				break; +			} +		} +		// Delay for a moment +		_system->delayMillis(10); +	} +} + +void VirtualKeyboardGUI::startDrag(int16 x, int16 y) { +	_drag = true; +	_dragPoint.x = x; +	_dragPoint.y = y; +} + +void VirtualKeyboardGUI::endDrag() { +	_drag = false; +} + +void VirtualKeyboardGUI::extendDirtyRect(const Rect &r) { +	if (_dirtyRect.isValidRect()) { +		_dirtyRect.extend(r); +	} else { +		_dirtyRect = r; +	} +	_dirtyRect.clip(Rect(_overlayBackup.w, _overlayBackup.h)); +} + +void VirtualKeyboardGUI::resetDirtyRect() { +	_dirtyRect.setWidth(-1); +} + +void VirtualKeyboardGUI::forceRedraw() { +	updateDisplay(); +	extendDirtyRect(Rect(_overlayBackup.w, _overlayBackup.h)); +	redraw(); +} + +void VirtualKeyboardGUI::redraw() { +	assert(_kbdSurface); +	int16 w = _dirtyRect.width(); +	int16 h = _dirtyRect.height(); +	if (w <= 0 || h <= 0) return; +	 +	Graphics::SurfaceKeyColored surf; +	surf.create(w, h, sizeof(OverlayColor)); + +	OverlayColor *dst = (OverlayColor *)surf.pixels; +	const OverlayColor *src = (OverlayColor *) _overlayBackup.getBasePtr(_dirtyRect.left, _dirtyRect.top); + +	while (h--) { +		memcpy(dst, src, surf.w * sizeof(OverlayColor)); +		dst += surf.w; +		src += _overlayBackup.w; +	} + +	surf.blit(_kbdSurface, _kbdBound.left - _dirtyRect.left,  +			  _kbdBound.top - _dirtyRect.top, _kbdTransparentColor); +	if (_displayEnabled) { +		surf.blit(&_dispSurface, _dispX - _dirtyRect.left,  +				  _dispY - _dirtyRect.top, _dispBackColor); +	} +	_system->copyRectToOverlay((OverlayColor*)surf.pixels, surf.w,  +							   _dirtyRect.left, _dirtyRect.top, surf.w, surf.h); + +	surf.free(); +	 +	resetDirtyRect(); +} + +uint VirtualKeyboardGUI::calculateEndIndex(const String& str, uint startIndex) { +	int16 w = 0; +	while (w <= _dispSurface.w && startIndex < str.size()) { +		w += _dispFont->getCharWidth(str[startIndex++]); +	} +	if (w > _dispSurface.w) startIndex--; +	return startIndex; +} + +void VirtualKeyboardGUI::animateCaret() { +	if (!_displayEnabled) return; + +	if (_system->getMillis() % kCaretBlinkTime < kCaretBlinkTime / 2) { +		if (!_drawCaret) { +			_drawCaret = true;			 +			_dispSurface.drawLine(_caretX, 0, _caretX, _dispSurface.h, _dispForeColor); +			extendDirtyRect(Rect(_dispX + _caretX, _dispY, _dispX + _caretX + 1, _dispY + _dispSurface.h)); +		} +	} else { +		if (_drawCaret) { +			_drawCaret = false; +			_dispSurface.drawLine(_caretX, 0, _caretX, _dispSurface.h, _dispBackColor); +			extendDirtyRect(Rect(_dispX + _caretX, _dispY, _dispX + _caretX + 1, _dispY + _dispSurface.h)); +		} +	} +} + +void VirtualKeyboardGUI::updateDisplay() { +	if (!_displayEnabled) return; + +	// calculate the text to display +	uint cursorPos = _kbd->_keyQueue.getInsertIndex(); +	String wholeText = _kbd->_keyQueue.getString(); +	uint dispTextEnd; +	if (_dispI > cursorPos) +		_dispI = cursorPos; +	 +	dispTextEnd = calculateEndIndex(wholeText, _dispI); +	while (cursorPos > dispTextEnd) +		dispTextEnd = calculateEndIndex(wholeText, ++_dispI); +	 +	String dispText = String(wholeText.c_str() + _dispI, wholeText.c_str() + dispTextEnd); + +	// draw to display surface +	_dispSurface.fillRect(Rect(_dispSurface.w, _dispSurface.h), _dispBackColor); +	_dispFont->drawString(&_dispSurface, dispText, 0, 0, _dispSurface.w, _dispForeColor); +	 +	String beforeCaret(wholeText.c_str() + _dispI, wholeText.c_str() + cursorPos); +	_caretX = _dispFont->getStringWidth(beforeCaret); +	if (_drawCaret) _dispSurface.drawLine(_caretX, 0, _caretX, _dispSurface.h, _dispForeColor); + +	extendDirtyRect(Rect(_dispX, _dispY, _dispX + _dispSurface.w, _dispY + _dispSurface.h)); +} + +void VirtualKeyboardGUI::setupCursor() { +	const byte palette[] = { +		255, 255, 255, 0, +		255, 255, 255, 0, +		171, 171, 171, 0, +		87,  87,  87, 0 +	}; + +	CursorMan.pushCursorPalette(palette, 0, 4); +	CursorMan.pushCursor(NULL, 0, 0, 0, 0); +	CursorMan.showMouse(true); +} + +void VirtualKeyboardGUI::animateCursor() { +	int time = _system->getMillis(); +	if (time > _cursorAnimateTimer + kCursorAnimateDelay) { +		for (int i = 0; i < 15; i++) { +			if ((i < 6) || (i > 8)) { +				_cursor[16 * 7 + i] = _cursorAnimateCounter; +				_cursor[16 * i + 7] = _cursorAnimateCounter; +			} +		} + +		CursorMan.replaceCursor(_cursor, 16, 16, 7, 7); + +		_cursorAnimateTimer = time; +		_cursorAnimateCounter = (_cursorAnimateCounter + 1) % 4; +	} +} + +void VirtualKeyboardGUI::removeCursor() { +	CursorMan.popCursor(); +	CursorMan.popCursorPalette(); +} + +} // end of namespace Common diff --git a/backends/vkeybd/virtual-keyboard-gui.h b/backends/vkeybd/virtual-keyboard-gui.h new file mode 100644 index 0000000000..e99d552479 --- /dev/null +++ b/backends/vkeybd/virtual-keyboard-gui.h @@ -0,0 +1,153 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* 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. +* +* $URL$ +* $Id$ +* +*/ + +#ifndef COMMON_VIRTUAL_KEYBOARD_GUI +#define COMMON_VIRTUAL_KEYBOARD_GUI + +#include "backends/vkeybd/virtual-keyboard.h" +#include "common/rect.h" +#include "common/system.h" +#include "graphics/fontman.h" +#include "graphics/surface-keycolored.h" + +namespace Common { + +/** + * Class to handle the drawing of the virtual keyboard to the overlay, and the + * execution of the keyboard's main loop. + * This includes the blitting of the appropriate bitmap in the correct location, + * as well as the drawing of the virtual keyboard display. + */ +class VirtualKeyboardGUI { + +public: + +	VirtualKeyboardGUI(VirtualKeyboard *kbd); +	~VirtualKeyboardGUI(); + +	/** +	 * Updates the GUI when the Mode of the keyboard is changes +	 */ +	void initMode(VirtualKeyboard::Mode *mode); + +	/** +	 * Starts the drawing of the keyboard, and runs the main event loop. +	 */ +	void run(); + +	/** +	 * Interrupts the event loop and resets the overlay to its initial state. +	 */	  +	void close(); +	 +	bool isDisplaying() { return _displaying; } + +	/** +	 * Reset the class to an initial state +	 */ +	void reset(); + +	/** +	 * Activates drag mode. Takes the keyboard-relative coordinates of the  +	 * cursor as an argument. +	 */ +	void startDrag(int16 x, int16 y); + +	/** +	 * Deactivates drag mode +	 * */ +	void endDrag(); +	 +	/** +	 * Checks for a screen change in the backend and re-inits the virtual  +	 * keyboard if it has. +	 */ +	void checkScreenChanged(); +	 +	/** +	 * Sets the GUI's internal screen size variables +	 */  +	void initSize(int16 w, int16 h); + +private: + +	OSystem *_system; + +	VirtualKeyboard *_kbd; +	Rect _kbdBound; +	Graphics::Surface *_kbdSurface; +	OverlayColor _kbdTransparentColor; + +	Point _dragPoint; +	bool _drag; +	static const int SNAP_WIDTH = 10; + +	Graphics::Surface _overlayBackup; +	Rect _dirtyRect; + +	bool _displayEnabled; +	Graphics::Surface _dispSurface; +	const Graphics::Font *_dispFont; +	int16 _dispX, _dispY; +	uint _dispI; +	OverlayColor _dispForeColor, _dispBackColor; + +	int _lastScreenChanged; +	int16 _screenW, _screenH; + +	bool _displaying; +	bool _firstRun; + +	void setupDisplayArea(Rect& r, OverlayColor forecolor); +	void move(int16 x, int16 y); +	void moveToDefaultPosition(); +	void screenChanged(); +	void mainLoop(); +	void extendDirtyRect(const Rect &r); +	void resetDirtyRect(); +	void redraw(); +	void forceRedraw(); +	void updateDisplay(); +	bool fontIsSuitable(const Graphics::Font *font, const Rect& rect); +	uint calculateEndIndex(const String& str, uint startIndex); + +	bool _drawCaret; +	int16 _caretX; +	static const int kCaretBlinkTime = 500; +	void animateCaret(); + +	static const int kCursorAnimateDelay = 250; +	int _cursorAnimateCounter; +	int	_cursorAnimateTimer; +	byte _cursor[2048]; +	void setupCursor(); +	void removeCursor(); +	void animateCursor(); + +}; + +} // end of namespace Common + +#endif diff --git a/backends/vkeybd/virtual-keyboard-parser.cpp b/backends/vkeybd/virtual-keyboard-parser.cpp new file mode 100644 index 0000000000..a2b035f1b5 --- /dev/null +++ b/backends/vkeybd/virtual-keyboard-parser.cpp @@ -0,0 +1,363 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/vkeybd/virtual-keyboard-parser.h" + +#include "common/keyboard.h" +#include "graphics/imageman.h" +#include "common/util.h" + +namespace Common { + +VirtualKeyboardParser::VirtualKeyboardParser(VirtualKeyboard *kbd)  +	: XMLParser(), _keyboard(kbd) { +} + +void VirtualKeyboardParser::cleanup() { +	_mode = 0; +	_kbdParsed = false; +	_initialModeName.clear(); +	if (_parseMode == kParseFull) { +		// reset keyboard to remove existing config +		_keyboard->reset(); +	} +} + +bool VirtualKeyboardParser::closedKeyCallback(ParserNode *node) { +	if (node->name.equalsIgnoreCase("keyboard")) { +		_kbdParsed = true; +		if (!_keyboard->_initialMode) +			return parserError("Initial mode of keyboard pack not defined"); +	} else if (node->name.equalsIgnoreCase("mode")) { +		if (!_layoutParsed) { +			return parserError("'%s' layout missing from '%s' mode",  +				_mode->resolution.c_str(), _mode->name.c_str()); +		} +	} +	return true; +} + +bool VirtualKeyboardParser::parserCallback_keyboard(ParserNode *node) { + +	if (_kbdParsed) +		return parserError("Only a single keyboard element is allowed"); + +	// if not full parse then we're done +	if (_parseMode == kParseCheckResolutions) +		return true; + +	_initialModeName = node->values["initial_mode"]; + +	if (node->values.contains("h_align")) { +		String h = node->values["h_align"]; +		if (h.equalsIgnoreCase("left")) +			_keyboard->_hAlignment = VirtualKeyboard::kAlignLeft; +		else if (h.equalsIgnoreCase("centre") || h.equalsIgnoreCase("center")) +			_keyboard->_hAlignment = VirtualKeyboard::kAlignCentre; +		else if (h.equalsIgnoreCase("right")) +			_keyboard->_hAlignment = VirtualKeyboard::kAlignRight; +	} + +	if (node->values.contains("v_align")) { +		String v = node->values["h_align"]; +		if (v.equalsIgnoreCase("top")) +			_keyboard->_vAlignment = VirtualKeyboard::kAlignTop; +		else if (v.equalsIgnoreCase("middle") || v.equalsIgnoreCase("center")) +			_keyboard->_vAlignment = VirtualKeyboard::kAlignMiddle; +		else if (v.equalsIgnoreCase("bottom")) +			_keyboard->_vAlignment = VirtualKeyboard::kAlignBottom; +	} + +	return true; +} + +bool VirtualKeyboardParser::parserCallback_mode(ParserNode *node) { + +	String name = node->values["name"]; + +	if (_parseMode == kParseFull) { +		// if full parse then add new mode to keyboard +		if (_keyboard->_modes.contains(name)) +			return parserError("Mode '%s' has already been defined", name.c_str()); + +		VirtualKeyboard::Mode mode; +		mode.name = name; +		_keyboard->_modes[name] = mode; +	} + +	_mode = &(_keyboard->_modes[name]); +	if (name == _initialModeName) +		_keyboard->_initialMode = _mode; + +	String resolutions = node->values["resolutions"]; +	StringTokenizer tok (resolutions, " ,"); + +	// select best resolution simply by minimising the difference between the  +	// overlay size and the resolution dimensions. +	// TODO: improve this by giving preference to a resolution that is smaller +	// than the overlay res (so the keyboard can't be too big for the screen) +	uint16 scrW = g_system->getOverlayWidth(), scrH = g_system->getOverlayHeight(); +	uint32 diff = 0xFFFFFFFF; +	String newResolution; +	for (String res = tok.nextToken(); res.size() > 0; res = tok.nextToken()) { +		int resW, resH; +		if (sscanf(res.c_str(), "%dx%d", &resW, &resH) != 2) { +			return parserError("Invalid resolution specification"); +		} else { +			if (resW == scrW && resH == scrH) { +				newResolution = res; +				break; +			} else { +				uint32 newDiff = ABS(scrW - resW) + ABS(scrH - resH); +				if (newDiff < diff) { +					diff = newDiff; +					newResolution = res; +				} +			} +		} +	} + +	if (newResolution.empty()) +		return parserError("No acceptable resolution was found"); + +	if (_parseMode == kParseCheckResolutions) { +		if (_mode->resolution == newResolution) { +			node->ignore = true; +			return true; +		} else { +			// remove data relating to old resolution +			ImageMan.unregisterSurface(_mode->bitmapName); +			_mode->bitmapName.clear(); +			_mode->image = 0; +			_mode->imageMap.removeAllAreas(); +			delete _mode->displayArea; +			_mode->displayArea = 0; +		} +	} + +	_mode->resolution = newResolution; +	_layoutParsed = false; + +	return true; +} + +bool VirtualKeyboardParser::parserCallback_event(ParserNode *node) { +	 +	// if just checking resolutions we're done +	if (_parseMode == kParseCheckResolutions) +		return true; + +	String name = node->values["name"]; +	if (_mode->events.contains(name)) +		return parserError("Event '%s' has already been defined", name.c_str()); + +	VirtualKeyboard::VKEvent *evt = new VirtualKeyboard::VKEvent(); +	evt->name = name; + +	String type = node->values["type"]; +	if (type.equalsIgnoreCase("key")) { +		if (!node->values.contains("code") || !node->values.contains("ascii")) { +			delete evt; +			return parserError("Key event element must contain code and ascii attributes"); +		} +		evt->type = VirtualKeyboard::kVKEventKey; + +		KeyState *ks = (KeyState*) malloc(sizeof(KeyState)); +		ks->keycode = (KeyCode)atoi(node->values["code"].c_str()); +		ks->ascii = atoi(node->values["ascii"].c_str()); +		ks->flags = 0; +		if (node->values.contains("modifiers")) +			ks->flags = parseFlags(node->values["modifiers"]); +		evt->data = ks; + +	} else if (type.equalsIgnoreCase("modifier")) { +		if (!node->values.contains("modifiers")) { +			delete evt; +			return parserError("Key modifier element must contain modifier attributes"); +		} +		 +		evt->type = VirtualKeyboard::kVKEventModifier; +		byte *flags = (byte*) malloc(sizeof(byte)); +		*(flags) = parseFlags(node->values["modifiers"]); +		evt->data = flags; + +	} else if (type.equalsIgnoreCase("switch_mode")) { +		if (!node->values.contains("mode")) { +			delete evt; +			return parserError("Switch mode event element must contain mode attribute"); +		} + +		evt->type = VirtualKeyboard::kVKEventSwitchMode; +		String& mode = node->values["mode"]; +		char *str = (char*) malloc(sizeof(char) * mode.size() + 1); +		memcpy(str, mode.c_str(), sizeof(char) * mode.size()); +		str[mode.size()] = 0; +		evt->data = str; +	} else if (type.equalsIgnoreCase("submit")) { +		evt->type = VirtualKeyboard::kVKEventSubmit; +	} else if (type.equalsIgnoreCase("cancel")) { +		evt->type = VirtualKeyboard::kVKEventCancel; +	} else if (type.equalsIgnoreCase("clear")) { +		evt->type = VirtualKeyboard::kVKEventClear; +	} else if (type.equalsIgnoreCase("delete")) { +		evt->type = VirtualKeyboard::kVKEventDelete; +	} else if (type.equalsIgnoreCase("move_left")) { +		evt->type = VirtualKeyboard::kVKEventMoveLeft; +	} else if (type.equalsIgnoreCase("move_right")) { +		evt->type = VirtualKeyboard::kVKEventMoveRight; +	} else { +		delete evt; +		return parserError("Event type '%s' not known", type.c_str()); +	} + +	_mode->events[name] = evt; + +	return true; +} + +bool VirtualKeyboardParser::parserCallback_layout(ParserNode *node) { + +	assert(!_mode->resolution.empty()); + +	String res = node->values["resolution"]; + +	if (res != _mode->resolution) { +		node->ignore = true; +		return true; +	} + +	_mode->bitmapName = node->values["bitmap"]; +	_mode->image = ImageMan.getSurface(_mode->bitmapName); +	if (!_mode->image) { +		if (!ImageMan.registerSurface(_mode->bitmapName, 0)) +			return parserError("Error loading bitmap '%s'", _mode->bitmapName.c_str()); + +		_mode->image = ImageMan.getSurface(_mode->bitmapName); +		if (!_mode->image) +			return parserError("Error loading bitmap '%s'", _mode->bitmapName.c_str()); +	} +	 +	if (node->values.contains("transparent_color")) { +		int r, g, b; +		if (!parseIntegerKey(node->values["transparent_color"].c_str(), 3, &r, &g, &b)) +			return parserError("Could not parse color value"); +		_mode->transparentColor = g_system->RGBToColor(r, g, b); +	} else // default to purple +		_mode->transparentColor = g_system->RGBToColor(255, 0, 255);  + +	if (node->values.contains("display_font_color")) { +		int r, g, b; +		if (!parseIntegerKey(node->values["display_font_color"].c_str(), 3, &r, &g, &b)) +			return parserError("Could not parse color value"); +		_mode->displayFontColor = g_system->RGBToColor(r, g, b); +	} else +		_mode->displayFontColor = g_system->RGBToColor(0, 0, 0); // default to black + +	_layoutParsed = true; + +	return true; +} + +bool VirtualKeyboardParser::parserCallback_map(ParserNode *node) { +	return true; +} + +bool VirtualKeyboardParser::parserCallback_area(ParserNode *node) { + +	String& shape = node->values["shape"]; +	String& target = node->values["target"]; +	String& coords = node->values["coords"]; + +	if (target.equalsIgnoreCase("display_area")) { +		if (! shape.equalsIgnoreCase("rect")) +			return parserError("display_area must be a rect area"); +		_mode->displayArea = new Rect(); +		return parseRect(_mode->displayArea, coords); +	} else if (shape.equalsIgnoreCase("rect")) { +		Polygon *poly = _mode->imageMap.createArea(target); +		return parseRectAsPolygon(poly, coords); +	} else if (shape.equalsIgnoreCase("poly")) { +		Polygon *poly = _mode->imageMap.createArea(target); +		return parsePolygon(poly, coords); +	} +	return parserError("Area shape '%s' not known", shape.c_str()); +} + +byte VirtualKeyboardParser::parseFlags(const String& flags) { +	if (flags.empty()) +		return 0; + +	StringTokenizer tok(flags, ", "); +	byte val = 0; +	for (String fl = tok.nextToken(); !fl.empty(); fl = tok.nextToken()) { +		if (fl == "ctrl" || fl == "control") +			val |= KBD_CTRL; +		else if (fl == "alt") +			val |= KBD_ALT; +		else if (fl == "shift") +			val |= KBD_SHIFT; +	} +	return val; +} + +bool VirtualKeyboardParser::parseRect(Rect *rect, const String& coords) { +	int x1, y1, x2, y2; +	if (!parseIntegerKey(coords.c_str(), 4, &x1, &y1, &x2, &y2)) +		return parserError("Invalid coords for rect area"); +	rect->left = x1; rect->top = y1; rect->right = x2; rect->bottom = y2; +	if (!rect->isValidRect()) +		return parserError("Rect area is not a valid rectangle"); +	return true; +} + +bool VirtualKeyboardParser::parsePolygon(Polygon *poly, const String& coords) { +	StringTokenizer tok (coords, ", "); +	for (String st = tok.nextToken(); !st.empty(); st = tok.nextToken()) { +		int x, y; +		if (sscanf(st.c_str(), "%d", &x) != 1) +			return parserError("Invalid coords for polygon area"); +		st = tok.nextToken(); +		if (sscanf(st.c_str(), "%d", &y) != 1) +			return parserError("Invalid coords for polygon area"); +		poly->addPoint(x, y); +	} +	if (poly->getPointCount() < 3) +		return parserError("Invalid coords for polygon area"); + +	return true; +} + +bool VirtualKeyboardParser::parseRectAsPolygon(Polygon *poly, const String& coords) { +	Rect rect; +	if (!parseRect(&rect, coords)) +		return false; +	poly->addPoint(rect.left, rect.top); +	poly->addPoint(rect.right, rect.top); +	poly->addPoint(rect.right, rect.bottom); +	poly->addPoint(rect.left, rect.bottom); +	return true; +} + +} // end of namespace GUI diff --git a/backends/vkeybd/virtual-keyboard-parser.h b/backends/vkeybd/virtual-keyboard-parser.h new file mode 100644 index 0000000000..5ad353c516 --- /dev/null +++ b/backends/vkeybd/virtual-keyboard-parser.h @@ -0,0 +1,267 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef COMMON_VIRTUAL_KEYBOARD_PARSER +#define COMMON_VIRTUAL_KEYBOARD_PARSER + +#include "common/xmlparser.h" +#include "backends/vkeybd/virtual-keyboard.h" + +/** + +                   *************************************** +                   ** Virtual Keyboard Pack File Format ** +                   *************************************** + +The new virtual keyboard for ScummVM is implemented in the same way as a HTML  +ImageMap. It uses a single bitmap of the entire keyboard layout and then a  +image map description allows certain areas of the bitmap to be given special  +actions. Most of these actions will be a virtual key press event, but there  +will also be special keys that will change the keyboard layout or close the  +keyboard. The HTML image map description is contained in a larger XML file that +can describe all the different modes of the keyboard, and also different  +keyboard layouts for different screen resolutions. + +                ******************************************** +                ** Example keyboard pack description file ** +                ******************************************** + +<keyboard modes="normal,caps" initial_mode="normal" v_align="bottom" h_align="centre"> +  <mode name="normal" resolutions="640x400,320x200"> +    <layout resolution="640x400" bitmap="normal_640x400.bmp" transparent_color="255,0,255"> +      <map> +        <area shape="poly" coords="65,50,67,48,94,48,96,50,96,77,94,79,67,79,65,77" target="q" /> +        <area shape="poly" coords="105,50,107,48,134,48,136,50,136,77,134,79,107,79,105,77" target="w" /> +        <area shape="poly" coords="146,50,148,48,174,48,176,50,176,77,174,79,148,79,146,77" target="e" /> +		... +        <area shape="poly" coords="11,89,12,88,69,88,70,89,70,116,69,117,12,117,11,116" target="caps" /> +      </map> +	</layout> +	<layout resolution="320x200" bitmap="normal_320x200.bmp" transparent_color="255,0,255"> +	  ... +	</layout> +    <event name="a" type="key" code="97" ascii="97" modifiers="" /> +    <event name="b" type="key" code="98" ascii="98" modifiers="" /> +    <event name="c" type="key" code="99" ascii="99" modifiers="" /> +	... +	<event name="caps" type="switch_mode" mode="caps" /> +  </mode> + +  <mode name="caps" resolutions="640x400"> +    <layout resolution="640x400" bitmap="caps_640x480.bmp" transparent_color="255,0,255"> +      <map> +        <area shape="poly" coords="65,50,67,48,94,48,96,50,96,77,94,79,67,79,65,77" target="Q" /> +		... +      </map> +    </layout> +    <event name="A" type="key" code="97" ascii="65" modifiers="shift" /> +    <event name="B" type="key" code="98" ascii="66" modifiers="shift" /> +    <event name="C" type="key" code="99" ascii="67" modifiers="shift" /> +	... +  </mode> +</keyboard> + +************************* +** Description of tags ** +************************* + +<keyboard> + +This is the required, root element of the file format.  + +required attributes: + - initial_mode: name of the mode the keyboard will show initially + +optional attributes: + - v_align/h_align: where on the screen should the keyboard appear initially +   (defaults to bottom/center). + +child tags: + - mode + +------------------------------------------------------------------------------- + +<mode> + +This tag encapsulates a single mode of the keyboard. Within are a number of  +layouts, which provide the specific implementation at different resolutions. + +required attributes: + - name: the name of the mode  + - resolutions: list of the different layout resolutions  + +child tags: + - layout + - event + +------------------------------------------------------------------------------- + +<event> + +These tags describe a particular event that will be triggered by a mouse click +on a particular area. The target attribute of each image map area should be the +same as an event's name. + +required attributes: + - name: name of the event + - type: key | modifier | switch_mode | submit | cancel | clear | delete |  +   move_left | move_right - see VirtualKeyboard::EventType for explanation +for key events + - code / ascii: describe a key press in ScummVM KeyState format +for key and modifier events + - modifiers: modifier keystate as comma-separated list of shift, ctrl and/or  +   alt. +for switch_mode events + - mode: name of the mode that should be switched to +------------------------------------------------------------------------------- + +<layout> + +These tags encapsulate an implementation of a mode at a particular resolution. + +required attributes: + - resolution: the screen resolution that this layout is designed for + - bitmap: filename of the 24-bit bitmap that will be used for this layout + +optional attributes:  + - transparent_color: color in r,g,b format that will be used for keycolor  +   transparency (defaults to (255,0,255). + - display_font_color: color in r,g,b format that will be used for the text of +   the keyboard display (defaults to (0,0,0). + +child nodes: + - map: this describes the image map using the same format as html image maps + +------------------------------------------------------------------------------- + +<map> + +These tags describe the image map for a particular layout. It uses the same  +format as HTML image maps. The only area shapes that are supported are  +rectangles and polygons. The target attribute of each area should be the name +of an event for this mode (see <event> tag). They will usually be generated by +an external tool such as GIMP's Image Map plugin, and so will not be written  +by hand, but for more information on HTML image map format see  + - http://www.w3schools.com/TAGS/tag_map.asp  + - http://www.w3schools.com/TAGS/tag_area.asp + +*/ + +namespace Common { + +/** + * Subclass of Common::XMLParser that parses the virtual keyboard pack + * description file + */  +class VirtualKeyboardParser : public XMLParser { + +public: + +	/** +	 * Enum dictating how extensive a parse will be  +	 */ +	enum ParseMode { +		/** +		 * Full parse - when loading keyboard pack for first time +		 */ +		kParseFull,  +		/**  +		 * Just check resolutions and reload layouts if needed - following a  +		 * change in screen size +		 */ +		kParseCheckResolutions +	}; + +	VirtualKeyboardParser(VirtualKeyboard *kbd); +	void setParseMode(ParseMode m) { +		_parseMode = m; +	} + +protected: +	CUSTOM_XML_PARSER(VirtualKeyboardParser) { +		XML_KEY(keyboard) +			XML_PROP(initial_mode, true) +			XML_PROP(v_align, false) +			XML_PROP(h_align, false) +			XML_KEY(mode) +				XML_PROP(name, true) +				XML_PROP(resolutions, true) +				XML_KEY(layout) +					XML_PROP(resolution, true) +					XML_PROP(bitmap, true) +					XML_PROP(transparent_color, false) +					XML_PROP(display_font_color, false) +					XML_KEY(map) +						XML_KEY(area) +							XML_PROP(shape, true) +							XML_PROP(coords, true) +							XML_PROP(target, true) +						KEY_END() +					KEY_END() +				KEY_END() +				XML_KEY(event) +					XML_PROP(name, true) +					XML_PROP(type, true) +					XML_PROP(code, false) +					XML_PROP(ascii, false) +					XML_PROP(modifiers, false) +					XML_PROP(mode, false) +				KEY_END() +			KEY_END() +		KEY_END() +	} PARSER_END() + +protected: +	VirtualKeyboard *_keyboard; + +	/** internal state variables of parser */ +	ParseMode _parseMode; +	VirtualKeyboard::Mode *_mode; +	String _initialModeName; +	bool _kbdParsed; +	bool _layoutParsed; + +	/** Cleanup internal state before parse */ +	virtual void cleanup(); + +	/** Parser callback function */ +	bool parserCallback_keyboard(ParserNode *node); +	bool parserCallback_mode(ParserNode *node); +	bool parserCallback_event(ParserNode *node); +	bool parserCallback_layout(ParserNode *node); +	bool parserCallback_map(ParserNode *node); +	bool parserCallback_area(ParserNode *node); +	virtual bool closedKeyCallback(ParserNode *node); + +	/** Parse helper functions */ +	byte parseFlags(const String& flags); +	bool parseRect(Rect *rect, const String& coords); +	bool parsePolygon(Polygon *poly, const String& coords); +	bool parseRectAsPolygon(Polygon *poly, const String& coords); +}; + +} // end of namespace GUI + +#endif diff --git a/backends/vkeybd/virtual-keyboard.cpp b/backends/vkeybd/virtual-keyboard.cpp new file mode 100644 index 0000000000..fab2d80d30 --- /dev/null +++ b/backends/vkeybd/virtual-keyboard.cpp @@ -0,0 +1,390 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/vkeybd/virtual-keyboard.h" +#include "backends/vkeybd/virtual-keyboard-gui.h" +#include "backends/vkeybd/virtual-keyboard-parser.h" +#include "backends/vkeybd/keycode-descriptions.h" +#include "common/config-manager.h" +#include "common/fs.h" +#include "graphics/imageman.h" +#include "common/unzip.h" + +#define KEY_START_CHAR ('[') +#define KEY_END_CHAR (']') + +namespace Common { + +VirtualKeyboard::VirtualKeyboard() : _currentMode(0) { +	assert(g_system); +	_system = g_system; + +	_parser = new VirtualKeyboardParser(this); +	_kbdGUI = new VirtualKeyboardGUI(this); +	_submitKeys = _loaded = false; + +} + +VirtualKeyboard::~VirtualKeyboard() { +	deleteEvents(); +	delete _kbdGUI; +	delete _parser; +} + +void VirtualKeyboard::deleteEvents() { +	ModeMap::iterator it_m; +	VKEventMap::iterator it_e; +	for (it_m = _modes.begin(); it_m != _modes.end(); it_m++) { +		VKEventMap *evt = &(it_m->_value.events); +		for (it_e = evt->begin(); it_e != evt->end(); it_e++) +			delete it_e->_value; +	} +} + +void VirtualKeyboard::reset() { +	deleteEvents(); +	_modes.clear(); +	_initialMode = _currentMode = 0; +	_hAlignment = kAlignCentre; +	_vAlignment = kAlignBottom; +	_keyQueue.clear(); +	_loaded = false; +	_kbdGUI->reset(); +} + +bool VirtualKeyboard::loadKeyboardPack(String packName) { + +	_kbdGUI->initSize(_system->getOverlayWidth(), _system->getOverlayHeight()); + +	FilesystemNode *vkDir = 0; +	if (ConfMan.hasKey("vkeybdpath")) { +		vkDir = new FilesystemNode(ConfMan.get("vkeybdpath")); +	} else if (ConfMan.hasKey("extrapath")) { +		vkDir = new FilesystemNode(ConfMan.get("extrapath")); +	} else { // use current directory +		vkDir = new FilesystemNode("."); +	} + +	if (vkDir->getChild(packName + ".xml").exists()) { +		// uncompressed keyboard pack +		 +		if (!_parser->loadFile(vkDir->getChild(packName + ".xml"))) +			return false; +		 +	} else if (vkDir->getChild(packName + ".zip").exists()) { +		// compressed keyboard pack +#ifdef USE_ZLIB +		ZipArchive arch(vkDir->getChild(packName + ".zip").getPath().c_str()); +		if (arch.hasFile(packName + ".xml")) { +			if (!_parser->loadStream(arch.openFile(packName + ".xml"))) +				return false; +		} else { +			warning("Could not find %s.xml file in %s.zip keyboard pack", packName.c_str(), packName.c_str()); +			return false; +		} +		ImageMan.addArchive(vkDir->getChild(packName + ".zip").getPath().c_str()); +#else +		return false; +#endif +	} else { +		warning("Keyboard pack not found"); +		return false; +	} + +	_parser->setParseMode(VirtualKeyboardParser::kParseFull); +	_loaded = _parser->parse(); +	if (_loaded) +		printf("Keyboard pack '%s' loaded successfully!\n", packName.c_str()); + +	return _loaded; +} + +bool VirtualKeyboard::checkModeResolutions() +{ +	_parser->setParseMode(VirtualKeyboardParser::kParseCheckResolutions); +	_loaded = _parser->parse(); +	if (_currentMode) _kbdGUI->initMode(_currentMode); +	return _loaded; +} + +String VirtualKeyboard::findArea(int16 x, int16 y) { +	return _currentMode->imageMap.findMapArea(x, y); +} + +void VirtualKeyboard::processAreaClick(const String& area) { +	if (!_currentMode->events.contains(area)) return; +	VKEvent *evt = _currentMode->events[area]; + +	switch (evt->type) { +	case kVKEventKey: { +		// add virtual keypress to queue +		_keyQueue.insertKey(*(KeyState*)evt->data); +		break; +	} +	case kVKEventModifier: +		_keyQueue.toggleFlags(*(byte*)(evt->data)); +		break; +	case kVKEventSwitchMode: +		// switch to new mode +		switchMode((char *)evt->data); +		_keyQueue.clearFlags(); +		break; +	case kVKEventSubmit: +		close(true); +		break; +	case kVKEventCancel: +		close(false); +		break; +	case kVKEventClear: +		_keyQueue.clear(); +		break; +	case kVKEventDelete: +		_keyQueue.deleteKey(); +		break; +	case kVKEventMoveLeft: +		_keyQueue.moveLeft(); +		break; +	case kVKEventMoveRight: +		_keyQueue.moveRight(); +		break; +	} +} + +void VirtualKeyboard::switchMode(Mode *newMode) { +	_kbdGUI->initMode(newMode); +	_currentMode = newMode; +} + +void VirtualKeyboard::switchMode(const String& newMode) { +	if (!_modes.contains(newMode)) { +		warning("Keyboard mode '%s' unknown", newMode.c_str()); +		return; +	} +	switchMode(&_modes[newMode]); +} + +void VirtualKeyboard::handleMouseDown(int16 x, int16 y) { +	_areaDown = findArea(x, y); +	if (_areaDown.empty()) +		_kbdGUI->startDrag(x, y); +} + +void VirtualKeyboard::handleMouseUp(int16 x, int16 y) { +	if (!_areaDown.empty() && _areaDown == findArea(x, y)) { +		processAreaClick(_areaDown); +		_areaDown.clear(); +	} +	_kbdGUI->endDrag(); +} + +void VirtualKeyboard::show() { +	if (_loaded) _kbdGUI->checkScreenChanged(); +	if (!_loaded) { +		warning("Virtual keyboard not loaded"); +		return; +	} + +	switchMode(_initialMode); +	_kbdGUI->run(); + +	if (_submitKeys) { +		EventManager *eventMan = _system->getEventManager(); +		assert(eventMan); + +		// push keydown & keyup events into the event manager +		Event evt; +		evt.synthetic = false; +		while (!_keyQueue.empty()) { +			evt.kbd = _keyQueue.pop(); +			evt.type = EVENT_KEYDOWN; +			eventMan->pushEvent(evt); +			evt.type = EVENT_KEYUP; +			eventMan->pushEvent(evt); +		} +	} else { +		_keyQueue.clear(); +	} +} + +void VirtualKeyboard::close(bool submit) { +	_submitKeys = submit; +	_kbdGUI->close(); +} + +bool VirtualKeyboard::isDisplaying() {  +	return _kbdGUI->isDisplaying(); +} + +VirtualKeyboard::KeyPressQueue::KeyPressQueue() { +	_keyPos = _keys.end(); +	_strPos = 0; +	_strChanged = false; +	_flags = 0; +} + +void VirtualKeyboard::KeyPressQueue::toggleFlags(byte fl) { +	_flags ^= fl; +	_flagsStr.clear(); +	if (_flags) { +		_flagsStr = KEY_START_CHAR; +		if (_flags & KBD_CTRL) +			_flagsStr += "Ctrl+"; +		if (_flags & KBD_ALT) +			_flagsStr += "Alt+"; +		if (_flags & KBD_SHIFT) +			_flagsStr += "Shift+"; +	} +	_strChanged = true; +} + +void VirtualKeyboard::KeyPressQueue::clearFlags() { +	_flags = 0; +	_flagsStr.clear(); +	_strChanged = true; +} + +void VirtualKeyboard::KeyPressQueue::insertKey(KeyState key) { +	_strChanged = true; +	key.flags ^= _flags; +	if ((key.keycode >= KEYCODE_a) && (key.keycode <= KEYCODE_z)) +		key.ascii = (key.flags & KBD_SHIFT) ? key.keycode - 32 : key.keycode; +	clearFlags(); + +	String keyStr; +	if (key.flags & KBD_CTRL) keyStr += "Ctrl+"; +	if (key.flags & KBD_ALT) keyStr += "Alt+"; + +	if (key.ascii >= 32 && key.ascii <= 255) { +		if (key.flags & KBD_SHIFT && (key.ascii < 65 || key.ascii > 90)) +			keyStr += "Shift+"; +		keyStr += (char)key.ascii; +	} else { +		if (key.flags & KBD_SHIFT) keyStr += "Shift+"; +		if (key.keycode >= 0 && key.keycode < keycodeDescTableSize) +			keyStr += keycodeDescTable[key.keycode]; +	} + +	if (keyStr.empty()) keyStr += "???"; + +	_keysStr.insertChar(KEY_START_CHAR, _strPos++); +	const char *k = keyStr.c_str(); +	while (char ch = *k++) +		_keysStr.insertChar(ch, _strPos++); +	_keysStr.insertChar(KEY_END_CHAR, _strPos++); + +	VirtualKeyPress kp; +	kp.key = key; +	kp.strLen = keyStr.size() + 2; +	_keys.insert(_keyPos, kp); +} + +void VirtualKeyboard::KeyPressQueue::deleteKey() { +	if (_keyPos == _keys.begin()) +		return; +	List<VirtualKeyPress>::iterator it = _keyPos; +	it--; +	_strPos -= it->strLen; +	while((it->strLen)-- > 0) +		_keysStr.deleteChar(_strPos); +	_keys.erase(it); +	_strChanged = true; +} + +void VirtualKeyboard::KeyPressQueue::moveLeft() { +	if (_keyPos == _keys.begin()) +		return; +	_keyPos--; +	_strPos -= _keyPos->strLen; +	_strChanged = true; +} + +void VirtualKeyboard::KeyPressQueue::moveRight() { +	if (_keyPos == _keys.end()) +		return; +	_strPos += _keyPos->strLen; +	_keyPos++; +	_strChanged = true; +} + +KeyState VirtualKeyboard::KeyPressQueue::pop() { +	bool front = (_keyPos == _keys.begin()); +	VirtualKeyPress kp = *(_keys.begin()); +	_keys.pop_front(); + +	if (front) +		_keyPos = _keys.begin(); +	else +		_strPos -= kp.strLen; + +	while (kp.strLen-- > 0) +		_keysStr.deleteChar(0); + +	return kp.key; +} + +void VirtualKeyboard::KeyPressQueue::clear() { +	_keys.clear(); +	_keyPos = _keys.end(); +	_keysStr.clear(); +	_strPos = 0; +	clearFlags(); +	_strChanged = true; +} + +bool VirtualKeyboard::KeyPressQueue::empty() +{  +	return _keys.empty(); +} + +String VirtualKeyboard::KeyPressQueue::getString() +{ +	if (_keysStr.empty()) +		return _flagsStr; +	if (_flagsStr.empty()) +		return _keysStr; +	if (_strPos == _keysStr.size()) +		return _keysStr + _flagsStr; + +	uint len = _keysStr.size() + _flagsStr.size(); +	char *str = new char[len]; +	memcpy(str, _keysStr.c_str(), _strPos); +	memcpy(str + _strPos, _flagsStr.c_str(), _flagsStr.size()); +	memcpy(str + _strPos + _flagsStr.size(), _keysStr.c_str() + _strPos, _keysStr.size() - _strPos); +	String ret(str, len); +	delete[] str; +	return ret; +} + +uint VirtualKeyboard::KeyPressQueue::getInsertIndex() { +	return _strPos + _flagsStr.size(); +} + +bool VirtualKeyboard::KeyPressQueue::hasStringChanged() { +	bool ret = _strChanged; +	_strChanged = false; +	return ret; +} + +} // end of namespace Common diff --git a/backends/vkeybd/virtual-keyboard.h b/backends/vkeybd/virtual-keyboard.h new file mode 100644 index 0000000000..f2f7485c6d --- /dev/null +++ b/backends/vkeybd/virtual-keyboard.h @@ -0,0 +1,253 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef COMMON_VIRTUAL_KEYBOARD_H +#define COMMON_VIRTUAL_KEYBOARD_H + +class OSystem; + +#include "common/events.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "backends/vkeybd/image-map.h" +#include "common/keyboard.h" +#include "common/list.h" +#include "common/str.h" + +namespace Common { + +class VirtualKeyboardGUI; +class VirtualKeyboardParser; + +/** + * Class that handles the functionality of the virtual keyboard. + * This includes storage of the virtual key press events when the user clicks + * a key and delivery of them when the keyboard is closed, as well as managing + * the internal state of the keyboard, such as its active mode. + */   +class VirtualKeyboard { +protected: + +	/** +	 * Enum to describe the different types of events that can be associated +	 * with an area of the virtual keyboard bitmap. +	 */ +	enum VKEventType { +		/** Standard key press event */ +		kVKEventKey, +		/** Modifier key press event */ +		kVKEventModifier, +		/** Switch the mode of the keyboard */ +		kVKEventSwitchMode, +		/** Close the keyboard, submitting all keypresses */ +		kVKEventSubmit, +		/** Close the keyboard, without submitting keypresses */ +		kVKEventCancel, +		/** Clear the virtual keypress queue */  +		kVKEventClear, +		/** Move the keypress queue insert position backwards */ +		kVKEventMoveLeft, +		/** Move the keypress queue insert position forwards */ +		kVKEventMoveRight, +		/** Delete keypress from queue at the current insert position */ +		kVKEventDelete +	}; + +	/** VKEvent struct encapsulates data on a virtual keyboard event */ +	struct VKEvent { +		String name; +		VKEventType type; +		/**  +		 * Void pointer that will point to different types of data depending +		 * on the type of the event, these are: +		 * - KeyState struct for kVKEventKey events +		 * - a flags byte for kVKEventModifier events +		 * - c-string stating the name of the new mode for kSwitchMode events +		 */  +		void *data; +		 +		VKEvent() : data(0) {} +		~VKEvent() { +			if (data) free(data); +		} +	}; +	 +	typedef HashMap<String, VKEvent*> VKEventMap;  + +	/** +	 * Mode struct encapsulates all the data for each mode of the keyboard +	 */ +	struct Mode { +		String				name; +		String				resolution; +		String				bitmapName; +		Graphics::Surface	*image; +		OverlayColor		transparentColor; +		ImageMap			imageMap; +		VKEventMap			events; +		Rect				*displayArea; +		OverlayColor		displayFontColor; + +		Mode() : image(0), displayArea(0) {} +		~Mode() { delete displayArea; } +	}; +	 +	typedef HashMap<String, Mode, IgnoreCase_Hash, IgnoreCase_EqualTo> ModeMap; + +	enum HorizontalAlignment { +		kAlignLeft, +		kAlignCentre, +		kAlignRight +	}; + +	enum VerticalAlignment { +		kAlignTop, +		kAlignMiddle, +		kAlignBottom +	}; + +	struct VirtualKeyPress { +		KeyState key; +		/** length of the key presses description string */ +		uint strLen; +	}; + +	/**  +	 * Class that stores the queue of virtual key presses, as well as +	 * maintaining a string that represents a preview of the queue +	 */ +	class KeyPressQueue { +	public: +		KeyPressQueue(); +		void toggleFlags(byte fl); +		void clearFlags(); +		void insertKey(KeyState key); +		void deleteKey(); +		void moveLeft(); +		void moveRight(); +		KeyState pop(); +		void clear(); +		bool empty(); +		String getString(); +		uint getInsertIndex(); +		bool hasStringChanged(); + +	private: +		byte _flags; +		String _flagsStr; + + +		List<VirtualKeyPress> _keys; +		String _keysStr; + +		bool _strChanged; + +		List<VirtualKeyPress>::iterator _keyPos; +		uint _strPos; +	}; + +public: + +	VirtualKeyboard(); + +	virtual ~VirtualKeyboard(); +	 +	/** +	 * Loads the keyboard pack with the given name. +	 * The system first looks for an uncompressed keyboard pack by searching  +	 * for packName.xml in the filesystem, if this does not exist then it  +	 * searches for a compressed keyboard pack by looking for packName.zip. +	 * @param packName	name of the keyboard pack +	 */ +	bool loadKeyboardPack(String packName); + +	/** +	 * Shows the keyboard, starting an event loop that will intercept all +	 * user input (like a modal GUI dialog). +	 * It is assumed that the game has been paused, before this is called +	 */ +	void show(); + +	/** +	 * Hides the keyboard, ending the event loop. +	 * @param submit	if true all accumulated key presses are submitted to  +	 *					the event manager +	 */ +	void close(bool submit); + +	/** +	 * Returns true if the keyboard is currently being shown +	 */ +	bool isDisplaying(); + +	/** +	 * Returns true if the keyboard is loaded and ready to be shown +	 */ +	bool isLoaded() { +		return _loaded; +	} + +protected:	 + +	OSystem *_system; + +	friend class VirtualKeyboardGUI; +	VirtualKeyboardGUI	*_kbdGUI; + +	KeyPressQueue _keyQueue; +	 +	friend class VirtualKeyboardParser; +	VirtualKeyboardParser *_parser; + +	void reset(); +	void deleteEvents(); +	bool checkModeResolutions(); +	void switchMode(Mode *newMode); +	void switchMode(const String& newMode); +	void handleMouseDown(int16 x, int16 y); +	void handleMouseUp(int16 x, int16 y); +	String findArea(int16 x, int16 y); +	void processAreaClick(const String &area); + +	bool _loaded; + +	ModeMap _modes; +	Mode *_initialMode; +	Mode *_currentMode; + +	HorizontalAlignment  _hAlignment; +	VerticalAlignment    _vAlignment; + +	String _areaDown; + +	bool _submitKeys; + +}; + + +} // End of namespace Common + + +#endif diff --git a/backends/vkeybd/vkeybd.zip b/backends/vkeybd/vkeybd.zip Binary files differnew file mode 100644 index 0000000000..216512fef2 --- /dev/null +++ b/backends/vkeybd/vkeybd.zip diff --git a/base/main.cpp b/base/main.cpp index d571363f4a..b7179e7231 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -55,36 +55,6 @@  static bool launcherDialog(OSystem &system) { - -	system.beginGFXTransaction(); -		// Set the user specified graphics mode (if any). -		system.setGraphicsMode(ConfMan.get("gfx_mode").c_str()); - -		system.initSize(320, 200); - -		if (ConfMan.hasKey("aspect_ratio")) -			system.setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio")); -		if (ConfMan.hasKey("fullscreen")) -			system.setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen")); -	system.endGFXTransaction(); - -	// When starting up launcher for the first time, the user might have specified -	// a --gui-theme option, to allow that option to be working, we need to initialize -	// GUI here. -	// FIXME: Find a nicer way to allow --gui-theme to be working -	GUI::NewGui::instance(); - -	// Discard any command line options. Those that affect the graphics -	// mode and the others (like bootparam etc.) should not -	// blindly be passed to the first game launched from the launcher. -	ConfMan.getDomain(Common::ConfigManager::kTransientDomain)->clear(); - -	// Set initial window caption -	system.setWindowCaption(gScummVMFullVersion); - -	// Clear the main screen -	system.clearScreen(); -  #if defined(_WIN32_WCE)  	CELauncherDialog dlg;  #elif defined(__DC__) @@ -229,6 +199,38 @@ static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::St  	return result;  } +static void setupGraphics(OSystem &system) { +	 +	system.beginGFXTransaction(); +		// Set the user specified graphics mode (if any). +		system.setGraphicsMode(ConfMan.get("gfx_mode").c_str()); + +		system.initSize(320, 200); + +		if (ConfMan.hasKey("aspect_ratio")) +			system.setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio")); +		if (ConfMan.hasKey("fullscreen")) +			system.setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen")); +	system.endGFXTransaction(); + +	// When starting up launcher for the first time, the user might have specified +	// a --gui-theme option, to allow that option to be working, we need to initialize +	// GUI here. +	// FIXME: Find a nicer way to allow --gui-theme to be working +	GUI::NewGui::instance(); + +	// Discard any command line options. Those that affect the graphics +	// mode and the others (like bootparam etc.) should not +	// blindly be passed to the first game launched from the launcher. +	ConfMan.getDomain(Common::ConfigManager::kTransientDomain)->clear(); + +	// Set initial window caption +	system.setWindowCaption(gScummVMFullVersion); + +	// Clear the main screen +	system.clearScreen(); +} +  extern "C" int scummvm_main(int argc, char *argv[]) {  	Common::String specialDebug; @@ -285,6 +287,12 @@ extern "C" int scummvm_main(int argc, char *argv[]) {  	// the command line params) was read.  	system.initBackend(); +	setupGraphics(system); + +	// Init the event manager. As the virtual keyboard is loaded here, it must  +	// take place after the backend is initiated and the screen has been setup +	system.getEventManager()->init(); +  	// Unless a game was specified, show the launcher dialog  	if (0 == ConfMan.getActiveDomain())  		launcherDialog(system); @@ -329,7 +337,9 @@ extern "C" int scummvm_main(int argc, char *argv[]) {  			// screen to draw on yet.  			warning("Could not find any engine capable of running the selected game");  		} - +		 +		// reset the graphics to default +		setupGraphics(system);  		launcherDialog(system);  	}  	PluginManager::instance().unloadPlugins(); diff --git a/common/events.h b/common/events.h index f01282765a..645d9e4aed 100644 --- a/common/events.h +++ b/common/events.h @@ -123,6 +123,7 @@ struct Event {  	Common::Point mouse;  }; +class Keymapper;  /**   * The EventManager provides user input events to the client code. @@ -139,6 +140,12 @@ public:  		RBUTTON = 1 << 1  	}; + +	/** +	 * Initialise the event manager. +	 * @note	called after graphics system has been set up +	 */ +	virtual void init() {}  	/**  	 * Get the next event in the event queue.  	 * @param event	point to an Event struct, which will be filled with the event data. @@ -147,9 +154,9 @@ public:  	virtual bool pollEvent(Common::Event &event) = 0;  	/** -	 * Pushes a "fake" event of the specified type into the event queue +	 * Pushes a "fake" event into the event queue  	 */ -	virtual void pushEvent(Common::Event event) = 0; +	virtual void pushEvent(const Common::Event &event) = 0;  	/** Register random source so it can be serialized in game test purposes **/  	virtual void registerRandomSource(Common::RandomSource &rnd, const char *name) = 0; @@ -193,6 +200,9 @@ public:  	// TODO: Consider removing OSystem::getScreenChangeID and  	// replacing it by a generic getScreenChangeID method here + +	virtual Common::Keymapper *getKeymapper() = 0; +  protected:  	Common::Queue<Common::Event> artificialEventQueue; diff --git a/common/keyboard.h b/common/keyboard.h index 9b6558dbff..6a4445728f 100644 --- a/common/keyboard.h +++ b/common/keyboard.h @@ -259,6 +259,10 @@ struct KeyState {  		keycode = KEYCODE_INVALID;  		ascii = flags = 0;  	} + +	bool operator ==(const KeyState &x) const { +		return keycode == x.keycode && ascii == x.ascii && flags == x.flags; +	}  };  } // End of namespace Common diff --git a/common/module.mk b/common/module.mk index 599ffcf8a6..e04af5270b 100644 --- a/common/module.mk +++ b/common/module.mk @@ -17,6 +17,7 @@ MODULE_OBJS := \  	system.o \  	unarj.o \  	unzip.o \ +	xmlparser.o \  	zlib.o  # Include common rules diff --git a/common/queue.h b/common/queue.h index f1881345e8..be6df0148a 100644 --- a/common/queue.h +++ b/common/queue.h @@ -18,7 +18,7 @@   * along with this program; if not, write to the Free Software   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.   * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/branches/gsoc2008-rtl/common/stack.h $ + * $URL$   * $Id$   */ diff --git a/common/stack.h b/common/stack.h index 876efacc3f..238d0f6433 100644 --- a/common/stack.h +++ b/common/stack.h @@ -88,7 +88,11 @@ protected:  public:  	Stack<T>() {}  	Stack<T>(const Array<T> &stackContent) : _stack(stackContent) {} - +	 +	Stack<T>& operator=(const Stack<T> &st) { +		_stack = st._stack; +		return *this; +	}  	bool empty() const {  		return _stack.empty();  	} diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp new file mode 100644 index 0000000000..b93a5205be --- /dev/null +++ b/common/xmlparser.cpp @@ -0,0 +1,340 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/util.h" +#include "common/system.h" +#include "common/events.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/xmlparser.h" + +namespace Common { + +using namespace Graphics; + +bool XMLParser::parserError(const char *errorString, ...) { +	_state = kParserError; + +	int original_pos = _stream->pos(); +	int pos = original_pos; +	int lineCount = 1; +	int lineStart = 0; + +	if (_fileName == "Memory Stream") { +		lineStart = MAX(0, _pos - 35); +		lineCount = 0; +	} else { +		do { +			if (_char == '\n' || _char == '\r') { +				lineCount++; +		 +				if (lineStart == 0) +					lineStart = MAX(pos + 1, _pos - 60); +			} +			 +			_stream->seek(-1, SEEK_CUR); + +		} while (_stream->pos() > 0); +	} + +	char lineStr[70]; +	_stream->seek(original_pos - 35, SEEK_SET); +	_stream->readLine_NEW(lineStr, 70); +	 +	for (int i = 0; i < 70; ++i) +		if (lineStr[i] == '\n') +			lineStr[i] = ' '; + +	printf("\n  File <%s>, line %d:\n", _fileName.c_str(), lineCount); + +	bool startFull = lineStr[0] == '<'; +	bool endFull = lineStr[strlen(lineStr) - 1] == '>'; + +	printf("%s%s%s\n", startFull ? "" : "...", lineStr, endFull ? "" : "..."); + +	int cursor = 35; + +	if (!startFull) +		cursor += 3; + +	while (cursor--) +		printf(" "); + +	printf("^\n"); +	printf("Parser error: "); + +	va_list args; +	va_start(args, errorString); +	vprintf(errorString, args); +	va_end(args); + +	printf("\n\n"); + +	return false; +} + +bool XMLParser::parseActiveKey(bool closed) { +	bool ignore = false; +	assert(_activeKey.empty() == false); + +	ParserNode *key = _activeKey.top(); +	XMLKeyLayout *layout = (_activeKey.size() == 1) ? _XMLkeys : getParentNode(key)->layout; +	 +	if (layout->children.contains(key->name)) { +		key->layout = layout->children[key->name]; +	 +		Common::StringMap localMap = key->values; +		int keyCount = localMap.size(); +	 +		for (Common::List<XMLKeyLayout::XMLKeyProperty>::const_iterator i = key->layout->properties.begin(); i != key->layout->properties.end(); ++i) { +			if (i->required && !localMap.contains(i->name)) +				return parserError("Missing required property '%s' inside key '%s'", i->name.c_str(), key->name.c_str()); +			else if (localMap.contains(i->name)) +				keyCount--; +		} +	 +		if (keyCount > 0) +			return parserError("Unhandled property inside key '%s': '%s'", key->name.c_str(), localMap.begin()->_key.c_str()); +			 +	} else { +		return parserError("Unexpected key in the active scope: '%s'.", key->name.c_str()); +	} + +	// check if any of the parents must be ignored. +	// if a parent is ignored, all children are too. +	for (int i = _activeKey.size() - 1; i >= 0; --i) { +		if (_activeKey[i]->ignore) +			ignore = true; +	} + +	if (ignore == false && keyCallback(key) == false) { +		// HACK:  People may be stupid and overlook the fact that +		// when keyCallback() fails, a parserError() must be set. +		// We set it manually in that case. +		if (_state != kParserError) +			parserError("Unhandled exception when parsing '%s' key.", key->name.c_str()); +			 +		return false; +	} +	 +	if (closed) +		return closeKey(); + +	return true; +} + +bool XMLParser::parseKeyValue(Common::String keyName) { +	assert(_activeKey.empty() == false); + +	if (_activeKey.top()->values.contains(keyName)) +		return false; + +	_token.clear(); +	char stringStart; + +	if (_char == '"' || _char == '\'') { +		stringStart = _char; +		_char = _stream->readByte(); + +		while (_char && _char != stringStart) { +			_token += _char; +			_char = _stream->readByte(); +		} + +		if (_char == 0) +			return false; + +		_char = _stream->readByte(); + +	} else if (!parseToken()) { +		return false; +	} + +	_activeKey.top()->values[keyName] = _token; +	return true; +} + +bool XMLParser::closeKey() { +	bool ignore = false; +	bool result = true; +	 +	for (int i = _activeKey.size() - 1; i >= 0; --i) { +		if (_activeKey[i]->ignore) +			ignore = true; +	} +	 +	if (ignore == false) +		result = closedKeyCallback(_activeKey.top()); +		 +	delete _activeKey.pop(); +	 +	return result; +} + +bool XMLParser::parse() { + +	if (_stream == 0) +		return parserError("XML stream not ready for reading."); +		 +	if (_XMLkeys == 0) +		buildLayout(); + +	while (!_activeKey.empty()) +		delete _activeKey.pop(); + +	cleanup(); + +	bool activeClosure = false; +	bool selfClosure = false; + +	_state = kParserNeedKey; +	_pos = 0; +	_activeKey.clear(); + +	_char = _stream->readByte(); +	 +	while (_char && _state != kParserError) { +		if (skipSpaces()) +			continue; + +		if (skipComments()) +			continue; + +		switch (_state) { +			case kParserNeedKey: +				if (_char != '<') { +					parserError("Parser expecting key start."); +					break; +				} + +				if ((_char = _stream->readByte()) == 0) { +					parserError("Unexpected end of file."); +					break; +				} + +				if (_char == '/') { // FIXME: What if it's a comment start +					_char = _stream->readByte(); +					activeClosure = true; +				} + +				_state = kParserNeedKeyName; +				break; + +			case kParserNeedKeyName: +				if (!parseToken()) { +					parserError("Invalid key name."); +					break; +				} + +				if (activeClosure) { +					if (_activeKey.empty() || _token != _activeKey.top()->name) { +						parserError("Unexpected closure."); +						break; +					} +				} else { +					ParserNode *node = new ParserNode; +					node->name = _token; +					node->ignore = false; +					node->depth = _activeKey.size(); +					node->layout = 0; +					_activeKey.push(node); +				} + +				_state = kParserNeedPropertyName; +				break; + +			case kParserNeedPropertyName: +				if (activeClosure) { +					if (!closeKey()) { +						parserError("Missing data when closing key '%s'.", _activeKey.top()->name.c_str());  +						break; +					} + +					activeClosure = false; + +					if (_char != '>') +						parserError("Invalid syntax in key closure."); +					else  +						_state = kParserNeedKey; + +					_char = _stream->readByte(); +					break; +				} + +				selfClosure = false; + +				if (_char == '/') { // FIXME: comment start? +					selfClosure = true; +					_char = _stream->readByte(); +				} + +				if (_char == '>') { +					if (parseActiveKey(selfClosure)) { +						_char = _stream->readByte(); +						_state = kParserNeedKey; +					} +					break; +				} + +				if (!parseToken())  +					parserError("Error when parsing key value."); +				else  +					_state = kParserNeedPropertyOperator; + +				break; + +			case kParserNeedPropertyOperator: +				if (_char != '=')  +					parserError("Syntax error after key name."); +				else   +					_state = kParserNeedPropertyValue; + +				_char = _stream->readByte(); +				break; + +			case kParserNeedPropertyValue: +				if (!parseKeyValue(_token))  +					parserError("Invalid key value."); +				else  +					_state = kParserNeedPropertyName; + +				break; + +			default: +				break; +		} +	} + +	if (_state == kParserError) +		return false; + +	if (_state != kParserNeedKey || !_activeKey.empty()) +		return parserError("Unexpected end of file."); + +	return true; +} + +} + diff --git a/common/xmlparser.h b/common/xmlparser.h new file mode 100644 index 0000000000..dcbfc60c2f --- /dev/null +++ b/common/xmlparser.h @@ -0,0 +1,467 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef XML_PARSER_H +#define XML_PARSER_H + +#include "common/scummsys.h" +#include "graphics/surface.h" +#include "common/system.h" +#include "common/xmlparser.h" +#include "common/stream.h" +#include "common/file.h" +#include "common/fs.h" + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/stack.h" + +namespace Common { +	 +/* +	XMLParser.cpp/h -- Generic XML Parser +	===================================== + +	External documentation available at: +		http://www.smartlikearoboc.com/scummvm_doc/xmlparser_doc.html +*/ +			 +#define XML_KEY(keyName) {\ +		lay =  new CustomXMLKeyLayout;\ +		lay->callback = (&kLocalParserName::parserCallback_##keyName);\ +		layout.top()->children[#keyName] = lay;\ +		layout.push(lay); \ +		_layoutList.push_back(lay);\ +		for (Common::List<XMLKeyLayout::XMLKeyProperty>::const_iterator p = globalProps.begin(); p != globalProps.end(); ++p){\ +			layout.top()->properties.push_back(*p);} +		 +#define XML_KEY_RECURSIVE(keyName) {\ +			layout.top()->children[#keyName] = layout.top();\ +			layout.push(layout.top());\ +		} + +#define KEY_END() layout.pop(); } + +#define XML_PROP(propName, req) {\ +		prop.name = #propName; \ +		prop.required = req; \ +		layout.top()->properties.push_back(prop); } +		 +#define XML_GLOBAL_PROP(propName, req) {\ +		prop.name = #propName; \ +		prop.required = req;\ +		globalProps.push_back(prop); } +		 +	 +#define CUSTOM_XML_PARSER(parserName) \ +	protected: \ +	typedef parserName kLocalParserName; \ +	bool keyCallback(ParserNode *node) {return node->layout->doCallback(this, node); }\ +	struct CustomXMLKeyLayout : public XMLKeyLayout {\ +		typedef bool (parserName::*ParserCallback)(ParserNode *node);\ +		ParserCallback callback;\ +		bool doCallback(XMLParser *parent, ParserNode *node) {return ((kLocalParserName*)parent->*callback)(node);} };\ +	virtual void buildLayout() { \ +		Common::Stack<XMLKeyLayout*> layout; \ +		CustomXMLKeyLayout *lay = 0; \ +		XMLKeyLayout::XMLKeyProperty prop; \ +		Common::List<XMLKeyLayout::XMLKeyProperty> globalProps; \ +		_XMLkeys = new CustomXMLKeyLayout; \ +		layout.push(_XMLkeys); +	 +#define PARSER_END() layout.clear(); } + +/** + * The base XMLParser class implements generic functionality for parsing + * XML-like files. + * + * In order to use it, it must be inherited with a child class that implements + * the XMLParser::keyCallback() function. + * + * @see XMLParser::keyCallback() + */ +class XMLParser { + +public: +	/** +	 * Parser constructor. +	 */ +	XMLParser() : _XMLkeys(0), _stream(0) {} + +	virtual ~XMLParser() { +		while (!_activeKey.empty()) +			delete _activeKey.pop(); + +		delete _XMLkeys; +		delete _stream; + +		for (Common::List<XMLKeyLayout*>::iterator i = _layoutList.begin(); +			i != _layoutList.end(); ++i) +			delete *i; + +		_layoutList.clear(); +	} + +	/** Active state for the parser */ +	enum ParserState { +		kParserNeedKey, +		kParserNeedKeyName, + +		kParserNeedPropertyName, +		kParserNeedPropertyOperator, +		kParserNeedPropertyValue, + +		kParserError +	}; +	 +	struct XMLKeyLayout; +	struct ParserNode; +	 +	typedef Common::HashMap<Common::String, XMLParser::XMLKeyLayout*, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ChildMap; +	 +	/** nested struct representing the layout of the XML file */ +	struct XMLKeyLayout { +		struct XMLKeyProperty { +			Common::String name; +			bool required; +		}; +		 +		Common::List<XMLKeyProperty> properties; +		ChildMap children; +		 +		virtual bool doCallback(XMLParser *parent, ParserNode *node) = 0; +		 +		virtual ~XMLKeyLayout() { +			properties.clear(); +		} +	} *_XMLkeys; + +	/** Struct representing a parsed node */ +	struct ParserNode { +		Common::String name; +		Common::StringMap values; +		bool ignore; +		int depth; +		XMLKeyLayout *layout; +	}; + +	/** +	 * Loads a file into the parser. +	 * Used for the loading of Theme Description files +	 * straight from the filesystem. +	 * +	 * @param filename Name of the file to load. +	 */ +	bool loadFile(const Common::String &filename) { +		Common::File *f = new Common::File; + +		if (!f->open(filename)) { +			delete f; +			return false; +		} + +		_fileName = filename; +		_stream = f; +		return true; +	} +	 +	bool loadFile(const FilesystemNode &node) { +		Common::File *f = new Common::File; +		 +		if (!f->open(node)) { +			delete f; +			return false; +		} +		 +		_fileName = node.getName(); +		_stream = f; +		return true; +	} + +	/** +	 * Loads a memory buffer into the parser. +	 * Used for loading the default theme fallback directly +	 * from memory if no themes can be found. +	 * +	 * @param buffer Pointer to the buffer. +	 * @param size Size of the buffer +	 * @param disposable Sets if the XMLParser owns the buffer, +	 *                   i.e. if it can be freed safely after it's +	 *                   no longer needed by the parser. +	 */ +	bool loadBuffer(const byte *buffer, uint32 size, bool disposable = false) { +		_stream = new MemoryReadStream(buffer, size, disposable); +		_fileName = "Memory Stream"; +		return true; +	} +	 +	bool loadStream(SeekableReadStream *stream) { +		_stream = stream; +		_fileName = "Compressed File Stream"; +		return true; +	} + +	/** +	 * The actual parsing function. +	 * Parses the loaded data stream, returns true if successful. +	 */ +	bool parse(); + +	/** +	 * Returns the active node being parsed (the one on top of +	 * the node stack). +	 */ +	ParserNode *getActiveNode() { +		if (!_activeKey.empty()) +			return _activeKey.top(); + +		return 0; +	} + +	/** +	 * Returns the parent of a given node in the stack. +	 */ +	ParserNode *getParentNode(ParserNode *child) { +		return child->depth > 0 ? _activeKey[child->depth - 1] : 0; +	} + +protected: +	 +	/** +	 * The buildLayout function builds the layout for the parser to use +	 * based on a series of helper macros. This function is automatically +	 * generated by the CUSTOM_XML_PARSER() macro on custom parsers. +	 * +	 * See the documentation regarding XML layouts. +	 */ +	virtual void buildLayout() = 0; +	 +	/** +	 * The keycallback function is automatically overloaded on custom parsers +	 * when using the CUSTOM_XML_PARSER() macro.  +	 * +	 * Its job is to call the corresponding Callback function for the given node. +	 * A function for each key type must be declared separately. See the custom +	 * parser creation instructions. +	 * +	 * When parsing a key in such function, one may chose to skip it, e.g. because it's not needed +	 * on the current configuration. In order to ignore a key, you must set +	 * the "ignore" field of its KeyNode struct to "true": The key and all its children +	 * will then be automatically ignored by the parser. +	 * +	 * The callback function must return true if the key was properly handled (this includes the case when the +	 * key is being ignored). False otherwise. The return of keyCallback() is the same as +	 * the callback function's. +	 * See the sample implementation in GUI::ThemeParser. +	 */ +	virtual bool keyCallback(ParserNode *node) = 0; + +	/** +	 * The closed key callback function MAY be overloaded by inheriting classes to +	 * implement parser-specific functions. +	 * +	 * The closedKeyCallback is issued once a key has been finished parsing, to let +	 * the parser verify that all the required subkeys, etc, were included. +	 * +	 * Unlike the keyCallbacks(), there's just a closedKeyCallback() for all keys. +	 * Use "node->name" to distinguish between each key type. +	 * +	 * Returns true if the key was properly closed, false otherwise. +	 * By default, all keys are properly closed. +	 */ +	virtual bool closedKeyCallback(ParserNode *node) { +		return true; +	} +	 +	/** +	 * Called when a node is closed. Manages its cleanup and calls the +	 * closing callback function if needed. +	 */ +	bool closeKey(); + +	/** +	 * Parses the value of a given key. There's no reason to overload this. +	 */ +	bool parseKeyValue(Common::String keyName); + +	/** +	 * Called once a key has been parsed. It handles the closing/cleanup of the +	 * node stack and calls the keyCallback. +	 */ +	bool parseActiveKey(bool closed); + +	/** +	 * Prints an error message when parsing fails and stops the parser. +	 * Parser error always returns "false" so we can pass the return value directly +	 * and break down the parsing. +	 */ +	bool parserError(const char *errorString, ...) GCC_PRINTF(2, 3); + +	/** +	 * Skips spaces/whitelines etc. Returns true if any spaces were skipped. +	 */ +	bool skipSpaces() { +		if (!isspace(_char)) +			return false; + +		while (_char && isspace(_char)) +			_char = _stream->readByte(); + +		return true; +	} + +	/** +	 * Skips comment blocks and comment lines. +	 * Returns true if any comments were skipped. +	 * Overload this if you want to disable comments on your XML syntax +	 * or to change the commenting syntax. +	 */ +	virtual bool skipComments() { +		char endComment1 = 0, endComment2 = 0; + +		if (_char == '/') { +			_char = _stream->readByte(); + +			if (_char != '*') { +				_stream->seek(-1, SEEK_CUR); +				_char = '/'; +				return false; +			} +			 +			_char = _stream->readByte(); + +			while (_char) { +				endComment1 = endComment2; +				endComment2 = _char; +				_char = _stream->readByte(); + +				if (endComment1 == '*' && endComment2 == '/') +					break; + +				if (_char == 0) +					parserError("Comment has no closure."); +			} +			_char = _stream->readByte(); +			return true; +		} + +		return false; +	} + +	/** +	 * Check if a given character can be part of a KEY or VALUE name. +	 * Overload this if you want to support keys with strange characters +	 * in their name. +	 */ +	virtual inline bool isValidNameChar(char c) { +		return isalnum(c) || c == '_'; +	} + +	/** +	 * Parses a the first textual token found. +	 * There's no reason to overload this. +	 */ +	bool parseToken() { +		_token.clear(); + +		while (isValidNameChar(_char)) { +			_token += _char; +			_char = _stream->readByte(); +		} + +		return isspace(_char) != 0 || _char == '>' || _char == '=' || _char == '/'; +	} + +	/** +	 * Parses the values inside an integer key. +	 * The count parameter specifies the number of values inside +	 * the key, which are expected to be separated with commas. +	 * +	 * Sample usage: +	 * parseIntegerKey("255, 255, 255", 3, &red, &green, &blue); +	 * [will parse each field into its own integer] +	 * +	 * parseIntegerKey("1234", 1, &number); +	 * [will parse the single number into the variable] +	 * +	 * @param key String containing the integers to be parsed. +	 * @param count Number of comma-separated ints in the string. +	 * @param ... Integer variables to store the parsed ints, passed +	 *            by reference. +	 * @returns True if the parsing succeeded. +	 */ +	bool parseIntegerKey(const char *key, int count, ...) { +		char *parseEnd; +		int *num_ptr; + +		va_list args; +		va_start(args, count); + +		while (count--) { +			while (isspace(*key)) +				key++; + +			num_ptr = va_arg(args, int*); +			*num_ptr = strtol(key, &parseEnd, 10); + +			key = parseEnd; + +			while (isspace(*key)) +				key++; + +			if (count && *key++ != ',') +				return false; +		} + +		va_end(args); +		return (*key == 0); +	} + +	/** +	 * Overload if your parser needs to support parsing the same file +	 * several times, so you can clean up the internal state of the  +	 * parser before each parse. +	 */ +	virtual void cleanup() {} + +	Common::List<XMLKeyLayout*> _layoutList; + +private: +	int _pos; /** Current position on the XML buffer. */ +	char _char; +	SeekableReadStream *_stream; +	Common::String _fileName; + +	ParserState _state; /** Internal state of the parser */ + +	Common::String _error; /** Current error message */ +	Common::String _token; /** Current text token */ + +	Common::Stack<ParserNode*> _activeKey; /** Node stack of the parsed keys */ +}; + +} + +#endif diff --git a/dists/msvc8/scummvm.vcproj b/dists/msvc8/scummvm.vcproj index 5247336dc0..ceb98da482 100644 --- a/dists/msvc8/scummvm.vcproj +++ b/dists/msvc8/scummvm.vcproj @@ -1,7 +1,7 @@  <?xml version="1.0" encoding="windows-1252"?>  <VisualStudioProject  	ProjectType="Visual C++" -	Version="8,00" +	Version="8.00"  	Name="scummvm"  	ProjectGUID="{8434CB15-D08F-427D-9E6D-581AE5B28440}"  	RootNamespace="scummvm" @@ -465,6 +465,14 @@  				>  			</File>  			<File +				RelativePath="..\..\common\xmlparser.cpp" +				> +			</File> +			<File +				RelativePath="..\..\common\xmlparser.h" +				> +			</File> +			<File  				RelativePath="..\..\common\zlib.cpp"  				>  			</File> @@ -1039,6 +1047,98 @@  					</File>  				</Filter>  			</Filter> +			<Filter +				Name="vkeybd" +				> +				<File +					RelativePath="..\..\backends\vkeybd\image-map.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\image-map.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\keycode-descriptions.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\polygon.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\polygon.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard-gui.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard-gui.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard-parser.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard-parser.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\vkeybd\virtual-keyboard.h" +					> +				</File> +			</Filter> +			<Filter +				Name="keymapper" +				> +				<File +					RelativePath="..\..\backends\keymapper\action.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\action.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\hardware-key.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\keymap.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\keymap.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\keymapper.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\keymapper.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\remap-dialog.cpp" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\remap-dialog.h" +					> +				</File> +				<File +					RelativePath="..\..\backends\keymapper\types.h" +					> +				</File> +			</Filter>  		</Filter>  		<Filter  			Name="gui" @@ -1332,6 +1432,14 @@  				>  			</File>  			<File +				RelativePath="..\..\graphics\surface-keycolored.cpp" +				> +			</File> +			<File +				RelativePath="..\..\graphics\surface-keycolored.h" +				> +			</File> +			<File  				RelativePath="..\..\graphics\surface.cpp"  				>  			</File> diff --git a/graphics/module.mk b/graphics/module.mk index f658b056df..6a5f7f9e22 100644 --- a/graphics/module.mk +++ b/graphics/module.mk @@ -17,7 +17,8 @@ MODULE_OBJS := \  	scaler.o \  	scaler/thumbnail_intern.o \  	surface.o \ -	thumbnail.o +	thumbnail.o \ +	surface-keycolored.o  ifndef DISABLE_SCALERS  MODULE_OBJS += \ diff --git a/graphics/surface-keycolored.cpp b/graphics/surface-keycolored.cpp new file mode 100644 index 0000000000..86873a3944 --- /dev/null +++ b/graphics/surface-keycolored.cpp @@ -0,0 +1,44 @@ +#include "graphics/surface-keycolored.h" + +namespace Graphics { + +void SurfaceKeyColored::blit(Surface *surf_src, int16 x, int16 y, OverlayColor transparent) { +	 +	if (bytesPerPixel != sizeof(OverlayColor) || surf_src->bytesPerPixel != sizeof(OverlayColor)) return ; + +	const OverlayColor *src = (const OverlayColor*)surf_src->pixels; +	int blitW = surf_src->w; +	int blitH = surf_src->h; + +	// clip co-ordinates +	if (x < 0) { +		blitW += x; +		src -= x; +		x = 0; +	} +	if (y < 0) { +		blitH += y; +		src -= y * surf_src->w; +		y = 0; +	} +	if (blitW > w - x) blitW = w - x; +	if (blitH > h - y) blitH = h - y; +	if (blitW <= 0 || blitH <= 0) +		return; + +	OverlayColor *dst = (OverlayColor*) getBasePtr(x, y); +	int dstAdd = w - blitW; +	int srcAdd = surf_src->w - blitW; + +	for (int i = 0; i < blitH; ++i) {  +		for (int j = 0; j < blitW; ++j, ++dst, ++src) {  +			OverlayColor col = *src; +			if (col != transparent) +				*dst = col; +		} +		dst += dstAdd; +		src += srcAdd;  +	} +} + +} // end of namespace Graphics diff --git a/graphics/surface-keycolored.h b/graphics/surface-keycolored.h new file mode 100644 index 0000000000..43d5413275 --- /dev/null +++ b/graphics/surface-keycolored.h @@ -0,0 +1,17 @@ + +#ifndef GRAPHICS_SURFACE_KEYCOLORED_H +#define GRAPHICS_SURFACE_KEYCOLORED_H + +#include "graphics/surface.h" + +namespace Graphics { + +struct SurfaceKeyColored : Surface { + +	void blit(Surface *surf_src, int16 x, int16 y, OverlayColor transparent); +}; + + +} // end of namespace Graphics + +#endif diff --git a/gui/dialog.cpp b/gui/dialog.cpp index ef396301be..6fce837aa0 100644 --- a/gui/dialog.cpp +++ b/gui/dialog.cpp @@ -80,12 +80,12 @@ int Dialog::runModal() {  }  void Dialog::open() { -	Widget *w = _firstWidget;  	_result = 0;  	_visible = true;  	g_gui.openDialog(this); +	Widget *w = _firstWidget;  	// Search for the first objects that wantsFocus() (if any) and give it the focus  	while (w && !w->wantsFocus()) {  		w = w->_next; @@ -331,14 +331,18 @@ void Dialog::removeWidget(Widget *del) {  	Widget *w = _firstWidget;  	if (del == _firstWidget) { -		_firstWidget = _firstWidget->_next; +		Widget *del_next = del->_next; +		del->_next = 0; +		_firstWidget = del_next;  		return;  	}  	w = _firstWidget;  	while (w) {  		if (w->_next == del) { -			w->_next = w->_next->_next; +			Widget *del_next = del->_next; +			del->_next = 0; +			w->_next = del_next;  			return;  		}  		w = w->_next; diff --git a/gui/newgui.cpp b/gui/newgui.cpp index c340c1e8fd..4afc59367b 100644 --- a/gui/newgui.cpp +++ b/gui/newgui.cpp @@ -22,6 +22,7 @@   * $Id$   */ +#include "backends/keymapper/keymapper.h"  #include "common/events.h"  #include "common/system.h"  #include "common/util.h" @@ -236,7 +237,7 @@ void NewGui::runLoop() {  	}  	Common::EventManager *eventMan = _system->getEventManager(); - +	eventMan->getKeymapper()->pushKeymap("gui");  	while (!_dialogStack.empty() && activeDialog == getTopDialog()) {  		if (_needRedraw) {  			redraw(); @@ -328,6 +329,7 @@ void NewGui::runLoop() {  		// Delay for a moment  		_system->delayMillis(10);  	} +	eventMan->getKeymapper()->popKeymap();  	// HACK: since we reopen all dialogs anyway on redraw  	// we for now use Theme::closeAllDialogs here, until diff --git a/gui/theme-config.cpp b/gui/theme-config.cpp index 9fc23c5e7d..81e0a5c1d7 100644 --- a/gui/theme-config.cpp +++ b/gui/theme-config.cpp @@ -377,6 +377,14 @@ const char *Theme::_defaultConfigINI =  "scummsaveload_delete=prev.x (prev.y - 30) prev.w prev.h\n"  "scummsaveload_extinfo.visible=true\n"  "\n" +"# Keymapper remap dialog\n" +"remap=(10) (10) (w - 20) (h - 20)\n" +"remap_spacing=10\n" +"remap_popup=remap_spacing remap_spacing (remap.w - remap_spacing * 2) (kLineHeight + 2)\n" +"remap_keymap_area=remap_spacing (remap_popup.y + remap_popup.h + remap_spacing) (remap.w - remap_spacing * 2) (remap.h - self.y - remap_spacing * 2 - kBigButtonHeight)\n" +"remap_label_width=100\n" +"remap_button_width=80\n" +"remap_close_button=((remap.w - kButtonWidth) / 2) (remap_keymap_area.y + remap_keymap_area.h + remap_spacing) kButtonWidth kBigButtonHeight\n"  "############################################\n"  "[chooser]\n"  "chooserW=(w - 2 * 8)\n"  | 
