diff options
author | Jean-Francois Mauguit <jfmauguit@mac.com> | 2024-09-24 09:03:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-24 09:03:25 -0400 |
commit | bab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp | |
parent | 4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff) | |
parent | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff) | |
download | winamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz |
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp new file mode 100644 index 00000000..a5715c88 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/Settings.cpp @@ -0,0 +1,523 @@ +/* + * Settings.cpp + * ------------ + * Purpose: Application setting handling framework. + * Notes : (currently none) + * Authors: Joern Heusipp + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" + +#include "Settings.h" + +#include "mpt/binary/hex.hpp" + +#include "../common/misc_util.h" +#include "../common/mptStringBuffer.h" +#include "Mptrack.h" +#include "Mainfrm.h" + +#include <algorithm> +#include "../common/mptFileIO.h" + + +OPENMPT_NAMESPACE_BEGIN + + +mpt::ustring SettingValue::FormatTypeAsString() const +{ + if(GetType() == SettingTypeNone) + { + return U_("nil"); + } + mpt::ustring result; + switch(GetType()) + { + case SettingTypeBool: + result += U_("bool"); + break; + case SettingTypeInt: + result += U_("int"); + break; + case SettingTypeFloat: + result += U_("float"); + break; + case SettingTypeString: + result += U_("string"); + break; + case SettingTypeBinary: + result += U_("binary"); + break; + case SettingTypeNone: + default: + result += U_("nil"); + break; + } + if(HasTypeTag() && !GetTypeTag().empty()) + { + result += U_(":") + mpt::ToUnicode(mpt::Charset::ASCII, GetTypeTag()); + } + return result; +} + + +mpt::ustring SettingValue::FormatValueAsString() const +{ + switch(GetType()) + { + case SettingTypeBool: + return mpt::ufmt::val(as<bool>()); + break; + case SettingTypeInt: + return mpt::ufmt::val(as<int32>()); + break; + case SettingTypeFloat: + return mpt::ufmt::val(as<double>()); + break; + case SettingTypeString: + return as<mpt::ustring>(); + break; + case SettingTypeBinary: + return mpt::encode_hex(mpt::as_span(as<std::vector<std::byte>>())); + break; + case SettingTypeNone: + default: + return mpt::ustring(); + break; + } +} + + +void SettingValue::SetFromString(const AnyStringLocale &newVal) +{ + switch(GetType()) + { + case SettingTypeBool: + value = ConvertStrTo<bool>(newVal); + break; + case SettingTypeInt: + value = ConvertStrTo<int32>(newVal); + break; + case SettingTypeFloat: + value = ConvertStrTo<double>(newVal); + break; + case SettingTypeString: + value = newVal; + break; + case SettingTypeBinary: + value = mpt::decode_hex(newVal); + break; + case SettingTypeNone: + default: + break; + } +} + + +SettingValue SettingsContainer::BackendsReadSetting(const SettingPath &path, const SettingValue &def) const +{ + return backend->ReadSetting(path, def); +} + +void SettingsContainer::BackendsWriteSetting(const SettingPath &path, const SettingValue &val) +{ + backend->WriteSetting(path, val); +} + +void SettingsContainer::BackendsRemoveSetting(const SettingPath &path) +{ + backend->RemoveSetting(path); +} + +void SettingsContainer::BackendsRemoveSection(const mpt::ustring §ion) +{ + backend->RemoveSection(section); +} + +SettingValue SettingsContainer::ReadSetting(const SettingPath &path, const SettingValue &def) const +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + auto entry = map.find(path); + if(entry == map.end()) + { + entry = map.insert(map.begin(), std::make_pair(path, SettingState(def).assign(BackendsReadSetting(path, def), false))); + } + return entry->second; +} + +bool SettingsContainer::IsDefaultSetting(const SettingPath &path) const +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + auto entry = map.find(path); + if(entry == map.end()) + { + return true; + } + return entry->second.IsDefault(); +} + +void SettingsContainer::WriteSetting(const SettingPath &path, const SettingValue &val, SettingFlushMode flushMode) +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + auto entry = map.find(path); + if(entry == map.end()) + { + map[path] = val; + entry = map.find(path); + } else + { + entry->second = val; + } + NotifyListeners(path); + if(immediateFlush || flushMode == SettingWriteThrough) + { + BackendsWriteSetting(path, val); + entry->second.Clean(); + } +} + +void SettingsContainer::ForgetSetting(const SettingPath &path) +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + map.erase(path); +} + +void SettingsContainer::ForgetAll() +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + map.clear(); +} + +void SettingsContainer::RemoveSetting(const SettingPath &path) +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + map.erase(path); + BackendsRemoveSetting(path); +} + +void SettingsContainer::RemoveSection(const mpt::ustring §ion) +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + std::vector<SettingPath> pathsToRemove; + for(const auto &entry : map) + { + if(entry.first.GetSection() == section) + { + pathsToRemove.push_back(entry.first); + } + } + for(const auto &path : pathsToRemove) + { + map.erase(path); + } + BackendsRemoveSection(section); +} + +void SettingsContainer::NotifyListeners(const SettingPath &path) +{ + const auto entry = mapListeners.find(path); + if(entry != mapListeners.end()) + { + for(auto &it : entry->second) + { + it->SettingChanged(path); + } + } +} + +void SettingsContainer::WriteSettings() +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + for(auto &[path, value] : map) + { + if(value.IsDirty()) + { + BackendsWriteSetting(path, value); + value.Clean(); + } + } +} + +void SettingsContainer::Flush() +{ + ASSERT(theApp.InGuiThread()); + ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler. + WriteSettings(); +} + +void SettingsContainer::SetImmediateFlush(bool newImmediateFlush) +{ + if(newImmediateFlush) + { + Flush(); + } + immediateFlush = newImmediateFlush; +} + +void SettingsContainer::Register(ISettingChanged *listener, const SettingPath &path) +{ + mapListeners[path].insert(listener); +} + +void SettingsContainer::UnRegister(ISettingChanged *listener, const SettingPath &path) +{ + mapListeners[path].erase(listener); +} + +SettingsContainer::~SettingsContainer() +{ + WriteSettings(); +} + + +SettingsContainer::SettingsContainer(ISettingsBackend *backend) + : backend(backend) +{ + MPT_ASSERT(backend); +} + + + + +std::vector<std::byte> IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, const std::vector<std::byte> &def) const +{ + std::vector<std::byte> result = def; + if(!mpt::in_range<UINT>(result.size())) + { + return result; + } + ::GetPrivateProfileStruct(GetSection(path).c_str(), GetKey(path).c_str(), result.data(), static_cast<UINT>(result.size()), filename.AsNative().c_str()); + return result; +} + +mpt::ustring IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, const mpt::ustring &def) const +{ + std::vector<TCHAR> buf(128); + while(::GetPrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(def).c_str(), buf.data(), static_cast<DWORD>(buf.size()), filename.AsNative().c_str()) == buf.size() - 1) + { + if(buf.size() == std::numeric_limits<DWORD>::max()) + { + return def; + } + buf.resize(mpt::exponential_grow(buf.size(), std::numeric_limits<DWORD>::max())); + } + return mpt::ToUnicode(mpt::winstring(buf.data())); +} + +double IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, double def) const +{ + std::vector<TCHAR> buf(128); + while(::GetPrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(def).c_str(), buf.data(), static_cast<DWORD>(buf.size()), filename.AsNative().c_str()) == buf.size() - 1) + { + if(buf.size() == std::numeric_limits<DWORD>::max()) + { + return def; + } + buf.resize(mpt::exponential_grow(buf.size(), std::numeric_limits<DWORD>::max())); + } + return ConvertStrTo<double>(mpt::winstring(buf.data())); +} + +int32 IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, int32 def) const +{ + return (int32)::GetPrivateProfileInt(GetSection(path).c_str(), GetKey(path).c_str(), (UINT)def, filename.AsNative().c_str()); +} + +bool IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, bool def) const +{ + return ::GetPrivateProfileInt(GetSection(path).c_str(), GetKey(path).c_str(), def?1:0, filename.AsNative().c_str()) ? true : false; +} + + +void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, const std::vector<std::byte> &val) +{ + MPT_ASSERT(mpt::in_range<UINT>(val.size())); + ::WritePrivateProfileStruct(GetSection(path).c_str(), GetKey(path).c_str(), (LPVOID)val.data(), static_cast<UINT>(val.size()), filename.AsNative().c_str()); +} + +void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, const mpt::ustring &val) +{ + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(val).c_str(), filename.AsNative().c_str()); + + if(mpt::ToUnicode(mpt::Charset::Locale, mpt::ToCharset(mpt::Charset::Locale, val)) != val) // explicit round-trip + { + // Value is not representable in ANSI CP. + // Now check if the string got stored correctly. + if(ReadSettingRaw(path, mpt::ustring()) != val) + { + // The ini file is probably ANSI encoded. + ConvertToUnicode(); + // Re-write non-ansi-representable value. + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(val).c_str(), filename.AsNative().c_str()); + } + } +} + +void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, double val) +{ + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str()); +} + +void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, int32 val) +{ + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str()); +} + +void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, bool val) +{ + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str()); +} + +void IniFileSettingsBackend::RemoveSettingRaw(const SettingPath &path) +{ + ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), NULL, filename.AsNative().c_str()); +} + +void IniFileSettingsBackend::RemoveSectionRaw(const mpt::ustring §ion) +{ + ::WritePrivateProfileSection(mpt::ToWin(section).c_str(), _T("\0"), filename.AsNative().c_str()); +} + + +mpt::winstring IniFileSettingsBackend::GetSection(const SettingPath &path) +{ + return mpt::ToWin(path.GetSection()); +} +mpt::winstring IniFileSettingsBackend::GetKey(const SettingPath &path) +{ + return mpt::ToWin(path.GetKey()); +} + + + +IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename) + : filename(filename) +{ + return; +} + +IniFileSettingsBackend::~IniFileSettingsBackend() +{ + return; +} + +static std::vector<char> ReadFile(const mpt::PathString &filename) +{ + mpt::ifstream s(filename, std::ios::binary); + std::vector<char> result; + while(s) + { + char buf[4096]; + s.read(buf, 4096); + std::streamsize count = s.gcount(); + result.insert(result.end(), buf, buf + count); + } + return result; +} + +static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str) +{ + static_assert(sizeof(wchar_t) == 2); + mpt::SafeOutputFile sinifile(filename, std::ios::binary, mpt::FlushMode::Full); + mpt::ofstream& inifile = sinifile; + const uint8 UTF16LE_BOM[] = { 0xff, 0xfe }; + inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2); + inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type)); +} + +void IniFileSettingsBackend::ConvertToUnicode(const mpt::ustring &backupTag) +{ + // Force ini file to be encoded in UTF16. + // This causes WINAPI ini file functions to keep it in UTF16 encoding + // and thus support storing unicode strings uncorrupted. + // This is backwards compatible because even ANSI WINAPI behaves the + // same way in this case. + const std::vector<char> data = ReadFile(filename); + if(!data.empty() && IsTextUnicode(data.data(), mpt::saturate_cast<int>(data.size()), NULL)) + { + return; + } + const mpt::PathString backupFilename = filename + mpt::PathString::FromUnicode(backupTag.empty() ? U_(".ansi.bak") : U_(".ansi.") + backupTag + U_(".bak")); + CopyFile(filename.AsNative().c_str(), backupFilename.AsNative().c_str(), FALSE); + WriteFileUTF16LE(filename, mpt::ToWide(mpt::Charset::Locale, mpt::buffer_cast<std::string>(data))); +} + +SettingValue IniFileSettingsBackend::ReadSetting(const SettingPath &path, const SettingValue &def) const +{ + switch(def.GetType()) + { + case SettingTypeBool: return SettingValue(ReadSettingRaw(path, def.as<bool>()), def.GetTypeTag()); break; + case SettingTypeInt: return SettingValue(ReadSettingRaw(path, def.as<int32>()), def.GetTypeTag()); break; + case SettingTypeFloat: return SettingValue(ReadSettingRaw(path, def.as<double>()), def.GetTypeTag()); break; + case SettingTypeString: return SettingValue(ReadSettingRaw(path, def.as<mpt::ustring>()), def.GetTypeTag()); break; + case SettingTypeBinary: return SettingValue(ReadSettingRaw(path, def.as<std::vector<std::byte> >()), def.GetTypeTag()); break; + default: return SettingValue(); break; + } +} + +void IniFileSettingsBackend::WriteSetting(const SettingPath &path, const SettingValue &val) +{ + ASSERT(val.GetType() != SettingTypeNone); + switch(val.GetType()) + { + case SettingTypeBool: WriteSettingRaw(path, val.as<bool>()); break; + case SettingTypeInt: WriteSettingRaw(path, val.as<int32>()); break; + case SettingTypeFloat: WriteSettingRaw(path, val.as<double>()); break; + case SettingTypeString: WriteSettingRaw(path, val.as<mpt::ustring>()); break; + case SettingTypeBinary: WriteSettingRaw(path, val.as<std::vector<std::byte> >()); break; + default: break; + } +} + +void IniFileSettingsBackend::RemoveSetting(const SettingPath &path) +{ + RemoveSettingRaw(path); +} + +void IniFileSettingsBackend::RemoveSection(const mpt::ustring §ion) +{ + RemoveSectionRaw(section); +} + + + + + +IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename) + : IniFileSettingsBackend(filename) + , SettingsContainer(this) +{ + return; +} + +IniFileSettingsContainer::~IniFileSettingsContainer() +{ + return; +} + + + +DefaultSettingsContainer::DefaultSettingsContainer() + : IniFileSettingsContainer(theApp.GetConfigFileName()) +{ + return; +} + +DefaultSettingsContainer::~DefaultSettingsContainer() +{ + return; +} + + +OPENMPT_NAMESPACE_END |