Basic internationalization (i18n) framework.

This commit is contained in:
Henrik Rydgard 2013-04-18 11:52:18 +02:00
parent 909b67743e
commit b25cfe209b
10 changed files with 1178 additions and 1 deletions

View File

@ -1,8 +1,19 @@
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <string>
#include <sstream>
#include <iomanip>
#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<unsigned long>(0x100000000ull)
&& value <= static_cast<unsigned long>(0xFFFFFFFF00000000ull))
return false;
}
*output = static_cast<uint32_t>(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<std::string>& output)
{
std::istringstream iss(str);
output.resize(1);
while (std::getline(iss, *output.rbegin(), delim))
output.push_back("");
output.pop_back();
}

View File

@ -4,6 +4,8 @@
#include <cstdio>
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
#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 <typename N>
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<std::string>& output);
template <typename N>
static std::string ValueToString(const N value)
{
std::stringstream string;
string << value;
return string.str();
}

580
file/ini_file.cpp Normal file
View File

@ -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 <stdlib.h>
#include <stdio.h>
#ifndef _MSC_VER
#include <strings.h>
#endif
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
#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<std::string>::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<std::string>& newValues)
{
std::string temp;
// Join the strings with ,
std::vector<std::string>::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<std::string>& 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<std::string>::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<std::string, std::string> IniFile::Section::ToMap() const
{
std::map<std::string, std::string> outMap;
for (std::vector<std::string>::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<std::string>::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<Section>::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<Section>::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 = &sections[sections.size() - 1];
}
return section;
}
bool IniFile::DeleteSection(const char* sectionName)
{
Section* s = GetSection(sectionName);
if (!s)
return false;
for (std::vector<Section>::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<std::string> &lines)
{
Section* section = GetOrCreateSection(sectionName);
section->lines.clear();
for (std::vector<std::string>::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<std::string>::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<std::string>& keys) const
{
const Section* section = GetSection(sectionName);
if (!section)
return false;
keys.clear();
for (std::vector<std::string>::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<std::string>& lines, const bool remove_comments) const
{
const Section* section = GetSection(sectionName);
if (!section)
return false;
lines.clear();
for (std::vector<std::string>::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<Section>::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<std::string>::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<std::string>& 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;
}
*/

179
file/ini_file.h Normal file
View File

@ -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 <string>
#include <vector>
#include <map>
#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<std::string, std::string> 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<std::string>& newValues);
template<typename U, typename V>
void Set(const char* key, const std::map<U,V>& newValues)
{
std::vector<std::string> temp;
for(typename std::map<U,V>::const_iterator it = newValues.begin(); it != newValues.end(); it++)
{
temp.push_back(ValueToString<U>(it->first)+"_"+ValueToString<V>(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<std::string>& values);
template<typename U, typename V>
bool Get(const char* key, std::map<U,V>& values)
{
std::vector<std::string> temp;
if(!Get(key,temp))
{
return false;
}
values.clear();
for(size_t i = 0; i < temp.size(); i++)
{
std::vector<std::string> key_val;
SplitString(temp[i],'_',key_val);
if(key_val.size() < 2)
continue;
U mapKey;
V mapValue;
if(!TryParse<U>(key_val[0],&mapKey))
continue;
if(!TryParse<V>(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<std::string> 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<std::string>& 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<std::string>& values);
template<typename T> 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<std::string>& keys) const;
void SetLines(const char* sectionName, const std::vector<std::string> &lines);
bool GetLines(const char* sectionName, std::vector<std::string>& 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<Section> &Sections() { return sections; }
Section* GetOrCreateSection(const char* section);
private:
std::vector<Section> 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);
};

View File

@ -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;

105
i18n/i18n.cpp Normal file
View File

@ -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<std::string, std::string> &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<IniFile::Section> &sections = 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<std::string, std::string> 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<std::string, std::string> &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<std::string, I18NEntry> &entries = cat->GetMap();
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
section->Set(iter->first, iter->second.text);
}
cat->ClearMissed();
}

95
i18n/i18n.h Normal file
View File

@ -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 <map>
#include <string>
#include <vector>
#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<std::string, std::string> &Missed() const {
return missedKeyLog_;
}
void SetMap(const std::map<std::string, std::string> &m);
const std::map<std::string, I18NEntry> &GetMap() { return map_; }
void ClearMissed() { missedKeyLog_.clear(); }
private:
I18NCategory(I18NRepo *repo) {}
std::map<std::string, I18NEntry> map_;
std::map<std::string, std::string> 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<std::string, I18NCategory *> 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);
}

View File

@ -211,6 +211,7 @@
<ClInclude Include="file\easy_file.h" />
<ClInclude Include="file\fd_util.h" />
<ClInclude Include="file\file_util.h" />
<ClInclude Include="file\ini_file.h" />
<ClInclude Include="file\vfs.h" />
<ClInclude Include="file\zip_read.h" />
<ClInclude Include="gfx\gl_debug_log.h" />
@ -223,6 +224,7 @@
<ClInclude Include="gfx_es2\glsl_program.h" />
<ClInclude Include="gfx_es2\gl_state.h" />
<ClInclude Include="gfx_es2\vertex_format.h" />
<ClInclude Include="i18n\i18n.h" />
<ClInclude Include="image\png_load.h" />
<ClInclude Include="image\zim_load.h" />
<ClInclude Include="image\zim_save.h" />
@ -305,6 +307,7 @@
<ClCompile Include="file\easy_file.cpp" />
<ClCompile Include="file\fd_util.cpp" />
<ClCompile Include="file\file_util.cpp" />
<ClCompile Include="file\ini_file.cpp" />
<ClCompile Include="file\zip_read.cpp" />
<ClCompile Include="gfx\gl_debug_log.cpp" />
<ClCompile Include="gfx\gl_lost_manager.cpp" />
@ -316,6 +319,7 @@
<ClCompile Include="gfx_es2\glsl_program.cpp" />
<ClCompile Include="gfx_es2\gl_state.cpp" />
<ClCompile Include="gfx_es2\vertex_format.cpp" />
<ClCompile Include="i18n\i18n.cpp" />
<ClCompile Include="image\png_load.cpp" />
<ClCompile Include="image\zim_load.cpp" />
<ClCompile Include="image\zim_save.cpp" />

View File

@ -245,6 +245,12 @@
<ClInclude Include="thread\threadutil.h">
<Filter>thread</Filter>
</ClInclude>
<ClInclude Include="i18n\i18n.h">
<Filter>i18n</Filter>
</ClInclude>
<ClInclude Include="file\ini_file.h">
<Filter>file</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gfx\gl_debug_log.cpp">
@ -432,6 +438,12 @@
<ClCompile Include="thread\threadutil.cpp">
<Filter>thread</Filter>
</ClCompile>
<ClCompile Include="i18n\i18n.cpp">
<Filter>i18n</Filter>
</ClCompile>
<ClCompile Include="file\ini_file.cpp">
<Filter>file</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="gfx">
@ -494,5 +506,8 @@
<Filter Include="thread">
<UniqueIdentifier>{caa41117-1d90-47cc-9fba-f7e670e315a3}</UniqueIdentifier>
</Filter>
<Filter Include="i18n">
<UniqueIdentifier>{02e8ef95-82c7-4420-b029-a189a5e0fcbd}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -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);