diff --git a/binaries/data/mods/public/gui/civinfo/civinfo.js b/binaries/data/mods/public/gui/civinfo/civinfo.js
index 9996e14ed3..e67dcd28ff 100644
--- a/binaries/data/mods/public/gui/civinfo/civinfo.js
+++ b/binaries/data/mods/public/gui/civinfo/civinfo.js
@@ -1,11 +1,8 @@
-
var g_CivData = {};
function init(settings)
{
- // Initialize civ list
initCivNameList();
- // TODO: Separate control for factions?
}
// Sort by culture, then by code equals culture and then by name ignoring case
@@ -78,7 +75,7 @@ function heading(string, size)
function escapeChars(str)
{
- return str.replace(/\[/g, "[").replace(/\]/g, "]").replace(/"/g, """);
+ return str.replace(/"/g, "\\\\\"");
};
function subHeading(obj)
diff --git a/binaries/data/mods/public/gui/common/functions_utility_loadsave.js b/binaries/data/mods/public/gui/common/functions_utility_loadsave.js
index ed73f52774..4ca61f4665 100644
--- a/binaries/data/mods/public/gui/common/functions_utility_loadsave.js
+++ b/binaries/data/mods/public/gui/common/functions_utility_loadsave.js
@@ -6,7 +6,7 @@ function sortDecreasingDate(a, b)
function generateLabel(metadata, engineInfo)
{
var dateTimeString = Engine.FormatMillisecondsIntoDateString(metadata.time*1000, translate("yyyy-MM-dd HH:mm:ss"));
- var dateString = sprintf(translate("[%(date)s]"), { date: dateTimeString });
+ var dateString = sprintf(translate("\\[%(date)s]"), { date: dateTimeString });
if (engineInfo)
{
if (!hasSameVersion(metadata, engineInfo))
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
index db0dc5f524..8780a5880e 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -202,9 +202,7 @@
sprite="BackgroundTranslucent"
hidden="true"
size="20 100%-56 100%-312 100%-24"
- >
- [Tooltip text]
-
+ />
diff --git a/binaries/data/mods/public/gui/manual/intro.txt b/binaries/data/mods/public/gui/manual/intro.txt
index b25219d573..a4f6c840dc 100644
--- a/binaries/data/mods/public/gui/manual/intro.txt
+++ b/binaries/data/mods/public/gui/manual/intro.txt
@@ -46,10 +46,10 @@ F2: Take screenshot (in .png format, location is displayed in the top left of th
Shift + F2: Take huge screenshot (6400px*4800px, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
[font="sans-bold-14"]In Game
-[font="sans-14"]Double Left Click [on unit]: Select all of your units of the same kind on the screen (even if they're different ranks)
-Triple Left Click [on unit]: Select all of your units of the same kind and the same rank on the screen
-Alt + Double Left Click [on unit]: Select all your units of the same kind on the entire map (even if the are different ranks)
-Alt + Triple Left Click [on unit]: Select all your units of the same kind and rank on the entire map
+[font="sans-14"]Double Left Click \[on unit]: Select all of your units of the same kind on the screen (even if they're different ranks)
+Triple Left Click \[on unit]: Select all of your units of the same kind and the same rank on the screen
+Alt + Double Left Click \[on unit]: Select all your units of the same kind on the entire map (even if the are different ranks)
+Alt + Triple Left Click \[on unit]: Select all your units of the same kind and rank on the entire map
Shift + F5: Quicksave
Shift + F8: Quickload
F10: Open/close menu
@@ -90,14 +90,14 @@ Alt + S: Toggle unit silhouettes (might give a small performance boost)
Alt + Z: Toggle sky
[font="sans-bold-14"]Camera manipulation
-[font="sans-14"]W or [up]: Pan screen up
-S or [down]: Pan screen down
-A or [left]: Pan screen left
-D or [right]: Pan screen right
-Ctrl + W or [up]: Rotate camera to look upward
-Ctrl + S or [down]: Rotate camera to look downward
-Ctrl + A or [left]: Rotate camera clockwise around terrain
-Ctrl + D or [right]: Rotate camera anticlockwise around terrain
+[font="sans-14"]W or \[up]: Pan screen up
+S or \[down]: Pan screen down
+A or \[left]: Pan screen left
+D or \[right]: Pan screen right
+Ctrl + W or \[up]: Rotate camera to look upward
+Ctrl + S or \[down]: Rotate camera to look downward
+Ctrl + A or \[left]: Rotate camera clockwise around terrain
+Ctrl + D or \[right]: Rotate camera anticlockwise around terrain
Q: Rotate camera clockwise around terrain
E: Rotate camera anticlockwise around terrain
Shift + Mouse Wheel Rotate Up: Rotate camera clockwise around terrain
@@ -110,6 +110,6 @@ Alt + W: Toggle through wireframe modes
Middle Mouse Button or / (Forward Slash): Keep pressed and move the mouse to pan
[font="sans-bold-14"]During Building Placement
-[font="sans-14"][: Rotate building 15 degrees counter-clockwise
+[font="sans-14"]\[: Rotate building 15 degrees counter-clockwise
]: Rotate building 15 degrees clockwise
Left Drag: Rotate building using mouse (foundation will be placed on mouse release)
diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.js b/binaries/data/mods/public/gui/pregame/mainmenu.js
index 0b7212208f..74aad07bd9 100644
--- a/binaries/data/mods/public/gui/pregame/mainmenu.js
+++ b/binaries/data/mods/public/gui/pregame/mainmenu.js
@@ -294,7 +294,7 @@ function pressedScenarioEditorButton()
function getLobbyDisabledByBuild()
{
- return translate("Launch the multiplayer lobby. [DISABLED BY BUILD]");
+ return translate("Launch the multiplayer lobby. \\\\[DISABLED BY BUILD]");
}
function getTechnicalDetails()
diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.xml b/binaries/data/mods/public/gui/pregame/mainmenu.xml
index 656b963ad8..fcafde9f1d 100644
--- a/binaries/data/mods/public/gui/pregame/mainmenu.xml
+++ b/binaries/data/mods/public/gui/pregame/mainmenu.xml
@@ -167,7 +167,7 @@
enabled="false"
>
Campaigns
- Relive history through historical military campaigns. [NOT YET IMPLEMENTED]
+ Relive history through historical military campaigns. \\\\[NOT YET IMPLEMENTED]
closeMenu();
.
*/
-/*
-GUI text
-*/
-
#include "precompiled.h"
-#include "GUI.h"
-#include "graphics/FontMetrics.h"
-#include "ps/CLogger.h"
-#include "ps/Parser.h"
#include
+#include "GUI.h"
+#include "lib/utf8.h"
+#include "graphics/FontMetrics.h"
+#include "ps/CLogger.h"
+
-static const wchar_t TagStart = '[';
-static const wchar_t TagEnd = ']';
// List of word demlimitor bounds
// The list contains ranges of word delimitors. The odd indexed chars are the start
// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER
@@ -52,36 +47,6 @@ void CGUIString::SFeedback::Reset()
m_NewLine=false;
}
-// TODO this method should be removed at one point
-// It is a solution for CParser not recognising backslash-escaped quoted
-CStrW UnescapeString(CStrW originalString)
-{
- // no reason to mess with more memory if there is nothing to unescape
- if (originalString.Find('&') == -1)
- return originalString;
- // clone the string
- CStrW newString = CStrW();
- for (size_t i=0; i < originalString.length(); ++i)
- {
- if (originalString[i] == '&' && originalString[i+1] == '#')
- {
- i += 2;
- wchar_t c = 0;
- while (originalString[i] >= '0' && originalString[i] <= '9')
- {
- c = c*10 + (originalString[i] - '0');
- ++i;
- }
- ENSURE(originalString[i] == ';');
- if (c >= ' ')
- newString += c;
- }
- else
- newString += originalString[i];
- }
- return newString;
-}
-
void CGUIString::GenerateTextCall(const CGUI *pGUI,
SFeedback &Feedback,
CStrIntern DefaultFont,
@@ -120,121 +85,103 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
// should really belong to the beginning of the next one
if (_to == to && to >= 1)
{
- if (GetRawString()[to-1] == ' ' ||
- GetRawString()[to-1] == '-' ||
- GetRawString()[to-1] == '\n')
+ if (m_RawString[to-1] == ' ' ||
+ m_RawString[to-1] == '-' ||
+ m_RawString[to-1] == '\n')
continue;
}
// This std::string is just a break
if (_from == from && from >= 1)
{
- if (GetRawString()[from] == '\n' &&
- GetRawString()[from-1] != '\n' &&
- GetRawString()[from-1] != ' ' &&
- GetRawString()[from-1] != '-')
+ if (m_RawString[from] == '\n' &&
+ m_RawString[from-1] != '\n' &&
+ m_RawString[from-1] != ' ' &&
+ m_RawString[from-1] != '-')
continue;
}
- // Single tags
- if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT)
+ const CGUIString::TextChunk::Tag& tag = itTextChunk->m_Tags[0];
+ ENSURE(tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT
+ || tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT
+ || tag.m_TagType == CGUIString::TextChunk::Tag::TAG_ICON);
+
+ const std::string& path = utf8_from_wstring(tag.m_TagValue);
+ if (!pGUI->IconExists(path))
{
- // Only add the image if the icon exists.
- if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
- {
- Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue);
- }
- else if (pObject)
- {
- LOGERROR(L"Trying to use an [imgleft]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
- }
+ if (pObject)
+ LOGERROR(L"Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%hs\").", path.c_str());
+ continue;
}
- else
- if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT)
+
+ switch (tag.m_TagType)
{
- // Only add the image if the icon exists.
- if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
+ case CGUIString::TextChunk::Tag::TAG_IMGLEFT:
+ Feedback.m_Images[SFeedback::Left].push_back(path);
+ break;
+ case CGUIString::TextChunk::Tag::TAG_IMGRIGHT:
+ Feedback.m_Images[SFeedback::Right].push_back(path);
+ break;
+
+ case CGUIString::TextChunk::Tag::TAG_ICON:
+ // We'll need to setup a text-call that will point
+ // to the icon, this is to be able to iterate
+ // through the text-calls without having to
+ // complex the structure virtually for nothing more.
+ SGUIText::STextCall TextCall;
+
+ // Also add it to the sprites being rendered.
+ SGUIText::SSpriteCall SpriteCall;
+
+ // Get Icon from icon database in pGUI
+ SGUIIcon icon = pGUI->GetIcon(path);
+
+ CSize size = icon.m_Size;
+
+ // append width, and make maximum height the height.
+ Feedback.m_Size.cx += size.cx;
+ Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
+
+ // These are also needed later
+ TextCall.m_Size = size;
+ SpriteCall.m_Area = size;
+
+ // Handle additional attributes
+ std::vector::const_iterator att_it;
+ for (att_it = tag.m_TagAttributes.begin(); att_it != tag.m_TagAttributes.end(); ++att_it)
{
- Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue);
- }
- else if (pObject)
- {
- LOGERROR(L"Trying to use an [imgright]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
- }
- }
- else
- if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON)
- {
- // Only add the image if the icon exists.
- if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
- {
- // We'll need to setup a text-call that will point
- // to the icon, this is to be able to iterate
- // through the text-calls without having to
- // complex the structure virtually for nothing more.
- SGUIText::STextCall TextCall;
+ const TextChunk::Tag::TagAttribute& tagAttrib = *att_it;
- // Also add it to the sprites being rendered.
- SGUIText::SSpriteCall SpriteCall;
-
- // Get Icon from icon database in pGUI
- SGUIIcon icon = pGUI->GetIcon(itTextChunk->m_Tags[0].m_TagValue);
-
- CSize size = icon.m_Size;
-
- // append width, and make maximum height the height.
- Feedback.m_Size.cx += size.cx;
- Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
-
- // These are also needed later
- TextCall.m_Size = size;
- SpriteCall.m_Area = size;
-
- // Handle additional attributes
- std::vector::const_iterator att_it;
- for(att_it = itTextChunk->m_Tags[0].m_TagAttributes.begin(); att_it != itTextChunk->m_Tags[0].m_TagAttributes.end(); ++att_it)
+ if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty())
{
- TextChunk::Tag::TagAttribute tagAttrib = (TextChunk::Tag::TagAttribute)(*att_it);
-
- if (tagAttrib.attrib == "displace" && !tagAttrib.value.empty())
- { //Displace the sprite
- CSize displacement;
- // Parse the value
- if (!GUI::ParseString(CStr(tagAttrib.value).FromUTF8(), displacement))
- LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
- else
- SpriteCall.m_Area += displacement;
-
- }
- else if(tagAttrib.attrib == "tooltip")
- {
- SpriteCall.m_Tooltip = CStr(tagAttrib.value).FromUTF8();
- }
- else if(tagAttrib.attrib == "tooltip_style")
- {
- SpriteCall.m_TooltipStyle = CStr(tagAttrib.value).FromUTF8();
- }
+ //Displace the sprite
+ CSize displacement;
+ // Parse the value
+ if (!GUI::ParseString(tagAttrib.value, displacement))
+ LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
+ else
+ SpriteCall.m_Area += displacement;
}
-
- SpriteCall.m_Sprite = icon.m_SpriteName;
- SpriteCall.m_CellID = icon.m_CellID;
-
- // Add sprite call
- Feedback.m_SpriteCalls.push_back(SpriteCall);
-
- // Finalize text call
- TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
-
- // Add text call
- Feedback.m_TextCalls.push_back(TextCall);
- }
- else if (pObject)
- {
- LOGERROR(L"Trying to use an [icon]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
+ else if(tagAttrib.attrib == L"tooltip")
+ SpriteCall.m_Tooltip = tagAttrib.value;
+ else if(tagAttrib.attrib == L"tooltip_style")
+ SpriteCall.m_TooltipStyle = tagAttrib.value;
}
+
+ SpriteCall.m_Sprite = icon.m_SpriteName;
+ SpriteCall.m_CellID = icon.m_CellID;
+
+ // Add sprite call
+ Feedback.m_SpriteCalls.push_back(SpriteCall);
+
+ // Finalize text call
+ TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
+
+ // Add text call
+ Feedback.m_TextCalls.push_back(TextCall);
+ break;
}
}
- else
- if (_to > _from && !Feedback.m_NewLine)
+ else if (_to > _from && !Feedback.m_NewLine)
{
SGUIText::STextCall TextCall;
@@ -242,30 +189,28 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
TextCall.m_Font = DefaultFont;
TextCall.m_UseCustomColor = false;
- // Extract substd::string from RawString.
- TextCall.m_String = UnescapeString(GetRawString().substr(_from, _to-_from));
+ TextCall.m_String = m_RawString.substr(_from, _to-_from);
// Go through tags and apply changes.
std::vector::const_iterator it2;
for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
{
- if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR)
+ switch (it2->m_TagType)
{
- // Set custom color
+ case CGUIString::TextChunk::Tag::TAG_COLOR:
TextCall.m_UseCustomColor = true;
- // Try parsing the color std::string
- if (!GUI::ParseString(CStr(it2->m_TagValue).FromUTF8(), TextCall.m_Color))
- {
- if (pObject)
- LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
- }
- }
- else
- if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT)
- {
+ if (!GUI::ParseString(it2->m_TagValue, TextCall.m_Color)
+ && pObject)
+ LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
+ break;
+ case CGUIString::TextChunk::Tag::TAG_FONT:
// TODO Gee: (2004-08-15) Check if Font exists?
- TextCall.m_Font = CStrIntern(it2->m_TagValue);
+ TextCall.m_Font = CStrIntern(utf8_from_wstring(it2->m_TagValue));
+ break;
+ default:
+ LOGERROR(L"Encountered unexpected tag applied to text");
+ break;
}
}
@@ -276,7 +221,7 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
// For anything other than the first line, the line spacing
// needs to be considered rather than just the height of the text
- if (! FirstLine)
+ if (!FirstLine)
cy = font.GetLineSpacing();
size.cx = (float)cx;
@@ -289,13 +234,8 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
// These are also needed later
TextCall.m_Size = size;
- if (! TextCall.m_String.empty())
- {
- if (TextCall.m_String[0] == '\n')
- {
- Feedback.m_NewLine = true;
- }
- }
+ if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n')
+ Feedback.m_NewLine = true;
// Add text-chunk
Feedback.m_TextCalls.push_back(TextCall);
@@ -303,254 +243,202 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
}
}
-bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
+bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype)
{
- CStr _tagtype = tagtype.UpperCase();
+ TagType t = GetTagType(tagtype);
+ if (t == TAG_INVALID)
+ return false;
- if (_tagtype == CStr("COLOR"))
- {
- m_TagType = TAG_COLOR;
- return true;
- }
- else
- if (_tagtype == CStr("FONT"))
- {
- m_TagType = TAG_FONT;
- return true;
- }
- else
- if (_tagtype == CStr("ICON"))
- {
- m_TagType = TAG_ICON;
- return true;
- }
- else
- if (_tagtype == CStr("IMGLEFT"))
- {
- m_TagType = TAG_IMGLEFT;
- return true;
- }
- else
- if (_tagtype == CStr("IMGRIGHT"))
- {
- m_TagType = TAG_IMGRIGHT;
- return true;
- }
+ m_TagType = t;
+ return true;
+}
- return false;
+CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype)
+{
+ if (tagtype == L"color")
+ return TAG_COLOR;
+ if (tagtype == L"font")
+ return TAG_FONT;
+ if (tagtype == L"icon")
+ return TAG_ICON;
+ if (tagtype == L"imgleft")
+ return TAG_IMGLEFT;
+ if (tagtype == L"imgright")
+ return TAG_IMGRIGHT;
+
+ return TAG_INVALID;
}
void CGUIString::SetValue(const CStrW& str)
{
m_OriginalString = str;
- // clear
m_TextChunks.clear();
m_Words.clear();
- m_RawString = CStrW();
-
- // Setup parser
- // TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
- // TODO PT: Extended CParserCache so that the above is possible (since it currently only
- // likes one-task parsers)
- CParser Parser;
- // I've added the option of an additional parameter. Only used for icons when writing this.
- Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]");
- Parser.InputTaskType("end", "/$ident");
-
- long position = 0;
- long from=0; // the position in the raw std::string where the last tag ended
- long from_nonraw=0; // like from only in position of the REAL std::string, with tags.
- long curpos = 0;
+ m_RawString.clear();
// Current Text Chunk
CGUIString::TextChunk CurrentTextChunk;
+ CurrentTextChunk.m_From = 0;
- for (;;position = curpos+1)
+ int l = str.length();
+ int rawpos = 0;
+ CStrW tag;
+ std::vector tags;
+ bool closing = false;
+ for (int p = 0; p < l; ++p)
{
- // Find next TagStart character
- curpos = str.Find(position, TagStart);
-
- if (curpos == -1)
+ TextChunk::Tag tag_;
+ switch (str[p])
{
- m_RawString += str.substr(position);
-
- if (from != (long)m_RawString.length())
- {
- CurrentTextChunk.m_From = from;
- CurrentTextChunk.m_To = (int)m_RawString.length();
+ case L'[':
+ CurrentTextChunk.m_To = rawpos;
+ // Add the current chunks if it is not empty
+ if (CurrentTextChunk.m_From != rawpos)
m_TextChunks.push_back(CurrentTextChunk);
- }
+ CurrentTextChunk.m_From = rawpos;
- break;
- }
- else
- {
- // First check if there is another TagStart before a TagEnd,
- // in that case it's just a regular TagStart and we can continue.
- long pos_left = str.Find(curpos+1, TagStart);
- long pos_right = str.Find(curpos+1, TagEnd);
-
- if (pos_right == -1)
+ closing = false;
+ if (++p == l)
{
- m_RawString += str.substr(position, curpos-position+1);
- continue;
+ LOGERROR(L"Partial tag at end of string '%ls'", str.c_str());
+ break;
}
- else
- if (pos_left != -1 && pos_left < pos_right)
+ if (str[p] == L'/')
{
- m_RawString += str.substr(position, pos_left-position);
- continue;
- }
- else
- {
- m_RawString += str.substr(position, curpos-position);
-
- // Okay we've found a TagStart and TagEnd, positioned
- // at pos and pos_right. Now let's extract the
- // interior and try parsing.
- CStrW tagstr (str.substr(curpos+1, pos_right-curpos-1));
-
- CParserLine Line;
- Line.ParseString(Parser, tagstr.ToUTF8());
-
- // Set to true if the tag is just text.
- bool justtext = false;
-
- if (Line.m_ParseOK)
+ closing = true;
+ if (tags.empty())
{
- if (Line.m_TaskTypeName == "start")
+ LOGERROR(L"Encountered closing tag without having any open tags. At %d in '%ls'", p, str.c_str());
+ break;
+ }
+ if (++p == l)
+ {
+ LOGERROR(L"Partial closing tag at end of string '%ls'", str.c_str());
+ break;
+ }
+ }
+ tag.clear();
+ // Parse tag
+ for (; p < l && str[p] != L']'; ++p)
+ {
+ CStrW name, param;
+ switch (str[p])
+ {
+ case L' ':
+ if (closing) // We still parse them to make error handling cleaner
+ LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
+
+ // parse something="something else"
+ for (++p; p < l && str[p] != L'='; ++p)
+ name.push_back(str[p]);
+
+ if (p == l)
{
- // The tag
- TextChunk::Tag tag;
- std::string Str_TagType;
+ LOGERROR(L"Parameter without value at pos %d '%ls'", p, str.c_str());
+ break;
+ }
+ // fall-through
+ case L'=':
+ // parse a quoted parameter
+ if (closing) // We still parse them to make error handling cleaner
+ LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
- Line.GetArgString(0, Str_TagType);
-
- if (!tag.SetTagType(Str_TagType))
+ if (++p == l)
+ {
+ LOGERROR(L"Expected parameter, got end of string '%ls'", str.c_str());
+ break;
+ }
+ if (str[p] != L'"')
+ {
+ LOGERROR(L"Unquoted parameters are not supported (at pos %d '%ls')", p, str.c_str());
+ break;
+ }
+ for (++p; p < l && str[p] != L'"'; ++p)
+ {
+ switch (str[p])
{
- justtext = true;
+ case L'\\':
+ if (++p == l)
+ {
+ LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
+ break;
+ }
+ // fall-through
+ default:
+ param.push_back(str[p]);
}
- else
- {
- // Check for possible value-std::strings
- if (Line.GetArgCount() >= 2)
- Line.GetArgString(1, tag.m_TagValue);
+ }
- //Handle arbitrary number of additional parameters
- size_t argn;
- for(argn = 2; argn < Line.GetArgCount(); argn += 2)
- {
- TextChunk::Tag::TagAttribute a;
-
- Line.GetArgString(argn, a.attrib);
- Line.GetArgString(argn+1, a.value);
-
- tag.m_TagAttributes.push_back(a);
- }
-
- // Finalize last
- if (curpos != from_nonraw)
- {
- CurrentTextChunk.m_From = from;
- CurrentTextChunk.m_To = from + curpos - from_nonraw;
- m_TextChunks.push_back(CurrentTextChunk);
- from = CurrentTextChunk.m_To;
- }
- from_nonraw = pos_right+1;
-
- // Some tags does not have a closure, and should be
- // stored without text. Like a in XML.
- if (tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
- tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
- tag.m_TagType == TextChunk::Tag::TAG_ICON)
- {
- // We need to use a fresh text chunk
- // because 'tag' should be the *only* tag.
- TextChunk FreshTextChunk;
-
- // They does not work with the text system.
- FreshTextChunk.m_From = from + pos_right+1 - from_nonraw;
- FreshTextChunk.m_To = from + pos_right+1 - from_nonraw;
-
- FreshTextChunk.m_Tags.push_back(tag);
-
- m_TextChunks.push_back(FreshTextChunk);
- }
- else
- {
- // Add that tag, but first, erase previous occurences of the
- // same tag.
- std::vector::iterator it;
- for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
- {
- if (it->m_TagType == tag.m_TagType)
- {
- CurrentTextChunk.m_Tags.erase(it);
- break;
- }
- }
-
- // Add!
- CurrentTextChunk.m_Tags.push_back(tag);
- }
- }
+ if (!name.empty())
+ {
+ TextChunk::Tag::TagAttribute a = {name, param};
+ tag_.m_TagAttributes.push_back(a);
}
else
- if (Line.m_TaskTypeName == "end")
- {
- // The tag
- TextChunk::Tag tag;
- std::string Str_TagType;
-
- Line.GetArgString(0, Str_TagType);
-
- if (!tag.SetTagType(Str_TagType))
- {
- justtext = true;
- }
- else
- {
- // Finalize the previous chunk
- if (curpos != from_nonraw)
- {
- CurrentTextChunk.m_From = from;
- CurrentTextChunk.m_To = from + curpos - from_nonraw;
- m_TextChunks.push_back(CurrentTextChunk);
- from = CurrentTextChunk.m_To;
- }
- from_nonraw = pos_right+1;
-
- // Search for the tag, if it's not added, then
- // pass it as plain text.
- std::vector::iterator it;
- for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
- {
- if (it->m_TagType == tag.m_TagType)
- {
- CurrentTextChunk.m_Tags.erase(it);
- break;
- }
- }
- }
- }
+ tag_.m_TagValue = param;
+ break;
+ default:
+ tag.push_back(str[p]);
+ break;
}
- else justtext = true;
-
- if (justtext)
- {
- // What was within the tags could not be interpreted
- // so we'll assume it's just text.
- m_RawString += str.substr(curpos, pos_right-curpos+1);
- }
-
- curpos = pos_right;
-
- continue;
}
+
+ if (!tag_.SetTagType(tag))
+ {
+ LOGERROR(L"Invalid tag '%ls' at %d in '%ls'", tag.c_str(), p, str.c_str());
+ break;
+ }
+ if (!closing)
+ {
+ if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT
+ || tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT
+ || tag_.m_TagType == TextChunk::Tag::TAG_ICON)
+ {
+ TextChunk FreshTextChunk = { rawpos, rawpos };
+ FreshTextChunk.m_Tags.push_back(tag_);
+ m_TextChunks.push_back(FreshTextChunk);
+ }
+ else
+ {
+ tags.push_back(tag);
+ CurrentTextChunk.m_Tags.push_back(tag_);
+ }
+ }
+ else
+ {
+ if (tag != tags.back())
+ {
+ LOGERROR(L"Closing tag '%ls' does not match last opened tag '%ls' at %d in '%ls'", tag.c_str(), tags.back().c_str(), p, str.c_str());
+ break;
+ }
+
+ tags.pop_back();
+ CurrentTextChunk.m_Tags.pop_back();
+ }
+ break;
+ case L'\\':
+ if (++p == l)
+ {
+ LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
+ break;
+ }
+ // fall-through
+ default:
+ ++rawpos;
+ m_RawString.push_back(str[p]);
+ break;
}
}
+ // Add the chunk after the last tag
+ if (CurrentTextChunk.m_From != rawpos)
+ {
+ CurrentTextChunk.m_To = rawpos;
+ m_TextChunks.push_back(CurrentTextChunk);
+ }
+
+
// Add a delimiter at start and at end, it helps when
// processing later, because we don't have make exceptions for
// those cases.
@@ -581,37 +469,21 @@ void CGUIString::SetValue(const CStrW& str)
m_Words.push_back((int)m_RawString.length());
// Remove duplicates (only if larger than 2)
- if (m_Words.size() > 2)
+ if (m_Words.size() <= 2)
+ return;
+
+ std::vector::iterator it;
+ int last_word = -1;
+ for (it = m_Words.begin(); it != m_Words.end(); )
{
- std::vector::iterator it;
- int last_word = -1;
- for (it = m_Words.begin(); it != m_Words.end(); )
+ if (last_word == *it)
{
- if (last_word == *it)
- {
- it = m_Words.erase(it);
- }
- else
- {
- last_word = *it;
- ++it;
- }
+ it = m_Words.erase(it);
+ }
+ else
+ {
+ last_word = *it;
+ ++it;
}
}
-
-#if 0
- for (int i=0; i<(int)m_Words.size(); ++i)
- {
- LOGMESSAGE(L"m_Words[%d] = %d", i, m_Words[i]);
- }
-
- for (int i=0; i<(int)m_TextChunks.size(); ++i)
- {
- LOGMESSAGE(L"m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To);
- for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j)
- {
- LOGMESSAGE(L"--Tag: %d \"%hs\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str());
- }
- }
-#endif
}
diff --git a/source/gui/GUItext.h b/source/gui/GUItext.h
index a4b2354c6a..b2d49d0956 100644
--- a/source/gui/GUItext.h
+++ b/source/gui/GUItext.h
@@ -187,7 +187,7 @@ public:
struct TextChunk
{
/**
- * A tag looks like this "Hello [B]there[/B] little"
+ * A tag looks like this "Hello [b]there[/b] little"
*/
struct Tag
{
@@ -203,34 +203,37 @@ public:
TAG_COLOR,
TAG_IMGLEFT,
TAG_IMGRIGHT,
- TAG_ICON
+ TAG_ICON,
+ TAG_INVALID
};
struct TagAttribute
{
- std::string attrib;
- std::string value;
+ std::wstring attrib;
+ std::wstring value;
};
/**
* Set tag from string
*
- * @param tagtype TagType by string, like 'IMG' for [IMG]
+ * @param tagtype TagType by string, like 'img' for [img]
* @return True if m_TagType was set.
*/
- bool SetTagType(const CStr& tagtype);
+ bool SetTagType(const CStrW& tagtype);
+ TagType GetTagType(const CStrW& tagtype);
+
/**
- * In [B=Hello][/B]
+ * In [b="Hello"][/b]
* m_TagType is TAG_B
*/
TagType m_TagType;
/**
- * In [B=Hello][/B]
+ * In [b="Hello"][/b]
* m_TagValue is 'Hello'
*/
- std::string m_TagValue;
+ std::wstring m_TagValue;
/**
* Some tags need an additional attributes
@@ -244,7 +247,7 @@ public:
int m_From, m_To;
/**
- * Tags that are present. [A][B]
+ * Tags that are present. [a][b]
*/
std::vector m_Tags;
};
@@ -292,11 +295,6 @@ public:
*/
void SetValue(const CStrW& str);
- /**
- * Get String, without tags
- */
- const CStrW& GetRawString() const { return m_RawString; }
-
/**
* Get String, with tags
*/
@@ -333,12 +331,12 @@ public:
*/
std::vector m_Words;
+private:
/**
* TextChunks
*/
std::vector m_TextChunks;
-private:
/**
* The full raw string. Stripped of tags.
*/