BRIDGE+GUI: new settings provider (should fix the issue of the settings resetting). Also: commit 1500, YAY!
This commit is contained in:
parent
f55e4bd020
commit
2035bfdf54
|
@ -0,0 +1,326 @@
|
|||
#ifndef _UTF8INI_H
|
||||
#define _UTF8INI_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class Utf8Ini
|
||||
{
|
||||
public:
|
||||
/**
|
||||
\brief Serialize to the INI file format.
|
||||
*/
|
||||
inline std::string Serialize() const
|
||||
{
|
||||
std::string output;
|
||||
for(const auto & section : _sections)
|
||||
{
|
||||
if(output.length())
|
||||
appendLine(output, "");
|
||||
appendLine(output, makeSectionText(section.first));
|
||||
for(const auto & keyvalue : section.second)
|
||||
if(keyvalue.first.length())
|
||||
appendLine(output, makeKeyValueText(keyvalue.first, keyvalue.second));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Deserialize from the INI file format.
|
||||
\param data The INI data.
|
||||
\param [out] errorLine The error line (only has a meaning when this function failed).
|
||||
\return true if it succeeds, false if it fails.
|
||||
*/
|
||||
inline bool Deserialize(const std::string & data, int & errorLine)
|
||||
{
|
||||
//initialize
|
||||
errorLine = 0;
|
||||
Clear();
|
||||
|
||||
//read lines
|
||||
std::vector<std::string> lines;
|
||||
std::string curLine;
|
||||
for(auto ch : data)
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case '\r':
|
||||
break;
|
||||
case '\n':
|
||||
lines.push_back(trim(curLine));
|
||||
curLine.clear();
|
||||
break;
|
||||
default:
|
||||
curLine += ch;
|
||||
}
|
||||
}
|
||||
if(curLine.length())
|
||||
lines.push_back(trim(curLine));
|
||||
|
||||
//parse lines
|
||||
std::string section = "";
|
||||
for(const auto & line : lines)
|
||||
{
|
||||
errorLine++;
|
||||
switch(getLineType(line))
|
||||
{
|
||||
case LineType::Invalid:
|
||||
MessageBoxA(0, line.c_str(), "line", 0);
|
||||
return false;
|
||||
|
||||
case LineType::Comment:
|
||||
case LineType::Empty:
|
||||
continue;
|
||||
|
||||
case LineType::KeyValue:
|
||||
{
|
||||
std::string key;
|
||||
std::string value;
|
||||
if(!section.length() ||
|
||||
!parseKeyValueLine(line, key, value) ||
|
||||
!SetValue(section, key, value))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case LineType::Section:
|
||||
if(!parseSectionLine(line, section))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Sets a value. This will overwrite older values.
|
||||
\param section The section. Must have a length greater than one.
|
||||
\param key The key. Must have a length greater than one.
|
||||
\param value The value. Can be empty (effectively removing the value).
|
||||
\return true if the value was set successfully, false otherwise.
|
||||
*/
|
||||
inline bool SetValue(const std::string & section, const std::string & key, const std::string & value)
|
||||
{
|
||||
auto trimmedSection = trim(section);
|
||||
auto trimmedKey = trim(key);
|
||||
if(!trimmedSection.length() || !trimmedKey.length())
|
||||
return false;
|
||||
auto found = _sections.find(trimmedSection);
|
||||
if(found != _sections.end())
|
||||
found->second[trimmedKey] = value;
|
||||
else
|
||||
{
|
||||
KeyValueMap keyValueMap;
|
||||
keyValueMap[trimmedKey] = value;
|
||||
_sections[trimmedSection] = keyValueMap;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Removes all key/value pairs from a section.
|
||||
\param section The section to clear.
|
||||
\return true if it succeeds, false otherwise.
|
||||
*/
|
||||
inline bool ClearSection(const std::string & section)
|
||||
{
|
||||
auto trimmedSection = trim(section);
|
||||
if(!trimmedSection.length())
|
||||
return false;
|
||||
auto found = _sections.find(trimmedSection);
|
||||
if(found == _sections.end())
|
||||
return false;
|
||||
_sections.erase(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Removes all sections.
|
||||
*/
|
||||
inline void Clear()
|
||||
{
|
||||
_sections.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Gets a value.
|
||||
\param section The section.
|
||||
\param key The key.
|
||||
\param [in,out] value The value.
|
||||
\return The value. Empty string when the value was not found or empty.
|
||||
*/
|
||||
inline std::string GetValue(const std::string & section, const std::string & key) const
|
||||
{
|
||||
auto trimmedSection = trim(section);
|
||||
auto trimmedKey = trim(key);
|
||||
if(!trimmedSection.length() || !trimmedKey.length())
|
||||
return "";
|
||||
auto sectionFound = _sections.find(trimmedSection);
|
||||
if(sectionFound == _sections.end())
|
||||
return "";
|
||||
const auto & keyValueMap = sectionFound->second;
|
||||
auto keyFound = keyValueMap.find(trimmedKey);
|
||||
if(keyFound == keyValueMap.end())
|
||||
return "";
|
||||
return keyFound->second;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, std::string> KeyValueMap;
|
||||
std::map<std::string, KeyValueMap> _sections;
|
||||
|
||||
enum class LineType
|
||||
{
|
||||
Invalid,
|
||||
Empty,
|
||||
Section,
|
||||
KeyValue,
|
||||
Comment
|
||||
};
|
||||
|
||||
static inline LineType getLineType(const std::string & line)
|
||||
{
|
||||
auto len = line.length();
|
||||
if(!len)
|
||||
return LineType::Empty;
|
||||
if(line[0] == '[' && line[len - 1] == ']')
|
||||
return LineType::Section;
|
||||
if(line[0] == ';')
|
||||
return LineType::Comment;
|
||||
if(line.find('=') != std::string::npos)
|
||||
return LineType::KeyValue;
|
||||
return LineType::Invalid;
|
||||
}
|
||||
|
||||
static inline std::string trim(const std::string & str)
|
||||
{
|
||||
auto len = str.length();
|
||||
if(!len)
|
||||
return "";
|
||||
size_t pre = 0;
|
||||
while(str[pre] == ' ')
|
||||
pre++;
|
||||
size_t post = 0;
|
||||
while(str[len - post - 1] == ' ' && post < len)
|
||||
post++;
|
||||
auto sublen = len - post - pre;
|
||||
return sublen > 0 ? str.substr(pre, len - post - pre) : "";
|
||||
}
|
||||
|
||||
static inline bool parseKeyValueLine(const std::string & line, std::string & key, std::string & value)
|
||||
{
|
||||
auto pos = line.find('=');
|
||||
key = trim(line.substr(0, pos));
|
||||
value = trim(line.substr(pos + 1));
|
||||
auto len = value.length();
|
||||
if(len && value[0] == '\"' && value[len - 1] == '\"')
|
||||
value = unescapeValue(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool parseSectionLine(const std::string & line, std::string & section)
|
||||
{
|
||||
section = trim(line.substr(1, line.length() - 2));
|
||||
return section.length() > 0;
|
||||
}
|
||||
|
||||
static inline void appendLine(std::string & output, const std::string & line)
|
||||
{
|
||||
if(output.length())
|
||||
output += "\r\n";
|
||||
output += line;
|
||||
}
|
||||
|
||||
static inline std::string makeSectionText(const std::string & section)
|
||||
{
|
||||
return "[" + section + "]";
|
||||
}
|
||||
|
||||
static inline std::string makeKeyValueText(const std::string & key, const std::string & value)
|
||||
{
|
||||
return key + "=" + escapeValue(value);
|
||||
}
|
||||
|
||||
static inline bool needsEscaping(const std::string & value)
|
||||
{
|
||||
auto len = value.length();
|
||||
return len && (value[0] == ' ' || value[len - 1] == ' ' ||
|
||||
value.find('\n') != std::string::npos ||
|
||||
value.find('\"') != std::string::npos);
|
||||
}
|
||||
|
||||
static inline std::string escapeValue(const std::string & value)
|
||||
{
|
||||
if(!needsEscaping(value))
|
||||
return value;
|
||||
std::string escaped = "\"";
|
||||
for(auto ch : value)
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case '\"':
|
||||
escaped += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
escaped += "\\\\";
|
||||
break;
|
||||
case '\r':
|
||||
escaped += "\\r";
|
||||
break;
|
||||
case '\n':
|
||||
escaped += "\\n";
|
||||
break;
|
||||
case '\t':
|
||||
escaped += "\\t";
|
||||
default:
|
||||
escaped += ch;
|
||||
}
|
||||
}
|
||||
return escaped + "\"";
|
||||
}
|
||||
|
||||
static inline std::string unescapeValue(const std::string & str)
|
||||
{
|
||||
std::string result;
|
||||
bool bEscaped = false;
|
||||
for(auto ch : str)
|
||||
{
|
||||
if(!bEscaped)
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case '\"':
|
||||
break;
|
||||
case '\\':
|
||||
bEscaped = true;
|
||||
break;
|
||||
default:
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case 'r':
|
||||
result += '\r';
|
||||
break;
|
||||
case 'n':
|
||||
result += '\n';
|
||||
break;
|
||||
case 't':
|
||||
result += '\t';
|
||||
break;
|
||||
default:
|
||||
result += ch;
|
||||
}
|
||||
bEscaped = false;
|
||||
}
|
||||
}
|
||||
if(bEscaped)
|
||||
result += '\\';
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //_UTF8INI_H
|
|
@ -7,10 +7,10 @@
|
|||
#include "_global.h"
|
||||
#include "bridgemain.h"
|
||||
#include <stdio.h>
|
||||
#include "simpleini.h"
|
||||
#include "Utf8Ini.h"
|
||||
|
||||
static HINSTANCE hInst;
|
||||
|
||||
static Utf8Ini settings;
|
||||
static wchar_t szIniFile[MAX_PATH] = L"";
|
||||
static CRITICAL_SECTION csIni;
|
||||
|
||||
|
@ -90,7 +90,19 @@ BRIDGE_IMPEXP const char* BridgeStart()
|
|||
{
|
||||
if(!_dbg_dbginit || !_gui_guiinit)
|
||||
return "\"_dbg_dbginit\" || \"_gui_guiinit\" was not loaded yet, call BridgeInit!";
|
||||
int errorLine = 0;
|
||||
if(!BridgeSettingRead(&errorLine))
|
||||
{
|
||||
static char error[256] = "";
|
||||
if(!errorLine)
|
||||
strcpy_s(error, "Failed to read settings file from disk...");
|
||||
else
|
||||
sprintf_s(error, "Error in settings file on line %d...", errorLine);
|
||||
return error;
|
||||
}
|
||||
_gui_guiinit(0, 0); //remove arguments
|
||||
if(!BridgeSettingFlush())
|
||||
return "Failed to save settings!";
|
||||
DeleteCriticalSection(&csIni);
|
||||
return 0;
|
||||
}
|
||||
|
@ -117,19 +129,11 @@ BRIDGE_IMPEXP bool BridgeSettingGet(const char* section, const char* key, char*
|
|||
if(!section || !key || !value)
|
||||
return false;
|
||||
EnterCriticalSection(&csIni);
|
||||
CSimpleIniA inifile(true, false, false);
|
||||
bool success = false;
|
||||
if(inifile.LoadFile(szIniFile) >= 0)
|
||||
{
|
||||
const char* szValue = inifile.GetValue(section, key);
|
||||
if(szValue)
|
||||
{
|
||||
strcpy_s(value, MAX_SETTING_SIZE, szValue);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
auto foundValue = settings.GetValue(section, key);
|
||||
strcpy_s(value, MAX_SETTING_SIZE, settings.GetValue(section, key).c_str());
|
||||
bool result = foundValue.length() > 0;
|
||||
LeaveCriticalSection(&csIni);
|
||||
return success;
|
||||
return result;
|
||||
}
|
||||
|
||||
BRIDGE_IMPEXP bool BridgeSettingGetUint(const char* section, const char* key, duint* value)
|
||||
|
@ -155,13 +159,12 @@ BRIDGE_IMPEXP bool BridgeSettingSet(const char* section, const char* key, const
|
|||
if(section)
|
||||
{
|
||||
EnterCriticalSection(&csIni);
|
||||
CSimpleIniA inifile(true, false, false);
|
||||
inifile.LoadFile(szIniFile);
|
||||
if(!key || !value) //delete value/key when 0
|
||||
inifile.Delete(section, key, true);
|
||||
if(!key)
|
||||
success = settings.ClearSection(section);
|
||||
else if(!value)
|
||||
success = settings.SetValue(section, key, "");
|
||||
else
|
||||
inifile.SetValue(section, key, value);
|
||||
success = inifile.SaveFile(szIniFile, false) >= 0;
|
||||
success = settings.SetValue(section, key, value);
|
||||
LeaveCriticalSection(&csIni);
|
||||
}
|
||||
return success;
|
||||
|
@ -180,6 +183,63 @@ BRIDGE_IMPEXP bool BridgeSettingSetUint(const char* section, const char* key, du
|
|||
return BridgeSettingSet(section, key, newvalue);
|
||||
}
|
||||
|
||||
BRIDGE_IMPEXP bool BridgeSettingFlush()
|
||||
{
|
||||
EnterCriticalSection(&csIni);
|
||||
std::string iniData = settings.Serialize();
|
||||
LeaveCriticalSection(&csIni);
|
||||
bool success = false;
|
||||
HANDLE hFile = CreateFileW(szIniFile, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if(hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SetEndOfFile(hFile);
|
||||
DWORD written = 0;
|
||||
if(WriteFile(hFile, iniData.c_str(), (DWORD)iniData.length(), &written, nullptr))
|
||||
success = true;
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
BRIDGE_IMPEXP bool BridgeSettingRead(int* errorLine)
|
||||
{
|
||||
if(errorLine)
|
||||
*errorLine = 0;
|
||||
bool success = false;
|
||||
std::string iniData;
|
||||
HANDLE hFile = CreateFileW(szIniFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if(hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD fileSize = GetFileSize(hFile, nullptr);
|
||||
if(fileSize)
|
||||
{
|
||||
unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
|
||||
char* fileData = (char*)BridgeAlloc(sizeof(utf8bom) + fileSize + 1);
|
||||
DWORD read = 0;
|
||||
if(ReadFile(hFile, fileData, fileSize, &read, nullptr))
|
||||
{
|
||||
success = true;
|
||||
if(!memcmp(fileData, utf8bom, sizeof(utf8bom)))
|
||||
iniData.assign(fileData + sizeof(utf8bom));
|
||||
else
|
||||
iniData.assign(fileData);
|
||||
}
|
||||
BridgeFree(fileData);
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
if(success) //if we failed to read the file, the current settings are better than none at all
|
||||
{
|
||||
EnterCriticalSection(&csIni);
|
||||
int errline = 0;
|
||||
success = settings.Deserialize(iniData, errline);
|
||||
if(errorLine)
|
||||
*errorLine = errline;
|
||||
LeaveCriticalSection(&csIni);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
BRIDGE_IMPEXP int BridgeGetDbgVersion()
|
||||
{
|
||||
return DBG_VERSION;
|
||||
|
@ -1144,4 +1204,4 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|||
{
|
||||
hInst = hinstDLL;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
|
@ -48,6 +48,8 @@ BRIDGE_IMPEXP bool BridgeSettingGet(const char* section, const char* key, char*
|
|||
BRIDGE_IMPEXP bool BridgeSettingGetUint(const char* section, const char* key, duint* value);
|
||||
BRIDGE_IMPEXP bool BridgeSettingSet(const char* section, const char* key, const char* value);
|
||||
BRIDGE_IMPEXP bool BridgeSettingSetUint(const char* section, const char* key, duint value);
|
||||
BRIDGE_IMPEXP bool BridgeSettingFlush();
|
||||
BRIDGE_IMPEXP bool BridgeSettingRead(int* errorLine);
|
||||
BRIDGE_IMPEXP int BridgeGetDbgVersion();
|
||||
|
||||
//Debugger defines
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,7 +24,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bridgemain.h" />
|
||||
<ClInclude Include="simpleini.h" />
|
||||
<ClInclude Include="Utf8Ini.h" />
|
||||
<ClInclude Include="_global.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<ClInclude Include="bridgemain.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="simpleini.h">
|
||||
<ClInclude Include="Utf8Ini.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -326,6 +326,7 @@ void AppearanceDialog::on_buttonSave_clicked()
|
|||
Config()->writeColors();
|
||||
Config()->writeFonts();
|
||||
GuiUpdateAllViews();
|
||||
BridgeSettingFlush();
|
||||
GuiAddStatusBarMessage("Settings saved!\n");
|
||||
}
|
||||
|
||||
|
|
|
@ -297,6 +297,7 @@ void SettingsDialog::SaveSettings()
|
|||
if(settings.miscSymbolCache)
|
||||
BridgeSettingSet("Symbols", "CachePath", ui->editSymbolCache->text().toUtf8().constData());
|
||||
|
||||
BridgeSettingFlush();
|
||||
Config()->load();
|
||||
DbgSettingsUpdated();
|
||||
GuiUpdateAllViews();
|
||||
|
|
Loading…
Reference in New Issue