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