/* 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. * * Additional copyright for this file: * Copyright (C) 1995-1997 Presto Studios, Inc. * * 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. * */ #include "common/events.h" #include "common/system.h" #include "gui/gui-manager.h" #include "pegasus/cursor.h" #include "pegasus/input.h" #include "pegasus/pegasus.h" namespace Common { DECLARE_SINGLETON(Pegasus::InputDeviceManager); } namespace Pegasus { InputDeviceManager::InputDeviceManager() { // Set all keys to "not down" _keyMap[Common::KEYCODE_UP] = false; _keyMap[Common::KEYCODE_KP8] = false; _keyMap[Common::KEYCODE_LEFT] = false; _keyMap[Common::KEYCODE_KP4] = false; _keyMap[Common::KEYCODE_DOWN] = false; _keyMap[Common::KEYCODE_KP5] = false; _keyMap[Common::KEYCODE_RIGHT] = false; _keyMap[Common::KEYCODE_KP6] = false; _keyMap[Common::KEYCODE_RETURN] = false; _keyMap[Common::KEYCODE_SPACE] = false; _keyMap[Common::KEYCODE_t] = false; _keyMap[Common::KEYCODE_KP_EQUALS] = false; _keyMap[Common::KEYCODE_i] = false; _keyMap[Common::KEYCODE_KP_DIVIDE] = false; _keyMap[Common::KEYCODE_q] = false; _keyMap[Common::KEYCODE_ESCAPE] = false; _keyMap[Common::KEYCODE_p] = false; _keyMap[Common::KEYCODE_TILDE] = false; _keyMap[Common::KEYCODE_BACKQUOTE] = false; _keyMap[Common::KEYCODE_KP7] = false; _keyMap[Common::KEYCODE_BACKSPACE] = false; _keyMap[Common::KEYCODE_KP_MULTIPLY] = false; _keyMap[Common::KEYCODE_KP9] = false; _keyMap[Common::KEYCODE_LALT] = false; _keyMap[Common::KEYCODE_RALT] = false; _keyMap[Common::KEYCODE_e] = false; _keyMap[Common::KEYCODE_KP_ENTER] = false; g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 2, false); _lastRawBits = kAllUpBits; _consoleRequested = false; } InputDeviceManager::~InputDeviceManager() { g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); } void InputDeviceManager::getInput(Input &input, const InputBits filter) { // Poll for events, but ignore them! // We'll pick them up in notifyEvent() // We do that so that any pollEvent() call can update the variables // (ie. if one uses enter to access the restore menu, we never receive // the key up event, which leads to bad things) // This is to closely emulate what the GetKeys() function did on Mac OS pumpEvents(); // Now create the bitfield InputBits currentBits = 0; if (_keyMap[Common::KEYCODE_UP] || _keyMap[Common::KEYCODE_KP8]) currentBits |= (kRawButtonDown << kUpButtonShift); if (_keyMap[Common::KEYCODE_DOWN] || _keyMap[Common::KEYCODE_KP5]) currentBits |= (kRawButtonDown << kDownButtonShift); if (_keyMap[Common::KEYCODE_LEFT] || _keyMap[Common::KEYCODE_KP4]) currentBits |= (kRawButtonDown << kLeftButtonShift); if (_keyMap[Common::KEYCODE_RIGHT] || _keyMap[Common::KEYCODE_KP6]) currentBits |= (kRawButtonDown << kRightButtonShift); if (_keyMap[Common::KEYCODE_SPACE] || _keyMap[Common::KEYCODE_RETURN] || _keyMap[Common::KEYCODE_KP_ENTER]) currentBits |= (kRawButtonDown << kTwoButtonShift); if (_keyMap[Common::KEYCODE_t] || _keyMap[Common::KEYCODE_KP_EQUALS]) currentBits |= (kRawButtonDown << kThreeButtonShift); if (_keyMap[Common::KEYCODE_i] || _keyMap[Common::KEYCODE_KP_DIVIDE]) currentBits |= (kRawButtonDown << kFourButtonShift); if (_keyMap[Common::KEYCODE_q]) currentBits |= (kRawButtonDown << kMod1ButtonShift); if (_keyMap[Common::KEYCODE_ESCAPE] || _keyMap[Common::KEYCODE_p]) currentBits |= (kRawButtonDown << kMod3ButtonShift); // The original also used clear (aka "num lock" on Mac keyboards) here, but it doesn't // work right on most systems. Either SDL or the OS treats num lock specially and the // events don't come as expected. In many cases, the key down event is sent many times // causing the drawer to open and close constantly until pressed again. It only causes // more grief than anything else. // The original doesn't use KP7 for inventory, but we're using it as an alternative for // num lock. KP9 is used for the biochip drawer to balance things out. if (_keyMap[Common::KEYCODE_TILDE] || _keyMap[Common::KEYCODE_BACKQUOTE] || _keyMap[Common::KEYCODE_KP7]) currentBits |= (kRawButtonDown << kLeftFireButtonShift); if (_keyMap[Common::KEYCODE_BACKSPACE] || _keyMap[Common::KEYCODE_KP_MULTIPLY] || _keyMap[Common::KEYCODE_KP9]) currentBits |= (kRawButtonDown << kRightFireButtonShift); // Update mouse button state // Note that we don't use EVENT_LBUTTONUP/EVENT_LBUTTONDOWN because // they do not show if the button is being held down. We're treating // both mouse buttons as the same for ease of use. if (g_system->getEventManager()->getButtonState() != 0) currentBits |= (kRawButtonDown << kTwoButtonShift); // Update the mouse position too input.setInputLocation(g_system->getEventManager()->getMousePos()); // Set the outgoing bits InputBits filteredBits = currentBits & filter; input.setInputBits((filteredBits & kAllButtonDownBits) | (filteredBits & _lastRawBits & kAllAutoBits)); // Update the last bits _lastRawBits = currentBits; // Set the console to be requested or not input.setConsoleRequested(_consoleRequested); _consoleRequested = false; // WORKAROUND: The original had this in currentBits, but then // pressing alt would count as an event (and mess up someone // trying to do alt+enter or something). Since it's only used // as an easter egg, I'm just going to handle it as a separate // bool value. // WORKAROUND x2: I'm also accepting 'e' here since an // alt+click is often intercepted by the OS. 'e' is used as the // easter egg key in Buried in Time and Legacy of Time. input.setAltDown(_keyMap[Common::KEYCODE_LALT] || _keyMap[Common::KEYCODE_RALT] || _keyMap[Common::KEYCODE_e]); } // Wait until the input device stops returning input allowed by filter... void InputDeviceManager::waitInput(const InputBits filter) { if (filter != 0) { for (;;) { Input input; getInput(input, filter); if (!input.anyInput()) break; } } } uint InputDeviceManager::convertJoystickToKey(uint joybutton) { switch (joybutton) { case Common::JOYSTICK_BUTTON_A: return Common::KEYCODE_RETURN; // Action case Common::JOYSTICK_BUTTON_B: // nothing break; case Common::JOYSTICK_BUTTON_X: return Common::KEYCODE_i; // Display Object Info case Common::JOYSTICK_BUTTON_Y: return Common::KEYCODE_t; // Toggle Data Display case Common::JOYSTICK_BUTTON_LEFT_SHOULDER: return Common::KEYCODE_TILDE; // Open Inventory Panel case Common::JOYSTICK_BUTTON_RIGHT_SHOULDER: return Common::KEYCODE_KP_MULTIPLY; // Open Biochip Panel case Common::JOYSTICK_BUTTON_BACK: return Common::KEYCODE_p; // Pause case Common::JOYSTICK_BUTTON_DPAD_UP: return Common::KEYCODE_UP; case Common::JOYSTICK_BUTTON_DPAD_DOWN: return Common::KEYCODE_DOWN; case Common::JOYSTICK_BUTTON_DPAD_LEFT: return Common::KEYCODE_LEFT; case Common::JOYSTICK_BUTTON_DPAD_RIGHT: return Common::KEYCODE_RIGHT; default: break; } return 0; } bool InputDeviceManager::notifyEvent(const Common::Event &event) { if (GUI::GuiManager::instance().isActive()) { // For some reason, the engine hooks in the event system using an EventObserver. // So we need to explicitly ignore events that happen while ScummVM's GUI is open. return false; } // We're mapping from ScummVM events to pegasus events, which // are based on pippin events. switch (event.type) { case Common::EVENT_KEYDOWN: switch (event.kbd.keycode) { case Common::KEYCODE_d: if (event.kbd.flags & Common::KBD_CTRL) // Console! _consoleRequested = true; break; case Common::KEYCODE_s: // We support meta where available and control elsewhere if (event.kbd.flags & (Common::KBD_CTRL|Common::KBD_META)) ((PegasusEngine *)g_engine)->requestSave(); break; case Common::KEYCODE_o: // o for open (original) case Common::KEYCODE_l: // l for load (ScummVM terminology) // We support meta where available and control elsewhere if (event.kbd.flags & (Common::KBD_CTRL|Common::KBD_META)) ((PegasusEngine *)g_engine)->requestLoad(); break; default: // Otherwise, set the key to down if we have it if (_keyMap.contains(event.kbd.keycode)) _keyMap[event.kbd.keycode] = true; break; } break; case Common::EVENT_KEYUP: // Set the key to up if we have it if (_keyMap.contains(event.kbd.keycode)) _keyMap[event.kbd.keycode] = false; break; case Common::EVENT_JOYAXIS_MOTION: break; case Common::EVENT_JOYBUTTON_DOWN: if (_keyMap.contains(convertJoystickToKey(event.joystick.button))) _keyMap[convertJoystickToKey(event.joystick.button)] = true; break; case Common::EVENT_JOYBUTTON_UP: if (_keyMap.contains(convertJoystickToKey(event.joystick.button))) _keyMap[convertJoystickToKey(event.joystick.button)] = false; break; default: break; } return false; } void InputDeviceManager::pumpEvents() { PegasusEngine *vm = ((PegasusEngine *)g_engine); bool saveAllowed = vm->swapSaveAllowed(false); bool openAllowed = vm->swapLoadAllowed(false); // Just poll for events. notifyEvent() will pick up on them. Common::Event event; while (g_system->getEventManager()->pollEvent(event)) ; vm->swapSaveAllowed(saveAllowed); vm->swapLoadAllowed(openAllowed); } int operator==(const Input &arg1, const Input &arg2) { return arg1._inputState == arg2._inputState; } int operator!=(const Input &arg1, const Input &arg2) { return !operator==(arg1, arg2); } InputHandler *InputHandler::_inputHandler = 0; bool InputHandler::_invalHotspots = false; InputBits InputHandler::_lastFilter = kFilterNoInput; InputHandler *InputHandler::setInputHandler(InputHandler *currentHandler) { InputHandler *result = 0; if (_inputHandler != currentHandler && (!_inputHandler || _inputHandler->releaseInputFocus())) { result = _inputHandler; _inputHandler = currentHandler; if (_inputHandler) _inputHandler->grabInputFocus(); } return result; } void InputHandler::pollForInput() { if (_inputHandler) { Input input; Hotspot *cursorSpot = 0; InputHandler::getInput(input, cursorSpot); if (_inputHandler->isClickInput(input, cursorSpot)) _inputHandler->clickInHotspot(input, cursorSpot); else _inputHandler->handleInput(input, cursorSpot); } } void InputHandler::getInput(Input &input, Hotspot *&cursorSpot) { Cursor *cursor = ((PegasusEngine *)g_engine)->_cursor; if (_inputHandler) _lastFilter = _inputHandler->getInputFilter(); else _lastFilter = kFilterAllInput; InputDevice.getInput(input, _lastFilter); if (_inputHandler && _inputHandler->wantsCursor() && (_lastFilter & _inputHandler->getClickFilter()) != 0) { if (cursor->isVisible()) { g_allHotspots.deactivateAllHotspots(); _inputHandler->activateHotspots(); Common::Point cursorLocation; cursor->getCursorLocation(cursorLocation); cursorSpot = g_allHotspots.findHotspot(cursorLocation); if (_inputHandler) _inputHandler->updateCursor(cursorLocation, cursorSpot); } else { cursor->hideUntilMoved(); } } else { cursor->hide(); } } void InputHandler::readInputDevice(Input &input) { InputDevice.getInput(input, kFilterAllInput); } InputHandler::InputHandler(InputHandler *nextHandler) { _nextHandler = nextHandler; allowInput(true); } InputHandler::~InputHandler() { if (_inputHandler == this) setInputHandler(_nextHandler); } void InputHandler::handleInput(const Input &input, const Hotspot *cursorSpot) { if (_nextHandler) _nextHandler->handleInput(input, cursorSpot); } void InputHandler::clickInHotspot(const Input &input, const Hotspot *cursorSpot) { if (_nextHandler) _nextHandler->clickInHotspot(input, cursorSpot); } bool InputHandler::isClickInput(const Input &input, const Hotspot *cursorSpot) { if (_nextHandler) return _nextHandler->isClickInput(input, cursorSpot); return false; } void InputHandler::activateHotspots() { if (_nextHandler) _nextHandler->activateHotspots(); } InputBits InputHandler::getInputFilter() { if (_allowInput) { if (_nextHandler) return _nextHandler->getInputFilter(); else return kFilterAllInput; } return kFilterNoInput; } InputBits InputHandler::getClickFilter() { if (_allowInput && _nextHandler) return _nextHandler->getClickFilter(); return kFilterNoInput; } void InputHandler::updateCursor(const Common::Point cursorLocation, const Hotspot *cursorSpot) { if (_nextHandler) _nextHandler->updateCursor(cursorLocation, cursorSpot); } bool InputHandler::wantsCursor() { if (_allowInput) { if (_nextHandler) return _nextHandler->wantsCursor(); else return true; } return false; } Tracker *Tracker::_currentTracker = 0; void Tracker::handleInput(const Input &input, const Hotspot *) { if (stopTrackingInput(input)) stopTracking(input); else if (isTracking()) continueTracking(input); } void Tracker::startTracking(const Input &) { if (!isTracking()) { _savedHandler = InputHandler::setInputHandler(this); _currentTracker = this; } } void Tracker::stopTracking(const Input &) { if (isTracking()) { _currentTracker = NULL; InputHandler::setInputHandler(_savedHandler); } } bool Tracker::isClickInput(const Input &input, const Hotspot *hotspot) { return !isTracking() && InputHandler::isClickInput(input, hotspot); } } // End of namespace Pegasus