123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- /*
- * 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
|