diff --git a/binaries/data/mods/public/gui/options/options.js b/binaries/data/mods/public/gui/options/options.js index a67619ab59..94b3248d9c 100644 --- a/binaries/data/mods/public/gui/options/options.js +++ b/binaries/data/mods/public/gui/options/options.js @@ -5,7 +5,6 @@ function init(data) { if (data && data.callback) g_HasCallback = true; - let revert = data && data.revert; g_Controls = {}; var options = Engine.ReadJSONFile("gui/options/options.json"); @@ -21,9 +20,10 @@ function init(data) let label = Engine.GetGUIObjectByName(category + "Label[" + i + "]"); let config = option.parameters.config; g_Controls[config] = { - "control": setupControl(option, i, category, revert), + "control": setupControl(option, i, category), "type": option.type, - "dependencies": option.dependencies || undefined + "dependencies": option.dependencies || undefined, + "parameters": option.parameters }; label.caption = translate(option.label); label.tooltip = option.tooltip ? translate(option.tooltip) : ""; @@ -47,7 +47,7 @@ function init(data) continue; if (!opt.dependencies || opt.dependencies.indexOf(config) === -1) continue; - label.caption = " " + label.caption; + label.caption = " " + label.caption; break; } // Show element. @@ -55,9 +55,7 @@ function init(data) } } - updateDependencies(); - if (!revert) - updateStatus(); + updateOptionPanel(); } /** @@ -66,7 +64,7 @@ function init(data) * @param option Structure containing the data to setup an option. * @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i]. */ -function setupControl(option, i, category, revert) +function setupControl(option, i, category) { var control; var onUpdate; @@ -92,26 +90,21 @@ function setupControl(option, i, category, revert) { case "config": keyConfig = option.parameters.config; - if (checked === undefined || revert) - checked = Engine.ConfigDB_GetValue("user", keyConfig) === "true"; - else if ((Engine.ConfigDB_GetValue("user", keyConfig) === "true") !== checked) - { - Engine.ConfigDB_CreateValue("user", keyConfig, String(checked)); - updateStatus(true); - } + checked = Engine.ConfigDB_GetValue("user", keyConfig) === "true"; break; case "renderer": keyRenderer = option.parameters.renderer; if (!Engine["Renderer_Get" + keyRenderer + "Enabled"]) { - warn(" invalid renderer key " + keyRenderer); + warn("Invalid renderer key " + keyRenderer); keyRenderer = undefined; break; } - if (checked === undefined) - checked = Engine["Renderer_Get" + keyRenderer + "Enabled"](); - else if (Engine["Renderer_Get" + keyRenderer + "Enabled"]() !== checked) + if (Engine["Renderer_Get" + keyRenderer + "Enabled"]() !== checked) + { + warn("Incompatible renderer option value for " + keyRenderer); Engine["Renderer_Set" + keyRenderer + "Enabled"](checked); + } break; default: warn("Unknown option source type '" + param + "'"); @@ -130,9 +123,11 @@ function setupControl(option, i, category, revert) if (keyRenderer) Engine["Renderer_Set" + keyRenderer + "Enabled"](val); if (keyConfig) + { Engine.ConfigDB_CreateValue("user", keyConfig, String(val)); - updateDependencies(); - updateStatus(true); + Engine.ConfigDB_SetChanges("user", true); + } + updateOptionPanel(); }; }(keyRenderer, keyConfig, inverted); @@ -188,10 +183,10 @@ function setupControl(option, i, category, revert) if (Engine.ConfigDB_GetValue("user", key) === this.caption) return; Engine.ConfigDB_CreateValue("user", key, this.caption); + Engine.ConfigDB_SetChanges("user", true); if (functionBody) Engine[functionBody](+this.caption); - updateDependencies(); - updateStatus(true); + updateOptionPanel(); }; }(key, functionBody, minval, maxval); @@ -201,6 +196,7 @@ function setupControl(option, i, category, revert) break; case "dropdown": control = Engine.GetGUIObjectByName(category + "Dropdown[" + i + "]"); + control.onSelectionChange = function(){}; // just the time to setup the value var caption; var key; var functionBody; @@ -237,8 +233,8 @@ function setupControl(option, i, category, revert) if (key === "materialmgr.quality") val = val == 0 ? 2 : val == 1 ? 5 : 8; Engine.ConfigDB_CreateValue("user", key, val); - updateDependencies(); - updateStatus(true); + Engine.ConfigDB_SetChanges("user", true); + updateOptionPanel(); }; }(key); @@ -254,15 +250,23 @@ function setupControl(option, i, category, revert) return control; } -function updateStatus(val) +function updateOptionPanel() { - if (typeof val == "boolean") - Engine.ConfigDB_CreateValue("user", "nosave.haschanges", String(val)); - else - val = Engine.ConfigDB_GetValue("user", "nosave.haschanges") === "true"; + // Update dependencies + for (let item in g_Controls) + { + let control = g_Controls[item]; + if (control.type !== "boolean" && control.type !== "invertedboolean" || !control.dependencies) + continue; - Engine.GetGUIObjectByName("loadOptions").enabled = val; - Engine.GetGUIObjectByName("saveOptions").enabled = val; + for (let dependency of control.dependencies) + g_Controls[dependency].control.enabled = control.control.checked; + } + + // And main buttons + let hasChanges = Engine.ConfigDB_HasChanges("user"); + Engine.GetGUIObjectByName("revertChanges").enabled = hasChanges; + Engine.GetGUIObjectByName("saveChanges").enabled = hasChanges; } /** @@ -275,31 +279,50 @@ function registerChanges() g_Controls[item].control.onPress(); } -function updateDependencies() +function setDefaults() +{ + let btCaptions = [translate("No"), translate("Yes")]; + let btCode = [null, function(){ reallySetDefaults(); }]; + messageBox(500, 200, translate("Resetting the options will erase your saved settings. Do you want to continue ?"), + translate("Warning"), 0, btCaptions, btCode); +} + +function reallySetDefaults() { for (let item in g_Controls) - { - let control = g_Controls[item]; - if (control.type !== "boolean" && control.type !== "invertedboolean" || !control.dependencies) - continue; + Engine.ConfigDB_RemoveValue("user", item); - for (let dependency of control.dependencies) - g_Controls[dependency].control.enabled = control.control.checked; - } + Engine.ConfigDB_WriteFile("user", "config/user.cfg"); + revertChanges(); } function revertChanges() { Engine.ConfigDB_Reload("user"); - updateStatus(false); - init({ "revert": true }); + // needs to update renderer values (which are all of boolean type) + for (let item in g_Controls) + { + let control = g_Controls[item]; + if (!control.parameters.renderer) + continue; + if (control.type !== "boolean" && control.type !== "invertedboolean") + { + warn("Invalid type option defined in renderer '" + control.type + "': will not be reverted"); + continue; + } + let checked = Engine.ConfigDB_GetValue("user", item) === "true"; + Engine["Renderer_Set" + control.parameters.renderer + "Enabled"](checked); + } + Engine.ConfigDB_SetChanges("user", false); + init(); } function saveChanges() { registerChanges(); Engine.ConfigDB_WriteFile("user", "config/user.cfg"); - updateStatus(false); + Engine.ConfigDB_SetChanges("user", false); + updateOptionPanel(); } /** @@ -308,7 +331,7 @@ function saveChanges() function closePage() { registerChanges(); - if (Engine.ConfigDB_GetValue("user", "nosave.haschanges") === "true") + if (Engine.ConfigDB_HasChanges("user")) { let btCaptions = [translate("No"), translate("Yes")]; let btCode = [null, function(){ closePageWithoutConfirmation(); }]; diff --git a/binaries/data/mods/public/gui/options/options.json b/binaries/data/mods/public/gui/options/options.json index 57b04a57b5..4f833e8d72 100644 --- a/binaries/data/mods/public/gui/options/options.json +++ b/binaries/data/mods/public/gui/options/options.json @@ -68,13 +68,13 @@ "type": "boolean", "label": "Prefer GLSL", "tooltip": "Use OpenGL 2.0 shaders (recommended)", - "parameters": { "renderer": "PreferGLSL", "config": "preferglsl" } + "parameters": { "config": "preferglsl", "renderer": "PreferGLSL" } }, { "type": "boolean", "label": "Post Processing", "tooltip": "Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)", - "parameters": { "renderer": "Postproc", "config": "postproc" } + "parameters": { "config": "postproc", "renderer": "Postproc" } }, { "type": "dropdown", @@ -86,75 +86,75 @@ "type": "boolean", "label": "Shadows", "tooltip": "Enable shadows", - "parameters": { "renderer": "Shadows", "config": "shadows"}, + "parameters": { "config": "shadows", "renderer": "Shadows" }, "dependencies": [ "shadowpcf" ] }, { "type": "boolean", "label": "Shadow Filtering", "tooltip": "Smooth shadows", - "parameters": { "renderer": "ShadowPCF", "config": "shadowpcf" } + "parameters": { "config": "shadowpcf", "renderer": "ShadowPCF" } }, { "type": "boolean", "label": "Unit Silhouettes", "tooltip": "Show outlines of units behind buildings", - "parameters": { "renderer": "Silhouettes", "config": "silhouettes" } + "parameters": { "config": "silhouettes", "renderer": "Silhouettes" } }, { "type": "boolean", "label": "Particles", "tooltip": "Enable particles", - "parameters": { "renderer": "Particles", "config": "particles" } + "parameters": { "config": "particles", "renderer": "Particles" } }, { "type": "invertedboolean", "label": "Activate water effects", "tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.", - "parameters": { "renderer": "WaterUgly", "config": "waterugly" }, + "parameters": { "config": "waterugly", "renderer": "WaterUgly" }, "dependencies": [ "waterfancyeffects", "waterrealdepth", "waterreflection", "waterrefraction", "watershadows" ] }, { "type": "boolean", "label": "HQ Water Effects", "tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.", - "parameters": { "renderer": "WaterFancyEffects", "config": "waterfancyeffects" } + "parameters": { "config": "waterfancyeffects", "renderer": "WaterFancyEffects" } }, { "type": "boolean", "label": "Real Water Depth", "tooltip": "Use actual water depth in rendering calculations", - "parameters": { "renderer": "WaterRealDepth", "config": "waterrealdepth" } + "parameters": { "config": "waterrealdepth", "renderer": "WaterRealDepth" } }, { "type": "boolean", "label": "Water Reflections", "tooltip": "Allow water to reflect a mirror image", - "parameters": { "renderer": "WaterReflection", "config": "waterreflection" } + "parameters": { "config": "waterreflection", "renderer": "WaterReflection" } }, { "type": "boolean", "label": "Water Refraction", "tooltip": "Use a real water refraction map and not transparency", - "parameters": { "renderer": "WaterRefraction", "config": "waterrefraction" } + "parameters": { "config": "waterrefraction", "renderer": "WaterRefraction" } }, { "type": "boolean", "label": "Shadows on Water", "tooltip": "Cast shadows on water", - "parameters": { "renderer": "WaterShadows", "config": "watershadows" } + "parameters": { "config": "watershadows", "renderer": "WaterShadows" } }, { "type": "boolean", "label": "Smooth LOS", "tooltip": "Lift darkness and fog-of-war smoothly", - "parameters": { "renderer": "SmoothLOS", "config": "smoothlos" } + "parameters": { "config": "smoothlos", "renderer": "SmoothLOS" } }, { "type": "boolean", "label": "Show Sky", "tooltip": "Render Sky", - "parameters": { "renderer": "ShowSky", "config": "showsky" } + "parameters": { "config": "showsky", "renderer": "ShowSky" } }, { "type": "boolean", diff --git a/binaries/data/mods/public/gui/options/options.xml b/binaries/data/mods/public/gui/options/options.xml index 16be3a57b2..3aefdf3554 100644 --- a/binaries/data/mods/public/gui/options/options.xml +++ b/binaries/data/mods/public/gui/options/options.xml @@ -69,17 +69,22 @@ - + + Reset + Resets user settings to their game default + setDefaults(); + + Revert - Revert to previous saved settings + Reverts to previous saved settings revertChanges(); - + Save - Save changes + Saves changes saveChanges(); - + Close Unsaved changes affect this session only closePage(); diff --git a/source/ps/ConfigDB.cpp b/source/ps/ConfigDB.cpp index 44e8b6505a..3d2a4f3bb1 100644 --- a/source/ps/ConfigDB.cpp +++ b/source/ps/ConfigDB.cpp @@ -29,6 +29,7 @@ typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; VfsPath CConfigDB::m_ConfigFile[CFG_LAST]; +bool CConfigDB::m_HasChanges[CFG_LAST]; static pthread_mutex_t cfgdb_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -114,6 +115,22 @@ GETVAL(double) GETVAL(std::string) #undef GETVAL +bool CConfigDB::HasChanges(EConfigNamespace ns) const +{ + CHECK_NS(false); + + CScopeLock s(&cfgdb_mutex); + return m_HasChanges[ns]; +} + +void CConfigDB::SetChanges(EConfigNamespace ns, bool& value) +{ + CHECK_NS(;); + + CScopeLock s(&cfgdb_mutex); + m_HasChanges[ns] = value; +} + void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const { CHECK_NS(;); @@ -195,6 +212,17 @@ void CConfigDB::SetValueBool(EConfigNamespace ns, const CStr& name, const bool v SetValueString(ns, name, valueString); } +void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name) +{ + CHECK_NS(;); + + CScopeLock s(&cfgdb_mutex); + TConfigMap::iterator it = m_Map[ns].find(name); + if (it == m_Map[ns].end()) + return; + m_Map[ns].erase(it); +} + void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path) { CHECK_NS(;); @@ -384,8 +412,6 @@ bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) const char* pos = (char*)buf.get(); for (const std::pair& p : m_Map[ns]) { - if (boost::algorithm::starts_with(p.first, "nosave.")) - continue; size_t i; pos += sprintf(pos, "%s = ", p.first.c_str()); for (i = 0; i < p.second.size() - 1; ++i) diff --git a/source/ps/ConfigDB.h b/source/ps/ConfigDB.h index 9e72718964..d1142ffafe 100644 --- a/source/ps/ConfigDB.h +++ b/source/ps/ConfigDB.h @@ -52,6 +52,7 @@ class CConfigDB: public Singleton { static std::map m_Map[]; static VfsPath m_ConfigFile[]; + static bool m_HasChanges[]; public: CConfigDB(); @@ -71,6 +72,13 @@ public: ///@copydoc CConfigDB::GetValue void GetValue(EConfigNamespace ns, const CStr& name, std::string& value); + /** + * Returns true if changed with respect to last write on file + */ + bool HasChanges(EConfigNamespace ns) const; + + void SetChanges(EConfigNamespace ns, bool& value); + /** * Attempt to retrieve a vector of values corresponding to the given setting; * will search CFG_COMMAND first, and then all namespaces from the specified @@ -98,6 +106,11 @@ public: void SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value); void SetValueBool(EConfigNamespace ns, const CStr& name, const bool value); + + /** + * Remove a config value in the specified namespace. + */ + void RemoveValue(EConfigNamespace ns, const CStr& name); /** * Set the path to the config file used to populate the specified namespace diff --git a/source/ps/scripting/JSInterface_ConfigDB.cpp b/source/ps/scripting/JSInterface_ConfigDB.cpp index 1519b7394f..c5fcd1ba8e 100644 --- a/source/ps/scripting/JSInterface_ConfigDB.cpp +++ b/source/ps/scripting/JSInterface_ConfigDB.cpp @@ -42,6 +42,25 @@ bool JSI_ConfigDB::GetConfigNamespace(const std::wstring& cfgNsString, EConfigNa return true; } +bool JSI_ConfigDB::HasChanges(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& cfgNsString) +{ + EConfigNamespace cfgNs; + if (!GetConfigNamespace(cfgNsString, cfgNs)) + return false; + + return g_ConfigDB.HasChanges(cfgNs); +} + +bool JSI_ConfigDB::SetChanges(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& cfgNsString, bool value) +{ + EConfigNamespace cfgNs; + if (!GetConfigNamespace(cfgNsString, cfgNs)) + return false; + + g_ConfigDB.SetChanges(cfgNs, value); + return true; +} + std::string JSI_ConfigDB::GetValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& cfgNsString, const std::string& name) { EConfigNamespace cfgNs; @@ -63,6 +82,16 @@ bool JSI_ConfigDB::CreateValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), c return true; } +bool JSI_ConfigDB::RemoveValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& cfgNsString, const std::string& name) +{ + EConfigNamespace cfgNs; + if (!GetConfigNamespace(cfgNsString, cfgNs)) + return false; + + g_ConfigDB.RemoveValue(cfgNs, name); + return true; +} + bool JSI_ConfigDB::WriteFile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& cfgNsString, const Path& path) { EConfigNamespace cfgNs; @@ -105,8 +134,11 @@ bool JSI_ConfigDB::SetFile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const void JSI_ConfigDB::RegisterScriptFunctions(ScriptInterface& scriptInterface) { + scriptInterface.RegisterFunction("ConfigDB_HasChanges"); + scriptInterface.RegisterFunction("ConfigDB_SetChanges"); scriptInterface.RegisterFunction("ConfigDB_GetValue"); scriptInterface.RegisterFunction("ConfigDB_CreateValue"); + scriptInterface.RegisterFunction("ConfigDB_RemoveValue"); scriptInterface.RegisterFunction("ConfigDB_WriteFile"); scriptInterface.RegisterFunction("ConfigDB_WriteValueToFile"); scriptInterface.RegisterFunction("ConfigDB_SetFile"); diff --git a/source/ps/scripting/JSInterface_ConfigDB.h b/source/ps/scripting/JSInterface_ConfigDB.h index 6600e2cdeb..66f21909f0 100644 --- a/source/ps/scripting/JSInterface_ConfigDB.h +++ b/source/ps/scripting/JSInterface_ConfigDB.h @@ -24,8 +24,11 @@ namespace JSI_ConfigDB { bool GetConfigNamespace(const std::wstring& cfgNsString, EConfigNamespace& cfgNs); + bool HasChanges(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString); + bool SetChanges(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, bool value); std::string GetValue(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, const std::string& name); bool CreateValue(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value); + bool RemoveValue(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, const std::string& name); bool WriteFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, const Path& path); bool WriteValueToFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value, const Path& path); bool Reload(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& cfgNsString);