diff --git a/base/stringutil.cpp b/base/stringutil.cpp index b54b56e627..fb4acebc33 100644 --- a/base/stringutil.cpp +++ b/base/stringutil.cpp @@ -1,8 +1,19 @@ #include +#include +#include +#include +#include + +#include #include "base/buffer.h" #include "base/stringutil.h" +#ifdef _WIN32 +// Function Cross-Compatibility +#define strcasecmp _stricmp +#endif + unsigned int parseHex(const char *_szValue) { int Value = 0; @@ -51,3 +62,144 @@ void DataToHexString(const uint8 *data, size_t size, std::string *output) { } buffer.TakeAll(output); } + +std::string StringFromFormat(const char* format, ...) +{ + va_list args; + char *buf = NULL; + std::string temp = ""; +#ifdef _WIN32 + int required = 0; + + va_start(args, format); + required = _vscprintf(format, args); + buf = new char[required + 1]; + if(vsnprintf(buf, required, format, args) < 0) + buf[0] = '\0'; + va_end(args); + + buf[required] = '\0'; + temp = buf; + delete[] buf; +#else + va_start(args, format); + if(vasprintf(&buf, format, args) < 0) + buf = NULL; + va_end(args); + + if(buf != NULL) { + temp = buf; + free(buf); + } +#endif + return temp; +} + +std::string StringFromInt(int value) +{ + char temp[16]; + sprintf(temp, "%i", value); + return temp; +} + +std::string StringFromBool(bool value) +{ + return value ? "True" : "False"; +} + +// Turns " hej " into "hej". Also handles tabs. +std::string StripSpaces(const std::string &str) +{ + const size_t s = str.find_first_not_of(" \t\r\n"); + + if (str.npos != s) + return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1); + else + return ""; +} + +// "\"hello\"" is turned to "hello" +// This one assumes that the string has already been space stripped in both +// ends, as done by StripSpaces above, for example. +std::string StripQuotes(const std::string& s) +{ + if (s.size() && '\"' == s[0] && '\"' == *s.rbegin()) + return s.substr(1, s.size() - 2); + else + return s; +} + +// For Debugging. Read out an u8 array. +std::string ArrayToString(const uint8_t *data, uint32_t size, int line_len, bool spaces) +{ + std::ostringstream oss; + oss << std::setfill('0') << std::hex; + + for (int line = 0; size; ++data, --size) + { + oss << std::setw(2) << (int)*data; + if (line_len == ++line) + { + oss << '\n'; + line = 0; + } + else if (spaces) + oss << ' '; + } + + return oss.str(); +} + +bool TryParse(const std::string &str, uint32_t *const output) +{ + char *endptr = NULL; + + // Holy crap this is ugly. + + // Reset errno to a value other than ERANGE + errno = 0; + + unsigned long value = strtoul(str.c_str(), &endptr, 0); + + if (!endptr || *endptr) + return false; + + if (errno == ERANGE) + return false; + + if (ULONG_MAX > UINT_MAX) { +#ifdef _MSC_VER +#pragma warning (disable:4309) +#endif + // Note: The typecasts avoid GCC warnings when long is 32 bits wide. + if (value >= static_cast(0x100000000ull) + && value <= static_cast(0xFFFFFFFF00000000ull)) + return false; + } + + *output = static_cast(value); + return true; +} + +bool TryParse(const std::string &str, bool *const output) +{ + if ("1" == str || !strcasecmp("true", str.c_str())) + *output = true; + else if ("0" == str || !strcasecmp("false", str.c_str())) + *output = false; + else + return false; + + return true; +} + +void SplitString(const std::string& str, const char delim, std::vector& output) +{ + std::istringstream iss(str); + output.resize(1); + + while (std::getline(iss, *output.rbegin(), delim)) + output.push_back(""); + + output.pop_back(); +} diff --git a/base/stringutil.h b/base/stringutil.h index d34ce12de0..026c2d0ff3 100644 --- a/base/stringutil.h +++ b/base/stringutil.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "base/basictypes.h" @@ -55,6 +57,45 @@ public: bool operator ==(const ConstString &other) const { return ptr_ == other.ptr_ || !strcmp(ptr_, other.ptr_); } + const char *get() const { return ptr_; } private: const char *ptr_; }; + +std::string StringFromFormat(const char* format, ...); +std::string StringFromInt(int value); +std::string StringFromBool(bool value); + +std::string ArrayToString(const uint8_t *data, uint32_t size, int line_len = 20, bool spaces = true); + +std::string StripSpaces(const std::string &s); +std::string StripQuotes(const std::string &s); + +bool TryParse(const std::string &str, bool *const output); +bool TryParse(const std::string &str, uint32_t *const output); + +template +static bool TryParse(const std::string &str, N *const output) +{ + std::istringstream iss(str); + + N tmp = 0; + if (iss >> tmp) + { + *output = tmp; + return true; + } + else + return false; +} +void SplitString(const std::string& str, const char delim, std::vector& output); + + +template +static std::string ValueToString(const N value) +{ + std::stringstream string; + string << value; + return string.str(); +} + diff --git a/file/ini_file.cpp b/file/ini_file.cpp new file mode 100644 index 0000000000..e576289138 --- /dev/null +++ b/file/ini_file.cpp @@ -0,0 +1,580 @@ +// IniFile +// Taken from Dolphin but relicensed by me, Henrik Rydgard, under the MIT +// license as I wrote the whole thing originally and it has barely changed. + +#include +#include + +#ifndef _MSC_VER +#include +#endif + +#include +#include +#include +#include +#include + +#include "base/stringutil.h" +#include "file/ini_file.h" + +namespace { + +#ifdef _WIN32 + // Function Cross-Compatibility +#define strcasecmp _stricmp +#endif + +// Ugh, this is ugly. +static bool ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut, std::string* commentOut) +{ + int FirstEquals = (int)line.find("=", 0); + int FirstCommentChar = -1; + + // Comments + if (FirstCommentChar < 0) + FirstCommentChar = + (int)line.find("#", FirstEquals > 0 ? FirstEquals : 0); + if (FirstCommentChar < 0 && line[0] == ';') + FirstCommentChar = 0; + + // Allow preservation of spacing before comment + if (FirstCommentChar > 0) + { + while (line[FirstCommentChar - 1] == ' ' || line[FirstCommentChar - 1] == 9) // 9 == tab + { + FirstCommentChar--; + } + } + + if ((FirstEquals >= 0) && ((FirstCommentChar < 0) || (FirstEquals < FirstCommentChar))) + { + // Yes, a valid key/value line! + *keyOut = StripSpaces(line.substr(0, FirstEquals)); + if (commentOut) *commentOut = FirstCommentChar > 0 ? line.substr(FirstCommentChar) : std::string(""); + if (valueOut) *valueOut = StripQuotes(StripSpaces(line.substr(FirstEquals + 1, FirstCommentChar - FirstEquals - 1))); + return true; + } + return false; +} + +} + +std::string* IniFile::Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut) +{ + for (std::vector::iterator iter = lines.begin(); iter != lines.end(); ++iter) + { + std::string& line = *iter; + std::string lineKey; + ParseLine(line, &lineKey, valueOut, commentOut); + if (!strcasecmp(lineKey.c_str(), key)) + return &line; + } + return 0; +} + +void IniFile::Section::Set(const char* key, const char* newValue) +{ + std::string value, commented; + std::string* line = GetLine(key, &value, &commented); + if (line) + { + // Change the value - keep the key and comment + *line = StripSpaces(key) + " = " + newValue + commented; + } + else + { + // The key did not already exist in this section - let's add it. + lines.push_back(std::string(key) + " = " + newValue); + } +} + +void IniFile::Section::Set(const char* key, const std::string& newValue, const std::string& defaultValue) +{ + if (newValue != defaultValue) + Set(key, newValue); + else + Delete(key); +} + +bool IniFile::Section::Get(const char* key, std::string* value, const char* defaultValue) +{ + std::string* line = GetLine(key, value, 0); + if (!line) + { + if (defaultValue) + { + *value = defaultValue; + } + return false; + } + return true; +} + +void IniFile::Section::Set(const char* key, const float newValue, const float defaultValue) +{ + if (newValue != defaultValue) + Set(key, newValue); + else + Delete(key); +} + +void IniFile::Section::Set(const char* key, int newValue, int defaultValue) +{ + if (newValue != defaultValue) + Set(key, newValue); + else + Delete(key); +} + +void IniFile::Section::Set(const char* key, bool newValue, bool defaultValue) +{ + if (newValue != defaultValue) + Set(key, newValue); + else + Delete(key); +} + +void IniFile::Section::Set(const char* key, const std::vector& newValues) +{ + std::string temp; + // Join the strings with , + std::vector::const_iterator it; + for (it = newValues.begin(); it != newValues.end(); ++it) + { + temp += (*it) + ","; + } + // remove last , + if (temp.length()) + temp.resize(temp.length() - 1); + Set(key, temp.c_str()); +} + +bool IniFile::Section::Get(const char* key, std::vector& values) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (!retval || temp.empty()) + { + return false; + } + // ignore starting , if any + size_t subStart = temp.find_first_not_of(","); + size_t subEnd; + + // split by , + while (subStart != std::string::npos) { + + // Find next , + subEnd = temp.find_first_of(",", subStart); + if (subStart != subEnd) + // take from first char until next , + values.push_back(StripSpaces(temp.substr(subStart, subEnd - subStart))); + + // Find the next non , char + subStart = temp.find_first_not_of(",", subEnd); + } + + return true; +} + +bool IniFile::Section::Get(const char* key, int* value, int defaultValue) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (retval && TryParse(temp.c_str(), value)) + return true; + *value = defaultValue; + return false; +} + +bool IniFile::Section::Get(const char* key, uint32_t* value, uint32_t defaultValue) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (retval && TryParse(temp, value)) + return true; + *value = defaultValue; + return false; +} + +bool IniFile::Section::Get(const char* key, bool* value, bool defaultValue) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (retval && TryParse(temp.c_str(), value)) + return true; + *value = defaultValue; + return false; +} + +bool IniFile::Section::Get(const char* key, float* value, float defaultValue) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (retval && TryParse(temp.c_str(), value)) + return true; + *value = defaultValue; + return false; +} + +bool IniFile::Section::Get(const char* key, double* value, double defaultValue) +{ + std::string temp; + bool retval = Get(key, &temp, 0); + if (retval && TryParse(temp.c_str(), value)) + return true; + *value = defaultValue; + return false; +} + +bool IniFile::Section::Exists(const char *key) const +{ + for (std::vector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter) + { + std::string lineKey; + ParseLine(*iter, &lineKey, NULL, NULL); + if (!strcasecmp(lineKey.c_str(), key)) + return true; + } + return false; +} + +std::map IniFile::Section::ToMap() const +{ + std::map outMap; + for (std::vector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter) + { + std::string lineKey, lineValue; + if (ParseLine(*iter, &lineKey, &lineValue, NULL)) { + outMap[lineKey] = lineValue; + } + } + return outMap; +} + + +bool IniFile::Section::Delete(const char *key) +{ + std::string* line = GetLine(key, 0, 0); + for (std::vector::iterator liter = lines.begin(); liter != lines.end(); ++liter) + { + if (line == &*liter) + { + lines.erase(liter); + return true; + } + } + return false; +} + +// IniFile + +const IniFile::Section* IniFile::GetSection(const char* sectionName) const +{ + for (std::vector
::const_iterator iter = sections.begin(); iter != sections.end(); ++iter) + if (!strcasecmp(iter->name().c_str(), sectionName)) + return (&(*iter)); + return 0; +} + +IniFile::Section* IniFile::GetSection(const char* sectionName) +{ + for (std::vector
::iterator iter = sections.begin(); iter != sections.end(); ++iter) + if (!strcasecmp(iter->name().c_str(), sectionName)) + return (&(*iter)); + return 0; +} + +IniFile::Section* IniFile::GetOrCreateSection(const char* sectionName) +{ + Section* section = GetSection(sectionName); + if (!section) + { + sections.push_back(Section(sectionName)); + section = §ions[sections.size() - 1]; + } + return section; +} + +bool IniFile::DeleteSection(const char* sectionName) +{ + Section* s = GetSection(sectionName); + if (!s) + return false; + for (std::vector
::iterator iter = sections.begin(); iter != sections.end(); ++iter) + { + if (&(*iter) == s) + { + sections.erase(iter); + return true; + } + } + return false; +} + +bool IniFile::Exists(const char* sectionName, const char* key) const +{ + const Section* section = GetSection(sectionName); + if (!section) + return false; + return section->Exists(key); +} + +void IniFile::SetLines(const char* sectionName, const std::vector &lines) +{ + Section* section = GetOrCreateSection(sectionName); + section->lines.clear(); + for (std::vector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter) + { + section->lines.push_back(*iter); + } +} + +bool IniFile::DeleteKey(const char* sectionName, const char* key) +{ + Section* section = GetSection(sectionName); + if (!section) + return false; + std::string* line = section->GetLine(key, 0, 0); + for (std::vector::iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter) + { + if (line == &(*liter)) + { + section->lines.erase(liter); + return true; + } + } + return false; //shouldn't happen +} + +// Return a list of all keys in a section +bool IniFile::GetKeys(const char* sectionName, std::vector& keys) const +{ + const Section* section = GetSection(sectionName); + if (!section) + return false; + keys.clear(); + for (std::vector::const_iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter) + { + std::string key; + ParseLine(*liter, &key, 0, 0); + keys.push_back(key); + } + return true; +} + +// Return a list of all lines in a section +bool IniFile::GetLines(const char* sectionName, std::vector& lines, const bool remove_comments) const +{ + const Section* section = GetSection(sectionName); + if (!section) + return false; + + lines.clear(); + for (std::vector::const_iterator iter = section->lines.begin(); iter != section->lines.end(); ++iter) + { + std::string line = StripSpaces(*iter); + + if (remove_comments) + { + int commentPos = (int)line.find('#'); + if (commentPos == 0) + { + continue; + } + + if (commentPos != (int)std::string::npos) + { + line = StripSpaces(line.substr(0, commentPos)); + } + } + + lines.push_back(line); + } + + return true; +} + + +void IniFile::SortSections() +{ + std::sort(sections.begin(), sections.end()); +} + +bool IniFile::Load(const char* filename) +{ + // Maximum number of letters in a line + static const int MAX_BYTES = 1024*32; + + sections.clear(); + sections.push_back(Section("")); + // first section consists of the comments before the first real section + + // Open file + std::ifstream in; + in.open(filename, std::ios::in); + + if (in.fail()) return false; + + while (!in.eof()) + { + char templine[MAX_BYTES]; + in.getline(templine, MAX_BYTES); + std::string line = templine; + + // Remove UTF-8 byte order marks. + if (line.substr(0, 3) == "\xEF\xBB\xBF") + line = line.substr(3); + +#ifndef _WIN32 + // Check for CRLF eol and convert it to LF + if (!line.empty() && line.at(line.size()-1) == '\r') + { + line.erase(line.size()-1); + } +#endif + + if (in.eof()) break; + + if (line.size() > 0) + { + if (line[0] == '[') + { + size_t endpos = line.find("]"); + + if (endpos != std::string::npos) + { + // New section! + std::string sub = line.substr(1, endpos - 1); + sections.push_back(Section(sub)); + + if (endpos + 1 < line.size()) + { + sections[sections.size() - 1].comment = line.substr(endpos + 1); + } + } + } + else + { + sections[sections.size() - 1].lines.push_back(line); + } + } + } + + in.close(); + return true; +} + +bool IniFile::Save(const char* filename) +{ + std::ofstream out; + out.open(filename, std::ios::out); + + if (out.fail()) + { + return false; + } + + // UTF-8 byte order mark. To make sure notepad doesn't go nuts. + out << "\xEF\xBB\xBF"; + + // Currently testing if dolphin community can handle the requirements of C++11 compilation + // If you get a compiler error on this line, your compiler is probably old. + // Update to g++ 4.4 or a recent version of clang (XCode 4.2 on OS X). + // If you don't want to update, complain in a google code issue, the dolphin forums or #dolphin-emu. + for (std::vector
::iterator iter = sections.begin(); iter != sections.end(); ++iter) + { + const Section& section = *iter; + + if (section.name() != "") + { + out << "[" << section.name() << "]" << section.comment << std::endl; + } + + for (std::vector::const_iterator liter = section.lines.begin(); liter != section.lines.end(); ++liter) + { + std::string s = *liter; + out << s << std::endl; + } + } + + out.close(); + return true; +} + + +bool IniFile::Get(const char* sectionName, const char* key, std::string* value, const char* defaultValue) +{ + Section* section = GetSection(sectionName); + if (!section) { + if (defaultValue) { + *value = defaultValue; + } + return false; + } + return section->Get(key, value, defaultValue); +} + +bool IniFile::Get(const char *sectionName, const char* key, std::vector& values) +{ + Section *section = GetSection(sectionName); + if (!section) + return false; + return section->Get(key, values); +} + +bool IniFile::Get(const char* sectionName, const char* key, int* value, int defaultValue) +{ + Section *section = GetSection(sectionName); + if (!section) { + *value = defaultValue; + return false; + } else { + return section->Get(key, value, defaultValue); + } +} + +bool IniFile::Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue) +{ + Section *section = GetSection(sectionName); + if (!section) { + *value = defaultValue; + return false; + } else { + return section->Get(key, value, defaultValue); + } +} + +bool IniFile::Get(const char* sectionName, const char* key, bool* value, bool defaultValue) +{ + Section *section = GetSection(sectionName); + if (!section) { + *value = defaultValue; + return false; + } else { + return section->Get(key, value, defaultValue); + } +} + + +// Unit test. TODO: Move to the real unit test framework. +/* + int main() + { + IniFile ini; + ini.Load("my.ini"); + ini.Set("Hej", "A", "amaskdfl"); + ini.Set("Mossa", "A", "amaskdfl"); + ini.Set("Aissa", "A", "amaskdfl"); + //ini.Read("my.ini"); + std::string x; + ini.Get("Hej", "B", &x, "boo"); + ini.DeleteKey("Mossa", "A"); + ini.DeleteSection("Mossa"); + ini.SortSections(); + ini.Save("my.ini"); + //UpdateVars(ini); + return 0; + } + */ diff --git a/file/ini_file.h b/file/ini_file.h new file mode 100644 index 0000000000..fbf63e0328 --- /dev/null +++ b/file/ini_file.h @@ -0,0 +1,179 @@ +// IniFile +// Taken from Dolphin but relicensed by me, Henrik Rydgard, under the MIT +// license as I wrote the whole thing originally and it has barely changed. + +#pragma once + +#include +#include +#include + +#include "base/stringutil.h" + +class IniFile +{ +public: + class Section + { + friend class IniFile; + + public: + Section() {} + Section(const std::string& name) : name_(name) {} + + bool Exists(const char *key) const; + bool Delete(const char *key); + + std::map ToMap() const; + + std::string* GetLine(const char* key, std::string* valueOut, std::string* commentOut); + void Set(const char* key, const char* newValue); + void Set(const char* key, const std::string& newValue, const std::string& defaultValue); + + void Set(const std::string &key, const std::string &value) { + Set(key.c_str(), value.c_str()); + } + bool Get(const char* key, std::string* value, const char* defaultValue); + + void Set(const char* key, uint32_t newValue) { + Set(key, StringFromFormat("0x%08x", newValue).c_str()); + } + void Set(const char* key, float newValue) { + Set(key, StringFromFormat("%f", newValue).c_str()); + } + void Set(const char* key, const float newValue, const float defaultValue); + void Set(const char* key, double newValue) { + Set(key, StringFromFormat("%f", newValue).c_str()); + } + + void Set(const char* key, int newValue, int defaultValue); + void Set(const char* key, int newValue) { + Set(key, StringFromInt(newValue).c_str()); + } + + void Set(const char* key, bool newValue, bool defaultValue); + void Set(const char* key, bool newValue) { + Set(key, StringFromBool(newValue).c_str()); + } + void Set(const char* key, const std::vector& newValues); + + template + void Set(const char* key, const std::map& newValues) + { + std::vector temp; + for(typename std::map::const_iterator it = newValues.begin(); it != newValues.end(); it++) + { + temp.push_back(ValueToString(it->first)+"_"+ValueToString(it->second)); + } + Set(key,temp); + } + + bool Get(const char* key, int* value, int defaultValue = 0); + bool Get(const char* key, uint32_t* value, uint32_t defaultValue = 0); + bool Get(const char* key, bool* value, bool defaultValue = false); + bool Get(const char* key, float* value, float defaultValue = false); + bool Get(const char* key, double* value, double defaultValue = false); + bool Get(const char* key, std::vector& values); + template + bool Get(const char* key, std::map& values) + { + std::vector temp; + if(!Get(key,temp)) + { + return false; + } + values.clear(); + for(size_t i = 0; i < temp.size(); i++) + { + std::vector key_val; + SplitString(temp[i],'_',key_val); + if(key_val.size() < 2) + continue; + U mapKey; + V mapValue; + if(!TryParse(key_val[0],&mapKey)) + continue; + if(!TryParse(key_val[1],&mapValue)) + continue; + values[mapKey] = mapValue; + } + return true; + } + + bool operator < (const Section& other) const { + return name_ < other.name_; + } + + const std::string &name() const { + return name_; + } + + protected: + std::vector lines; + std::string name_; + std::string comment; + }; + + bool Load(const char* filename); + bool Load(const std::string &filename) { return Load(filename.c_str()); } + bool Save(const char* filename); + bool Save(const std::string &filename) { return Save(filename.c_str()); } + + // Returns true if key exists in section + bool Exists(const char* sectionName, const char* key) const; + + // TODO: Get rid of these, in favor of the Section ones. + void Set(const char* sectionName, const char* key, const char* newValue) { + GetOrCreateSection(sectionName)->Set(key, newValue); + } + void Set(const char* sectionName, const char* key, const std::string& newValue) { + GetOrCreateSection(sectionName)->Set(key, newValue.c_str()); + } + void Set(const char* sectionName, const char* key, int newValue) { + GetOrCreateSection(sectionName)->Set(key, newValue); + } + void Set(const char* sectionName, const char* key, uint32_t newValue) { + GetOrCreateSection(sectionName)->Set(key, newValue); + } + void Set(const char* sectionName, const char* key, bool newValue) { + GetOrCreateSection(sectionName)->Set(key, newValue); + } + void Set(const char* sectionName, const char* key, const std::vector& newValues) { + GetOrCreateSection(sectionName)->Set(key, newValues); + } + + // TODO: Get rid of these, in favor of the Section ones. + bool Get(const char* sectionName, const char* key, std::string* value, const char* defaultValue = ""); + bool Get(const char* sectionName, const char* key, int* value, int defaultValue = 0); + bool Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue = 0); + bool Get(const char* sectionName, const char* key, bool* value, bool defaultValue = false); + bool Get(const char* sectionName, const char* key, std::vector& values); + + template bool GetIfExists(const char* sectionName, const char* key, T value) + { + if (Exists(sectionName, key)) + return Get(sectionName, key, value); + return false; + } + + bool GetKeys(const char* sectionName, std::vector& keys) const; + + void SetLines(const char* sectionName, const std::vector &lines); + bool GetLines(const char* sectionName, std::vector& lines, const bool remove_comments = true) const; + + bool DeleteKey(const char* sectionName, const char* key); + bool DeleteSection(const char* sectionName); + + void SortSections(); + const std::vector
&Sections() { return sections; } + + Section* GetOrCreateSection(const char* section); + +private: + std::vector
sections; + + const Section* GetSection(const char* section) const; + Section* GetSection(const char* section); + std::string* GetLine(const char* section, const char* key); + void CreateSection(const char* section); +}; diff --git a/gfx_es2/draw_buffer.cpp b/gfx_es2/draw_buffer.cpp index aa5017aa0d..51bc62bad7 100644 --- a/gfx_es2/draw_buffer.cpp +++ b/gfx_es2/draw_buffer.cpp @@ -397,6 +397,8 @@ void DrawBuffer::DrawText(int font, const char *text, float x, float y, Color co continue; } const AtlasChar *ch = atlasfont.getChar(cval); + if (!ch) + ch = atlasfont.getChar('?'); if (ch) { const AtlasChar &c = *ch; float cx1, cy1, cx2, cy2; diff --git a/i18n/i18n.cpp b/i18n/i18n.cpp new file mode 100644 index 0000000000..8dfb50faf8 --- /dev/null +++ b/i18n/i18n.cpp @@ -0,0 +1,105 @@ +#include "i18n/i18n.h" +#include "file/ini_file.h" + +I18NRepo i18nrepo; + +I18NRepo::~I18NRepo() { + Clear(); +} + +void I18NRepo::Clear() { + for (auto iter = cats_.begin(); iter != cats_.end(); ++iter) { + delete iter->second; + } + cats_.clear(); +} + +const char *I18NCategory::T(const char *key, const char *def) { + auto iter = map_.find(key); + if (iter != map_.end()) { + return iter->second.text.c_str(); + } else { + if (def) + missedKeyLog_[key] = def; + else + missedKeyLog_[key] = key; + return def ? def : key; + } +} + +void I18NCategory::SetMap(const std::map &m) { + for (auto iter = m.begin(); iter != m.end(); ++iter) { + if (map_.find(iter->first) == map_.end()) { + map_[iter->first] = I18NEntry(iter->second); + } + } +} + +I18NCategory *I18NRepo::GetCategory(const char *category) { + auto iter = cats_.find(category); + if (iter != cats_.end()) { + return iter->second; + } else { + I18NCategory *c = new I18NCategory(this); + cats_[category] = c; + return c; + } +} + +std::string I18NRepo::GetIniPath(const std::string &languageID) const { + return "lang/" + languageID + ".ini"; +} + +void I18NRepo::LoadIni(const std::string &languageID) { + IniFile ini; + if (!ini.Load(GetIniPath(languageID))) { + return; + } + + Clear(); + + const std::vector §ions = ini.Sections(); + + for (auto iter = sections.begin(); iter != sections.end(); ++iter) { + if (iter->name() != "") { + cats_[iter->name()] = LoadSection(&(*iter)); + } + } +} + +I18NCategory *I18NRepo::LoadSection(const IniFile::Section *section) { + I18NCategory *cat = new I18NCategory(this); + std::map sectionMap = section->ToMap(); + cat->SetMap(sectionMap); + return cat; +} + +// This is a very light touched save variant - it won't overwrite +// anything, only create new entries. +void I18NRepo::SaveIni(const std::string &languageID) { + IniFile ini; + ini.Load(GetIniPath(languageID)); + for (auto iter = cats_.begin(); iter != cats_.end(); ++iter) { + std::string categoryName = iter->first; + IniFile::Section *section = ini.GetOrCreateSection(categoryName.c_str()); + SaveSection(ini, section, iter->second); + } + ini.Save(GetIniPath(languageID)); +} + +void I18NRepo::SaveSection(IniFile &ini, IniFile::Section *section, I18NCategory *cat) { + const std::map &missed = cat->Missed(); + + for (auto iter = missed.begin(); iter != missed.end(); ++iter) { + if (!section->Exists(iter->first.c_str())) { + section->Set(iter->first, iter->second); + } + } + + const std::map &entries = cat->GetMap(); + for (auto iter = entries.begin(); iter != entries.end(); ++iter) { + section->Set(iter->first, iter->second.text); + } + + cat->ClearMissed(); +} diff --git a/i18n/i18n.h b/i18n/i18n.h new file mode 100644 index 0000000000..41fb833c2c --- /dev/null +++ b/i18n/i18n.h @@ -0,0 +1,95 @@ +#pragma once + +// I18N = I....18..dots.....N = INTERNATIONALIZATION + +// Super simple I18N library. +// Just enough to be useful and usable. +// Spits out easy-to-edit utf-8 .INI files. + +// As usual, everything is UTF-8. Nothing else allowed. + +#include +#include +#include + +#include "base/stringutil.h" +#include "file/ini_file.h" + +// Reasonably thread safe. + +class I18NRepo; + +struct I18NEntry { + I18NEntry(const std::string &t) : text(t), readFlag(false) {} + I18NEntry() : readFlag(false) {} + std::string text; + bool readFlag; +}; + +struct I18NCandidate { + I18NCandidate() : key(0), defVal(0) {} + I18NCandidate(const char *k, const char *d) : key(k), defVal(d) {} + const char *key; + const char *defVal; +}; + +class I18NCategory { +public: + const char *T(const char *key, const char *def = 0); + + const std::map &Missed() const { + return missedKeyLog_; + } + + void SetMap(const std::map &m); + const std::map &GetMap() { return map_; } + void ClearMissed() { missedKeyLog_.clear(); } + +private: + I18NCategory(I18NRepo *repo) {} + + std::map map_; + std::map missedKeyLog_; + + // Noone else can create these. + friend class I18NRepo; + + DISALLOW_COPY_AND_ASSIGN(I18NCategory); +}; + +class I18NRepo { +public: + I18NRepo() {} + ~I18NRepo(); + + void LoadIni(const std::string &languageID); // NOT the filename! + void SaveIni(const std::string &languageID); + + I18NCategory *GetCategory(const char *categoryName); + const char *T(const char *category, const char *key, const char *def = 0); + +private: + std::string GetIniPath(const std::string &languageID) const; + void Clear(); + I18NCategory *LoadSection(const IniFile::Section *section); + void SaveSection(IniFile &ini, IniFile::Section *section, I18NCategory *cat); + + std::map cats_; + + DISALLOW_COPY_AND_ASSIGN(I18NRepo); +}; + +extern I18NRepo i18nrepo; + +// These are simply talking to the one global instance of I18NRepo. + +inline I18NCategory *GetI18NCategory(const char *categoryName) { + return i18nrepo.GetCategory(categoryName); +} + +inline const char *T(const char *category, const char *key, const char *def = 0) { + return i18nrepo.T(category, key, def); +} + + + diff --git a/native.vcxproj b/native.vcxproj index b9aa090825..2366edce00 100644 --- a/native.vcxproj +++ b/native.vcxproj @@ -211,6 +211,7 @@ + @@ -223,6 +224,7 @@ + @@ -305,6 +307,7 @@ + @@ -316,6 +319,7 @@ + diff --git a/native.vcxproj.filters b/native.vcxproj.filters index 3b902f1bd9..af6190b45d 100644 --- a/native.vcxproj.filters +++ b/native.vcxproj.filters @@ -245,6 +245,12 @@ thread + + i18n + + + file + @@ -432,6 +438,12 @@ thread + + i18n + + + file + @@ -494,5 +506,8 @@ {caa41117-1d90-47cc-9fba-f7e670e315a3} + + {02e8ef95-82c7-4420-b029-a189a5e0fcbd} + \ No newline at end of file diff --git a/ui/ui.cpp b/ui/ui.cpp index eb2c69c903..301fc9cda8 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -286,9 +286,13 @@ int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w } // Render button - const int dropsize = 10; + int dropsize = 10; if (drop_shadow && texture) { + if (txOffset) { + dropsize = 3; + y += txOffset * 2; + } ui_draw2d.DrawImage4Grid(drop_shadow, x - dropsize, y, x+w + dropsize, y+h+dropsize*1.5, alphaMul(color,0.5f), 1.0f); ui_draw2d.Flush(true);