/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2020 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include "keybindingmanager_lua.inl" namespace openspace::interaction { KeybindingManager::KeybindingManager() : DocumentationGenerator( "Keybindings", "keybinding", { { "keybindingTemplate", "${WEB}/documentation/keybinding.hbs" } } ) {} void KeybindingManager::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { if (action == KeyAction::Press || action == KeyAction::Repeat) { // iterate over key bindings auto ret = _keyLua.equal_range({ key, modifier }); for (auto it = ret.first; it != ret.second; ++it) { using RS = scripting::ScriptEngine::RemoteScripting; global::scriptEngine.queueScript( it->second.command, it->second.synchronization ? RS::Yes : RS::No ); } } } void KeybindingManager::resetKeyBindings() { _keyLua.clear(); } void KeybindingManager::bindKeyLocal(Key key, KeyModifier modifier, std::string luaCommand, std::string documentation, std::string name, std::string guiPath) { #ifdef WIN32 const bool isShift = hasKeyModifier(modifier, KeyModifier::Shift); const bool isKeypad = key == Key::Keypad0 || key == Key::Keypad1 || key == Key::Keypad2 || key == Key::Keypad3 || key == Key::Keypad4 || key == Key::Keypad5 || key == Key::Keypad6 || key == Key::Keypad7 || key == Key::Keypad8 || key == Key::Keypad9 || key == Key::KeypadEnter || key == Key::KeypadAdd || key == Key::KeypadSubtract || key == Key::KeypadMultiply || key == Key::KeypadDivide; if (isShift && isKeypad) { LWARNINGC( "bindKey", "Windows does not support binding keys to Shift + Keyboard as it will " "internally convert these into Home, End, etc, keys." ); } #endif // WIN32 _keyLua.insert({ { key, modifier }, { std::move(luaCommand), IsSynchronized::No, std::move(documentation), std::move(name), std::move(guiPath) } }); } void KeybindingManager::bindKey(Key key, KeyModifier modifier, std::string luaCommand, std::string documentation, std::string name, std::string guiPath) { #ifdef WIN32 const bool isShift = hasKeyModifier(modifier, KeyModifier::Shift); const bool isKeypad = key == Key::Keypad0 || key == Key::Keypad1 || key == Key::Keypad2 || key == Key::Keypad3 || key == Key::Keypad4 || key == Key::Keypad5 || key == Key::Keypad6 || key == Key::Keypad7 || key == Key::Keypad8 || key == Key::Keypad9 || key == Key::KeypadEnter || key == Key::KeypadAdd || key == Key::KeypadSubtract || key == Key::KeypadMultiply || key == Key::KeypadDivide; if (isShift && isKeypad) { LWARNINGC( "bindKey", "Windows does not support binding keys to Shift + Keypad as it will " "internally convert these into Home, End, etc, keys." ); } #endif // WIN32 _keyLua.insert({ { key, modifier }, { std::move(luaCommand), IsSynchronized::Yes, std::move(documentation), std::move(name), std::move(guiPath) } }); } void KeybindingManager::removeKeyBinding(const std::string& key) { // Erase-remove idiom does not work for std::multimap so we have to do this on foot KeyWithModifier k = stringToKey(key); for (auto it = _keyLua.begin(); it != _keyLua.end(); ) { // If the current iterator is the key that we are looking for, delete it // (std::multimap::erase will return the iterator to the next element for us) if (it->first == k) { it = _keyLua.erase(it); } else { // If it is not, we continue iteration ++it; } } } std::vector> KeybindingManager::keyBinding(const std::string& key) const { std::vector> result; KeyWithModifier k = stringToKey(key); auto itRange = _keyLua.equal_range(k); for (auto it = itRange.first; it != itRange.second; ++it) { result.emplace_back(it->first, it->second); } return result; } const std::multimap& KeybindingManager::keyBindings() const { return _keyLua; } std::string KeybindingManager::generateJson() const { ZoneScoped std::stringstream json; json << "["; bool first = true; for (const std::pair& p : _keyLua) { if (!first) { json << ","; } first = false; json << "{"; json << R"("key": ")" << ghoul::to_string(p.first) << "\","; json << R"("script": ")" << escapedJson(p.second.command) << "\","; json << R"("remoteScripting": )" << (p.second.synchronization ? "true," : "false,"); json << R"("documentation": ")" << escapedJson(p.second.documentation) << "\","; json << R"("name": ")" << escapedJson(p.second.name) << "\""; json << "}"; } json << "]"; return json.str(); } scripting::LuaLibrary KeybindingManager::luaLibrary() { return { "", { { "clearKeys", &luascriptfunctions::clearKeys, {}, "", "Clear all key bindings" }, { "clearKey", &luascriptfunctions::clearKey, {}, "string or strings", "Unbinds the key or keys that have been provided. This function can be " "called with a single key or with an array of keys to remove all of the " "provided keys at once" }, { "bindKey", &luascriptfunctions::bindKey, {}, "string, string [, string]", "Binds a key by name to a lua string command to execute both locally " "and to broadcast to clients if this is the host of a parallel session. " "The first argument is the key, the second argument is the Lua command " "that is to be executed, and the optional third argument is a human " "readable description of the command for documentation purposes." }, { "bindKeyLocal", &luascriptfunctions::bindKeyLocal, {}, "string, string [, string]", "Binds a key by name to a lua string command to execute only locally. " "The first argument is the key, the second argument is the Lua command " "that is to be executed, and the optional third argument is a human " "readable description of the command for documentation purposes." }, { "getKeyBinding", &luascriptfunctions::getKeyBindings, {}, "string", "Returns a list of information about the keybindings for the provided " "key. Each element in the list is a table describing the 'Command' that " "was bound and whether it was a 'Remote' script or not." } } }; } } // namespace openspace::interaction