2004-05-29 06:06:50 +02:00
/*
GUI text
by Gustav Larsson
gee @ pyro . nu
*/
2004-06-03 20:38:14 +02:00
# include "precompiled.h"
2004-05-29 06:06:50 +02:00
# include "GUI.h"
2004-08-31 04:09:58 +02:00
# include "CLogger.h"
2004-05-29 06:06:50 +02:00
# include "Parser.h"
# include "OverlayText.h"
# include <algorithm>
2004-08-31 04:09:58 +02:00
# define LOG_CATEGORY "gui"
2004-08-28 00:08:30 +02:00
2004-08-31 04:09:58 +02:00
# include "ps/Font.h"
2004-05-29 06:06:50 +02:00
2004-06-11 04:20:59 +02:00
using namespace std ;
2004-05-29 06:06:50 +02:00
static const TCHAR TagStart = ' [ ' ;
static const TCHAR TagEnd = ' ] ' ;
void CGUIString : : SFeedback : : Reset ( )
{
m_Images [ Left ] . clear ( ) ;
m_Images [ Right ] . clear ( ) ;
m_TextCalls . clear ( ) ;
m_SpriteCalls . clear ( ) ;
m_Size = CSize ( ) ;
m_NewLine = false ;
}
void CGUIString : : GenerateTextCall ( SFeedback & Feedback ,
2004-08-31 04:09:58 +02:00
const CStr & DefaultFont ,
2004-09-04 22:35:12 +02:00
const int & from , const int & to ,
const bool FirstLine ,
2004-08-31 04:09:58 +02:00
const IGUIObject * pObject ) const
2004-05-29 06:06:50 +02:00
{
// Reset width and height, because they will be determined with incrementation
// or comparisons.
Feedback . Reset ( ) ;
// Check out which text chunk this is within.
//bool match_found = false;
vector < TextChunk > : : const_iterator itTextChunk ;
for ( itTextChunk = m_TextChunks . begin ( ) ; itTextChunk ! = m_TextChunks . end ( ) ; + + itTextChunk )
{
2004-08-31 04:09:58 +02:00
// - GL - Temp
TextChunk tc = * itTextChunk ;
// -- GL
2004-05-29 06:06:50 +02:00
// Get the area that is overlapped by both the TextChunk and
// by the from/to inputted.
int _from , _to ;
_from = max ( from , itTextChunk - > m_From ) ;
_to = min ( to , itTextChunk - > m_To ) ;
// If from is larger than to, than they are not overlapping
2004-08-31 04:09:58 +02:00
if ( _to = = _from & & itTextChunk - > m_From = = itTextChunk - > m_To )
2004-05-29 06:06:50 +02:00
{
// These should never be able to have more than one tag.
assert ( itTextChunk - > m_Tags . size ( ) = = 1 ) ;
2004-08-31 04:09:58 +02:00
// Now do second check
// because icons and images are placed on exactly one position
// in the words-list, it can be counted twice if placed on an
// edge. But there is always only one logical preference that
// we want. This check filters the unwanted.
// it's in the end of one word, and the icon
// should really belong to the beginning of the next one
2004-09-02 05:02:32 +02:00
if ( _to = = to & & to > = 1 )
2004-08-31 04:09:58 +02:00
{
if ( GetRawString ( ) [ to - 1 ] = = ' ' | |
GetRawString ( ) [ to - 1 ] = = ' - ' | |
GetRawString ( ) [ to - 1 ] = = ' \n ' )
continue ;
}
// This string is just a break
if ( _from = = from & & from > = 1 )
{
if ( GetRawString ( ) [ from ] = = ' \n ' & &
GetRawString ( ) [ from - 1 ] ! = ' \n ' & &
GetRawString ( ) [ from - 1 ] ! = ' ' & &
GetRawString ( ) [ from - 1 ] ! = ' - ' )
continue ;
}
2004-05-29 06:06:50 +02:00
// Single tags
if ( itTextChunk - > m_Tags [ 0 ] . m_TagType = = CGUIString : : TextChunk : : Tag : : TAG_IMGLEFT )
{
2004-08-31 04:09:58 +02:00
// Only add the image if the icon exists.
if ( g_GUI . IconExists ( itTextChunk - > m_Tags [ 0 ] . m_TagValue ) )
{
2004-05-29 06:06:50 +02:00
Feedback . m_Images [ SFeedback : : Left ] . push_back ( itTextChunk - > m_Tags [ 0 ] . m_TagValue ) ;
2004-08-31 04:09:58 +02:00
}
else if ( pObject )
{
LOG ( ERROR , LOG_CATEGORY , " Trying to use an [imgleft]-tag with an undefined icon ( \" %s \" ). " , itTextChunk - > m_Tags [ 0 ] . m_TagValue . c_str ( ) ) ;
}
2004-05-29 06:06:50 +02:00
}
else
if ( itTextChunk - > m_Tags [ 0 ] . m_TagType = = CGUIString : : TextChunk : : Tag : : TAG_IMGRIGHT )
{
2004-08-31 04:09:58 +02:00
// Only add the image if the icon exists.
if ( g_GUI . IconExists ( itTextChunk - > m_Tags [ 0 ] . m_TagValue ) )
{
2004-05-29 06:06:50 +02:00
Feedback . m_Images [ SFeedback : : Right ] . push_back ( itTextChunk - > m_Tags [ 0 ] . m_TagValue ) ;
2004-08-31 04:09:58 +02:00
}
else if ( pObject )
{
LOG ( ERROR , LOG_CATEGORY , " Trying to use an [imgright]-tag with an undefined icon ( \" %s \" ). " , itTextChunk - > m_Tags [ 0 ] . m_TagValue . c_str ( ) ) ;
}
2004-05-29 06:06:50 +02:00
}
else
if ( itTextChunk - > m_Tags [ 0 ] . m_TagType = = CGUIString : : TextChunk : : Tag : : TAG_ICON )
{
2004-08-31 04:09:58 +02:00
// Only add the image if the icon exists.
if ( g_GUI . 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 ;
// Also add it to the sprites being rendered.
SGUIText : : SSpriteCall SpriteCall ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// Get Icon from icon database in g_GUI
SGUIIcon icon = g_GUI . GetIcon ( itTextChunk - > m_Tags [ 0 ] . m_TagValue ) ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
CSize size = icon . m_Size ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// append width, and make maximum height the height.
Feedback . m_Size . cx + = size . cx ;
Feedback . m_Size . cy = max ( Feedback . m_Size . cy , size . cy ) ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// These are also needed later
TextCall . m_Size = size ;
SpriteCall . m_Area = size ;
2004-05-29 06:06:50 +02:00
2004-10-14 04:32:26 +02:00
// Now displace the sprite if the additional value in the tag
// exists
if ( itTextChunk - > m_Tags [ 0 ] . m_TagAdditionalValue ! = string ( ) )
{
CSize displacement ;
// Parse the value
if ( ! GUI < CSize > : : ParseString ( itTextChunk - > m_Tags [ 0 ] . m_TagAdditionalValue , displacement ) )
LOG ( ERROR , LOG_CATEGORY , " Error parsing 'displace' value for tag [ICON] " ) ;
else
SpriteCall . m_Area + = displacement ;
}
2004-12-15 22:24:46 +01:00
SpriteCall . m_Sprite = icon . m_TextureName ;
2004-12-18 14:32:00 +01:00
SpriteCall . m_CellID = icon . m_CellID ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// Add sprite call
Feedback . m_SpriteCalls . push_back ( SpriteCall ) ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// Finalize text call
TextCall . m_pSpriteCall = & Feedback . m_SpriteCalls . back ( ) ;
2004-05-29 06:06:50 +02:00
2004-08-31 04:09:58 +02:00
// Add text call
Feedback . m_TextCalls . push_back ( TextCall ) ;
}
else if ( pObject )
{
LOG ( ERROR , LOG_CATEGORY , " Trying to use an [icon]-tag with an undefined icon ( \" %s \" ). " , itTextChunk - > m_Tags [ 0 ] . m_TagValue . c_str ( ) ) ;
}
2004-05-29 06:06:50 +02:00
}
}
else
if ( _to > _from & & ! Feedback . m_NewLine )
{
SGUIText : : STextCall TextCall ;
// Set defaults
TextCall . m_Font = DefaultFont ;
TextCall . m_UseCustomColor = false ;
// Extract substring from RawString.
TextCall . m_String = GetRawString ( ) . GetSubstring ( _from , _to - _from ) ;
// Go through tags and apply changes.
vector < CGUIString : : TextChunk : : Tag > : : const_iterator it2 ;
for ( it2 = itTextChunk - > m_Tags . begin ( ) ; it2 ! = itTextChunk - > m_Tags . end ( ) ; + + it2 )
{
if ( it2 - > m_TagType = = CGUIString : : TextChunk : : Tag : : TAG_COLOR )
{
// Set custom color
TextCall . m_UseCustomColor = true ;
2004-08-31 04:09:58 +02:00
// Try parsing the color string
if ( ! GUI < CColor > : : ParseString ( it2 - > m_TagValue , TextCall . m_Color ) )
{
if ( pObject )
LOG ( ERROR , LOG_CATEGORY , " Error parsing the value of a [color]-tag in GUI text when reading object \" %s \" . " , pObject - > GetPresentableName ( ) . c_str ( ) ) ;
}
2004-05-29 06:06:50 +02:00
}
else
if ( it2 - > m_TagType = = CGUIString : : TextChunk : : Tag : : TAG_FONT )
{
2004-08-31 04:09:58 +02:00
// TODO Gee: (2004-08-15) Check if Font exists?
2004-05-29 06:06:50 +02:00
TextCall . m_Font = it2 - > m_TagValue ;
}
}
2004-08-28 00:08:30 +02:00
// Calculate the size of the font
2004-05-29 06:06:50 +02:00
CSize size ;
2004-09-03 07:48:47 +02:00
int cx , cy ;
2004-08-28 00:08:30 +02:00
CFont font ( TextCall . m_Font ) ;
2004-09-03 07:48:47 +02:00
font . CalculateStringSize ( TextCall . m_String , cx , cy ) ;
2004-09-04 22:35:12 +02:00
// For anything other than the first line, the line spacing
// needs to be considered rather than just the height of the text
if ( ! FirstLine )
cy = font . GetLineSpacing ( ) ;
2004-09-03 07:48:47 +02:00
size . cx = ( float ) cx ;
size . cy = ( float ) cy ;
2004-05-29 06:06:50 +02:00
2004-08-28 00:08:30 +02:00
// Append width, and make maximum height the height.
2004-05-29 06:06:50 +02:00
Feedback . m_Size . cx + = size . cx ;
Feedback . m_Size . cy = max ( Feedback . m_Size . cy , size . cy ) ;
2004-09-06 04:21:21 +02:00
// These are also needed later
2004-05-29 06:06:50 +02:00
TextCall . m_Size = size ;
if ( TextCall . m_String . Length ( ) > = 1 )
{
if ( TextCall . m_String [ 0 ] = = ' \n ' )
{
Feedback . m_NewLine = true ;
}
}
// Add text-chunk
Feedback . m_TextCalls . push_back ( TextCall ) ;
}
}
}
2004-06-18 16:07:06 +02:00
bool CGUIString : : TextChunk : : Tag : : SetTagType ( const CStr & tagtype )
2004-05-29 06:06:50 +02:00
{
CStr _tagtype = tagtype . UpperCase ( ) ;
if ( _tagtype = = CStr ( " B " ) )
{
m_TagType = TAG_B ;
return true ;
}
else
if ( _tagtype = = CStr ( " I " ) )
{
m_TagType = TAG_I ;
return true ;
}
else
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 ;
}
return false ;
}
2004-09-06 04:21:21 +02:00
void CGUIString : : SetValue ( const CStrW & str )
2004-05-29 06:06:50 +02:00
{
// clear
m_TextChunks . clear ( ) ;
m_Words . clear ( ) ;
2004-09-06 04:21:21 +02:00
m_RawString = CStrW ( ) ;
2004-05-29 06:06:50 +02:00
// Setup parser
2004-08-31 04:09:58 +02:00
// TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
2004-12-21 14:37:24 +01:00
// TODO PT: Extended CParserCache so that the above is possible (since it currently only
// likes one-task parsers)
2004-05-29 06:06:50 +02:00
CParser Parser ;
2004-10-14 04:32:26 +02:00
// I've added the option of an additional parameter. Only used for icons when writing this.
Parser . InputTaskType ( " start " , " $ident[_=_$value_[$ident_=_$value_]] " ) ;
2004-05-29 06:06:50 +02:00
Parser . InputTaskType ( " end " , " /$ident " ) ;
2004-06-01 19:34:12 +02:00
long position = 0 ;
2004-08-31 04:09:58 +02:00
long from = 0 ; // the position in the raw string where the last tag ended
2004-06-01 19:34:12 +02:00
long from_nonraw = 0 ; // like from only in position of the REAL string, with tags.
long curpos = 0 ;
2004-05-29 06:06:50 +02:00
// Current Text Chunk
CGUIString : : TextChunk CurrentTextChunk ;
for ( ; ; position = curpos + 1 )
{
// Find next TagStart character
curpos = str . Find ( position , TagStart ) ;
if ( curpos = = - 1 )
{
m_RawString + = str . GetSubstring ( position , str . Length ( ) - position ) ;
2004-07-24 16:04:40 +02:00
if ( from ! = ( long ) m_RawString . Length ( ) )
2004-05-29 06:06:50 +02:00
{
CurrentTextChunk . m_From = from ;
2004-05-29 13:59:59 +02:00
CurrentTextChunk . m_To = ( int ) m_RawString . Length ( ) ;
2004-05-29 06:06:50 +02:00
m_TextChunks . push_back ( CurrentTextChunk ) ;
}
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.
2004-06-01 19:34:12 +02:00
long pos_left = str . Find ( curpos + 1 , TagStart ) ;
long pos_right = str . Find ( curpos + 1 , TagEnd ) ;
2004-05-29 06:06:50 +02:00
if ( pos_right = = - 1 )
{
m_RawString + = str . GetSubstring ( position , curpos - position + 1 ) ;
continue ;
}
else
if ( pos_left ! = - 1 & & pos_left < pos_right )
{
m_RawString + = str . GetSubstring ( position , pos_left - position ) ;
continue ;
}
else
{
m_RawString + = str . GetSubstring ( 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.
CStr tagstr = str . GetSubstring ( curpos + 1 , pos_right - curpos - 1 ) ;
CParserLine Line ;
Line . ParseString ( Parser , ( const char * ) tagstr ) ;
// Set to true if the tag is just text.
bool justtext = false ;
if ( Line . m_ParseOK )
{
if ( Line . m_TaskTypeName = = " start " )
{
2004-10-14 04:32:26 +02:00
// The tag
2004-05-29 06:06:50 +02:00
TextChunk : : Tag tag ;
2004-07-14 00:48:53 +02:00
std : : string Str_TagType ;
2004-05-29 06:06:50 +02:00
2004-07-14 00:48:53 +02:00
Line . GetArgString ( 0 , Str_TagType ) ;
2004-05-29 06:06:50 +02:00
if ( ! tag . SetTagType ( Str_TagType ) )
{
justtext = true ;
}
else
{
// Check for possible value-strings
2004-10-14 04:32:26 +02:00
if ( Line . GetArgCount ( ) > = 2 )
2004-07-31 13:28:24 +02:00
Line . GetArgString ( 1 , tag . m_TagValue ) ;
2004-05-29 06:06:50 +02:00
2004-10-14 04:32:26 +02:00
// Check if an additional parameter was specified
if ( Line . GetArgCount ( ) = = 4 )
{
string str ;
Line . GetArgString ( 2 , str ) ;
if ( tag . m_TagType = = TextChunk : : Tag : : TAG_ICON & &
str = = " displace " )
{
Line . GetArgString ( 3 , tag . m_TagAdditionalValue ) ;
}
else
{
LOG ( WARNING , LOG_CATEGORY , " Trying to declare an additional attribute ('%s') in a [%s]-tag, which the tag isn't accepting " , str . c_str ( ) , Str_TagType . c_str ( ) ) ;
}
}
2004-05-29 06:06:50 +02:00
// 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 <tag /> 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.
vector < TextChunk : : Tag > : : 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 ) ;
}
}
}
else
if ( Line . m_TaskTypeName = = " end " )
{
// The tag
TextChunk : : Tag tag ;
2004-07-14 00:48:53 +02:00
std : : string Str_TagType ;
2004-05-29 06:06:50 +02:00
2004-07-14 00:48:53 +02:00
Line . GetArgString ( 0 , Str_TagType ) ;
2004-05-29 06:06:50 +02:00
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.
vector < TextChunk : : Tag > : : 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 ;
}
}
}
}
}
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 . GetSubstring ( curpos , pos_right - curpos + 1 ) ;
}
curpos = pos_right ;
continue ;
}
}
}
// Add a delimiter at start and at end, it helps when
// processing later, because we don't have make exceptions for
// those cases.
// We'll sort later.
m_Words . push_back ( 0 ) ;
2004-05-29 13:59:59 +02:00
m_Words . push_back ( ( int ) m_RawString . Length ( ) ) ;
2004-05-29 06:06:50 +02:00
// Space: ' '
for ( position = 0 , curpos = 0 ; ; position = curpos + 1 )
{
// Find the next word-delimiter.
2004-06-01 19:34:12 +02:00
long dl = m_RawString . Find ( position , ' ' ) ;
2004-05-29 06:06:50 +02:00
if ( dl = = - 1 )
break ;
curpos = dl ;
m_Words . push_back ( ( int ) dl + 1 ) ;
}
// Dash: '-'
for ( position = 0 , curpos = 0 ; ; position = curpos + 1 )
{
// Find the next word-delimiter.
2004-06-01 19:34:12 +02:00
long dl = m_RawString . Find ( position , ' - ' ) ;
2004-05-29 06:06:50 +02:00
if ( dl = = - 1 )
break ;
curpos = dl ;
m_Words . push_back ( ( int ) dl + 1 ) ;
}
// New Line: '\n'
for ( position = 0 , curpos = 0 ; ; position = curpos + 1 )
{
// Find the next word-delimiter.
2004-06-01 19:34:12 +02:00
long dl = m_RawString . Find ( position , ' \n ' ) ;
2004-05-29 06:06:50 +02:00
if ( dl = = - 1 )
break ;
curpos = dl ;
// Add before and
m_Words . push_back ( ( int ) dl ) ;
m_Words . push_back ( ( int ) dl + 1 ) ;
}
sort ( m_Words . begin ( ) , m_Words . end ( ) ) ;
2004-08-31 04:09:58 +02:00
// Remove duplicates (only if larger than 2)
if ( m_Words . size ( ) > 2 )
2004-05-29 06:06:50 +02:00
{
2004-08-31 04:09:58 +02:00
vector < int > : : iterator it ;
int last_word = - 1 ;
for ( it = m_Words . begin ( ) ; it ! = m_Words . end ( ) ; )
2004-07-31 13:28:24 +02:00
{
2004-08-31 04:09:58 +02:00
if ( last_word = = * it )
{
it = m_Words . erase ( it ) ;
}
else
{
last_word = * it ;
+ + it ;
}
2004-07-31 13:28:24 +02:00
}
2004-08-31 04:09:58 +02:00
}
2004-08-31 05:25:36 +02:00
#if 0
2004-08-31 04:09:58 +02:00
for ( int i = 0 ; i < ( int ) m_Words . size ( ) ; + + i )
{
LOG ( NORMAL , LOG_CATEGORY , " m_Words[%d] = %d " , i , m_Words [ i ] ) ;
}
for ( int i = 0 ; i < ( int ) m_TextChunks . size ( ) ; + + i )
{
LOG ( NORMAL , LOG_CATEGORY , " 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 )
2004-07-31 13:28:24 +02:00
{
2004-08-31 04:09:58 +02:00
LOG ( NORMAL , LOG_CATEGORY , " --Tag: %d \" %s \" " , ( int ) m_TextChunks [ i ] . m_Tags [ j ] . m_TagType , m_TextChunks [ i ] . m_Tags [ j ] . m_TagValue . c_str ( ) ) ;
2004-07-31 13:28:24 +02:00
}
2004-05-29 06:06:50 +02:00
}
2004-08-31 04:09:58 +02:00
# endif
2004-11-07 22:30:47 +01:00
}