diff options
33 files changed, 3057 insertions, 49 deletions
diff --git a/Makefile.common b/Makefile.common index c2704f2219..9644426180 100644 --- a/Makefile.common +++ b/Makefile.common @@ -27,7 +27,12 @@ MODULES += \ sound \ common \ engines \ - backends + backends \ + common \ + graphics # HACK/FIXME: this extra 'common' was added because of a circular dependency between + # backends and common (the newly added Virtual Keyboard stuff depends on things + # from common). 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/common/hardware-key.h b/backends/common/hardware-key.h new file mode 100644 index 0000000000..de1984dc31 --- /dev/null +++ b/backends/common/hardware-key.h @@ -0,0 +1,95 @@ +#ifndef COMMON_HARDWAREKEY +#define COMMON_HARDWAREKEY + +#include "backends/common/user-action.h" + +namespace Common { + +/** +* Describes an available hardware key +*/ +struct HardwareKey { + /** unique id used for saving/loading to config */ + int32 id; + /** Human readable description */ + String description; + /** + * The KeyState that is generated by the back-end + * when this hardware key is pressed. + */ + KeyState key; + + UserActionCategory preferredCategory; + UserActionType preferredType; + int16 group; + + HardwareKey(KeyState ks = KeyState(), String des = "", + UserActionCategory cat = kGenericUserActionCategory, + UserActionType ty = kGenericUserActionType, int gr = 0) { + key = ks; + description = des; + preferredCategory = cat; + preferredType = ty; + group = gr; + } +}; + + +/** + * 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() {} + ~HardwareKeySet() { + List<HardwareKey*>::iterator it; + for (it = _keys.begin(); it != _keys.end(); it++) + delete *it; + } + + void addHardwareKey(HardwareKey *key) { + checkForKey(key); + _keys.push_back(key); + } + + const HardwareKey *findHardwareKey(int32 id) const { + List<HardwareKey*>::iterator it; + for (it = _keys.begin(); it != _keys.end(); it++) { + if ((*it)->id == id) + return (*it); + } + return 0; + } + + const HardwareKey *findHardwareKey(const KeyState& keystate) const { + List<HardwareKey*>::iterator it; + for (it = _keys.begin(); it != _keys.end(); it++) { + if ((*it)->key == keystate) + return (*it); + } + return 0; + } + + +private: + + void checkForKey(HardwareKey *key) { + List<HardwareKey*>::iterator it; + for (it = _keys.begin(); it != _keys.end(); it++) { + if ((*it)->id == key->id) + error("HardwareKey with id %d already given!\n", key->id); + else if ((*it)->key == key->key) + error("HardwareKey with same KeyState already given!\n"); + } + } + + List<HardwareKey*> _keys; +}; + + +} // end of namespace Common + +#endif
\ No newline at end of file diff --git a/backends/common/keymap-manager.cpp b/backends/common/keymap-manager.cpp new file mode 100644 index 0000000000..73ce705414 --- /dev/null +++ b/backends/common/keymap-manager.cpp @@ -0,0 +1,65 @@ +#include "backends/common/keymap-manager.h" + +#define GLOBAL_ID_STR "___GLOBAL" + +namespace Common { + + +void KeymapManager::Domain::addDefaultKeymap(Keymap *map) { + _defaultKeymap = map; +} + +void KeymapManager::Domain::addKeymap(const String& name, Keymap *map) { + if (_keymaps.contains(name)) + delete _keymaps[name]; + _keymaps[name] = map; +} + +void KeymapManager::Domain::deleteAllKeyMaps() { + KeymapMap::iterator it; + for (it = _keymaps.begin(); it != _keymaps.end(); it++) + delete it->_value; + _keymaps.clear(); +} + +Keymap *KeymapManager::Domain::getDefaultKeymap() { + return _defaultKeymap; +} + +Keymap *KeymapManager::Domain::getKeymap(const String& name) { + KeymapMap::iterator it = _keymaps.find(name); + if (it != _keymaps.end()) + return it->_value; + else + return 0; +} + + +void KeymapManager::registerDefaultGlobalKeymap(Keymap *map) { + _globalDomain.addDefaultKeymap(map); +} + +void KeymapManager::registerGlobalKeymap(const String& name, Keymap *map) { + _globalDomain.addKeymap(name, map); +} + +void KeymapManager::registerDefaultGameKeymap(Keymap *map) { + _gameDomain.addDefaultKeymap(map); +} + +void KeymapManager::registerGameKeymap(const String& name, Keymap *map) { + _gameDomain.addKeymap(name, map); +} + +void KeymapManager::unregisterAllGameKeymaps() { + _gameDomain.deleteAllKeyMaps(); +} + +Keymap *KeymapManager::getKeymap(const String& name) { + Keymap *keymap = _gameDomain.getKeymap(name); + if (!keymap) + _globalDomain.getKeymap(name); + return keymap; +} + +} // end of namespace Common
\ No newline at end of file diff --git a/backends/common/keymap-manager.h b/backends/common/keymap-manager.h new file mode 100644 index 0000000000..e5152d2426 --- /dev/null +++ b/backends/common/keymap-manager.h @@ -0,0 +1,51 @@ +#ifndef COMMON_KEYMAP_MANAGER +#define COMMON_KEYMAP_MANAGER + +#include "backends/common/keymap.h" +#include "common/hash-str.h" +#include "common/hashmap.h" + +namespace Common { + +class KeymapManager { +public: + + class Domain { + public: + Domain() : _defaultKeymap(0) {} + + void addDefaultKeymap(Keymap *map); + void addKeymap(const String& name, Keymap *map); + + void deleteAllKeyMaps(); + + Keymap *getDefaultKeymap(); + Keymap *getKeymap(const String& name); + + private: + typedef HashMap<String, Keymap*, + IgnoreCase_Hash, IgnoreCase_EqualTo> KeymapMap; + + Keymap *_defaultKeymap; + KeymapMap _keymaps; + }; + + void registerDefaultGlobalKeymap(Keymap *map); + void registerGlobalKeymap(const String& name, Keymap *map); + + void registerDefaultGameKeymap(Keymap *map); + void registerGameKeymap(const String& name, Keymap *map); + + void unregisterAllGameKeymaps(); + + Keymap *KeymapManager::getKeymap(const String& name); + +private: + + Domain _globalDomain; + Domain _gameDomain; +}; + +} // end of namespace Common + +#endif
\ No newline at end of file diff --git a/backends/common/keymap.cpp b/backends/common/keymap.cpp new file mode 100644 index 0000000000..9fb80bcc58 --- /dev/null +++ b/backends/common/keymap.cpp @@ -0,0 +1,83 @@ +#include "backends/common/keymap.h" + +namespace Common { + +Keymap::Keymap(const Keymap& km) : _actions(km._actions), _keymap() { + init(); + for (uint i = 0; i < _actions.size(); i++) { + if (_actions[i].hwKey) { + _keymap[_actions[i].hwKey->key] = &_actions[i]; + } + } +} + +void Keymap::init() { + _actions.reserve(20); +} + +void Keymap::addAction(const UserAction& action) { + if (findUserAction(action.id)) + error("UserAction with id %d already in KeyMap!\n", action.id); + _actions.push_back(action); + _actions[_actions.size()-1].hwKey = 0; +} + +void Keymap::mapKeyToAction(UserAction *action, HardwareKey *key) { + for (uint i = 0; i < _actions.size(); i++) { + if (&_actions[i] == action) { + internalMapKey(action, key); + return; + } + } + error("UserAction not contained in KeyMap\n"); +} + +void Keymap::mapKeyToAction(int32 id, HardwareKey *key) { + UserAction *act = findUserAction(id); + if (act) + internalMapKey(act, key); +} + +void Keymap::internalMapKey(UserAction *action, HardwareKey *hwKey) { + HashMap<KeyState, UserAction*>::iterator it; + it = _keymap.find(hwKey->key); + // if key is already mapped to an action then un-map it + if (it != _keymap.end()) + it->_value->hwKey = 0; + + action->hwKey = hwKey; + _keymap[hwKey->key] = action; +} + +const UserAction *Keymap::getUserAction(int32 id) const { + return findUserAction(id); +} + +UserAction *Keymap::findUserAction(int32 id) { + Array<UserAction>::iterator it; + for (it = _actions.begin(); it != _actions.end(); it++) { + if (it->id == id) + return &*it; + } + return 0; +} + +const UserAction *Keymap::findUserAction(int32 id) const { + Array<UserAction>::const_iterator it; + for (it = _actions.begin(); it != _actions.end(); it++) { + if (it->id == id) + return &*it; + } + return 0; +} + +UserAction *Keymap::getMappedAction(KeyState ks) const { + HashMap<KeyState, UserAction*>::iterator it; + it = _keymap.find(ks); + if (it == _keymap.end()) + return 0; + else + return it->_value; +} + +} // end of namespace Common
\ No newline at end of file diff --git a/backends/common/keymap.h b/backends/common/keymap.h new file mode 100644 index 0000000000..a2e204827e --- /dev/null +++ b/backends/common/keymap.h @@ -0,0 +1,89 @@ +#ifndef COMMON_KEYMAP +#define COMMON_KEYMAP + +#include "backends/common/hardware-key.h" +#include "backends/common/user-action.h" +#include "common/array.h" +#include "common/keyboard.h" +#include "common/func.h" +#include "common/hashmap.h" + +namespace Common { + +/** + * 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() { init(); } + Keymap(const Keymap& km); +private: + void init(); + +public: + /** + * Adds a new UserAction to this Map, + * adding it at the back of the internal array + * @param action the UserAction to add + */ + void addAction(const UserAction& action); + + /** + * Maps a HardwareKey to the given UserAction + * @param action must point to a UserAction in this Keymap + * @param key pointer to HardwareKey to map + * @note if action does not point to a UserAction in this Keymap a + * fatal error will occur + */ + void mapKeyToAction(UserAction *action, HardwareKey *key); + + /** + * Maps a HardwareKey to the UserAction of the given id + * @param id id of the UserAction to map to + * @param key pointer to HardwareKey to map + */ + void mapKeyToAction(int32 id, HardwareKey *key); + + /** + * Retrieves the UserAction with the given id + * @param id id of UserAction to retrieve + * @return Pointer to the UserAction or 0 if not found + */ + const UserAction *getUserAction(int32 id) const; + + /** + * Get a read-only array of all the UserActions contained in this Keymap + */ + const Array<UserAction>& getUserActions() const { return _actions; } + + /** + * Find the UserAction that a key is mapped to + * @param key the key that is mapped to the required UserAction + * @return a pointer to the UserAction or 0 if no + */ + UserAction *getMappedAction(KeyState key) const; + +private: + + UserAction *findUserAction(int32 id); + const UserAction *findUserAction(int32 id) const; + + void internalMapKey(UserAction *action, HardwareKey *hwKey); + + Array<UserAction> _actions; + HashMap<KeyState, UserAction*> _keymap; + +}; + + +} // end of namespace Common + +#endif
\ No newline at end of file diff --git a/backends/common/keymapper.cpp b/backends/common/keymapper.cpp new file mode 100644 index 0000000000..a70a691907 --- /dev/null +++ b/backends/common/keymapper.cpp @@ -0,0 +1,61 @@ +#include "backends/common/keymapper.h" +#include "backends/common/keymap-manager.h" +#include "common/config-manager.h" +namespace Common { + +Keymapper::Keymapper(EventManager *evtMgr) { + _eventMan = evtMgr; + _keymapMan = new KeymapManager(); + _currentMap = 0; + _hardwareKeys = 0; +} + +void Keymapper::registerHardwareKeySet(HardwareKeySet *keys) { + if (_hardwareKeys) + error("Hardware key set already registered!\n"); + _hardwareKeys = keys; +} + +const HardwareKeySet *Keymapper::getHardwareKeySet() const { + return _hardwareKeys; +} + +void Keymapper::addGlobalKeyMap(const String& name, Keymap *keymap) { + _keymapMan->registerGlobalKeymap(name, keymap); +} + +void Keymapper::addGameKeyMap(const String& name, Keymap *keymap) { + if (_gameId.size() == 0) { + initGame(); + if (_gameId.size() == 0) + return; + } + _keymapMan->registerGameKeymap(name, keymap); +} + +void Keymapper::initGame() { + if (ConfMan.getActiveDomain() == 0) + error("Call to Keymapper::initGame when no game loaded\n"); + + if (_gameId.size() > 0) + deInitGame(); + _gameId = ConfMan.getActiveDomainName(); +} + +void Keymapper::deInitGame() { + _keymapMan->unregisterAllGameKeymaps(); + _gameId.clear(); +} + + +bool Keymapper::switchKeymap(const String& name) { + Keymap *new_map = _keymapMan->getKeymap(name); + if (!new_map) { + warning("Keymap '%s' could not be found\n", name.c_str()); + return false; + } + _currentMap = new_map; + return true; +} + +} // end of namespace Common diff --git a/backends/common/keymapper.h b/backends/common/keymapper.h new file mode 100644 index 0000000000..d9b1b050fb --- /dev/null +++ b/backends/common/keymapper.h @@ -0,0 +1,46 @@ +#ifndef COMMON_KEYMAPPER +#define COMMON_KEYMAPPER + +#include "backends/common/keymap.h" +#include "common/list.h" + +namespace Common { + +class KeymapManager; + +class Keymapper { +public: + + Keymapper(EventManager *eventMan); + + void registerHardwareKeySet(HardwareKeySet *keys); + + const HardwareKeySet *getHardwareKeySet() const; + + void addGlobalKeyMap(const String& name, Keymap *keymap); + + void addGameKeyMap(const String& name, Keymap *keymap); + + void initGame(); + void deInitGame(); + + bool switchKeymap(const String& name); + +private: + + typedef List<HardwareKey*>::iterator Iterator; + + EventManager *_eventMan; + KeymapManager *_keymapMan; + + String _gameId; + + Keymap *_currentMap; + + const HardwareKeySet *_hardwareKeys; + +}; + +} // end of namespace Common + +#endif
\ No newline at end of file diff --git a/backends/common/user-action.h b/backends/common/user-action.h new file mode 100644 index 0000000000..31b5473708 --- /dev/null +++ b/backends/common/user-action.h @@ -0,0 +1,70 @@ +#ifndef COMMON_USERACTION +#define COMMON_USERACTION + +#include "common/events.h" +#include "common/list.h" +#include "common/str.h" + +namespace Common { + +struct HardwareKey; + +enum UserActionType { + kGenericUserActionType, + + // common actions + kDirectionUpUserAction, + kDirectionDownUserAction, + kDirectionLeftUserAction, + kDirectionRightUserAction, + kLeftClickUserAction, + kRightClickUserAction, + kSaveUserAction, + kMenuUserAction, + + kUserActionTypeMax +}; + +enum UserActionCategory { + kGenericUserActionCategory, + // classes of action - probably need to be slightly more specific than this + kInGameUserAction, // effects the actual gameplay + kSystemUserAction, //show a menu / change volume / etc + + kUserActionCategoryMax +}; + +struct UserAction { + /** unique id used for saving/loading to config */ + int32 id; + /** Human readable description */ + String description; + /** Events to be sent when mapped key is pressed */ + List<Event> events; + + UserActionCategory category; + UserActionType type; + int priority; + int group; + int flags; + + /** Hardware key that is mapped to this UserAction */ + HardwareKey *hwKey; + + UserAction( String des = "", + UserActionCategory cat = kGenericUserActionCategory, + UserActionType ty = kGenericUserActionType, + int pr = 0, int gr = 0, int fl = 0 ) { + description = des; + category = cat; + type = ty; + priority = pr; + group = gr; + flags = fl; + hwKey = 0; + } +}; + +} // end of namespace Common + +#endif
\ No newline at end of file diff --git a/backends/common/virtual-keyboard-parser.cpp b/backends/common/virtual-keyboard-parser.cpp new file mode 100644 index 0000000000..7b1e79e937 --- /dev/null +++ b/backends/common/virtual-keyboard-parser.cpp @@ -0,0 +1,365 @@ +/* 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/common/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; + + _callbacks["keyboard"] = &VirtualKeyboardParser::parserCallback_Keyboard; + _callbacks["mode"] = &VirtualKeyboardParser::parserCallback_Mode; + _callbacks["event"] = &VirtualKeyboardParser::parserCallback_Event; + _callbacks["layout"] = &VirtualKeyboardParser::parserCallback_Layout; + _callbacks["map"] = &VirtualKeyboardParser::parserCallback_Map; + _callbacks["area"] = &VirtualKeyboardParser::parserCallback_Area; + + _closedCallbacks["keyboard"] = &VirtualKeyboardParser::parserCallback_KeyboardClosed; + _closedCallbacks["mode"] = &VirtualKeyboardParser::parserCallback_ModeClosed; +} + +void VirtualKeyboardParser::cleanup() { + _mode = 0; + _kbdParsed = false; + _initialModeName.clear(); + if (_parseMode == kParseFull) { + // reset keyboard to remove existing config + _keyboard->reset(); + } +} + +bool VirtualKeyboardParser::keyCallback(Common::String keyName) { + if (!_callbacks.contains(_activeKey.top()->name)) + return parserError("%s is not a valid key name.", keyName.c_str()); + + return (this->*(_callbacks[_activeKey.top()->name]))(); +} + +bool VirtualKeyboardParser::closedKeyCallback(Common::String keyName) { + if (!_closedCallbacks.contains(_activeKey.top()->name)) + return true; + + return (this->*(_closedCallbacks[_activeKey.top()->name]))(); +} + +bool VirtualKeyboardParser::parserCallback_Keyboard() { + ParserNode *kbdNode = getActiveNode(); + + assert(kbdNode->name == "keyboard"); + + if (getParentNode(kbdNode) != 0) + return parserError("Keyboard element must be root"); + + if (_kbdParsed) + return parserError("Only a single keyboard element is allowed"); + + // if not full parse then we're done + if (_parseMode == kParseCheckResolutions) + return true; + + if (!kbdNode->values.contains("initial_mode")) + return parserError("Keyboard element must contain initial_mode attribute"); + + _initialModeName = kbdNode->values["initial_mode"]; + + if (kbdNode->values.contains("h_align")) { + Common::String h = kbdNode->values["h_align"]; + if (h == "left") + _keyboard->_hAlignment = VirtualKeyboard::kAlignLeft; + else if (h == "centre" || h == "center") + _keyboard->_hAlignment = VirtualKeyboard::kAlignCentre; + else if (h == "right") + _keyboard->_hAlignment = VirtualKeyboard::kAlignRight; + } + + if (kbdNode->values.contains("v_align")) { + Common::String v = kbdNode->values["h_align"]; + if (v == "top") + _keyboard->_vAlignment = VirtualKeyboard::kAlignTop; + else if (v == "middle" || v == "center") + _keyboard->_vAlignment = VirtualKeyboard::kAlignMiddle; + else if (v == "bottom") + _keyboard->_vAlignment = VirtualKeyboard::kAlignBottom; + } + + return true; +} + +bool VirtualKeyboardParser::parserCallback_KeyboardClosed() { + _kbdParsed = true; + if (!_keyboard->_initialMode) + return parserError("Initial mode of keyboard pack not defined"); + return true; +} + +bool VirtualKeyboardParser::parserCallback_Mode() { + ParserNode *modeNode = getActiveNode(); + + assert(modeNode->name == "mode"); + + if (getParentNode(modeNode) == 0 || getParentNode(modeNode)->name != "keyboard") + return parserError("Mode element must be child of keyboard element"); + + if (!modeNode->values.contains("name") || !modeNode->values.contains("resolutions")) + return parserError("Mode element must contain name and resolutions attributes"); + + Common::String name = modeNode->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; + } else + _mode = &(_keyboard->_modes[name]); + + Common::String resolutions = modeNode->values["resolutions"]; + Common::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; + Common::String newResolution; + for (Common::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) { + modeNode->ignore = true; + return true; + } else { + // remove data relating to old resolution + ImageMan.unregisterSurface(_mode->bitmapName); + _mode->bitmapName.clear(); + _mode->image = 0; + _mode->imageMap.removeAllAreas(); + } + } + + _mode->resolution = newResolution; + _layoutParsed = false; + + return true; +} + +bool VirtualKeyboardParser::parserCallback_ModeClosed() { + if (!_layoutParsed) { + return parserError("'%s' layout missing from '%s' mode", _mode->resolution.c_str(), _mode->name.c_str()); + } + return true; +} + +bool VirtualKeyboardParser::parserCallback_Event() { + ParserNode *evtNode = getActiveNode(); + + assert(evtNode->name == "event"); + + if (getParentNode(evtNode) == 0 || getParentNode(evtNode)->name != "mode") + return parserError("Event element must be child of mode element"); + + if (!evtNode->values.contains("name") || !evtNode->values.contains("type")) + return parserError("Event element must contain name and type attributes"); + + assert(_mode); + + // if just checking resolutions we're done + if (_parseMode == kParseCheckResolutions) + return true; + + Common::String name = evtNode->values["name"]; + if (_mode->events.contains(name)) + return parserError("Event '%s' has already been defined", name.c_str()); + + VirtualKeyboard::Event evt; + evt.name = name; + + Common::String type = evtNode->values["type"]; + if (type == "key") { + if (!evtNode->values.contains("code") || !evtNode->values.contains("ascii")) + return parserError("Key event element must contain code and ascii attributes"); + + evt.type = VirtualKeyboard::kEventKey; + + Common::KeyCode code = (Common::KeyCode)atoi(evtNode->values["code"].c_str()); + uint16 ascii = atoi(evtNode->values["ascii"].c_str()); + + byte flags = 0; + if (evtNode->values.contains("flags")) { + Common::StringTokenizer tok(evtNode->values["flags"], ", "); + for (Common::String fl = tok.nextToken(); !fl.empty(); fl = tok.nextToken()) { + if (fl == "ctrl" || fl == "control") + flags &= Common::KBD_CTRL; + else if (fl == "alt") + flags &= Common::KBD_ALT; + else if (fl == "shift") + flags &= Common::KBD_SHIFT; + } + } + + evt.data = new Common::KeyState(code, ascii, flags); + + } else if (type == "switch_mode") { + if (!evtNode->values.contains("mode")) + return parserError("Switch mode event element must contain mode attribute"); + + evt.type = VirtualKeyboard::kEventSwitchMode; + evt.data = new Common::String(evtNode->values["mode"]); + } else if (type == "close") { + evt.type = VirtualKeyboard::kEventClose; + evt.data = 0; + } else + return parserError("Event type '%s' not known", type.c_str()); + + _mode->events[name] = evt; + + return true; +} + +bool VirtualKeyboardParser::parserCallback_Layout() { + ParserNode *layoutNode = getActiveNode(); + + assert(layoutNode->name == "layout"); + + if (getParentNode(layoutNode) == 0 || getParentNode(layoutNode)->name != "mode") + return parserError("Layout element must be child of mode element"); + + if (!layoutNode->values.contains("resolution") || !layoutNode->values.contains("bitmap")) + return parserError("Layout element must contain resolution and bitmap attributes"); + + assert(!_mode->resolution.empty()); + + Common::String res = layoutNode->values["resolution"]; + + if (res != _mode->resolution) { + layoutNode->ignore = true; + return true; + } + + _mode->bitmapName = layoutNode->values["bitmap"]; + + 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 (layoutNode->values.contains("transparent_color")) { + int r, g, b; + if (!parseIntegerKey(layoutNode->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 + _mode->transparentColor = g_system->RGBToColor(255, 0, 255); // default to purple + + _layoutParsed = true; + + return true; +} + +bool VirtualKeyboardParser::parserCallback_Map() { + ParserNode *mapNode = getActiveNode(); + + assert(mapNode->name == "map"); + + if (getParentNode(mapNode) == 0 || getParentNode(mapNode)->name != "layout") + return parserError("Map element must be child of layout element"); + + return true; +} + +bool VirtualKeyboardParser::parserCallback_Area() { + ParserNode *areaNode = getActiveNode(); + + assert(areaNode->name == "area"); + + if (getParentNode(areaNode) == 0 || getParentNode(areaNode)->name != "map") + return parserError("Area element must be child of map element"); + + if (!areaNode->values.contains("shape") || !areaNode->values.contains("coords") || !areaNode->values.contains("target")) + return parserError("Area element must contain shape, coords and target attributes"); + + Common::String shape = areaNode->values["shape"]; + if (shape == "rect") { + Common::Rect *rect = _mode->imageMap.createRectArea(areaNode->values["target"]); + int x1, y1, x2, y2; + if (!parseIntegerKey(areaNode->values["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; + } else if (shape == "poly") { + Common::StringTokenizer tok (areaNode->values["coords"], ", "); + Common::Polygon *poly = _mode->imageMap.createPolygonArea(areaNode->values["target"]); + for (Common::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"); + } else + return parserError("Area shape '%s' not known", shape.c_str()); + + return true; +} + +} // end of namespace GUI diff --git a/backends/common/virtual-keyboard-parser.h b/backends/common/virtual-keyboard-parser.h new file mode 100644 index 0000000000..cd2ea28faf --- /dev/null +++ b/backends/common/virtual-keyboard-parser.h @@ -0,0 +1,212 @@ +/* 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/common/virtual-keyboard.h" + +/** + TODO - information about optional attributes and their default values + + + *************************************** + ** 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. + +attributes: + - modes: lists all the modes that the keyboard pack contains + - initial_mode: which mode the keyboard should show initially + - v_align/h_align: where on the screen should the keyboard appear initially + +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. + +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. + +attributes: + - name: name of the event + - type: what sort of event is it (key | switch_mode | close) + - for key events + - code / ascii / modifiers: describe a key press in ScummVM KeyState format + - for switch_mode events + - mode: the mode that should be switched to + +------------------------------------------------------------------------------- + +<layout> + +These tags encapsulate an implementation of a mode at a particular resolution. + +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 + - transparent_color: color in r,b,g format that will be used for keycolor + transparency. + +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 exact +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). For 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 { + +enum ParseMode { + kParseFull, // when loading keyboard pack for first time + kParseCheckResolutions // when re-parsing following a change in screen size +}; + +class VirtualKeyboardParser : public Common::XMLParser { + + typedef bool (VirtualKeyboardParser::*ParserCallback)(); + +public: + + VirtualKeyboardParser(VirtualKeyboard *kbd); + void setParseMode(ParseMode m) { + _parseMode = m; + } + +protected: + VirtualKeyboard *_keyboard; + + /** internal state variables of parser */ + ParseMode _parseMode; + VirtualKeyboard::Mode *_mode; // pointer to mode currently being parsed + Common::String _initialModeName; + bool _kbdParsed; + bool _layoutParsed; + + bool keyCallback(Common::String keyName); + bool closedKeyCallback(Common::String keyName); + void cleanup(); + + bool parserCallback_Keyboard(); + bool parserCallback_Mode(); + bool parserCallback_Event(); + bool parserCallback_Layout(); + bool parserCallback_Map(); + bool parserCallback_Area(); + + bool parserCallback_KeyboardClosed(); + bool parserCallback_ModeClosed(); + + Common::HashMap<Common::String, ParserCallback, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _callbacks; + Common::HashMap<Common::String, ParserCallback, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _closedCallbacks; +}; + +} // end of namespace GUI + +#endif diff --git a/backends/common/virtual-keyboard.cpp b/backends/common/virtual-keyboard.cpp new file mode 100644 index 0000000000..21d188dced --- /dev/null +++ b/backends/common/virtual-keyboard.cpp @@ -0,0 +1,385 @@ +/* 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/common/virtual-keyboard.h" +#include "backends/common/virtual-keyboard-parser.h" +#include "common/config-manager.h" +#include "common/events.h" +#include "common/unzip.h" +#include "graphics/cursorman.h" +#include "graphics/imageman.h" +#include "graphics/surface-keycolored.h" +#include "gui/newgui.h" + +namespace Common { + +VirtualKeyboard::VirtualKeyboard() : _currentMode(0), _keyDown(0) { + assert(g_system); + _system = g_system; + + _parser = new VirtualKeyboardParser(this); + _loaded = _displaying = _drag = false; + _lastScreenChanged = _system->getScreenChangeID(); + + memset(_cursor, 0xFF, sizeof(_cursor)); +} + +VirtualKeyboard::~VirtualKeyboard() { + // TODO: clean up event data pointers + deleteEventData(); + delete _parser; +} + +void VirtualKeyboard::reset() { + // TODO: clean up event data pointers + deleteEventData(); + _modes.clear(); + _initialMode = _currentMode = 0; + _kbdBound.left = _kbdBound.top + = _kbdBound.right = _kbdBound.bottom = 0; + _hAlignment = kAlignCentre; + _vAlignment = kAlignBottom; + _keyQueue.clear(); + _keyDown = 0; + _displaying = _drag = false; + _firstRun = true; + _lastScreenChanged = _system->getScreenChangeID(); +} + +void VirtualKeyboard::deleteEventData() { + ModeMap::iterator it_m; + EventMap::iterator it_e; + for (it_m = _modes.begin(); it_m != _modes.end(); it_m++) { + EventMap *evt = &(it_m->_value.events); + for (it_e = evt->begin(); it_e != evt->end(); it_e++) + delete it_e->_value.data; + } +} + +bool VirtualKeyboard::loadKeyboardPack(Common::String packName) { + + if (Common::File::exists(packName + ".xml")) { + // uncompressed keyboard pack + if (!_parser->loadFile(packName + ".xml")) + return false; + + } else if (Common::File::exists(packName + ".zip")) { + // compressed keyboard pack +#ifdef USE_ZLIB + unzFile zipFile = unzOpen((packName + ".zip").c_str()); + if (zipFile && unzLocateFile(zipFile, (packName + ".xml").c_str(), 2) == UNZ_OK) { + unz_file_info fileInfo; + unzOpenCurrentFile(zipFile); + unzGetCurrentFileInfo(zipFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0); + byte *buffer = new byte[fileInfo.uncompressed_size+1]; + assert(buffer); + memset(buffer, 0, (fileInfo.uncompressed_size+1)*sizeof(byte)); + unzReadCurrentFile(zipFile, buffer, fileInfo.uncompressed_size); + unzCloseCurrentFile(zipFile); + if (!_parser->loadBuffer(buffer, fileInfo.uncompressed_size+1, true)) { + unzClose(zipFile); + return false; + } + } else { + warning("Could not find %s.xml file in %s.zip keyboard pack\n", packName.c_str(), packName.c_str()); + unzClose(zipFile); + return false; + } + unzClose(zipFile); + + ImageMan.addArchive(packName + ".zip"); +#else + return false; +#endif + } else { + warning("Keyboard pack not found"); + return false; + } + + _parser->setParseMode(kParseFull); + _loaded = _parser->parse(); + if (_loaded) + printf("Keyboard pack '%s' loaded successfully!\n", packName.c_str()); + + return _loaded; +} + +void VirtualKeyboard::setDefaultPosition() +{ + int16 scrW = _system->getOverlayWidth(), scrH = _system->getOverlayHeight(); + int16 kbdW = _kbdBound.width(), kbdH = _kbdBound.height(); + int16 posX = 0, posY = 0; + if (scrW != kbdW) { + switch (_hAlignment) { + case kAlignLeft: + posX = 0; + break; + case kAlignCentre: + posX = (scrW - kbdW) / 2; + break; + case kAlignRight: + posX = scrW - kbdW; + break; + } + } + if (scrH != kbdH) { + switch (_vAlignment) { + case kAlignTop: + posY = 0; + break; + case kAlignMiddle: + posY = (scrH - kbdH) / 2; + break; + case kAlignBottom: + posY = scrH - kbdH; + break; + } + } + _kbdBound.moveTo(posX, posY); +} + +bool VirtualKeyboard::checkModeResolutions() +{ + _parser->setParseMode(kParseCheckResolutions); + _loaded = _parser->parse(); + return _loaded; +} + +void VirtualKeyboard::move(int16 x, int16 y) { + // snap to edge of screen + if (ABS(x) < SNAP_WIDTH) + x = 0; + int16 x2 = _system->getOverlayWidth() - _kbdBound.width(); + if (ABS(x - x2) < SNAP_WIDTH) + x = x2; + if (ABS(y) < SNAP_WIDTH) + y = 0; + int16 y2 = _system->getOverlayHeight() - _kbdBound.height(); + if (ABS(y - y2) < SNAP_WIDTH) + y = y2; + + _kbdBound.moveTo(x, y); +} + +Common::String VirtualKeyboard::findArea(int16 x, int16 y) { + x -= _kbdBound.left; + y -= _kbdBound.top; + if (x < 0 || x > _kbdBound.width()) return ""; + if (y < 0 || y > _kbdBound.height()) return ""; + return _currentMode->imageMap.findMapArea(x, y); +} + +void VirtualKeyboard::processClick(const Common::String& area) { + if (!_currentMode->events.contains(area)) return; + Event evt = _currentMode->events[area]; + + switch (evt.type) { + case kEventKey: + // add virtual keypress to queue + _keyQueue.push(*(Common::KeyState*)evt.data); + break; + case kEventSwitchMode: + // switch to new mode + switchMode(*(Common::String *)evt.data); + break; + case kEventClose: + // close virtual keyboard + _displaying = false; + break; + } +} + +void VirtualKeyboard::switchMode(Mode *newMode) { + _kbdBound.setWidth(newMode->image->w); + _kbdBound.setHeight(newMode->image->h); + _currentMode = newMode; + _needRedraw = true; +} + +void VirtualKeyboard::switchMode(const Common::String& newMode) { + if (!_modes.contains(newMode)) { + warning("Keyboard mode '%s' unknown", newMode.c_str()); + return; + } + switchMode(&_modes[newMode]); +} + +void VirtualKeyboard::show() { + if (!_loaded) { + // if not loaded then load default "vkeybd" pack + if (!loadKeyboardPack("vkeybd")) { + warning("Keyboard not loaded therefore can't be shown"); + return; + } + } + if (_lastScreenChanged != _system->getScreenChangeID()) + screenChanged(); + switchMode(_initialMode); + _displaying = true; + if (_firstRun) { + _firstRun = false; + setDefaultPosition(); + } + + if (!g_gui.isActive()) { + _system->showOverlay(); + _system->clearOverlay(); + } + + _overlayBackup.create(_system->getOverlayWidth(), _system->getOverlayHeight(), sizeof(OverlayColor)); + _system->grabOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w); + setupCursor(); + + runLoop(); + + removeCursor(); + _system->copyRectToOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w, 0, 0, _overlayBackup.w, _overlayBackup.h); + if (!g_gui.isActive()) _system->hideOverlay(); + _overlayBackup.free(); +} + +void VirtualKeyboard::hide() { + _displaying = false; +} + +void VirtualKeyboard::screenChanged() { + _lastScreenChanged = _system->getScreenChangeID(); + if (!checkModeResolutions()) + _displaying = false; +} + +void VirtualKeyboard::runLoop() { + Common::EventManager *eventMan = _system->getEventManager(); + + while (_displaying) { + if (_needRedraw) redraw(); + + animateCursor(); + _system->updateScreen(); + Common::Event event; + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + if (_kbdBound.contains(event.mouse)) { + _areaDown = findArea(event.mouse.x, event.mouse.y); + if (_areaDown.empty()) { + _drag = true; + _dragPoint.x = event.mouse.x - _kbdBound.left; + _dragPoint.y = event.mouse.y - _kbdBound.top; + } + } else + _areaDown.clear(); + break; + case Common::EVENT_LBUTTONUP: + if (_drag) _drag = false; + if (!_areaDown.empty() && _areaDown == findArea(event.mouse.x, event.mouse.y)) { + processClick(_areaDown); + _areaDown.clear(); + } + break; + case Common::EVENT_MOUSEMOVE: + if (_drag) { + move(event.mouse.x - _dragPoint.x, + event.mouse.y - _dragPoint.y); + _needRedraw = true; + } + break; + case Common::EVENT_SCREEN_CHANGED: + screenChanged(); + break; + case Common::EVENT_QUIT: + _system->quit(); + return; + default: + break; + } + } + // Delay for a moment + _system->delayMillis(10); + } + + // push keydown & keyup events into the event manager + Common::Event evt; + evt.synthetic = false; + while (!_keyQueue.empty()) { + evt.kbd = _keyQueue.pop(); + evt.type = Common::EVENT_KEYDOWN; + eventMan->pushEvent(evt); + evt.type = Common::EVENT_KEYUP; + eventMan->pushEvent(evt); + } +} + +void VirtualKeyboard::redraw() { + Graphics::SurfaceKeyColored surf; + + surf.create(_system->getOverlayWidth(), _system->getOverlayHeight(), sizeof(OverlayColor)); + + memcpy(surf.pixels, _overlayBackup.pixels, surf.w * surf.h * sizeof(OverlayColor)); + surf.blit(_currentMode->image, _kbdBound.left, _kbdBound.top, _currentMode->transparentColor); + + _system->copyRectToOverlay((OverlayColor*)surf.pixels, surf.w, 0, 0, surf.w, surf.h); + + surf.free(); + + _needRedraw = false; +} + +void VirtualKeyboard::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 VirtualKeyboard::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 VirtualKeyboard::removeCursor() { + CursorMan.popCursor(); + CursorMan.popCursorPalette(); +} + +} // end of namespace Common diff --git a/backends/common/virtual-keyboard.h b/backends/common/virtual-keyboard.h new file mode 100644 index 0000000000..fc2300c24c --- /dev/null +++ b/backends/common/virtual-keyboard.h @@ -0,0 +1,185 @@ +/* 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 "common/image-map.h" +#include "common/keyboard.h" +#include "common/queue.h" +#include "common/str.h" +#include "graphics/surface.h" + +namespace Common { + +class VirtualKeyboardParser; + +class VirtualKeyboard { + + enum EventType { + kEventKey, + kEventSwitchMode, + kEventClose + }; + + struct Event { + Common::String name; + EventType type; + Graphics::Surface *rollOverImage; + void *data; + }; + + typedef Common::HashMap<Common::String, Event, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> EventMap; + + struct Mode { + Common::String name; + Common::String resolution; + Common::String bitmapName; + Graphics::Surface *image; + OverlayColor transparentColor; + Common::ImageMap imageMap; + EventMap events; + Mode() : image(0) {} + }; + + typedef Common::HashMap<Common::String, Mode, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ModeMap; + + enum HorizontalAlignment { + kAlignLeft, + kAlignCentre, + kAlignRight + }; + + enum VerticalAlignment { + kAlignTop, + kAlignMiddle, + kAlignBottom + }; + +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(Common::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. + */ + void hide(); + + /** + * Returns true if the keyboard is currently being shown + */ + bool isDisplaying() { + return _displaying; + } + + /** + * Returns true if the keyboard is loaded and ready to be shown + */ + bool isLoaded() { + return _loaded; + } + +protected: + OSystem *_system; + + static const int SNAP_WIDTH = 10; + + friend class VirtualKeyboardParser; + VirtualKeyboardParser *_parser; + + // TODO : sort order of all this stuff + void reset(); + void deleteEventData(); + void screenChanged(); + bool checkModeResolutions(); + void setDefaultPosition(); + void move(int16 x, int16 y); + void switchMode(Mode *newMode); + void switchMode(const Common::String& newMode); + Common::String findArea(int16 x, int16 y); + void processClick(const Common::String &area); + void runLoop(); + void redraw(); + + Graphics::Surface _overlayBackup; + + bool _loaded; + bool _displaying; + bool _needRedraw; + bool _firstRun; + + ModeMap _modes; + Mode *_initialMode; + Mode *_currentMode; + + int _lastScreenChanged; + Common::Rect _kbdBound; + + HorizontalAlignment _hAlignment; + VerticalAlignment _vAlignment; + + Common::String _areaDown; + Common::Point _dragPoint; + bool _drag; + + Common::Queue<Common::KeyState> _keyQueue; + Common::KeyState *_keyDown; + + static const int kCursorAnimateDelay = 250; + int _cursorAnimateCounter; + int _cursorAnimateTimer; + byte _cursor[2048]; + void setupCursor(); + void removeCursor(); + void animateCursor(); + +}; + + +} // End of namespace GUI + + +#endif diff --git a/backends/events/default/default-events.cpp b/backends/events/default/default-events.cpp index 0caba25792..3a77b0114c 100644 --- a/backends/events/default/default-events.cpp +++ b/backends/events/default/default-events.cpp @@ -191,9 +191,13 @@ DefaultEventManager::DefaultEventManager(OSystem *boss) : _hasPlaybackEvent = false; } + + _vk = new Common::VirtualKeyboard(); + _artificialEventCounter = 0; } DefaultEventManager::~DefaultEventManager() { + delete _vk; _boss->lockMutex(_timeMutex); _boss->lockMutex(_recorderMutex); _recordMode = kPassthrough; @@ -348,8 +352,17 @@ void DefaultEventManager::processMillis(uint32 &millis) { bool DefaultEventManager::pollEvent(Common::Event &event) { uint32 time = _boss->getMillis(); bool result; - - result = _boss->pollEvent(event); + + if (!_artificialEventQueue.empty()) { + // delay the feeding of artificial events + if (++_artificialEventCounter % kArtificialEventDelay == 0) { + event = _artificialEventQueue.pop(); + result = true; + _artificialEventCounter = 0; + } else + result = _boss->pollEvent(event); + } else + result = _boss->pollEvent(event); if (_recordMode != kPassthrough) { @@ -384,6 +397,20 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { _currentKeyDown.flags = event.kbd.flags; _keyRepeatTime = time + kKeyRepeatInitialDelay; #endif + + // HACK to show/hide keyboard (keyboard is not shown if gui is active) + if (event.kbd.keycode == Common::KEYCODE_F6 && event.kbd.flags == 0) { + if (_vk->isDisplaying()) { + _vk->hide(); + } 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; + } + } + break; case Common::EVENT_KEYUP: _modifierState = event.kbd.flags; @@ -447,4 +474,8 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { return result; } +void DefaultEventManager::pushEvent(const Common::Event &event) { + _artificialEventQueue.push(event); +} + #endif // !defined(DISABLE_DEFAULT_EVENTMANAGER) diff --git a/backends/events/default/default-events.h b/backends/events/default/default-events.h index 98dcd4b3de..891ee3e744 100644 --- a/backends/events/default/default-events.h +++ b/backends/events/default/default-events.h @@ -27,7 +27,9 @@ #define BACKEND_EVENTS_DEFAULT_H #include "common/events.h" +#include "common/queue.h" #include "common/savefile.h" +#include "backends/common/virtual-keyboard.h" /* At some point we will remove pollEvent from OSystem and change @@ -44,6 +46,14 @@ use a subclass of EventProvider. class DefaultEventManager : public Common::EventManager { OSystem *_boss; + Common::VirtualKeyboard *_vk; + + Common::Queue<Common::Event> _artificialEventQueue; + int _artificialEventCounter; + enum { + kArtificialEventDelay = 10 + }; + Common::Point _mousePos; int _buttonState; int _modifierState; @@ -107,6 +117,7 @@ public: ~DefaultEventManager(); virtual bool pollEvent(Common::Event &event); + virtual void pushEvent(const Common::Event &event); virtual void registerRandomSource(Common::RandomSource &rnd, const char *name); virtual void processMillis(uint32 &millis); diff --git a/backends/module.mk b/backends/module.mk index 6642a3a281..fba5bbe907 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -27,7 +27,9 @@ 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 \ + common/virtual-keyboard.o \ + common/virtual-keyboard-parser.o # Include common rules include $(srcdir)/rules.mk diff --git a/common/events.h b/common/events.h index d0cb740692..ba31610e92 100644 --- a/common/events.h +++ b/common/events.h @@ -142,6 +142,11 @@ public: */ virtual bool pollEvent(Common::Event &event) = 0; + /** + * Pushes a "fake" event into the event queue + */ + 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; diff --git a/common/image-map.cpp b/common/image-map.cpp new file mode 100644 index 0000000000..f9201618a4 --- /dev/null +++ b/common/image-map.cpp @@ -0,0 +1,79 @@ +/* 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/image-map.h" + +namespace Common { + +ImageMap::~ImageMap() { + removeAllAreas(); +} + +Rect *ImageMap::createRectArea(const String& id) { + if (_areas.contains(id)) { + warning("Image map already contains an area with target of '%s'"); + return 0; + } + Rect *r = new Rect(); + _areas[id] = r; + return r; +} + +Polygon *ImageMap::createPolygonArea(const String& id) { + if (_areas.contains(id)) { + warning("Image map already contains an area with target of '%s'"); + 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, Shape*>::iterator it; + for (it = _areas.begin(); it != _areas.end(); it++) { + delete it->_value; + } + _areas.clear(); +} + +String ImageMap::findMapArea(int16 x, int16 y) { + HashMap<String, Shape*>::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/common/image-map.h b/common/image-map.h new file mode 100644 index 0000000000..eaf803178d --- /dev/null +++ b/common/image-map.h @@ -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$ + * + */ + +#ifndef COMMON_IMAGEMAP_H +#define COMMON_IMAGEMAP_H + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/rect.h" +#include "common/polygon.h" + +namespace Common { + +class ImageMap { + +public: + + ~ImageMap(); + + Rect *createRectArea(const String& id); + Polygon *createPolygonArea(const String& id); + void removeArea(const String& id); + void removeAllAreas(); + String findMapArea(int16 x, int16 y); + +protected: + HashMap<String, Shape*> _areas; +}; + + +} // End of namespace Common + +#endif diff --git a/common/keyboard.h b/common/keyboard.h index 93579cbed6..a0ae941a08 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/list.h b/common/list.h index c4e7b47644..0372d217b7 100644 --- a/common/list.h +++ b/common/list.h @@ -209,6 +209,11 @@ public: ++i; } + void pop_front() { + iterator i = begin(); + i = erase(i); + } + List<t_T> &operator=(const List<t_T> &list) { if (this != &list) { diff --git a/common/module.mk b/common/module.mk index c3f2a38c3f..e947b7ddd9 100644 --- a/common/module.mk +++ b/common/module.mk @@ -6,16 +6,19 @@ MODULE_OBJS := \ config-manager.o \ file.o \ fs.o \ + image-map.o \ hashmap.o \ memorypool.o \ md5.o \ mutex.o \ + polygon.o \ str.o \ stream.o \ util.o \ system.o \ unarj.o \ unzip.o \ + xmlparser.o \ zlib.o # Include common rules diff --git a/common/polygon.cpp b/common/polygon.cpp new file mode 100644 index 0000000000..c1332b0bc4 --- /dev/null +++ b/common/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 "common/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/common/polygon.h b/common/polygon.h new file mode 100644 index 0000000000..e4a518193e --- /dev/null +++ b/common/polygon.h @@ -0,0 +1,115 @@ +/* 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" +#include "common/shape.h" + +namespace Common { + +struct Polygon : public Shape { + + + Polygon() {} + Polygon(const Polygon& p) : Shape(), _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/common/queue.h b/common/queue.h new file mode 100644 index 0000000000..cb29c59058 --- /dev/null +++ b/common/queue.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_QUEUE_H +#define COMMON_QUEUE_H + +#include "common/scummsys.h" +#include "common/list.h" + +namespace Common { + +/** + * Variable size Queue class, implemented using our Array class. + */ +template<class T> +class Queue { +protected: + List<T> _queue; +public: + Queue<T>() {} + Queue<T>(const List<T> &queueContent) : _queue(queueContent) {} + + bool empty() const { + return _queue.empty(); + } + void clear() { + _queue.clear(); + } + void push(const T &x) { + _queue.push_back(x); + } + T back() const { + return _queue.reverse_begin().operator*(); + } + T front() const { + return _queue.begin().operator*(); + } + T pop() { + T tmp = front(); + _queue.pop_front(); + return tmp; + } + int size() const { + return _queue.size(); + } +}; + +} // End of namespace Common + +#endif diff --git a/common/rect.h b/common/rect.h index f71124434a..0c149f613f 100644 --- a/common/rect.h +++ b/common/rect.h @@ -26,45 +26,10 @@ #ifndef COMMON_RECT_H #define COMMON_RECT_H -#include "common/scummsys.h" -#include "common/util.h" +#include "common/shape.h" namespace Common { -/*! @brief simple class for handling both 2D position and size - - This small class is an helper for position and size values. -*/ -struct Point { - int16 x; //!< The horizontal part of the point - int16 y; //!< The vertical part of the point - - Point() : x(0), y(0) {} - Point(const Point &p) : x(p.x), y(p.y) {} - explicit Point(int16 x1, int16 y1) : x(x1), y(y1) {} - Point & operator=(const Point & p) { x = p.x; y = p.y; return *this; }; - bool operator==(const Point & p) const { return x == p.x && y == p.y; }; - bool operator!=(const Point & p) const { return x != p.x || y != p.y; }; - - /** - * Return the square of the distance between this point and the point p. - * - * @param p the other point - * @return the distance between this and p - */ - uint sqrDist(const Point & p) const { - int diffx = ABS(p.x - x); - if (diffx >= 0x1000) - return 0xFFFFFF; - - int diffy = ABS(p.y - y); - if (diffy >= 0x1000) - return 0xFFFFFF; - - return uint(diffx*diffx + diffy*diffy); - } -}; - /*! @brief simple class for handling a rectangular zone. This small class is an helper for rectangles. @@ -75,7 +40,7 @@ struct Point { Another very wide spread approach to rectangle classes treats (bottom,right) also as a part of the rectangle. - Coneptually, both are sound, but the approach we use saves many intermediate + Conceptually, both are sound, but the approach we use saves many intermediate computations (like computing the height in our case is done by doing this: height = bottom - top; while in the alternate system, it would be @@ -83,7 +48,7 @@ struct Point { When writing code using our Rect class, always keep this principle in mind! */ -struct Rect { +struct Rect : public Shape { int16 top, left; //!< The point at the top left of the rectangle (part of the rect). int16 bottom, right; //!< The point at the bottom right of the rectangle (not part of the rect). @@ -92,6 +57,8 @@ struct Rect { Rect(int16 x1, int16 y1, int16 x2, int16 y2) : top(y1), left(x1), bottom(y2), right(x2) { assert(isValidRect()); } + virtual ~Rect() {} + int16 width() const { return right - left; } int16 height() const { return bottom - top; } @@ -110,7 +77,7 @@ struct Rect { @return true if the given position is inside this rectangle, false otherwise */ - bool contains(int16 x, int16 y) const { + virtual bool contains(int16 x, int16 y) const { return (left <= x) && (x < right) && (top <= y) && (y < bottom); } @@ -120,7 +87,7 @@ struct Rect { @return true if the given point is inside this rectangle, false otherwise */ - bool contains(const Point &p) const { + virtual bool contains(const Point &p) const { return contains(p.x, p.y); } @@ -181,19 +148,19 @@ struct Rect { return (left <= right && top <= bottom); } - void moveTo(int16 x, int16 y) { + virtual void moveTo(int16 x, int16 y) { bottom += y - top; right += x - left; top = y; left = x; } - void translate(int16 dx, int16 dy) { + virtual void translate(int16 dx, int16 dy) { left += dx; right += dx; top += dy; bottom += dy; } - void moveTo(const Point &p) { + virtual void moveTo(const Point &p) { moveTo(p.x, p.y); } @@ -207,6 +174,10 @@ struct Rect { h /= 2; return Rect(cx - w, cy - h, cx + w, cy + h); } + + virtual Rect getBoundingRect() const { + return *this; + } }; } // End of namespace Common diff --git a/common/shape.h b/common/shape.h new file mode 100644 index 0000000000..2cce365bfc --- /dev/null +++ b/common/shape.h @@ -0,0 +1,105 @@ +/* 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_SHAPE_H +#define COMMON_SHAPE_H + +#include "common/scummsys.h" +#include "common/util.h" + +namespace Common { + +struct Rect; + +/*! @brief simple class for handling both 2D position and size + + This small class is an helper for position and size values. +*/ +struct Point { + int16 x; //!< The horizontal part of the point + int16 y; //!< The vertical part of the point + + Point() : x(0), y(0) {} + Point(const Point &p) : x(p.x), y(p.y) {} + explicit Point(int16 x1, int16 y1) : x(x1), y(y1) {} + Point & operator=(const Point & p) { x = p.x; y = p.y; return *this; }; + bool operator==(const Point & p) const { return x == p.x && y == p.y; }; + bool operator!=(const Point & p) const { return x != p.x || y != p.y; }; + + /** + * Return the square of the distance between this point and the point p. + * + * @param p the other point + * @return the distance between this and p + */ + uint sqrDist(const Point & p) const { + int diffx = ABS(p.x - x); + if (diffx >= 0x1000) + return 0xFFFFFF; + + int diffy = ABS(p.y - y); + if (diffy >= 0x1000) + return 0xFFFFFF; + + return uint(diffx*diffx + diffy*diffy); + } +}; + +/*! @brief simple interface that provides common methods for 2D shapes + +*/ +struct Shape { + + virtual ~Shape() {} + /*! @brief check if given position is inside this shape + + @param x the horizontal position to check + @param y the vertical position to check + + @return true if the given position is inside this shape, false otherwise + */ + virtual bool contains(int16 x, int16 y) const = 0; + + /*! @brief check if given point is inside this shape + + @param p the point to check + + @return true if the given point is inside this shape, false otherwise + */ + virtual bool contains(const Point &p) const = 0; + + virtual void moveTo(int16 x, int16 y) = 0; + + virtual void moveTo(const Point &p) = 0; + + virtual void translate(int16 dx, int16 dy) = 0; + + virtual Rect getBoundingRect() const = 0; + +}; + +} // end of namespace Common + +#endif diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp new file mode 100644 index 0000000000..d0c89a9d3e --- /dev/null +++ b/common/xmlparser.cpp @@ -0,0 +1,261 @@ +/* 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 pos = _pos; + int lineCount = 1; + int lineStart = 0; + + do { + if (_text[pos] == '\n' || _text[pos] == '\r') { + lineCount++; + + if (lineStart == 0) + lineStart = MAX(pos + 1, _pos - 60); + } + } while (pos-- > 0); + + char lineStr[70]; + _text.stream()->seek(lineStart, SEEK_SET); + _text.stream()->readLine(lineStr, 70); + + printf(" 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 ? "" : "...", endFull ? "" : "...", lineStr); + + int cursor = MIN(_pos - lineStart, 70); + + 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"); + + return false; +} + +bool XMLParser::parseActiveKey(bool closed) { + bool ignore = false; + + // 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(_activeKey.top()->name) == false) { + return false; + } + + if (closed) { + delete _activeKey.pop(); + } + + 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 (_text[_pos] == '"' || _text[_pos] == '\'') { + stringStart = _text[_pos++]; + + while (_text[_pos] && _text[_pos] != stringStart) + _token += _text[_pos++]; + + if (_text[_pos++] == 0) + return false; + + } else if (!parseToken()) { + return false; + } + + _activeKey.top()->values[keyName] = _token; + return true; +} + +bool XMLParser::parse() { + + if (_text.ready() == false) + return parserError("XML stream not ready for reading."); + + cleanup(); + + bool activeClosure = false; + bool selfClosure = false; + + _state = kParserNeedKey; + _pos = 0; + _activeKey.clear(); + + while (_text[_pos] && _state != kParserError) { + if (skipSpaces()) + continue; + + if (skipComments()) + continue; + + switch (_state) { + case kParserNeedKey: + if (_text[_pos++] != '<') { + parserError("Parser expecting key start."); + break; + } + + if (_text[_pos] == 0) { + parserError("Unexpected end of file."); + break; + } + + if (_text[_pos] == '/' && _text[_pos + 1] != '*') { + _pos++; + 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(); + _activeKey.push(node); + } + + _state = kParserNeedPropertyName; + break; + + case kParserNeedPropertyName: + if (activeClosure) { + if (!closedKeyCallback(_activeKey.top()->name)) { + parserError("Missing data when closing key '%s'.", _activeKey.top()->name.c_str()); + break; + } + + activeClosure = false; + delete _activeKey.pop(); + + if (_text[_pos++] != '>') + parserError("Invalid syntax in key closure."); + else + _state = kParserNeedKey; + + break; + } + + selfClosure = (_text[_pos] == '/'); + + if ((selfClosure && _text[_pos + 1] == '>') || _text[_pos] == '>') { + if (parseActiveKey(selfClosure)) { + _pos += selfClosure ? 2 : 1; + _state = kParserNeedKey; + } + break; + } + + if (!parseToken()) + parserError("Error when parsing key value."); + else + _state = kParserNeedPropertyOperator; + + break; + + case kParserNeedPropertyOperator: + if (_text[_pos++] != '=') + parserError("Syntax error after key name."); + else + _state = kParserNeedPropertyValue; + + 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..4c77696482 --- /dev/null +++ b/common/xmlparser.h @@ -0,0 +1,369 @@ +/* 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/hashmap.h" +#include "common/hash-str.h" +#include "common/stack.h" + +namespace Common { + +class XMLStream { +protected: + SeekableReadStream *_stream; + int _pos; + +public: + XMLStream() : _stream(0), _pos(0) {} + + ~XMLStream() { + delete _stream; + } + + SeekableReadStream *stream() { + return _stream; + } + + char operator [](int idx) { + assert(_stream && idx >= 0); + + if (_pos + 1 != idx) + _stream->seek(idx, SEEK_SET); + + _pos = idx; + + return _stream->readByte(); + } + + void loadStream(SeekableReadStream *s) { + delete _stream; + _stream = s; + } + + bool ready() { + return _stream != 0; + } +}; + +/** + * 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() {} + + virtual ~XMLParser() { + while (!_activeKey.empty()) + delete _activeKey.pop(); + } + + /** Active state for the parser */ + enum ParserState { + kParserNeedKey, + kParserNeedKeyName, + + kParserNeedPropertyName, + kParserNeedPropertyOperator, + kParserNeedPropertyValue, + + kParserError + }; + + /** Struct representing a parsed node */ + struct ParserNode { + Common::String name; + Common::StringMap values; + bool ignore; + int depth; + }; + + /** + * 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. + */ + virtual bool loadFile(Common::String filename) { + Common::File *f = new Common::File; + + if (!f->open(filename, Common::File::kFileReadMode)) + return false; + + _fileName = filename; + _text.loadStream(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. + */ + virtual bool loadBuffer(const byte *buffer, uint32 size, bool disposable = false) { + _text.loadStream(new MemoryReadStream(buffer, size, disposable)); + _fileName = "Memory Stream"; + return true; + } + + /** + * The actual parsing function. + * Parses the loaded data stream, returns true if successful. + */ + virtual 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 keycallback function must be overloaded by inheriting classes + * to implement parser-specific functions. + * + * This function is called everytime a key has successfully been parsed. + * The keyName parameter contains the name of the key that has just been + * parsed; this same key is still on top of the Node Stack. + * + * Access the node stack to view this key's properties and its parents. + * Remember to leave the node stack _UNCHANGED_ in your own function. Removal + * of closed keys is done automatically. + * + * When parsing a key, 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. + * + * Return true if the key was properly handled (this includes the case when the + * key is being ignored). False otherwise. + * See the sample implementation in GUI::ThemeParser. + */ + virtual bool keyCallback(Common::String keyName) { + return false; + } + + /** + * The closed key callback function must 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. + * + * Returns true if the key was properly closed, false otherwise. + * By default, all keys are properly closed. + */ + virtual bool closedKeyCallback(Common::String keyName) { + return true; + } + + /** + * Parses the value of a given key. There's no reason to overload this. + */ + virtual 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. + * There's no reason to overload this. + */ + virtual 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. + */ + virtual bool parserError(const char *errorString, ...) GCC_PRINTF(2, 3); + + /** + * Skips spaces/whitelines etc. Returns true if any spaces were skipped. + * Overload this if you want to make your parser depend on newlines or + * whatever. + */ + virtual bool skipSpaces() { + if (!isspace(_text[_pos])) + return false; + + while (_text[_pos] && isspace(_text[_pos])) + _pos++; + + 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() { + if (_text[_pos] == '/' && _text[_pos + 1] == '*') { + _pos += 2; + while (_text[_pos++]) { + if (_text[_pos - 2] == '*' && _text[_pos - 1] == '/') + break; + if (_text[_pos] == 0) + parserError("Comment has no closure."); + } + return true; + } + + if (_text[_pos] == '/' && _text[_pos + 1] == '/') { + _pos += 2; + while (_text[_pos] && _text[_pos] != '\n' && _text[_pos] != '\r') + _pos++; + 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 bool isValidNameChar(char c) { + return isalnum(c) || c == '_'; + } + + /** + * Parses a the first textual token found. + * There's no reason to overload this. + */ + virtual bool parseToken() { + _token.clear(); + while (isValidNameChar(_text[_pos])) + _token += _text[_pos++]; + + return isspace(_text[_pos]) != 0 || _text[_pos] == '>' || _text[_pos] == '='; + } + + /** + * 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. + */ + virtual 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() {} + + int _pos; /** Current position on the XML buffer. */ + XMLStream _text; /** Buffer with the text being parsed */ + 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 b144dc0b79..bab456e3d7 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" @@ -335,6 +335,14 @@ > </File> <File + RelativePath="..\..\common\image-map.cpp" + > + </File> + <File + RelativePath="..\..\common\image-map.h" + > + </File> + <File RelativePath="..\..\common\keyboard.h" > </File> @@ -379,10 +387,22 @@ > </File> <File + RelativePath="..\..\common\polygon.cpp" + > + </File> + <File + RelativePath="..\..\common\polygon.h" + > + </File> + <File RelativePath="..\..\common\ptr.h" > </File> <File + RelativePath="..\..\common\queue.h" + > + </File> + <File RelativePath="..\..\common\rect.h" > </File> @@ -395,6 +415,10 @@ > </File> <File + RelativePath="..\..\common\shape.h" + > + </File> + <File RelativePath="..\..\common\singleton.h" > </File> @@ -455,6 +479,14 @@ > </File> <File + RelativePath="..\..\common\xmlparser.cpp" + > + </File> + <File + RelativePath="..\..\common\xmlparser.h" + > + </File> + <File RelativePath="..\..\common\zlib.cpp" > </File> @@ -1021,6 +1053,58 @@ </File> </Filter> </Filter> + <Filter + Name="common" + > + <File + RelativePath="..\..\backends\common\hardware-key.h" + > + </File> + <File + RelativePath="..\..\backends\common\keymap-manager.cpp" + > + </File> + <File + RelativePath="..\..\backends\common\keymap-manager.h" + > + </File> + <File + RelativePath="..\..\backends\common\keymap.cpp" + > + </File> + <File + RelativePath="..\..\backends\common\keymap.h" + > + </File> + <File + RelativePath="..\..\backends\common\keymapper.cpp" + > + </File> + <File + RelativePath="..\..\backends\common\keymapper.h" + > + </File> + <File + RelativePath="..\..\backends\common\user-action.h" + > + </File> + <File + RelativePath="..\..\backends\common\virtual-keyboard-parser.cpp" + > + </File> + <File + RelativePath="..\..\backends\common\virtual-keyboard-parser.h" + > + </File> + <File + RelativePath="..\..\backends\common\virtual-keyboard.cpp" + > + </File> + <File + RelativePath="..\..\backends\common\virtual-keyboard.h" + > + </File> + </Filter> </Filter> <Filter Name="gui" @@ -1314,6 +1398,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 93e2db26c5..a44042a044 100644 --- a/graphics/module.mk +++ b/graphics/module.mk @@ -16,7 +16,8 @@ MODULE_OBJS := \ primitives.o \ scaler.o \ scaler/thumbnail.o \ - surface.o + surface.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..f533213fb6 --- /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
\ No newline at end of file diff --git a/graphics/surface-keycolored.h b/graphics/surface-keycolored.h new file mode 100644 index 0000000000..608bf7a482 --- /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
\ No newline at end of file |