diff --git a/data/scene/atmosphereearth.scene b/data/scene/atmosphereearth.scene index 4bfbb611d8..7688996950 100644 --- a/data/scene/atmosphereearth.scene +++ b/data/scene/atmosphereearth.scene @@ -19,7 +19,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) diff --git a/data/scene/dawn.scene b/data/scene/dawn.scene index 17af47aae4..68c09bd636 100644 --- a/data/scene/dawn.scene +++ b/data/scene/dawn.scene @@ -19,7 +19,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) diff --git a/data/scene/default.scene b/data/scene/default.scene index 4e28cbb9a4..7fe255644f 100644 --- a/data/scene/default.scene +++ b/data/scene/default.scene @@ -18,8 +18,6 @@ local vrt_folders = { } } -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -74,7 +72,7 @@ function preInitialization() end function postInitialization() - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.addVirtualProperty( "BoolProperty", @@ -105,7 +103,7 @@ function postInitialization() -- Defined in scene_helper.lua -- Used to create focus buttons for a subset of scenegraph nodes - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "Earth", "Mars", "Moon" }) end diff --git a/data/scene/fieldlines.scene b/data/scene/fieldlines.scene index f83e512809..eec14509f8 100644 --- a/data/scene/fieldlines.scene +++ b/data/scene/fieldlines.scene @@ -19,7 +19,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) diff --git a/data/scene/juno.scene b/data/scene/juno.scene index 1a9a1a3924..23fc7bcab7 100755 --- a/data/scene/juno.scene +++ b/data/scene/juno.scene @@ -1,5 +1,3 @@ -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -27,7 +25,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) @@ -41,7 +39,7 @@ function postInitialization() openspace.printInfo("Done setting default values") - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "Jupiter", "Juno" }) end diff --git a/data/scene/newhorizons.scene b/data/scene/newhorizons.scene index 0dd32ec865..1fa63a8573 100644 --- a/data/scene/newhorizons.scene +++ b/data/scene/newhorizons.scene @@ -19,8 +19,6 @@ charon_image = "textures/NH_Charon_mosaic.png" charon_height = "textures/NH_Charon_DTM.png" -- charon_height = "textures/NH_Charon_DTM_8192.png" -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -28,7 +26,7 @@ function preInitialization() which the scene should start and other settings that might determine initialization critical objects. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.spice.loadKernel("${SPICE}/naif0012.tls") openspace.spice.loadKernel("${SPICE}/pck00010.tpc") @@ -193,7 +191,7 @@ function postInitialization() -- Defined in scene_helper.lua -- Used to create focus buttons for a subset of scenegraph nodes - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "Pluto", "NewHorizons", "Charon" }) end diff --git a/data/scene/osirisrex.scene b/data/scene/osirisrex.scene index a1b21a8e3c..ca77dc7cdf 100644 --- a/data/scene/osirisrex.scene +++ b/data/scene/osirisrex.scene @@ -1,7 +1,5 @@ KernelCase = 2 -- Right now we only have the image times for case 2 -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -83,7 +81,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) @@ -106,7 +104,7 @@ function postInitialization() openspace.navigation.resetCameraDirection() - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "OsirisRex", "BennuBarycenter", "Earth" }) end diff --git a/data/scene/rosetta.scene b/data/scene/rosetta.scene index 213ec20465..8c6f0acd04 100644 --- a/data/scene/rosetta.scene +++ b/data/scene/rosetta.scene @@ -1,5 +1,3 @@ -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -93,7 +91,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) @@ -107,7 +105,7 @@ function postInitialization() openspace.printInfo("Done setting default values") - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "Pluto", "NewHorizons", "Charon" }) end diff --git a/data/scene/volumetricmilkyway.scene b/data/scene/volumetricmilkyway.scene index 4b2b5ecabf..0a82b3b19a 100644 --- a/data/scene/volumetricmilkyway.scene +++ b/data/scene/volumetricmilkyway.scene @@ -19,7 +19,7 @@ function postInitialization() created and initialized, but before the first render call. This is the place to set graphical settings for the renderables. ]]-- - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.printInfo("Setting default values") openspace.setPropertyValue("Sun.renderable.Enabled", false) diff --git a/data/scene/voyager.scene b/data/scene/voyager.scene index 4b3d77c7fc..7be5b4a3cf 100644 --- a/data/scene/voyager.scene +++ b/data/scene/voyager.scene @@ -1,5 +1,3 @@ -dofile(openspace.absPath('${SCRIPTS}/scene_helper.lua')) - function preInitialization() --[[ The scripts in this function are executed after the scene is loaded but before the @@ -19,7 +17,7 @@ end function postInitialization() openspace.printInfo("Setting default values") - set_default_gui_sorting() + openspace.set_default_gui_sorting() openspace.setPropertyValueSingle("Global Properties.GlobeBrowsing.GdalWrapper.LogGdalErrors", false) openspace.setPropertyValueSingle("Earth.RenderableGlobe.Debug.LevelByProjectedAreaElseDistance", false) @@ -30,7 +28,7 @@ function postInitialization() -- Defined in scene_helper.lua -- Used to create focus buttons for a subset of scenegraph nodes - mark_interesting_nodes({ + openspace.mark_interesting_nodes({ "Earth", "Voyager 1", "Voyager 2", "Jupiter", "Saturn", "Uranus", "Neptune" }) end diff --git a/include/openspace/engine/configurationmanager.h b/include/openspace/engine/configurationmanager.h index 489435dac6..8086898f35 100644 --- a/include/openspace/engine/configurationmanager.h +++ b/include/openspace/engine/configurationmanager.h @@ -51,6 +51,9 @@ public: /// The key that stores the location of the SGCT configuration file that is used on /// application launch static const std::string KeyConfigSgct; + /// The key that defines a list of scripts for global customization that get executed + /// regardless of which scene is loaded + static const std::string KeyGlobalCustomizationScripts; /// The part of the key that defines the type static const std::string PartType; /// The part of the key that defines the file diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 987e03bf56..860c9f69bc 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -104,8 +104,6 @@ public: void writeDocumentation(); void toggleShutdownMode(); - void runPostInitializationScripts(const std::string& sceneDescription); - // Guaranteed to return a valid pointer ConfigurationManager& configurationManager(); LuaConsole& console(); @@ -178,6 +176,8 @@ private: void gatherCommandlineArguments(); void loadFonts(); void runPreInitializationScripts(const std::string& sceneDescription); + void runPostInitializationScripts(const std::string& sceneDescription); + void runGlobalCustomizationScripts(const std::string& sceneDescription); void configureLogging(); // Components diff --git a/include/openspace/interaction/keybindingmanager.h b/include/openspace/interaction/keybindingmanager.h index 7426610a99..b2f97ba854 100644 --- a/include/openspace/interaction/keybindingmanager.h +++ b/include/openspace/interaction/keybindingmanager.h @@ -40,6 +40,15 @@ namespace openspace::interaction { class KeyBindingManager : public DocumentationGenerator { public: + using IsLocalBind = ghoul::Boolean; + using IsSynchronized = ghoul::Boolean; + + struct KeyInformation { + std::string command; + IsSynchronized synchronization; + std::string documentation; + }; + KeyBindingManager(); ~KeyBindingManager() = default; @@ -59,19 +68,17 @@ public: std::string documentation = "" ); + void removeKeyBinding(const std::string& key); + + std::vector> keyBinding( + const std::string& key) const; + static scripting::LuaLibrary luaLibrary(); // Callback functions void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); private: - using Synchronized = ghoul::Boolean; - - struct KeyInformation { - std::string command; - Synchronized synchronization; - std::string documentation; - }; std::string generateJson() const override; diff --git a/include/openspace/util/keys.h b/include/openspace/util/keys.h index 0d30d02379..3cd47b36e5 100644 --- a/include/openspace/util/keys.h +++ b/include/openspace/util/keys.h @@ -217,6 +217,7 @@ struct KeyWithModifier { KeyWithModifier stringToKey(std::string str); bool operator<(const KeyWithModifier& lhs, const KeyWithModifier& rhs); +bool operator==(const KeyWithModifier& lhs, const KeyWithModifier& rhs); static const std::map KeyModifierMapping = { { "SHIFT", KeyModifier::Shift }, diff --git a/openspace.cfg b/openspace.cfg index 8c2e233c27..bbb6b007fc 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -36,6 +36,13 @@ return { TasksRoot = "${OPENSPACE_DATA}/tasks", + -- These scripts are executed after the postInitialization of each scene, thus making + -- it possible to have global overrides to default values or execute other scripts + -- regardless of the scene that is loaded + GlobalCustomizationScripts = { + "${SCRIPTS}/customization.lua" + }, + Paths = { SCRIPTS = "${BASE_PATH}/scripts", SHADERS = "${BASE_PATH}/shaders", diff --git a/scripts/common_scripts.lua b/scripts/common_scripts.lua new file mode 100644 index 0000000000..5c3475edc5 --- /dev/null +++ b/scripts/common_scripts.lua @@ -0,0 +1,20 @@ +openspace.documentation = { + { + Name = "rebindKey", + Arguments = "string, string", + Documentation = "Rebinds all scripts from the old key (first argument) to the " .. + "new key (second argument)." + } +} + +openspace.rebindKey = function(old_key, new_key) + local t = openspace.getKeyBinding(old_key) + openspace.clearKey(old_key) + for _, v in pairs(t) do + if v["Remote"] then + openspace.bindKey(new_key, v["Command"]) + else + openspace.bindKeyLocal(new_key, v["Command"]) + end + end +end diff --git a/scripts/scene_helper.lua b/scripts/scene_helper.lua index ce611a7df9..e8d19c7e98 100644 --- a/scripts/scene_helper.lua +++ b/scripts/scene_helper.lua @@ -1,4 +1,21 @@ -mark_interesting_nodes = function(nodes) +openspace.documentation = { + { + Name = "mark_interating_nodes", + Arguments = "List of nodes", + Documentation = "This function marks the scene graph nodes identified by name " .. + "as interesting, which will provide shortcut access to focus buttons and " .. + "featured properties." + }, + { + Name = "set_default_gui_sorting", + Arguments = "", + Documentation = "This function sets the default GUI sorting for the space " .. + "environment to increasing size, from solar system, through Milky Way, " .. + "Universe and finishing with other elements" + } +} + +openspace.mark_interesting_nodes = function(nodes) for _, n in pairs(nodes) do if openspace.hasSceneGraphNode(n) then openspace.addTag(n, "GUI.Interesting") @@ -6,7 +23,7 @@ mark_interesting_nodes = function(nodes) end end -set_default_gui_sorting = function() +openspace.set_default_gui_sorting = function() openspace.setPropertyValueSingle( 'Global Properties.ImGUI.Main.Properties.Ordering', { diff --git a/src/engine/configurationmanager.cpp b/src/engine/configurationmanager.cpp index 9df4ad3d66..d72afc6453 100644 --- a/src/engine/configurationmanager.cpp +++ b/src/engine/configurationmanager.cpp @@ -48,6 +48,8 @@ const string ConfigurationManager::KeyPaths = "Paths"; const string ConfigurationManager::KeyCache = "CACHE"; const string ConfigurationManager::KeyFonts = "Fonts"; const string ConfigurationManager::KeyConfigSgct = "SGCTConfig"; +const string ConfigurationManager::KeyGlobalCustomizationScripts = + "GlobalCustomizationScripts"; const string ConfigurationManager::PartType = "Type"; const string ConfigurationManager::PartFile = "File"; diff --git a/src/engine/configurationmanager_doc.inl b/src/engine/configurationmanager_doc.inl index bf1bbdf90b..3415e8baef 100644 --- a/src/engine/configurationmanager_doc.inl +++ b/src/engine/configurationmanager_doc.inl @@ -52,6 +52,14 @@ documentation::Documentation ConfigurationManager::Documentation() { "time and other scene-specific settings. More information is provided in " "the Scene documentation." }, + { + ConfigurationManager::KeyGlobalCustomizationScripts, + new StringListVerifier, + Optional::Yes, + "This value names a list of scripts that get executed after initialization " + "of any scene. These scripts can be used for user-specific customization, " + "such as a global rebinding of keys from the default." + }, { ConfigurationManager::KeyConfigTasksRoot, new StringAnnotationVerifier( diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 47267f22f9..0f629da564 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -720,6 +720,13 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) { LFATALC(e.component, e.message); } + // Run the global configuration scripts + try { + runGlobalCustomizationScripts(scenePath); + } catch (ghoul::RuntimeError& e) { + LERRORC(e.component, e.message); + } + // Write keyboard documentation. if (configurationManager().hasKey(ConfigurationManager::KeyKeyboardShortcuts)) { keyBindingManager().writeDocumentation( @@ -887,6 +894,36 @@ void OpenSpaceEngine::runPostInitializationScripts(const std::string& sceneDescr } } +void OpenSpaceEngine::runGlobalCustomizationScripts(const std::string& sceneDescription) { + // @CLEANUP: Move this into the scene loading? ---abock + LINFO("Running Global initialization scripts"); + ghoul::lua::LuaState state; + OsEng.scriptEngine().initializeLuaState(state); + + // First execute the script to get all global variables + ghoul::lua::runScriptFile(state, absPath(sceneDescription)); + + std::string k = ConfigurationManager::KeyGlobalCustomizationScripts; + if (_configurationManager->hasKey(k)) { + ghoul::Dictionary dict = _configurationManager->value(k); + for (int i = 1; i <= dict.size(); ++i) { + std::string script = dict.value(std::to_string(i)); + + if (FileSys.fileExists(script)) { + try { + LINFO("Running global customization script: " << script); + ghoul::lua::runScriptFile(state, absPath(script)); + } catch (ghoul::RuntimeError& e) { + LERRORC(e.component, e.message); + } + } + else { + LDEBUG("Ignoring non-existing script file: " << script); + } + } + } +} + void OpenSpaceEngine::loadFonts() { ghoul::Dictionary fonts; configurationManager().getValue(ConfigurationManager::KeyFonts, fonts); @@ -1506,6 +1543,9 @@ scripting::LuaLibrary OpenSpaceEngine::luaLibrary() { "string, string", "Removes a tag (second argument) from a scene graph node (first argument)" } + }, + { + absPath("${SCRIPTS}/common_scripts.lua") } }; } diff --git a/src/interaction/keybindingmanager.cpp b/src/interaction/keybindingmanager.cpp index 231d278846..e7752bf32f 100644 --- a/src/interaction/keybindingmanager.cpp +++ b/src/interaction/keybindingmanager.cpp @@ -86,7 +86,7 @@ void KeyBindingManager::bindKeyLocal(Key key, KeyModifier modifier, { key, modifier }, { std::move(luaCommand), - Synchronized::No, + IsSynchronized::No, std::move(documentation) } }); @@ -99,12 +99,55 @@ void KeyBindingManager::bindKey(Key key, KeyModifier modifier, { key, modifier }, { std::move(luaCommand), - Synchronized::Yes, + IsSynchronized::Yes, std::move(documentation) } }); } +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 { + // We if it is not, we continue iteration + ++it; + } + } + + // _keyLua.erase( + // std::remove_if( + // _keyLua.begin(), + // _keyLua.end(), + // [key](const std::pair& val) { + // KeyWithModifier k = stringToKey(key); + // return val.first == k; + // } + // ), + // _keyLua.end() + // ); +} + +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.push_back({ it->first, it->second }); + } + return result; +} + std::string KeyBindingManager::generateJson() const { std::stringstream json; json << "["; @@ -146,6 +189,12 @@ scripting::LuaLibrary KeyBindingManager::luaLibrary() { "", "Clear all key bindings" }, + { + "clearKey", + &luascriptfunctions::clearKey, + "string", + "Unbinds all of the scripts that are bound to the provided key + modifier" + }, { "bindKey", &luascriptfunctions::bindKey, @@ -165,6 +214,15 @@ scripting::LuaLibrary KeyBindingManager::luaLibrary() { "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." + + } } }; } diff --git a/src/interaction/keybindingmanager_lua.inl b/src/interaction/keybindingmanager_lua.inl index 11618975d7..6ea2cfa44d 100644 --- a/src/interaction/keybindingmanager_lua.inl +++ b/src/interaction/keybindingmanager_lua.inl @@ -118,17 +118,72 @@ int bindKeyLocal(lua_State* L) { return 0; } +/** +* \ingroup LuaScripts +* getKeyBindings(string): +* Returns the strings of the script that are bound to the passed key and whether they were +* local or remote key binds +*/ +int getKeyBindings(lua_State* L) { + int nArguments = lua_gettop(L); + if (nArguments != 1) { + return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); + } + + std::string key = luaL_checkstring(L, -1); + + using KeyInformation = interaction::KeyBindingManager::KeyInformation; + + std::vector> info = + OsEng.keyBindingManager().keyBinding(key); + + lua_createtable(L, static_cast(info.size()), 0); + int i = 1; + for (const std::pair& it : info) { + lua_pushnumber(L, i); + + lua_createtable(L, 2, 0); + lua_pushstring(L, "Command"); + lua_pushstring(L, it.second.command.c_str()); + lua_settable(L, -3); + lua_pushstring(L, "Remote"); + lua_pushboolean(L, it.second.synchronization); + lua_settable(L, -3); + + lua_settable(L, -3); + ++i; + } + return 1; +} + +/** +* \ingroup LuaScripts +* clearKey(string): +* Clears the keybinding of the key named as argument +*/ +int clearKey(lua_State* L) { + int nArguments = lua_gettop(L); + if (nArguments != 1) { + return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); + } + + std::string key = luaL_checkstring(L, -1); + + OsEng.keyBindingManager().removeKeyBinding(key); + + return 0; +} + /** * \ingroup LuaScripts * clearKeys(): * Clears all key bindings */ int clearKeys(lua_State* L) { - using ghoul::lua::luaTypeToString; - int nArguments = lua_gettop(L); - if (nArguments != 0) + if (nArguments != 0) { return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); + } OsEng.keyBindingManager().resetKeyBindings(); diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 0478b80f73..6ff87f9d0f 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -489,6 +489,9 @@ scripting::LuaLibrary Scene::luaLibrary() { "Checks whether the specifies SceneGraphNode is present in the current " "scene" } + }, + { + absPath("${SCRIPTS}/scene_helper.lua") } }; } diff --git a/src/util/keys.cpp b/src/util/keys.cpp index 26402835b8..986eecc49e 100644 --- a/src/util/keys.cpp +++ b/src/util/keys.cpp @@ -119,6 +119,11 @@ bool operator<(const KeyWithModifier& lhs, const KeyWithModifier& rhs) { } } +bool operator==(const KeyWithModifier& lhs, const KeyWithModifier& rhs) { + return (lhs.key == rhs.key) && (lhs.modifier == rhs.modifier); +} + + } // namespace openspace namespace std {