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. */