framework for multilanguage support

This commit is contained in:
dborth 2010-01-25 08:24:11 +00:00
parent 92cbfafede
commit b533b8824f
16 changed files with 515 additions and 19 deletions

View File

@ -18,7 +18,7 @@ include $(DEVKITPPC)/gamecube_rules
TARGET := snes9xgx-gc
TARGETDIR := executables
BUILD := build_gc
SOURCES := source/ngc/images source/ngc/sounds source/ngc/fonts \
SOURCES := source/ngc/images source/ngc/sounds source/ngc/fonts source/ngc/lang \
source/ngc/gui source/ngc source/snes9x source/sz
INCLUDES := source/snes9x source/ngc
@ -71,6 +71,7 @@ CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S)))
TTFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.ttf)))
LANGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.lang)))
PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png)))
PCMFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.pcm)))
@ -85,7 +86,8 @@ endif
export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \
$(sFILES:.s=.o) $(SFILES:.S=.o) \
$(TTFFILES:.ttf=.ttf.o) $(PNGFILES:.png=.png.o) \
$(TTFFILES:.ttf=.ttf.o) $(LANGFILES:.lang=.lang.o) \
$(PNGFILES:.png=.png.o) \
$(PCMFILES:.pcm=.pcm.o)
#---------------------------------------------------------------------------------
@ -137,11 +139,15 @@ $(OUTPUT).dol: $(OUTPUT).elf
$(OUTPUT).elf: $(OFILES)
#---------------------------------------------------------------------------------
# This rule links in binary data with .ttf, .png, and .mp3 extensions
# This rule links in binary data with these extensions: ttf lang png pcm
#---------------------------------------------------------------------------------
%.ttf.o : %.ttf
@echo $(notdir $<)
$(bin2o)
%.lang.o : %.lang
@echo $(notdir $<)
$(bin2o)
%.png.o : %.png
@echo $(notdir $<)

View File

@ -18,7 +18,7 @@ include $(DEVKITPPC)/wii_rules
TARGET := snes9xgx-wii
TARGETDIR := executables
BUILD := build_wii
SOURCES := source/ngc/images source/ngc/sounds source/ngc/fonts \
SOURCES := source/ngc/images source/ngc/sounds source/ngc/fonts source/ngc/lang \
source/ngc/gui source/ngc source/snes9x source/sz source/unzip
INCLUDES := source/snes9x source/ngc source/unzip
@ -71,6 +71,7 @@ CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S)))
TTFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.ttf)))
LANGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.lang)))
PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png)))
OGGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.ogg)))
PCMFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.pcm)))
@ -86,7 +87,8 @@ endif
export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \
$(sFILES:.s=.o) $(SFILES:.S=.o) \
$(TTFFILES:.ttf=.ttf.o) $(PNGFILES:.png=.png.o) \
$(TTFFILES:.ttf=.ttf.o) $(LANGFILES:.lang=.lang.o) \
$(PNGFILES:.png=.png.o) \
$(OGGFILES:.ogg=.ogg.o) $(PCMFILES:.pcm=.pcm.o)
#---------------------------------------------------------------------------------
@ -138,11 +140,15 @@ $(OUTPUT).dol: $(OUTPUT).elf
$(OUTPUT).elf: $(OFILES)
#---------------------------------------------------------------------------------
# This rule links in binary data with .ttf, .png, and .mp3 extensions
# This rule links in binary data with these extensions: ttf lang png ogg pcm
#---------------------------------------------------------------------------------
%.ttf.o : %.ttf
@echo $(notdir $<)
$(bin2o)
%.lang.o : %.lang
@echo $(notdir $<)
$(bin2o)
%.png.o : %.png
@echo $(notdir $<)

View File

@ -14,9 +14,32 @@
#include <gccore.h>
// Fonts
extern const u8 font_ttf[];
extern const u32 font_ttf_size;
// Languages
extern const u8 jp_lang[];
extern const u32 jp_lang_size;
extern const u8 en_lang[];
extern const u32 en_lang_size;
extern const u8 de_lang[];
extern const u32 de_lang_size;
extern const u8 fr_lang[];
extern const u32 fr_lang_size;
extern const u8 es_lang[];
extern const u32 es_lang_size;
extern const u8 it_lang[];
extern const u32 it_lang_size;
extern const u8 nl_lang[];
extern const u32 nl_lang_size;
extern const u8 zh_lang[];
extern const u32 zh_lang_size;
extern const u8 ko_lang[];
extern const u32 ko_lang_size;
// Sounds
extern const u8 bg_music_ogg[];
extern const u32 bg_music_ogg_size;
@ -32,6 +55,8 @@ extern const u32 button_over_pcm_size;
extern const u8 button_click_pcm[];
extern const u32 button_click_pcm_size;
// Graphics
extern const u8 logo_png[];
extern const u32 logo_png_size;

286
source/ngc/gettext.cpp Normal file
View File

@ -0,0 +1,286 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <gctypes.h>
#include <unistd.h>
#include "gettext.h"
#include "filelist.h"
#include "snes9xGX.h"
typedef struct _MSG
{
u32 id;
char* msgstr;
struct _MSG *next;
} MSG;
static MSG *baseMSG = 0;
#define HASHWORDBITS 32
/* Defines the so called `hashpjw' function by P.J. Weinberger
[see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
1986, 1987 Bell Telephone Laboratories, Inc.] */
static inline u32 hash_string(const char *str_param)
{
u32 hval, g;
const char *str = str_param;
/* Compute the hash value for the given string. */
hval = 0;
while (*str != '\0')
{
hval <<= 4;
hval += (u8) * str++;
g = hval & ((u32) 0xf << (HASHWORDBITS - 4));
if (g != 0)
{
hval ^= g >> (HASHWORDBITS - 8);
hval ^= g;
}
}
return hval;
}
/* Expand some escape sequences found in the argument string. */
static char *
expand_escape(const char *str)
{
char *retval, *rp;
const char *cp = str;
retval = (char *) malloc(strlen(str) + 1);
if (retval == NULL)
return NULL;
rp = retval;
while (cp[0] != '\0' && cp[0] != '\\')
*rp++ = *cp++;
if (cp[0] == '\0')
goto terminate;
do
{
/* Here cp[0] == '\\'. */
switch (*++cp)
{
case '\"': /* " */
*rp++ = '\"';
++cp;
break;
case 'a': /* alert */
*rp++ = '\a';
++cp;
break;
case 'b': /* backspace */
*rp++ = '\b';
++cp;
break;
case 'f': /* form feed */
*rp++ = '\f';
++cp;
break;
case 'n': /* new line */
*rp++ = '\n';
++cp;
break;
case 'r': /* carriage return */
*rp++ = '\r';
++cp;
break;
case 't': /* horizontal tab */
*rp++ = '\t';
++cp;
break;
case 'v': /* vertical tab */
*rp++ = '\v';
++cp;
break;
case '\\':
*rp = '\\';
++cp;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
{
int ch = *cp++ - '0';
if (*cp >= '0' && *cp <= '7')
{
ch *= 8;
ch += *cp++ - '0';
if (*cp >= '0' && *cp <= '7')
{
ch *= 8;
ch += *cp++ - '0';
}
}
*rp = ch;
}
break;
default:
*rp = '\\';
break;
}
while (cp[0] != '\0' && cp[0] != '\\')
*rp++ = *cp++;
} while (cp[0] != '\0');
/* Terminate string. */
terminate: *rp = '\0';
return retval;
}
static MSG *findMSG(u32 id)
{
MSG *msg;
for (msg = baseMSG; msg; msg = msg->next)
{
if (msg->id == id)
return msg;
}
return NULL;
}
static MSG *setMSG(const char *msgid, const char *msgstr)
{
u32 id = hash_string(msgid);
MSG *msg = findMSG(id);
if (!msg)
{
msg = (MSG *) malloc(sizeof(MSG));
msg->id = id;
msg->msgstr = NULL;
msg->next = baseMSG;
baseMSG = msg;
}
if (msg)
{
if (msgstr)
{
if (msg->msgstr)
free(msg->msgstr);
//msg->msgstr = strdup(msgstr);
msg->msgstr = expand_escape(msgstr);
}
return msg;
}
return NULL;
}
static void gettextCleanUp(void)
{
while (baseMSG)
{
MSG *nextMsg = baseMSG->next;
free(baseMSG->msgstr);
free(baseMSG);
baseMSG = nextMsg;
}
}
static char * memfgets(char * dst, int maxlen, char * src)
{
if(!src || !dst || maxlen <= 0)
return NULL;
char * newline = strchr(src, '\n');
if(newline == NULL)
return NULL;
memcpy(dst, src, (newline-src));
dst[(newline-src)] = 0;
return ++newline;
}
bool LoadLanguage()
{
char line[200];
char *lastID = NULL;
char *file, *eof;
switch(GCSettings.language)
{
case LANG_JAPANESE: file = (char *)jp_lang; eof = file + jp_lang_size; break;
case LANG_ENGLISH: file = (char *)en_lang; eof = file + en_lang_size; break;
case LANG_GERMAN: file = (char *)de_lang; eof = file + de_lang_size; break;
case LANG_FRENCH: file = (char *)fr_lang; eof = file + fr_lang_size; break;
case LANG_SPANISH: file = (char *)es_lang; eof = file + es_lang_size; break;
case LANG_ITALIAN: file = (char *)it_lang; eof = file + it_lang_size; break;
case LANG_DUTCH: file = (char *)nl_lang; eof = file + nl_lang_size; break;
case LANG_SIMP_CHINESE:
case LANG_TRAD_CHINESE: file = (char *)zh_lang; eof = file + zh_lang_size; break;
case LANG_KOREAN: file = (char *)ko_lang; eof = file + ko_lang_size; break;
default: return false;
}
gettextCleanUp();
while (file && file < eof)
{
file = memfgets(line, sizeof(line), file);
if(!file)
break;
// lines starting with # are comments
if (line[0] == '#')
continue;
if (strncmp(line, "msgid \"", 7) == 0)
{
char *msgid, *end;
if (lastID)
{
free(lastID);
lastID = NULL;
}
msgid = &line[7];
end = strrchr(msgid, '"');
if (end && end - msgid > 1)
{
*end = 0;
lastID = strdup(msgid);
}
}
else if (strncmp(line, "msgstr \"", 8) == 0)
{
char *msgstr, *end;
if (lastID == NULL)
continue;
msgstr = &line[8];
end = strrchr(msgstr, '"');
if (end && end - msgstr > 1)
{
*end = 0;
setMSG(lastID, msgstr);
}
free(lastID);
lastID = NULL;
}
}
return true;
}
const char *gettext(const char *msgid)
{
MSG *msg = findMSG(hash_string(msgid));
if (msg && msg->msgstr)
{
return msg->msgstr;
}
return msgid;
}

12
source/ngc/gettext.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef _GETTEXT_H_
#define _GETTEXT_H_
bool LoadLanguage();
/*
* input msg = a text in ASCII
* output = the translated msg in utf-8
*/
const char *gettext(const char *msg);
#endif /* _GETTEXT_H_ */

View File

@ -393,11 +393,15 @@ class GuiElement
//!\param hor Horizontal alignment (ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTRE)
//!\param vert Vertical alignment (ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE)
virtual void SetAlignment(int hor, int vert);
//!Called when the language has changed, to obtain new text values for all text elements
virtual void ResetText();
//!Called constantly to allow the element to respond to the current input data
//!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD
virtual void Update(GuiTrigger * t);
//!Called constantly to redraw the element
virtual void Draw();
//!Called constantly to redraw the element's tooltip
virtual void DrawTooltip();
protected:
GuiTrigger * trigger[2]; //!< GuiTriggers (input actions) that this element responds to
UpdateCallback updateCB; //!< Callback function to call when this element is updated
@ -495,8 +499,12 @@ class GuiWindow : public GuiElement
//!Moves the selected element to the element above or below
//!\param d Direction to move (-1 = up, 1 = down)
void MoveSelectionVert(int d);
//!Resets the text for all contained elements
void ResetText();
//!Draws all the elements in this GuiWindow
void Draw();
//!Draws all of the tooltips in this GuiWindow
void DrawTooltip();
//!Updates the window and all elements contains within
//!Allows the GuiWindow and all elements to respond to the input data specified
//!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD
@ -647,6 +655,8 @@ class GuiText : public GuiElement
//!\param hor Horizontal alignment (ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTRE)
//!\param vert Vertical alignment (ALIGN_TOP, ALIGN_BOTTOM, ALIGN_MIDDLE)
void SetAlignment(int hor, int vert);
//!Updates the text to the selected language
void ResetText();
//!Constantly called to draw the text
void Draw();
protected:
@ -664,6 +674,32 @@ class GuiText : public GuiElement
bool wrap; //!< Wrapping toggle
};
//!Display, manage, and manipulate tooltips in the GUI
class GuiTooltip : public GuiElement
{
public:
//!Constructor
//!\param t Text
GuiTooltip(const char *t);
//!Destructor
~GuiTooltip();
//!Gets the element's current scale
float GetScale();
//!Sets the text of the GuiTooltip element
//!\param t Text
void SetText(const char * t);
//!Constantly called to draw the GuiTooltip
void DrawTooltip();
time_t time1, time2; //!< Tooltip times
protected:
GuiImage leftImage; //!< Tooltip left image
GuiImage tileImage; //!< Tooltip tile image
GuiImage rightImage; //!< Tooltip right image
GuiText *text; //!< Tooltip text
};
//!Display, manage, and manipulate buttons in the GUI. Buttons can have images, icons, text, and sound set (all of which are optional)
class GuiButton : public GuiElement
{
@ -671,7 +707,7 @@ class GuiButton : public GuiElement
//!Constructor
//!\param w Width
//!\param h Height
GuiButton(int w, int h);
GuiButton(int w = 0, int h = 0);
//!Destructor
~GuiButton();
//!Sets the button's image
@ -723,8 +759,15 @@ class GuiButton : public GuiElement
//!Sets the sound to play on click
//!\param s Pointer to GuiSound object
void SetSoundClick(GuiSound * s);
//!Sets the tooltip for the button
//!\param t Tooltip
void SetTooltip(GuiTooltip * t);
//!Constantly called to draw the GuiButton
void Draw();
//!Constantly called to draw the GuiButton's tooltip
void DrawTooltip();
//!Resets the text for all contained elements
void ResetText();
//!Constantly called to allow the GuiButton to respond to updated input data
//!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD
void Update(GuiTrigger * t);
@ -744,6 +787,7 @@ class GuiButton : public GuiElement
GuiSound * soundOver; //!< Sound to play for STATE_SELECTED
GuiSound * soundHold; //!< Sound to play for STATE_HELD
GuiSound * soundClick; //!< Sound to play for STATE_CLICKED
GuiTooltip * tooltip; //!< Tooltip to display on over
};
typedef struct _keytype {
@ -808,6 +852,7 @@ class GuiOptionBrowser : public GuiElement
public:
GuiOptionBrowser(int w, int h, OptionList * l);
~GuiOptionBrowser();
void SetCol1Position(int x);
void SetCol2Position(int x);
int FindMenuItem(int c, int d);
int GetClickedOption();
@ -815,6 +860,7 @@ class GuiOptionBrowser : public GuiElement
void SetFocus(int f);
void Draw();
void TriggerUpdate();
void ResetText();
void Update(GuiTrigger * t);
GuiText * optionVal[PAGESIZE];
protected:
@ -921,6 +967,7 @@ class GuiFileBrowser : public GuiElement
void ResetState();
void SetFocus(int f);
void Draw();
void DrawTooltip();
void TriggerUpdate();
void Update(GuiTrigger * t);
GuiButton * fileList[FILE_PAGESIZE];

View File

@ -37,6 +37,7 @@ GuiButton::GuiButton(int w, int h)
soundOver = NULL;
soundHold = NULL;
soundClick = NULL;
tooltip = NULL;
selectable = true;
holdable = false;
clickable = true;
@ -121,6 +122,12 @@ void GuiButton::SetSoundClick(GuiSound * snd)
{
soundClick = snd;
}
void GuiButton::SetTooltip(GuiTooltip* t)
{
tooltip = t;
if(t)
tooltip->SetParent(this);
}
/**
* Draw the button on screen
@ -177,6 +184,23 @@ void GuiButton::Draw()
this->UpdateEffects();
}
void GuiButton::DrawTooltip()
{
if(tooltip)
tooltip->DrawTooltip();
}
void GuiButton::ResetText()
{
for(int i=0; i<3; i++)
{
if(label[i])
label[i]->ResetText();
if(labelOver[i])
labelOver[i]->ResetText();
}
}
void GuiButton::Update(GuiTrigger * t)
{
if(state == STATE_CLICKED || state == STATE_DISABLED || !t)

View File

@ -511,13 +511,18 @@ int GuiElement::GetSelected()
return -1;
}
/**
* Draw an element on screen.
*/
void GuiElement::ResetText()
{
}
void GuiElement::Draw()
{
}
void GuiElement::DrawTooltip()
{
}
bool GuiElement::IsInside(int x, int y)
{
if(unsigned(x - this->GetLeft()) < unsigned(width)

View File

@ -224,6 +224,10 @@ void GuiFileBrowser::Draw()
this->UpdateEffects();
}
void GuiFileBrowser::DrawTooltip()
{
}
void GuiFileBrowser::Update(GuiTrigger * t)
{
if(state == STATE_DISABLED || !t)

View File

@ -134,6 +134,12 @@ GuiOptionBrowser::~GuiOptionBrowser()
}
}
void GuiOptionBrowser::SetCol1Position(int x)
{
for(int i=0; i<PAGESIZE; i++)
optionTxt[i]->SetPosition(x,0);
}
void GuiOptionBrowser::SetCol2Position(int x)
{
for(int i=0; i<PAGESIZE; i++)
@ -234,6 +240,22 @@ void GuiOptionBrowser::TriggerUpdate()
listChanged = true;
}
void GuiOptionBrowser::ResetText()
{
int next = listOffset;
for(int i=0; i<PAGESIZE; i++)
{
if(next >= 0)
{
optionBtn[i]->ResetText();
next = this->FindMenuItem(next, 1);
}
else
break;
}
}
void GuiOptionBrowser::Update(GuiTrigger * t)
{
if(state == STATE_DISABLED || !t)

View File

@ -9,6 +9,7 @@
***************************************************************************/
#include "gui.h"
#include "../gettext.h"
static GXColor presetColor = (GXColor){255, 255, 255, 255};
static int currentSize = 0;
@ -46,7 +47,7 @@ GuiText::GuiText(const char * t, int s, GXColor c)
if(t)
{
origText = strdup(t);
text = charToWideChar(t);
text = charToWideChar(gettext(t));
}
}
@ -75,7 +76,7 @@ GuiText::GuiText(const char * t)
if(t)
{
origText = strdup(t);
text = charToWideChar(t);
text = charToWideChar(gettext(t));
}
}
@ -110,7 +111,7 @@ void GuiText::SetText(const char * t)
if(t)
{
origText = strdup(t);
text = charToWideChar(t);
text = charToWideChar(gettext(t));
}
}
@ -200,6 +201,16 @@ void GuiText::SetAlignment(int hor, int vert)
alignmentVert = vert;
}
void GuiText::ResetText()
{
if(!origText)
return;
if(text)
delete[] text;
text = charToWideChar(gettext(origText));
}
/**
* Draw the text on screen
*/

View File

@ -99,6 +99,18 @@ void GuiWindow::Draw()
Menu_DrawRectangle(0,0,screenwidth,screenheight,(GXColor){0xbe, 0xca, 0xd5, 0x70},1);
}
void GuiWindow::DrawTooltip()
{
if(_elements.size() == 0 || !this->IsVisible())
return;
for (u8 i = 0; i < _elements.size(); i++)
{
try { _elements.at(i)->DrawTooltip(); }
catch (const std::exception& e) { }
}
}
void GuiWindow::ResetState()
{
if(state != STATE_DISABLED)
@ -384,6 +396,15 @@ void GuiWindow::MoveSelectionVert(int dir)
}
}
void GuiWindow::ResetText()
{
for (u8 i = 0; i < _elements.size(); i++)
{
try { _elements.at(i)->ResetText(); }
catch (const std::exception& e) { }
}
}
void GuiWindow::Update(GuiTrigger * t)
{
if(_elements.size() == 0 || (state == STATE_DISABLED && parentElement))

View File

@ -263,6 +263,9 @@ UpdateGUI (void *arg)
UpdatePads();
mainWindow->Draw();
if (mainWindow->GetState() != STATE_DISABLED)
mainWindow->DrawTooltip();
#ifdef HW_RVL
i = 3;
do

View File

@ -346,7 +346,7 @@ void USBGeckoOutput()
int
main(int argc, char *argv[])
{
//USBGeckoOutput(); // uncomment to enable USB gecko output
USBGeckoOutput(); // uncomment to enable USB gecko output
__exception_setreload(8);
#ifdef HW_DOL

View File

@ -61,6 +61,19 @@ enum
const char ctrlName[6][24] =
{ "SNES Controller", "SNES Mouse", "Superscope", "Justifier", "SNES Controllers (2)", "SNES Controllers (4)" };
enum {
LANG_JAPANESE = 0,
LANG_ENGLISH,
LANG_GERMAN,
LANG_FRENCH,
LANG_SPANISH,
LANG_ITALIAN,
LANG_DUTCH,
LANG_SIMP_CHINESE,
LANG_TRAD_CHINESE,
LANG_KOREAN
};
struct SGCSettings{
int AutoLoad;
int AutoSave;
@ -89,6 +102,7 @@ struct SGCSettings{
int MusicVolume;
int SFXVolume;
int Rumble;
int language;
};
void ExitApp();

View File

@ -84,11 +84,8 @@ LoadSRAMAuto (bool silent)
return false;
if(LoadSRAM(filepath2, silent))
{
// rename this file - append Auto
rename(filepath2, filepath); // rename file (to avoid duplicates)
return true;
}
return false;
}
@ -141,8 +138,21 @@ SaveSRAMAuto (bool silent)
{
char filepath[1024];
if(!MakeFilePath(filepath, FILE_SRAM, Memory.ROMFilename, 0))
// look for file with no number or Auto appended
if(!MakeFilePath(filepath, FILE_SRAM, Memory.ROMFilename, -1))
return false;
FILE * fp = fopen (filepath, "rb");
if(fp) // file found
{
fclose (fp);
}
else
{
if(!MakeFilePath(filepath, FILE_SRAM, Memory.ROMFilename, 0))
return false;
}
return SaveSRAM(filepath, silent);
}