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" |