// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010-2021 Winch Gate Property Limited
//
// This source file has been modified by the following contributors:
// Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
// Copyright (C) 2019-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//#include <crtdbg.h>
# include "stdpch.h"
# include "nel/gui/group_html.h"
# include <string>
# include "nel/misc/types_nl.h"
# include "nel/misc/rgba.h"
# include "nel/misc/algo.h"
# include "nel/misc/utf_string_view.h"
# include "nel/gui/libwww.h"
# include "nel/gui/group_html.h"
# include "nel/gui/group_list.h"
# include "nel/gui/group_menu.h"
# include "nel/gui/group_container.h"
# include "nel/gui/view_link.h"
# include "nel/gui/ctrl_scroll.h"
# include "nel/gui/ctrl_button.h"
# include "nel/gui/ctrl_text_button.h"
# include "nel/gui/action_handler.h"
# include "nel/gui/group_paragraph.h"
# include "nel/gui/group_editbox.h"
# include "nel/gui/widget_manager.h"
# include "nel/gui/lua_manager.h"
# include "nel/gui/view_bitmap.h"
# include "nel/gui/dbgroup_combo_box.h"
# include "nel/gui/lua_ihm.h"
# include "nel/misc/i18n.h"
# include "nel/misc/md5.h"
# include "nel/3d/texture_file.h"
# include "nel/misc/big_file.h"
# include "nel/gui/url_parser.h"
# include "nel/gui/http_cache.h"
# include "nel/gui/http_hsts.h"
# include "nel/web/curl_certificates.h"
# include "nel/gui/html_parser.h"
# include "nel/gui/html_element.h"
# include "nel/gui/css_style.h"
# include "nel/gui/css_parser.h"
# include "nel/gui/css_border_renderer.h"
# include "nel/gui/css_background_renderer.h"
# include <curl/curl.h>
using namespace std ;
using namespace NLMISC ;
# ifdef DEBUG_NEW
# define new DEBUG_NEW
# endif
// Default maximum time the request is allowed to take
# define DEFAULT_RYZOM_CONNECTION_TIMEOUT (300.0)
// Allow up to 10 redirects, then give up
# define DEFAULT_RYZOM_REDIRECT_LIMIT (10)
//
# define FONT_WEIGHT_NORMAL 400
# define FONT_WEIGHT_BOLD 700
namespace NLGUI
{
// Uncomment nlwarning() to see the log about curl downloads
# define LOG_DL(fmt, ...) //nlwarning(fmt, ## __VA_ARGS__)
// Uncomment to log curl progess
//#define LOG_CURL_PROGRESS 1
CGroupHTML : : SWebOptions CGroupHTML : : options ;
// Return URL with https is host is in HSTS list
static std : : string upgradeInsecureUrl ( const std : : string & url )
{
if ( toLowerAscii ( url . substr ( 0 , 7 ) ) ! = " http:// " ) {
return url ;
}
CUrlParser uri ( url ) ;
if ( ! CStrictTransportSecurity : : getInstance ( ) - > isSecureHost ( uri . host ) ) {
return url ;
}
LOG_DL ( " HSTS url : '%s', using https " , url . c_str ( ) ) ;
uri . scheme = " https " ;
return uri . toString ( ) ;
}
// Active cURL www transfer
class CCurlWWWData
{
public :
CCurlWWWData ( CURL * curl , const std : : string & url )
: Request ( curl ) , Url ( url ) , Content ( " " ) , HeadersSent ( NULL )
{
}
~ CCurlWWWData ( )
{
if ( Request )
curl_easy_cleanup ( Request ) ;
if ( HeadersSent )
curl_slist_free_all ( HeadersSent ) ;
}
void sendHeaders ( const std : : vector < std : : string > headers )
{
for ( uint i = 0 ; i < headers . size ( ) ; + + i )
{
HeadersSent = curl_slist_append ( HeadersSent , headers [ i ] . c_str ( ) ) ;
}
curl_easy_setopt ( Request , CURLOPT_HTTPHEADER , HeadersSent ) ;
}
void setRecvHeader ( const std : : string & header )
{
size_t pos = header . find ( " : " ) ;
if ( pos = = std : : string : : npos )
return ;
std : : string key = toLowerAscii ( header . substr ( 0 , pos ) ) ;
if ( pos ! = std : : string : : npos )
{
HeadersRecv [ key ] = header . substr ( pos + 2 ) ;
//nlinfo(">> received header '%s' = '%s'", key.c_str(), HeadersRecv[key].c_str());
}
}
// return last received "Location: <url>" header or empty string if no header set
const std : : string getLocationHeader ( )
{
if ( HeadersRecv . count ( " location " ) > 0 )
return HeadersRecv [ " location " ] ;
return " " ;
}
const uint32 getExpires ( )
{
time_t ret = 0 ;
if ( HeadersRecv . count ( " expires " ) > 0 )
ret = curl_getdate ( HeadersRecv [ " expires " ] . c_str ( ) , NULL ) ;
return ret > - 1 ? ret : 0 ;
}
const std : : string getLastModified ( )
{
if ( HeadersRecv . count ( " last-modified " ) > 0 )
{
return HeadersRecv [ " last-modified " ] ;
}
return " " ;
}
const std : : string getEtag ( )
{
if ( HeadersRecv . count ( " etag " ) > 0 )
{
return HeadersRecv [ " etag " ] ;
}
return " " ;
}
bool hasHSTSHeader ( )
{
// ignore header if not secure connection
if ( toLowerAscii ( Url . substr ( 0 , 8 ) ) ! = " https:// " )
{
return false ;
}
return HeadersRecv . count ( " strict-transport-security " ) > 0 ;
}
const std : : string getHSTSHeader ( )
{
if ( hasHSTSHeader ( ) )
{
return HeadersRecv [ " strict-transport-security " ] ;
}
return " " ;
}
public :
CURL * Request ;
std : : string Url ;
std : : string Content ;
private :
// headers sent with curl request, must be released after transfer
curl_slist * HeadersSent ;
// headers received from curl transfer
std : : map < std : : string , std : : string > HeadersRecv ;
} ;
// cURL transfer callbacks
// ***************************************************************************
static size_t curlHeaderCallback ( char * buffer , size_t size , size_t nmemb , void * pCCurlWWWData )
{
CCurlWWWData * me = static_cast < CCurlWWWData * > ( pCCurlWWWData ) ;
if ( me )
{
std : : string header ;
header . append ( buffer , size * nmemb ) ;
me - > setRecvHeader ( header . substr ( 0 , header . find_first_of ( " \n \r " ) ) ) ;
}
return size * nmemb ;
}
// ***************************************************************************
static size_t curlDataCallback ( char * buffer , size_t size , size_t nmemb , void * pCCurlWWWData )
{
CCurlWWWData * me = static_cast < CCurlWWWData * > ( pCCurlWWWData ) ;
if ( me )
me - > Content . append ( buffer , size * nmemb ) ;
return size * nmemb ;
}
// ***************************************************************************
static size_t curlProgressCallback ( void * pCCurlWWWData , curl_off_t dltotal , curl_off_t dlnow , curl_off_t ultotal , curl_off_t ulnow )
{
CCurlWWWData * me = static_cast < CCurlWWWData * > ( pCCurlWWWData ) ;
if ( me )
{
if ( dltotal > 0 | | dlnow > 0 | | ultotal > 0 | | ulnow > 0 )
{
# ifdef LOG_CURL_PROGRESS
nlwarning ( " > dltotal %ld, dlnow %ld, ultotal %ld, ulnow %ld, url '%s' " , dltotal , dlnow , ultotal , ulnow , me - > Url . c_str ( ) ) ;
# endif
}
}
// return 1 to cancel download
return 0 ;
}
CGroupHTML : : CDataDownload : : ~ CDataDownload ( )
{
delete data ;
data = NULL ;
}
void CGroupHTML : : StylesheetDownloadCB : : finish ( )
{
if ( CFile : : fileExists ( tmpdest ) )
{
if ( CFile : : fileExists ( dest ) )
{
CFile : : deleteFile ( dest ) ;
}
CFile : : moveFile ( dest , tmpdest ) ;
}
Parent - > cssDownloadFinished ( url , dest ) ;
}
void CGroupHTML : : ImageDownloadCB : : addImage ( CViewBase * img , const CStyleParams & style , TImageType type )
{
Images . push_back ( SImageInfo ( img , style , type ) ) ;
}
void CGroupHTML : : ImageDownloadCB : : removeImage ( CViewBase * img )
{
for ( std : : vector < SImageInfo > : : iterator it = Images . begin ( ) ; it ! = Images . end ( ) ; + + it )
{
if ( it - > Image = = img )
{
Images . erase ( it ) ;
break ;
}
}
}
void CGroupHTML : : ImageDownloadCB : : finish ( )
{
// Image setTexture will remove itself from Images while iterating over it.
// Do the swap to keep iterator safe.
std : : vector < SImageInfo > vec ;
vec . swap ( Images ) ;
// tmpdest file does not exist if download skipped (ie cache was used)
if ( CFile : : fileExists ( tmpdest ) | | CFile : : getFileSize ( tmpdest ) = = 0 )
{
try {
// verify that image is not corrupted
uint32 w , h ;
CBitmap : : loadSize ( tmpdest , w , h ) ;
if ( w ! = 0 & & h ! = 0 )
{
if ( CFile : : fileExists ( dest ) )
CFile : : deleteFile ( dest ) ;
}
}
catch ( const NLMISC : : Exception & e )
{
// exception message has .tmp file name, so keep it for further analysis
nlwarning ( " Invalid image (%s) from url (%s): %s " , tmpdest . c_str ( ) , url . c_str ( ) , e . what ( ) ) ;
}
// to reload image on page, the easiest seems to be changing texture
// to temp file temporarily. that forces driver to reload texture from disk
// ITexture::touch() seem not to do this.
// cache was updated, first set texture as temp file
for ( std : : vector < SImageInfo > : : iterator it = vec . begin ( ) ; it ! = vec . end ( ) ; + + it )
{
SImageInfo & img = * it ;
Parent - > setImage ( img . Image , tmpdest , img . Type ) ;
Parent - > setImageSize ( img . Image , img . Style ) ;
}
CFile : : moveFile ( dest , tmpdest ) ;
}
if ( ! CFile : : fileExists ( dest ) | | CFile : : getFileSize ( dest ) = = 0 )
{
// placeholder if cached image failed
dest = " web_del.tga " ;
}
// even if image was cached, incase there was 'http://' image set to CViewBitmap
for ( std : : vector < SImageInfo > : : iterator it = vec . begin ( ) ; it ! = vec . end ( ) ; + + it )
{
SImageInfo & img = * it ;
Parent - > setImage ( img . Image , dest , img . Type ) ;
Parent - > setImageSize ( img . Image , img . Style ) ;
}
}
void CGroupHTML : : TextureDownloadCB : : finish ( )
{
// tmpdest file does not exist if download skipped (ie cache was used)
if ( CFile : : fileExists ( tmpdest ) & & CFile : : getFileSize ( tmpdest ) > 0 )
{
if ( CFile : : fileExists ( dest ) )
CFile : : deleteFile ( dest ) ;
CFile : : moveFile ( dest , tmpdest ) ;
}
CViewRenderer & rVR = * CViewRenderer : : getInstance ( ) ;
for ( uint i = 0 ; i < TextureIds . size ( ) ; i + + )
{
rVR . reloadTexture ( TextureIds [ i ] . first , dest ) ;
TextureIds [ i ] . second - > invalidateCoords ( ) ;
}
}
void CGroupHTML : : BnpDownloadCB : : finish ( )
{
bool verified = false ;
// no tmpfile if file was already in cache
if ( CFile : : fileExists ( tmpdest ) )
{
verified = m_md5sum . empty ( ) | | ( m_md5sum ! = getMD5 ( tmpdest ) . toString ( ) ) ;
if ( verified )
{
if ( CFile : : fileExists ( dest ) )
{
CFile : : deleteFile ( dest ) ;
}
CFile : : moveFile ( dest , tmpdest ) ;
}
else
{
CFile : : deleteFile ( tmpdest ) ;
}
}
else if ( CFile : : fileExists ( dest ) )
{
verified = m_md5sum . empty ( ) | | ( m_md5sum ! = getMD5 ( dest ) . toString ( ) ) ;
}
if ( ! m_lua . empty ( ) )
{
std : : string script = " \n local __CURRENT_WINDOW__ = \" " + Parent - > getId ( ) + " \" " ;
script + = toString ( " \n local __DOWNLOAD_STATUS__ = %s \n " , verified ? " true " : " false " ) ;
script + = m_lua ;
CLuaManager : : getInstance ( ) . executeLuaScript ( script , true ) ;
}
}
// Check if domain is on TrustedDomain
bool CGroupHTML : : isTrustedDomain ( const string & domain )
{
if ( domain = = options . webServerDomain )
return true ;
vector < string > : : iterator it ;
it = find ( options . trustedDomains . begin ( ) , options . trustedDomains . end ( ) , domain ) ;
return it ! = options . trustedDomains . end ( ) ;
}
// Update view after download has finished
void CGroupHTML : : setImage ( CViewBase * view , const string & file , const TImageType type )
{
CCtrlButton * btn = dynamic_cast < CCtrlButton * > ( view ) ;
if ( btn )
{
if ( type = = NormalImage )
{
btn - > setTexture ( file ) ;
btn - > setTexturePushed ( file ) ;
btn - > invalidateCoords ( ) ;
btn - > invalidateContent ( ) ;
paragraphChange ( ) ;
}
else
{
btn - > setTextureOver ( file ) ;
}
return ;
}
CViewBitmap * btm = dynamic_cast < CViewBitmap * > ( view ) ;
if ( btm )
{
btm - > setTexture ( file ) ;
btm - > invalidateCoords ( ) ;
btm - > invalidateContent ( ) ;
paragraphChange ( ) ;
return ;
}
CGroupCell * btgc = dynamic_cast < CGroupCell * > ( view ) ;
if ( btgc )
{
btgc - > setTexture ( file ) ;
btgc - > invalidateCoords ( ) ;
btgc - > invalidateContent ( ) ;
paragraphChange ( ) ;
return ;
}
CGroupTable * table = dynamic_cast < CGroupTable * > ( view ) ;
if ( table )
{
table - > setTexture ( file ) ;
return ;
}
}
// Force image width, height
void CGroupHTML : : setImageSize ( CViewBase * view , const CStyleParams & style )
{
sint32 width = style . Width ;
sint32 height = style . Height ;
sint32 maxw = style . MaxWidth ;
sint32 maxh = style . MaxHeight ;
sint32 imageWidth , imageHeight ;
bool changed = true ;
// get image texture size
// if image is being downloaded, then correct size is set after thats done
CCtrlButton * btn = dynamic_cast < CCtrlButton * > ( view ) ;
if ( btn )
{
btn - > fitTexture ( ) ;
imageWidth = btn - > getW ( false ) ;
imageHeight = btn - > getH ( false ) ;
}
else
{
CViewBitmap * btm = dynamic_cast < CViewBitmap * > ( view ) ;
if ( btm )
{
btm - > fitTexture ( ) ;
imageWidth = btm - > getW ( false ) ;
imageHeight = btm - > getH ( false ) ;
}
else
{
// not supported
return ;
}
}
// if width/height is not requested, then use image size
// else recalculate missing value, keep image ratio
if ( width = = - 1 & & height = = - 1 )
{
width = imageWidth ;
height = imageHeight ;
changed = false ;
}
else
if ( width = = - 1 | | height = = - 1 ) {
float ratio = ( float ) imageWidth / std : : max ( 1 , imageHeight ) ;
if ( width = = - 1 )
width = height * ratio ;
else
height = width / ratio ;
}
// apply max-width, max-height rules if asked
if ( maxw > - 1 | | maxh > - 1 )
{
_Style . applyCssMinMax ( width , height , 0 , 0 , maxw , maxh ) ;
changed = true ;
}
if ( changed )
{
CCtrlButton * btn = dynamic_cast < CCtrlButton * > ( view ) ;
if ( btn )
{
btn - > setScale ( true ) ;
btn - > setW ( width ) ;
btn - > setH ( height ) ;
}
else
{
CViewBitmap * image = dynamic_cast < CViewBitmap * > ( view ) ;
if ( image )
{
image - > setScale ( true ) ;
image - > setW ( width ) ;
image - > setH ( height ) ;
}
}
}
}
void CGroupHTML : : setTextButtonStyle ( CCtrlTextButton * ctrlButton , const CStyleParams & style )
{
// this will also set size for <a class="ryzom-ui-button"> treating it like "display: inline-block;"
if ( style . Width > 0 ) ctrlButton - > setWMin ( style . Width ) ;
if ( style . Height > 0 ) ctrlButton - > setHMin ( style . Height ) ;
CViewText * pVT = ctrlButton - > getViewText ( ) ;
if ( pVT )
{
setTextStyle ( pVT , style ) ;
}
if ( style . hasStyle ( " background-color " ) )
{
ctrlButton - > setColor ( style . Background . color ) ;
if ( style . hasStyle ( " -ryzom-background-color-over " ) )
{
ctrlButton - > setColorOver ( style . BackgroundColorOver ) ;
}
else
{
ctrlButton - > setColorOver ( style . Background . color ) ;
}
ctrlButton - > setTexture ( " " , " blank.tga " , " " , false ) ;
ctrlButton - > setTextureOver ( " " , " blank.tga " , " " ) ;
ctrlButton - > setProperty ( " force_text_over " , " true " ) ;
}
else if ( style . hasStyle ( " -ryzom-background-color-over " ) )
{
ctrlButton - > setColorOver ( style . BackgroundColorOver ) ;
ctrlButton - > setProperty ( " force_text_over " , " true " ) ;
ctrlButton - > setTextureOver ( " blank.tga " , " blank.tga " , " blank.tga " ) ;
}
}
void CGroupHTML : : setTextStyle ( CViewText * pVT , const CStyleParams & style )
{
if ( pVT )
{
pVT - > setColor ( style . TextColor ) ;
pVT - > setFontName ( style . FontFamily ) ;
pVT - > setFontSize ( style . FontSize , false ) ;
pVT - > setEmbolden ( style . FontWeight > = FONT_WEIGHT_BOLD ) ;
pVT - > setOblique ( style . FontOblique ) ;
pVT - > setUnderlined ( style . Underlined ) ;
pVT - > setStrikeThrough ( style . StrikeThrough ) ;
if ( style . TextShadow . Enabled )
{
pVT - > setShadow ( true ) ;
pVT - > setShadowColor ( style . TextShadow . Color ) ;
pVT - > setShadowOutline ( style . TextShadow . Outline ) ;
pVT - > setShadowOffset ( style . TextShadow . X , style . TextShadow . Y ) ;
}
}
}
// Get an url and return the local filename with the path where the url image should be
string CGroupHTML : : localImageName ( const string & url )
{
string dest = " cache/ " ;
dest + = getMD5 ( ( uint8 * ) url . c_str ( ) , ( uint32 ) url . size ( ) ) . toString ( ) ;
dest + = " .cache " ;
return dest ;
}
void CGroupHTML : : pumpCurlQueue ( )
{
if ( RunningCurls < options . curlMaxConnections )
{
std : : list < CDataDownload * > : : iterator it = Curls . begin ( ) ;
while ( it ! = Curls . end ( ) & & RunningCurls < options . curlMaxConnections )
{
if ( ( * it ) - > data = = NULL )
{
LOG_DL ( " (%s) starting new download '%s' " , _Id . c_str ( ) , it - > url . c_str ( ) ) ;
if ( ! startCurlDownload ( * it ) )
{
LOG_DL ( " (%s) failed to start '%s)' " , _Id . c_str ( ) , it - > url . c_str ( ) ) ;
finishCurlDownload ( * it ) ;
it = Curls . erase ( it ) ;
continue ;
}
}
+ + it ;
}
}
if ( RunningCurls > 0 | | ! Curls . empty ( ) )
LOG_DL ( " (%s) RunningCurls %d, _Curls %d " , _Id . c_str ( ) , RunningCurls , Curls . size ( ) ) ;
}
// Add url to MultiCurl queue and return cURL handle
bool CGroupHTML : : startCurlDownload ( CDataDownload * download )
{
if ( ! MultiCurl )
{
nlwarning ( " Invalid MultiCurl handle, unable to download '%s' " , download - > url . c_str ( ) ) ;
return false ;
}
time_t currentTime ;
time ( & currentTime ) ;
CHttpCacheObject cache ;
if ( CFile : : fileExists ( download - > dest ) )
cache = CHttpCache : : getInstance ( ) - > lookup ( download - > dest ) ;
if ( cache . Expires > currentTime )
{
LOG_DL ( " Cache for (%s) is not expired (%s, expires:%d) " , download - > url . c_str ( ) , download - > dest . c_str ( ) , cache . Expires - currentTime ) ;
return false ;
}
// use browser Id so that two browsers would not use same temp file
download - > tmpdest = localImageName ( _Id + download - > dest ) + " .tmp " ;
// erase the tmp file if exists
if ( CFile : : fileExists ( download - > tmpdest ) )
{
CFile : : deleteFile ( download - > tmpdest ) ;
}
FILE * fp = nlfopen ( download - > tmpdest , " wb " ) ;
if ( fp = = NULL )
{
nlwarning ( " Can't open file '%s' for writing: code=%d '%s' " , download - > tmpdest . c_str ( ) , errno , strerror ( errno ) ) ;
return false ;
}
CURL * curl = curl_easy_init ( ) ;
if ( ! curl )
{
fclose ( fp ) ;
CFile : : deleteFile ( download - > tmpdest ) ;
nlwarning ( " Creating cURL handle failed, unable to download '%s' " , download - > url . c_str ( ) ) ;
return false ;
}
LOG_DL ( " curl easy handle %p created for '%s' " , curl , download - > url . c_str ( ) ) ;
// https://
if ( toLowerAscii ( download - > url . substr ( 0 , 8 ) ) = = " https:// " )
{
// if supported, use custom SSL context function to load certificates
NLWEB : : CCurlCertificates : : useCertificates ( curl ) ;
}
download - > data = new CCurlWWWData ( curl , download - > url ) ;
download - > fp = fp ;
// initial connection timeout, curl default is 300sec
curl_easy_setopt ( curl , CURLOPT_CONNECTTIMEOUT , download - > ConnectionTimeout ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , true ) ;
curl_easy_setopt ( curl , CURLOPT_URL , download - > url . c_str ( ) ) ;
// limit curl to HTTP and HTTPS protocols only
curl_easy_setopt ( curl , CURLOPT_PROTOCOLS , CURLPROTO_HTTP | CURLPROTO_HTTPS ) ;
curl_easy_setopt ( curl , CURLOPT_REDIR_PROTOCOLS , CURLPROTO_HTTP | CURLPROTO_HTTPS ) ;
std : : vector < std : : string > headers ;
if ( ! cache . Etag . empty ( ) )
headers . push_back ( " If-None-Match: " + cache . Etag ) ;
if ( ! cache . LastModified . empty ( ) )
headers . push_back ( " If-Modified-Since: " + cache . LastModified ) ;
if ( headers . size ( ) > 0 )
download - > data - > sendHeaders ( headers ) ;
// catch headers
curl_easy_setopt ( curl , CURLOPT_HEADERFUNCTION , NLGUI : : curlHeaderCallback ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEHEADER , download - > data ) ;
std : : string userAgent = options . appName + " / " + options . appVersion ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , userAgent . c_str ( ) ) ;
CUrlParser uri ( download - > url ) ;
if ( ! uri . host . empty ( ) )
sendCookies ( curl , uri . host , isTrustedDomain ( uri . host ) ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , fp ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , fwrite ) ;
CURLMcode ret = curl_multi_add_handle ( MultiCurl , curl ) ;
if ( ret ! = CURLM_OK )
{
nlwarning ( " cURL multi handle %p error %d on '%s' " , curl , ret , download - > url . c_str ( ) ) ;
return false ;
}
RunningCurls + + ;
return true ;
}
void CGroupHTML : : finishCurlDownload ( CDataDownload * download )
{
if ( download )
{
download - > finish ( ) ;
delete download ;
}
else
{
nlwarning ( " Unknown CURL download (nullptr) " ) ;
}
}
// Add a image download request in the multi_curl
// return new textureId and download callback
ICurlDownloadCB * CGroupHTML : : addTextureDownload ( const string & url , sint32 & texId , CViewBase * view )
{
CViewRenderer & rVR = * CViewRenderer : : getInstance ( ) ;
// data:image/png;base64,AA...==
if ( startsWith ( url , " data:image/ " ) )
{
texId = rVR . createTextureFromDataURL ( url ) ;
return NULL ;
}
std : : string finalUrl ;
// load the image from local files/bnp
if ( lookupLocalFile ( finalUrl , std : : string ( CFile : : getPath ( url ) + CFile : : getFilenameWithoutExtension ( url ) + " .tga " ) . c_str ( ) , false ) )
{
texId = rVR . createTexture ( finalUrl ) ;
return NULL ;
}
finalUrl = upgradeInsecureUrl ( getAbsoluteUrl ( url ) ) ;
// use requested url for local name (cache)
string dest = localImageName ( url ) ;
LOG_DL ( " add to download '%s' dest '%s' " , finalUrl . c_str ( ) , dest . c_str ( ) ) ;
if ( CFile : : fileExists ( dest ) & & CFile : : getFileSize ( dest ) > 0 )
texId = rVR . createTexture ( dest ) ;
else
texId = rVR . newTextureId ( dest ) ;
// Search if we are not already downloading this url.
for ( std : : list < CDataDownload * > : : iterator it = Curls . begin ( ) ; it ! = Curls . end ( ) ; + + it )
{
if ( ( * it ) - > url = = finalUrl )
{
LOG_DL ( " already downloading '%s' img %p " , finalUrl . c_str ( ) , img ) ;
TextureDownloadCB * cb = dynamic_cast < TextureDownloadCB * > ( * it ) ;
if ( cb )
{
cb - > addTexture ( texId , view ) ;
// return pointer to shared ImageDownloadCB
return cb ;
}
else
{
nlwarning ( " Found texture download '%s', but casting to TextureDownloadCB failed " , finalUrl . c_str ( ) ) ;
}
}
}
Curls . push_back ( new TextureDownloadCB ( finalUrl , dest , texId , this ) ) ;
// as we return pointer to callback, skip starting downloads just now
//pumpCurlQueue();
return Curls . back ( ) ;
}
// Add a image download request in the multi_curl
ICurlDownloadCB * CGroupHTML : : addImageDownload ( const string & url , CViewBase * img , const CStyleParams & style , TImageType type , const std : : string & placeholder )
{
std : : string finalUrl ;
img - > setModulateGlobalColor ( style . GlobalColor ) ;
// data:image/png;base64,AA...==
if ( startsWith ( url , " data:image/ " ) )
{
setImage ( img , decodeURIComponent ( url ) , type ) ;
setImageSize ( img , style ) ;
return NULL ;
}
// load the image from local files/bnp
std : : string image = CFile : : getPath ( url ) + CFile : : getFilenameWithoutExtension ( url ) + " .tga " ;
if ( lookupLocalFile ( finalUrl , image . c_str ( ) , false ) )
{
setImage ( img , image , type ) ;
setImageSize ( img , style ) ;
return NULL ;
}
finalUrl = upgradeInsecureUrl ( getAbsoluteUrl ( url ) ) ;
// use requested url for local name (cache)
string dest = localImageName ( url ) ;
LOG_DL ( " add to download '%s' dest '%s' img %p " , finalUrl . c_str ( ) , dest . c_str ( ) , img ) ;
// Display cached image while downloading new
if ( type ! = OverImage )
{
std : : string temp = dest ;
if ( ! CFile : : fileExists ( temp ) | | CFile : : getFileSize ( temp ) = = 0 )
{
temp = placeholder ;
}
setImage ( img , temp , type ) ;
setImageSize ( img , style ) ;
}
// Search if we are not already downloading this url.
for ( std : : list < CDataDownload * > : : iterator it = Curls . begin ( ) ; it ! = Curls . end ( ) ; + + it )
{
if ( ( * it ) - > url = = finalUrl )
{
LOG_DL ( " already downloading '%s' img %p " , finalUrl . c_str ( ) , img ) ;
ImageDownloadCB * cb = dynamic_cast < ImageDownloadCB * > ( * it ) ;
if ( cb )
{
cb - > addImage ( img , style , type ) ;
// return pointer to shared ImageDownloadCB
return cb ;
}
else
{
nlwarning ( " Found image download '%s', but casting to ImageDownloadCB failed " , finalUrl . c_str ( ) ) ;
}
}
}
Curls . push_back ( new ImageDownloadCB ( finalUrl , dest , img , style , type , this ) ) ;
// as we return pointer to callback, skip starting downloads just now
//pumpCurlQueue();
return Curls . back ( ) ;
}
void CGroupHTML : : removeImageDownload ( ICurlDownloadCB * handle , CViewBase * img )
{
ImageDownloadCB * cb = dynamic_cast < ImageDownloadCB * > ( handle ) ;
if ( ! cb ) {
nlwarning ( " Trying to remove image from downloads, but ICurlDownloadCB pointer did not cast to ImageDownloadCB " ) ;
return ;
}
// image will be removed from handle, but handle is kept and image will be downloaded
cb - > removeImage ( img ) ;
}
void CGroupHTML : : initImageDownload ( )
{
LOG_DL ( " Init Image Download " ) ;
string pathName = " cache " ;
if ( ! CFile : : isExists ( pathName ) )
CFile : : createDirectory ( pathName ) ;
}
// Get an url and return the local filename with the path where the bnp should be
string CGroupHTML : : localBnpName ( const string & url )
{
size_t lastIndex = url . find_last_of ( " / " ) ;
string dest = " user/ " + url . substr ( lastIndex + 1 ) ;
return dest ;
}
// Add a bnp download request in the multi_curl, return true if already downloaded
bool CGroupHTML : : addBnpDownload ( string url , const string & action , const string & script , const string & md5sum )
{
url = upgradeInsecureUrl ( getAbsoluteUrl ( url ) ) ;
// Search if we are not already downloading this url.
for ( std : : list < CDataDownload * > : : const_iterator it = Curls . begin ( ) ; it ! = Curls . end ( ) ; + + it )
{
if ( ( * it ) - > url = = url )
{
LOG_DL ( " already downloading '%s' " , url . c_str ( ) ) ;
return false ;
}
}
string dest = localBnpName ( url ) ;
LOG_DL ( " add to download '%s' dest '%s' " , url . c_str ( ) , dest . c_str ( ) ) ;
// create/delete the local file
if ( NLMISC : : CFile : : fileExists ( dest ) )
{
if ( action = = " override " | | action = = " delete " )
{
CFile : : setRWAccess ( dest ) ;
NLMISC : : CFile : : deleteFile ( dest ) ;
}
else
{
return true ;
}
}
if ( action ! = " delete " )
{
Curls . push_back ( new BnpDownloadCB ( url , dest , md5sum , script , this ) ) ;
pumpCurlQueue ( ) ;
}
else
return true ;
return false ;
}
void CGroupHTML : : initBnpDownload ( )
{
if ( ! _TrustedDomain )
return ;
LOG_DL ( " Init Bnp Download " ) ;
string pathName = " user " ;
if ( ! CFile : : isExists ( pathName ) )
CFile : : createDirectory ( pathName ) ;
}
void CGroupHTML : : addStylesheetDownload ( const std : : vector < CHtmlParser : : StyleLink > links )
{
for ( uint i = 0 ; i < links . size ( ) ; + + i )
{
_StylesheetQueue . push_back ( links [ i ] ) ;
std : : string url = getAbsoluteUrl ( links [ i ] . Url ) ;
_StylesheetQueue . back ( ) . Url = url ;
// push to the front of the queue
Curls . push_front ( new StylesheetDownloadCB ( url , localImageName ( url ) , this ) ) ;
}
pumpCurlQueue ( ) ;
}
// Call this evenly to check if an element is downloaded and then manage it
void CGroupHTML : : checkDownloads ( )
{
//nlassert(_CrtCheckMemory());
if ( Curls . empty ( ) & & _CurlWWW = = NULL )
{
return ;
}
int NewRunningCurls = 0 ;
while ( CURLM_CALL_MULTI_PERFORM = = curl_multi_perform ( MultiCurl , & NewRunningCurls ) )
{
LOG_DL ( " more to do now %d - %d curls " , NewRunningCurls , Curls . size ( ) ) ;
}
LOG_DL ( " NewRunningCurls:%d, RunningCurls:%d " , NewRunningCurls , RunningCurls ) ;
// check which downloads are done
CURLMsg * msg ;
int msgs_left ;
while ( ( msg = curl_multi_info_read ( MultiCurl , & msgs_left ) ) )
{
LOG_DL ( " > (%s) msgs_left %d " , _Id . c_str ( ) , msgs_left ) ;
if ( msg - > msg = = CURLMSG_DONE )
{
if ( _CurlWWW & & _CurlWWW - > Request & & _CurlWWW - > Request = = msg - > easy_handle )
{
std : : string error ;
bool success = msg - > data . result = = CURLE_OK ;
if ( ! success )
{
error = curl_easy_strerror ( msg - > data . result ) ;
}
LOG_DL ( " html download finished with curl code %d (%s) " , ( sint ) msg - > msg , error . c_str ( ) ) ;
htmlDownloadFinished ( success , error ) ;
}
else
{
for ( std : : list < CDataDownload * > : : iterator it = Curls . begin ( ) ; it ! = Curls . end ( ) ; + + it )
{
if ( ( * it ) - > data & & ( * it ) - > data - > Request = = msg - > easy_handle )
{
std : : string error ;
bool success = msg - > data . result = = CURLE_OK ;
if ( ! success )
{
error = curl_easy_strerror ( msg - > data . result ) ;
}
LOG_DL ( " data download finished with curl code %d (%s) " , ( sint ) msg - > msg , error . c_str ( ) ) ;
dataDownloadFinished ( success , error , * it ) ;
Curls . erase ( it ) ;
break ;
}
}
}
}
}
RunningCurls = NewRunningCurls ;
pumpCurlQueue ( ) ;
}
void CGroupHTML : : releaseDownloads ( )
{
LOG_DL ( " Release Downloads " ) ;
if ( _CurlWWW )
{
LOG_DL ( " (%s) stop html url '%s' " , _Id . c_str ( ) , _CurlWWW - > Url . c_str ( ) ) ;
if ( MultiCurl )
curl_multi_remove_handle ( MultiCurl , _CurlWWW - > Request ) ;
delete _CurlWWW ;
_CurlWWW = NULL ;
}
releaseDataDownloads ( ) ;
}
void CGroupHTML : : releaseDataDownloads ( )
{
LOG_DL ( " Clear pointers to %d curls " , Curls . size ( ) ) ;
// remove all queued and already started downloads
for ( std : : list < CDataDownload * > : : iterator it = Curls . begin ( ) ; it ! = Curls . end ( ) ; + + it )
{
CDataDownload & dl = * ( * it ) ;
if ( dl . data )
{
LOG_DL ( " (%s) stop data url '%s' " , _Id . c_str ( ) , dl . url . c_str ( ) ) ;
if ( MultiCurl )
{
curl_multi_remove_handle ( MultiCurl , dl . data - > Request ) ;
}
// close and remove temp file
if ( dl . fp )
{
fclose ( dl . fp ) ;
if ( CFile : : fileExists ( dl . tmpdest ) )
{
CFile : : deleteFile ( dl . tmpdest ) ;
}
}
}
// release CDataDownload
delete * it ;
}
Curls . clear ( ) ;
// also clear css queue as it depends on Curls
_StylesheetQueue . clear ( ) ;
}
class CGroupListAdaptor : public CInterfaceGroup
{
public :
CGroupListAdaptor ( const TCtorParam & param )
: CInterfaceGroup ( param )
{ }
private :
void updateCoords ( )
{
if ( _Parent )
{
// Get the W max from the parent
_W = std : : min ( _Parent - > getMaxWReal ( ) , _Parent - > getWReal ( ) ) ;
_WReal = _W ;
}
CInterfaceGroup : : updateCoords ( ) ;
}
} ;
// ***************************************************************************
template < class A > void popIfNotEmpty ( A & vect ) { if ( ! vect . empty ( ) ) vect . pop_back ( ) ; }
// ***************************************************************************
TStyle CGroupHTML : : parseStyle ( const string & str_styles )
{
TStyle styles ;
vector < string > elements ;
NLMISC : : splitString ( str_styles , " ; " , elements ) ;
for ( uint i = 0 ; i < elements . size ( ) ; + + i )
{
vector < string > style ;
NLMISC : : splitString ( elements [ i ] , " : " , style ) ;
if ( style . size ( ) > = 2 )
{
string fullstyle = style [ 1 ] ;
for ( uint j = 2 ; j < style . size ( ) ; j + + )
fullstyle + = " : " + style [ j ] ;
styles [ trim ( style [ 0 ] ) ] = trimSeparators ( fullstyle ) ;
}
}
return styles ;
}
// ***************************************************************************
void CGroupHTML : : addText ( const char * buf , int len )
{
if ( _Browsing )
{
if ( _IgnoreText )
return ;
// Build a UTF8 string
if ( _ParsingLua & & _TrustedDomain )
{
// we are parsing a lua script
_LuaScript + = string ( buf , buf + len ) ;
// no more to do
return ;
}
// Build a unicode string
CUtfStringView inputStringView ( buf , len ) ;
// Build the final unicode string
string tmp ;
tmp . reserve ( len ) ;
u32char lastChar = 0 ;
u32char inputStringView0 = * inputStringView . begin ( ) ;
for ( CUtfStringView : : iterator it ( inputStringView . begin ( ) ) , end ( inputStringView . end ( ) ) ; it ! = end ; + + it )
{
u32char output ;
bool keep ;
// special treatment for 'nbsp' (which is returned as a discreet space)
if ( len = = 1 & & inputStringView0 = = 32 )
{
// this is a nbsp entity
output = * it ;
keep = true ;
}
else
{
// not nbsp, use normal white space removal routine
keep = translateChar ( output , * it , lastChar ) ;
}
if ( keep )
{
CUtfStringView : : append ( tmp , output ) ;
lastChar = output ;
}
}
if ( ! tmp . empty ( ) )
addString ( tmp ) ;
}
}
// ***************************************************************************
# define registerAnchorName(prefix) \
{ \
if ( present [ prefix # # _ID ] & & value [ prefix # # _ID ] ) \
_AnchorName . push_back ( value [ prefix # # _ID ] ) ; \
}
// ***************************************************************************
void CGroupHTML : : beginElement ( CHtmlElement & elm )
{
_Style . pushStyle ( ) ;
_CurrentHTMLElement = & elm ;
_CurrentHTMLNextSibling = elm . nextSibling ;
// set element style from css and style attribute
_Style . getStyleFor ( elm ) ;
if ( ! elm . Style . empty ( ) )
{
_Style . applyStyle ( elm . Style ) ;
}
if ( elm . hasNonEmptyAttribute ( " name " ) )
{
_AnchorName . push_back ( elm . getAttribute ( " name " ) ) ;
}
if ( elm . hasNonEmptyAttribute ( " id " ) )
{
_AnchorName . push_back ( elm . getAttribute ( " id " ) ) ;
}
if ( _Style . Current . DisplayBlock )
{
endParagraph ( ) ;
}
switch ( elm . ID )
{
case HTML_A : htmlA ( elm ) ; break ;
case HTML_BASE : htmlBASE ( elm ) ; break ;
case HTML_BODY : htmlBODY ( elm ) ; break ;
case HTML_BR : htmlBR ( elm ) ; break ;
case HTML_BUTTON : htmlBUTTON ( elm ) ; break ;
case HTML_DD : htmlDD ( elm ) ; break ;
case HTML_DEL : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_DIV : htmlDIV ( elm ) ; break ;
case HTML_DL : htmlDL ( elm ) ; break ;
case HTML_DT : htmlDT ( elm ) ; break ;
case HTML_EM : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_FONT : htmlFONT ( elm ) ; break ;
case HTML_FORM : htmlFORM ( elm ) ; break ;
case HTML_H1 : //no-break
case HTML_H2 : //no-break
case HTML_H3 : //no-break
case HTML_H4 : //no-break
case HTML_H5 : //no-break
case HTML_H6 : htmlH ( elm ) ; break ;
case HTML_HEAD : htmlHEAD ( elm ) ; break ;
case HTML_HR : htmlHR ( elm ) ; break ;
case HTML_HTML : htmlHTML ( elm ) ; break ;
case HTML_I : htmlI ( elm ) ; break ;
case HTML_IMG : htmlIMG ( elm ) ; break ;
case HTML_INPUT : htmlINPUT ( elm ) ; break ;
case HTML_LI : htmlLI ( elm ) ; break ;
case HTML_LUA : htmlLUA ( elm ) ; break ;
case HTML_META : htmlMETA ( elm ) ; break ;
case HTML_METER : htmlMETER ( elm ) ; break ;
case HTML_OBJECT : htmlOBJECT ( elm ) ; break ;
case HTML_OL : htmlOL ( elm ) ; break ;
case HTML_OPTION : htmlOPTION ( elm ) ; break ;
case HTML_P : htmlP ( elm ) ; break ;
case HTML_PRE : htmlPRE ( elm ) ; break ;
case HTML_PROGRESS : htmlPROGRESS ( elm ) ; break ;
case HTML_SCRIPT : htmlSCRIPT ( elm ) ; break ;
case HTML_SELECT : htmlSELECT ( elm ) ; break ;
case HTML_SMALL : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_SPAN : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_STRONG : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_STYLE : htmlSTYLE ( elm ) ; break ;
case HTML_TABLE : htmlTABLE ( elm ) ; break ;
case HTML_TBODY : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_TD : htmlTD ( elm ) ; break ;
case HTML_TEXTAREA : htmlTEXTAREA ( elm ) ; break ;
case HTML_TFOOT : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_TH : htmlTH ( elm ) ; break ;
case HTML_TITLE : htmlTITLE ( elm ) ; break ;
case HTML_TR : htmlTR ( elm ) ; break ;
case HTML_U : renderPseudoElement ( " :before " , elm ) ; break ;
case HTML_UL : htmlUL ( elm ) ; break ;
default :
renderPseudoElement ( " :before " , elm ) ;
break ;
}
}
// ***************************************************************************
void CGroupHTML : : endElement ( CHtmlElement & elm )
{
_CurrentHTMLElement = & elm ;
switch ( elm . ID )
{
case HTML_A : htmlAend ( elm ) ; break ;
case HTML_BASE : break ;
case HTML_BODY : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_BR : break ;
case HTML_BUTTON : htmlBUTTONend ( elm ) ; break ;
case HTML_DD : htmlDDend ( elm ) ; break ;
case HTML_DEL : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_DIV : htmlDIVend ( elm ) ; break ;
case HTML_DL : htmlDLend ( elm ) ; break ;
case HTML_DT : htmlDTend ( elm ) ; break ;
case HTML_EM : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_FONT : break ;
case HTML_FORM : htmlFORMend ( elm ) ; break ;
case HTML_H1 : //no-break
case HTML_H2 : //no-break
case HTML_H3 : //no-break
case HTML_H4 : //no-break
case HTML_H5 : //no-break
case HTML_H6 : htmlHend ( elm ) ; break ;
case HTML_HEAD : htmlHEADend ( elm ) ; break ;
case HTML_HR : break ;
case HTML_HTML : break ;
case HTML_I : htmlIend ( elm ) ; break ;
case HTML_IMG : break ;
case HTML_INPUT : break ;
case HTML_LI : htmlLIend ( elm ) ; break ;
case HTML_LUA : htmlLUAend ( elm ) ; break ;
case HTML_META : break ;
case HTML_METER : break ;
case HTML_OBJECT : htmlOBJECTend ( elm ) ; break ;
case HTML_OL : htmlOLend ( elm ) ; break ;
case HTML_OPTION : htmlOPTIONend ( elm ) ; break ;
case HTML_P : htmlPend ( elm ) ; break ;
case HTML_PRE : htmlPREend ( elm ) ; break ;
case HTML_SCRIPT : htmlSCRIPTend ( elm ) ; break ;
case HTML_SELECT : htmlSELECTend ( elm ) ; break ;
case HTML_SMALL : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_SPAN : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_STRONG : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_STYLE : htmlSTYLEend ( elm ) ; break ;
case HTML_TABLE : htmlTABLEend ( elm ) ; break ;
case HTML_TD : htmlTDend ( elm ) ; break ;
case HTML_TBODY : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_TEXTAREA : break ;
case HTML_TFOOT : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_TH : htmlTHend ( elm ) ; break ;
case HTML_TITLE : break ;
case HTML_TR : htmlTRend ( elm ) ; break ;
case HTML_U : renderPseudoElement ( " :after " , elm ) ; break ;
case HTML_UL : htmlULend ( elm ) ; break ;
default :
renderPseudoElement ( " :after " , elm ) ;
break ;
}
if ( _Style . Current . DisplayBlock )
{
endParagraph ( ) ;
}
_Style . popStyle ( ) ;
}
// ***************************************************************************
void CGroupHTML : : renderPseudoElement ( const std : : string & pseudo , const CHtmlElement & elm )
{
if ( pseudo ! = " :before " & & pseudo ! = " :after " )
return ;
if ( ! elm . hasPseudo ( pseudo ) )
return ;
_Style . pushStyle ( ) ;
_Style . applyStyle ( elm . getPseudo ( pseudo ) ) ;
// TODO: 'content' should already be tokenized in css parser as it has all the functions for that
std : : string content = trim ( _Style . getStyle ( " content " ) ) ;
if ( toLowerAscii ( content ) = = " none " | | toLowerAscii ( content ) = = " normal " )
{
_Style . popStyle ( ) ;
return ;
}
std : : string : : size_type pos = 0 ;
// TODO: tokenize by whitespace
while ( pos < content . size ( ) )
{
std : : string : : size_type start ;
std : : string token ;
// not supported
// counter, open-quote, close-quote, no-open-quote, no-close-quote
if ( content [ pos ] = = ' " ' | | content [ pos ] = = ' \' ' )
{
char quote = content [ pos ] ;
pos + + ;
start = pos ;
while ( pos < content . size ( ) & & content [ pos ] ! = quote )
{
if ( content [ pos ] = = ' \\ ' ) pos + + ;
pos + + ;
}
token = content . substr ( start , pos - start ) ;
addString ( token ) ;
// skip closing quote
pos + + ;
}
else if ( content [ pos ] = = ' u ' & & pos < content . size ( ) - 6 & & toLowerAscii ( content . substr ( pos , 4 ) ) = = " url( " )
{
// url(/path-to/image.jpg) / "Alt!"
// url("/path to/image.jpg") / "Alt!"
std : : string tooltip ;
start = pos + 4 ;
// fails if url contains ')'
pos = content . find ( " ) " , start ) ;
if ( pos = = std : : string : : npos )
break ;
token = trim ( content . substr ( start , pos - start ) ) ;
// skip ')'
pos + + ;
// scan for tooltip
start = pos ;
while ( pos < content . size ( ) & & content [ pos ] = = ' ' & & content [ pos ] ! = ' / ' )
{
pos + + ;
}
if ( pos < content . size ( ) & & content [ pos ] = = ' / ' )
{
// skip '/'
pos + + ;
// skip whitespace
while ( pos < content . size ( ) & & content [ pos ] = = ' ' )
{
pos + + ;
}
if ( pos < content . size ( ) & & ( content [ pos ] = = ' \' ' | | content [ pos ] = = ' " ' ) )
{
char openQuote = content [ pos ] ;
pos + + ;
start = pos ;
while ( pos < content . size ( ) & & content [ pos ] ! = openQuote )
{
if ( content [ pos ] = = ' \\ ' ) pos + + ;
pos + + ;
}
tooltip = content . substr ( start , pos - start ) ;
// skip closing quote
pos + + ;
}
else
{
// tooltip should be quoted
pos = start ;
tooltip . clear ( ) ;
}
}
else
{
// no tooltip
pos = start ;
}
if ( tooltip . empty ( ) )
{
addImage ( getId ( ) + pseudo , token , false , _Style . Current ) ;
}
else
{
tooltip = trimQuotes ( tooltip ) ;
addButton ( CCtrlButton : : PushButton , getId ( ) + pseudo , token , token , " " , " " , " " , tooltip . c_str ( ) , _Style . Current ) ;
}
}
else if ( content [ pos ] = = ' a ' & & pos < content . size ( ) - 7 )
{
// attr(title)
start = pos + 5 ;
std : : string : : size_type end = 0 ;
end = content . find ( " ) " , start ) ;
if ( end ! = std : : string : : npos )
{
token = content . substr ( start , end - start ) ;
// skip ')'
pos = end + 1 ;
if ( elm . hasAttribute ( token ) )
{
addString ( elm . getAttribute ( token ) ) ;
}
}
else
{
// skip over 'a'
pos + + ;
}
}
else
{
pos + + ;
}
}
_Style . popStyle ( ) ;
}
// ***************************************************************************
void CGroupHTML : : renderDOM ( CHtmlElement & elm )
{
if ( elm . Type = = CHtmlElement : : TEXT_NODE )
{
addText ( elm . Value . c_str ( ) , elm . Value . size ( ) ) ;
}
else
{
beginElement ( elm ) ;
if ( ! _IgnoreChildElements )
{
std : : list < CHtmlElement > : : iterator it = elm . Children . begin ( ) ;
while ( it ! = elm . Children . end ( ) )
{
renderDOM ( * it ) ;
+ + it ;
}
}
_IgnoreChildElements = false ;
endElement ( elm ) ;
}
}
// ***************************************************************************
NLMISC_REGISTER_OBJECT ( CViewBase , CGroupHTML , std : : string , " html " ) ;
// ***************************************************************************
uint32 CGroupHTML : : _GroupHtmlUIDPool = 0 ;
CGroupHTML : : TGroupHtmlByUIDMap CGroupHTML : : _GroupHtmlByUID ;
// ***************************************************************************
CGroupHTML : : CGroupHTML ( const TCtorParam & param )
: CGroupScrollText ( param ) ,
_TimeoutValue ( DEFAULT_RYZOM_CONNECTION_TIMEOUT ) ,
_RedirectsRemaining ( DEFAULT_RYZOM_REDIRECT_LIMIT ) ,
_CurrentHTMLElement ( NULL )
{
// add it to map of group html created
_GroupHtmlUID = + + _GroupHtmlUIDPool ; // valid assigned Id begin to 1!
_GroupHtmlByUID [ _GroupHtmlUID ] = this ;
// init
_TrustedDomain = false ;
_ParsingLua = false ;
_LuaHrefHack = false ;
_IgnoreText = false ;
_IgnoreChildElements = false ;
_BrowseNextTime = false ;
_PostNextTime = false ;
_Browsing = false ;
_CurrentViewLink = NULL ;
_CurrentViewImage = NULL ;
_Indent . clear ( ) ;
_LI = false ;
_SelectOption = false ;
_GroupListAdaptor = NULL ;
_UrlFragment . clear ( ) ;
_RefreshUrl . clear ( ) ;
_NextRefreshTime = 0.0 ;
_LastRefreshTime = 0.0 ;
_RenderNextTime = false ;
_WaitingForStylesheet = false ;
_AutoIdSeq = 0 ;
_FormOpen = false ;
// Register
CWidgetManager : : getInstance ( ) - > registerClockMsgTarget ( this ) ;
// HTML parameters
ErrorColor = CRGBA ( 255 , 0 , 0 ) ;
LinkColor = CRGBA ( 0 , 0 , 255 ) ;
ErrorColorGlobalColor = false ;
LinkColorGlobalColor = false ;
TextColorGlobalColor = false ;
LIBeginSpace = 4 ;
ULBeginSpace = 12 ;
PBeginSpace = 12 ;
TDBeginSpace = 0 ;
ULIndent = 30 ;
LineSpaceFontFactor = 0.5f ;
DefaultButtonGroup = " html_text_button " ;
DefaultFormTextGroup = " edit_box_widget " ;
DefaultFormTextAreaGroup = " edit_box_widget_multiline " ;
DefaultFormSelectGroup = " html_form_select_widget " ;
DefaultFormSelectBoxMenuGroup = " html_form_select_box_menu_widget " ;
DefaultCheckBoxBitmapNormal = " checkbox_normal.tga " ;
DefaultCheckBoxBitmapPushed = " checkbox_pushed.tga " ;
DefaultCheckBoxBitmapOver = " checkbox_over.tga " ;
DefaultRadioButtonBitmapNormal = " w_radiobutton.png " ;
DefaultRadioButtonBitmapPushed = " w_radiobutton_pushed.png " ;
DefaultBackgroundBitmapView = " bg " ;
clearContext ( ) ;
MultiCurl = curl_multi_init ( ) ;
# ifdef CURLMOPT_MAX_HOST_CONNECTIONS
if ( MultiCurl )
{
// added in libcurl 7.30.0
curl_multi_setopt ( MultiCurl , CURLMOPT_MAX_HOST_CONNECTIONS , options . curlMaxConnections ) ;
curl_multi_setopt ( MultiCurl , CURLMOPT_PIPELINING , 1 ) ;
}
# endif
RunningCurls = 0 ;
_CurlWWW = NULL ;
initImageDownload ( ) ;
initBnpDownload ( ) ;
// setup default browser style
setProperty ( " browser_css_file " , " browser.css " ) ;
}
// ***************************************************************************
CGroupHTML : : ~ CGroupHTML ( )
{
//releaseImageDownload();
// TestYoyo
//nlinfo("** CGroupHTML Destroy: %x, %s, uid%d", this, _Id.c_str(), _GroupHtmlUID);
/* Erase from map of Group HTML (thus requestTerminated() callback won't be called)
Do it first , just because don ' t want requestTerminated ( ) to be called while I ' m destroying
( useless and may be dangerous )
*/
_GroupHtmlByUID . erase ( _GroupHtmlUID ) ;
clearContext ( ) ;
releaseDownloads ( ) ;
if ( _CurlWWW )
delete _CurlWWW ;
if ( MultiCurl )
curl_multi_cleanup ( MultiCurl ) ;
}
std : : string CGroupHTML : : getProperty ( const std : : string & name ) const
{
if ( name = = " url " )
{
return _URL ;
}
else
if ( name = = " title_prefix " )
{
return _TitlePrefix ;
}
else
if ( name = = " error_color " )
{
return toString ( ErrorColor ) ;
}
else
if ( name = = " link_color " )
{
return toString ( LinkColor ) ;
}
else
if ( name = = " error_color_global_color " )
{
return toString ( ErrorColorGlobalColor ) ;
}
else
if ( name = = " link_color_global_color " )
{
return toString ( LinkColorGlobalColor ) ;
}
else
if ( name = = " text_color_global_color " )
{
return toString ( TextColorGlobalColor ) ;
}
else
if ( name = = " td_begin_space " )
{
return toString ( TDBeginSpace ) ;
}
else
if ( name = = " paragraph_begin_space " )
{
return toString ( PBeginSpace ) ;
}
else
if ( name = = " li_begin_space " )
{
return toString ( LIBeginSpace ) ;
}
else
if ( name = = " ul_begin_space " )
{
return toString ( ULBeginSpace ) ;
}
else
if ( name = = " ul_indent " )
{
return toString ( ULIndent ) ;
}
else
if ( name = = " multi_line_space_factor " )
{
return toString ( LineSpaceFontFactor ) ;
}
else
if ( name = = " form_text_area_group " )
{
return DefaultFormTextGroup ;
}
else
if ( name = = " form_select_group " )
{
return DefaultFormSelectGroup ;
}
else
if ( name = = " checkbox_bitmap_normal " )
{
return DefaultCheckBoxBitmapNormal ;
}
else
if ( name = = " checkbox_bitmap_pushed " )
{
return DefaultCheckBoxBitmapPushed ;
}
else
if ( name = = " checkbox_bitmap_over " )
{
return DefaultCheckBoxBitmapOver ;
}
else
if ( name = = " radiobutton_bitmap_normal " )
{
return DefaultRadioButtonBitmapNormal ;
}
else
if ( name = = " radiobutton_bitmap_pushed " )
{
return DefaultRadioButtonBitmapPushed ;
}
else
if ( name = = " radiobutton_bitmap_over " )
{
return DefaultRadioButtonBitmapOver ;
}
else
if ( name = = " background_bitmap_view " )
{
return DefaultBackgroundBitmapView ;
}
else
if ( name = = " home " )
{
return Home ;
}
else
if ( name = = " browse_next_time " )
{
return toString ( _BrowseNextTime ) ;
}
else
if ( name = = " browse_tree " )
{
return _BrowseTree ;
}
else
if ( name = = " browse_undo " )
{
return _BrowseUndoButton ;
}
else
if ( name = = " browse_redo " )
{
return _BrowseRedoButton ;
}
else
if ( name = = " browse_refresh " )
{
return _BrowseRefreshButton ;
}
else
if ( name = = " timeout " )
{
return toString ( _TimeoutValue ) ;
}
else
if ( name = = " browser_css_file " )
{
return _BrowserCssFile ;
}
else
return CGroupScrollText : : getProperty ( name ) ;
}
void CGroupHTML : : setProperty ( const std : : string & name , const std : : string & value )
{
if ( name = = " url " )
{
_URL = value ;
return ;
}
else
if ( name = = " title_prefix " )
{
_TitlePrefix = value ;
return ;
}
else
if ( name = = " error_color " )
{
CRGBA c ;
if ( fromString ( value , c ) )
ErrorColor = c ;
return ;
}
else
if ( name = = " link_color " )
{
CRGBA c ;
if ( fromString ( value , c ) )
LinkColor = c ;
return ;
}
else
if ( name = = " error_color_global_color " )
{
bool b ;
if ( fromString ( value , b ) )
ErrorColorGlobalColor = b ;
return ;
}
else
if ( name = = " link_color_global_color " )
{
bool b ;
if ( fromString ( value , b ) )
LinkColorGlobalColor = b ;
return ;
}
else
if ( name = = " text_color_global_color " )
{
bool b ;
if ( fromString ( value , b ) )
TextColorGlobalColor = b ;
return ;
}
else
if ( name = = " td_begin_space " )
{
uint i ;
if ( fromString ( value , i ) )
TDBeginSpace = i ;
return ;
}
else
if ( name = = " paragraph_begin_space " )
{
uint i ;
if ( fromString ( value , i ) )
PBeginSpace = i ;
return ;
}
else
if ( name = = " li_begin_space " )
{
uint i ;
if ( fromString ( value , i ) )
LIBeginSpace = i ;
return ;
}
else
if ( name = = " ul_begin_space " )
{
uint i ;
if ( fromString ( value , i ) )
ULBeginSpace = i ;
return ;
}
else
if ( name = = " ul_indent " )
{
uint i ;
if ( fromString ( value , i ) )
ULIndent = i ;
return ;
}
else
if ( name = = " multi_line_space_factor " )
{
float f ;
if ( fromString ( value , f ) )
LineSpaceFontFactor = f ;
return ;
}
else
if ( name = = " form_text_area_group " )
{
DefaultFormTextGroup = value ;
return ;
}
else
if ( name = = " form_select_group " )
{
DefaultFormSelectGroup = value ;
return ;
}
else
if ( name = = " checkbox_bitmap_normal " )
{
DefaultCheckBoxBitmapNormal = value ;
return ;
}
else
if ( name = = " checkbox_bitmap_pushed " )
{
DefaultCheckBoxBitmapPushed = value ;
return ;
}
else
if ( name = = " checkbox_bitmap_over " )
{
DefaultCheckBoxBitmapOver = value ;
return ;
}
else
if ( name = = " radiobutton_bitmap_normal " )
{
DefaultRadioButtonBitmapNormal = value ;
return ;
}
else
if ( name = = " radiobutton_bitmap_pushed " )
{
DefaultRadioButtonBitmapPushed = value ;
return ;
}
else
if ( name = = " radiobutton_bitmap_over " )
{
DefaultRadioButtonBitmapOver = value ;
return ;
}
else
if ( name = = " background_bitmap_view " )
{
DefaultBackgroundBitmapView = value ;
return ;
}
else
if ( name = = " home " )
{
Home = value ;
return ;
}
else
if ( name = = " browse_next_time " )
{
bool b ;
if ( fromString ( value , b ) )
_BrowseNextTime = b ;
return ;
}
else
if ( name = = " browse_tree " )
{
_BrowseTree = value ;
return ;
}
else
if ( name = = " browse_undo " )
{
_BrowseUndoButton = value ;
return ;
}
else
if ( name = = " browse_redo " )
{
_BrowseRedoButton = value ;
return ;
}
else
if ( name = = " browse_refresh " )
{
_BrowseRefreshButton = value ;
return ;
}
else
if ( name = = " timeout " )
{
double d ;
if ( fromString ( value , d ) )
_TimeoutValue = d ;
return ;
}
else
if ( name = = " browser_css_file " )
{
_BrowserStyle . reset ( ) ;
_BrowserCssFile = value ;
if ( ! _BrowserCssFile . empty ( ) )
{
std : : string filename = CPath : : lookup ( _BrowserCssFile , false , true , true ) ;
if ( ! filename . empty ( ) )
{
CIFile in ;
if ( in . open ( filename ) )
{
std : : string css ;
if ( in . readAll ( css ) )
_BrowserStyle . parseStylesheet ( css ) ;
else
nlwarning ( " Failed to read browser css from '%s' " , filename . c_str ( ) ) ;
}
else
{
nlwarning ( " Failed to open browser css file '%s' " , filename . c_str ( ) ) ;
}
}
else
{
nlwarning ( " Browser css file '%s' not found " , _BrowserCssFile . c_str ( ) ) ;
}
}
}
else
CGroupScrollText : : setProperty ( name , value ) ;
}
xmlNodePtr CGroupHTML : : serialize ( xmlNodePtr parentNode , const char * type ) const
{
xmlNodePtr node = CGroupScrollText : : serialize ( parentNode , type ) ;
if ( node = = NULL )
return NULL ;
xmlSetProp ( node , BAD_CAST " type " , BAD_CAST " html " ) ;
xmlSetProp ( node , BAD_CAST " url " , BAD_CAST _URL . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " title_prefix " , BAD_CAST _TitlePrefix . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " error_color " , BAD_CAST toString ( ErrorColor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " link_color " , BAD_CAST toString ( LinkColor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " error_color_global_color " ,
BAD_CAST toString ( ErrorColorGlobalColor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " link_color_global_color " ,
BAD_CAST toString ( LinkColorGlobalColor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " text_color_global_color " ,
BAD_CAST toString ( TextColorGlobalColor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " td_begin_space " , BAD_CAST toString ( TDBeginSpace ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " paragraph_begin_space " , BAD_CAST toString ( PBeginSpace ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " li_begin_space " , BAD_CAST toString ( LIBeginSpace ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " ul_begin_space " , BAD_CAST toString ( ULBeginSpace ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " ul_indent " , BAD_CAST toString ( ULIndent ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " multi_line_space_factor " , BAD_CAST toString ( LineSpaceFontFactor ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " form_text_area_group " , BAD_CAST DefaultFormTextGroup . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " form_select_group " , BAD_CAST DefaultFormSelectGroup . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " checkbox_bitmap_normal " , BAD_CAST DefaultCheckBoxBitmapNormal . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " checkbox_bitmap_pushed " , BAD_CAST DefaultCheckBoxBitmapPushed . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " checkbox_bitmap_over " , BAD_CAST DefaultCheckBoxBitmapOver . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " radiobutton_bitmap_normal " , BAD_CAST DefaultRadioButtonBitmapNormal . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " radiobutton_bitmap_pushed " , BAD_CAST DefaultRadioButtonBitmapPushed . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " radiobutton_bitmap_over " , BAD_CAST DefaultRadioButtonBitmapOver . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " background_bitmap_view " , BAD_CAST DefaultBackgroundBitmapView . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " home " , BAD_CAST Home . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browse_next_time " , BAD_CAST toString ( _BrowseNextTime ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browse_tree " , BAD_CAST _BrowseTree . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browse_undo " , BAD_CAST _BrowseUndoButton . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browse_redo " , BAD_CAST _BrowseRedoButton . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browse_refresh " , BAD_CAST _BrowseRefreshButton . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " timeout " , BAD_CAST toString ( _TimeoutValue ) . c_str ( ) ) ;
xmlSetProp ( node , BAD_CAST " browser_css_file " , BAD_CAST _BrowserCssFile . c_str ( ) ) ;
return node ;
}
// ***************************************************************************
bool CGroupHTML : : parse ( xmlNodePtr cur , CInterfaceGroup * parentGroup )
{
nlassert ( CWidgetManager : : getInstance ( ) - > isClockMsgTarget ( this ) ) ;
if ( ! CGroupScrollText : : parse ( cur , parentGroup ) )
return false ;
// TestYoyo
//nlinfo("** CGroupHTML parsed Ok: %x, %s, %s, uid%d", this, _Id.c_str(), typeid(this).name(), _GroupHtmlUID);
CXMLAutoPtr ptr ;
// Get the url
ptr = xmlGetProp ( cur , ( xmlChar * ) " url " ) ;
if ( ptr )
_URL = ( const char * ) ptr ;
// Bkup default for undo/redo
_AskedUrl = _URL ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " title_prefix " ) ;
if ( ptr )
_TitlePrefix = CI18N : : get ( ( const char * ) ptr ) ;
// Parameters
ptr = xmlGetProp ( cur , ( xmlChar * ) " error_color " ) ;
if ( ptr )
ErrorColor = convertColor ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " link_color " ) ;
if ( ptr )
LinkColor = convertColor ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " error_color_global_color " ) ;
if ( ptr )
ErrorColorGlobalColor = convertBool ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " link_color_global_color " ) ;
if ( ptr )
LinkColorGlobalColor = convertBool ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " text_color_global_color " ) ;
if ( ptr )
TextColorGlobalColor = convertBool ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " td_begin_space " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , TDBeginSpace ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " paragraph_begin_space " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , PBeginSpace ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " li_begin_space " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , LIBeginSpace ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " ul_begin_space " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , ULBeginSpace ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " ul_indent " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , ULIndent ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " multi_line_space_factor " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , LineSpaceFontFactor ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " form_text_group " ) ;
if ( ptr )
DefaultFormTextGroup = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " form_text_area_group " ) ;
if ( ptr )
DefaultFormTextAreaGroup = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " form_select_group " ) ;
if ( ptr )
DefaultFormSelectGroup = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " checkbox_bitmap_normal " ) ;
if ( ptr )
DefaultCheckBoxBitmapNormal = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " checkbox_bitmap_pushed " ) ;
if ( ptr )
DefaultCheckBoxBitmapPushed = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " checkbox_bitmap_over " ) ;
if ( ptr )
DefaultCheckBoxBitmapOver = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " radiobutton_bitmap_normal " ) ;
if ( ptr )
DefaultRadioButtonBitmapNormal = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " radiobutton_bitmap_pushed " ) ;
if ( ptr )
DefaultRadioButtonBitmapPushed = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " radiobutton_bitmap_over " ) ;
if ( ptr )
DefaultRadioButtonBitmapOver = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " background_bitmap_view " ) ;
if ( ptr )
DefaultBackgroundBitmapView = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " home " ) ;
if ( ptr )
Home = ( const char * ) ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browse_next_time " ) ;
if ( ptr )
_BrowseNextTime = convertBool ( ptr ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browse_tree " ) ;
if ( ptr )
_BrowseTree = ( const char * ) ptr ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browse_undo " ) ;
if ( ptr )
_BrowseUndoButton = ( const char * ) ptr ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browse_redo " ) ;
if ( ptr )
_BrowseRedoButton = ( const char * ) ptr ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browse_refresh " ) ;
if ( ptr )
_BrowseRefreshButton = ( const char * ) ptr ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " timeout " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , _TimeoutValue ) ;
ptr = xmlGetProp ( cur , ( xmlChar * ) " browser_css_file " ) ;
if ( ptr )
{
setProperty ( " browser_css_file " , ( const char * ) ptr ) ;
}
return true ;
}
// ***************************************************************************
bool CGroupHTML : : handleEvent ( const NLGUI : : CEventDescriptor & eventDesc )
{
bool traited = false ;
if ( eventDesc . getType ( ) = = NLGUI : : CEventDescriptor : : mouse )
{
const NLGUI : : CEventDescriptorMouse & mouseEvent = ( const NLGUI : : CEventDescriptorMouse & ) eventDesc ;
if ( mouseEvent . getEventTypeExtended ( ) = = NLGUI : : CEventDescriptorMouse : : mousewheel )
{
// Check if mouse wheel event was on any of multiline select box widgets
// Must do this before CGroupScrollText
for ( uint i = 0 ; i < _Forms . size ( ) & & ! traited ; i + + )
{
for ( uint j = 0 ; j < _Forms [ i ] . Entries . size ( ) & & ! traited ; j + + )
{
if ( _Forms [ i ] . Entries [ j ] . SelectBox )
{
if ( _Forms [ i ] . Entries [ j ] . SelectBox - > handleEvent ( eventDesc ) )
{
traited = true ;
break ;
}
}
}
}
}
}
if ( ! traited )
traited = CGroupScrollText : : handleEvent ( eventDesc ) ;
if ( eventDesc . getType ( ) = = NLGUI : : CEventDescriptor : : system )
{
const NLGUI : : CEventDescriptorSystem & systemEvent = ( const NLGUI : : CEventDescriptorSystem & ) eventDesc ;
if ( systemEvent . getEventTypeExtended ( ) = = NLGUI : : CEventDescriptorSystem : : clocktick )
{
// Handle now
handle ( ) ;
}
if ( systemEvent . getEventTypeExtended ( ) = = NLGUI : : CEventDescriptorSystem : : activecalledonparent )
{
if ( ! ( ( NLGUI : : CEventDescriptorActiveCalledOnParent & ) systemEvent ) . getActive ( ) )
{
// stop refresh when window gets hidden
_NextRefreshTime = 0 ;
}
}
}
return traited ;
}
// ***************************************************************************
void CGroupHTML : : endParagraph ( )
{
_Paragraph = NULL ;
paragraphChange ( ) ;
}
// ***************************************************************************
void CGroupHTML : : newParagraph ( uint beginSpace )
{
// Add a new paragraph
CGroupParagraph * newParagraph = new CGroupParagraph ( CViewBase : : TCtorParam ( ) ) ;
newParagraph - > setId ( getCurrentGroup ( ) - > getId ( ) + " :PARAGRAPH " + toString ( getNextAutoIdSeq ( ) ) ) ;
newParagraph - > setResizeFromChildH ( true ) ;
newParagraph - > setMarginLeft ( getIndent ( ) ) ;
if ( ! _Style . Current . TextAlign . empty ( ) )
{
if ( _Style . Current . TextAlign = = " left " )
newParagraph - > setTextAlign ( CGroupParagraph : : AlignLeft ) ;
else if ( _Style . Current . TextAlign = = " center " )
newParagraph - > setTextAlign ( CGroupParagraph : : AlignCenter ) ;
else if ( _Style . Current . TextAlign = = " right " )
newParagraph - > setTextAlign ( CGroupParagraph : : AlignRight ) ;
else if ( _Style . Current . TextAlign = = " justify " )
newParagraph - > setTextAlign ( CGroupParagraph : : AlignJustify ) ;
}
// Add to the group
addHtmlGroup ( newParagraph , beginSpace ) ;
_Paragraph = newParagraph ;
paragraphChange ( ) ;
}
// ***************************************************************************
void CGroupHTML : : browse ( const char * url )
{
// modify undo/redo
pushUrlUndoRedo ( url ) ;
// do the browse, with no undo/redo
doBrowse ( url ) ;
}
// ***************************************************************************
void CGroupHTML : : refresh ( )
{
if ( ! _URL . empty ( ) )
doBrowse ( _URL . c_str ( ) , true ) ;
}
// ***************************************************************************
void CGroupHTML : : doBrowse ( const char * url , bool force )
{
LOG_DL ( " (%s) Browsing URL : '%s' " , _Id . c_str ( ) , url ) ;
CUrlParser uri ( url ) ;
if ( ! uri . hash . empty ( ) )
{
// Anchor to scroll after page has loaded
_UrlFragment = uri . hash ;
uri . inherit ( _DocumentUrl ) ;
uri . hash . clear ( ) ;
// compare urls and see if we only navigating to new anchor
if ( ! force & & _DocumentUrl = = uri . toString ( ) )
{
// scroll happens in updateCoords()
invalidateCoords ( ) ;
return ;
}
}
else
_UrlFragment . clear ( ) ;
// go
_URL = uri . toString ( ) ;
_BrowseNextTime = true ;
_WaitingForStylesheet = false ;
// if a BrowseTree is bound to us, try to select the node that opens this URL (auto-locate)
if ( ! _BrowseTree . empty ( ) )
{
CGroupTree * groupTree = dynamic_cast < CGroupTree * > ( CWidgetManager : : getInstance ( ) - > getElementFromId ( _BrowseTree ) ) ;
if ( groupTree )
{
string nodeId = selectTreeNodeRecurs ( groupTree - > getRootNode ( ) , url ) ;
// select the node
if ( ! nodeId . empty ( ) )
{
groupTree - > selectNodeById ( nodeId ) ;
}
}
}
}
// ***************************************************************************
void CGroupHTML : : browseError ( const char * msg )
{
releaseDownloads ( ) ;
// Get the list group from CGroupScrollText
removeContent ( ) ;
newParagraph ( 0 ) ;
CViewText * viewText = new CViewText ( " " , ( string ( " Error : " ) + msg ) . c_str ( ) ) ;
viewText - > setColor ( ErrorColor ) ;
viewText - > setModulateGlobalColor ( ErrorColorGlobalColor ) ;
viewText - > setMultiLine ( true ) ;
getParagraph ( ) - > addChild ( viewText ) ;
if ( ! _TitlePrefix . empty ( ) )
setTitle ( _TitlePrefix ) ;
updateRefreshButton ( ) ;
invalidateCoords ( ) ;
}
void CGroupHTML : : browseErrorHtml ( const std : : string & html )
{
releaseDownloads ( ) ;
removeContent ( ) ;
renderHtmlString ( html ) ;
updateRefreshButton ( ) ;
invalidateCoords ( ) ;
}
// ***************************************************************************
bool CGroupHTML : : isBrowsing ( )
{
// do not show spinning cursor for image downloads (!Curls.empty())
return _BrowseNextTime | | _PostNextTime | | _RenderNextTime | |
_Browsing | | _WaitingForStylesheet | |
_CurlWWW ;
}
// ***************************************************************************
void CGroupHTML : : updateCoords ( )
{
CGroupScrollText : : updateCoords ( ) ;
// all elements are in their correct place, tell scrollbar to scroll to anchor
if ( ! _Browsing & & ! _UrlFragment . empty ( ) )
{
doBrowseAnchor ( _UrlFragment ) ;
_UrlFragment . clear ( ) ;
}
if ( ! m_HtmlBackground . isEmpty ( ) | | ! m_BodyBackground . isEmpty ( ) )
{
// get scroll offset from list
CGroupList * list = getList ( ) ;
if ( list )
{
CInterfaceElement * vp = list - > getParentPos ( ) ? list - > getParentPos ( ) : this ;
sint htmlW = std : : max ( vp - > getWReal ( ) , list - > getWReal ( ) ) ;
sint htmlH = list - > getHReal ( ) ;
sint htmlX = list - > getXReal ( ) + list - > getOfsX ( ) ;
sint htmlY = list - > getYReal ( ) + list - > getOfsY ( ) ;
if ( ! m_HtmlBackground . isEmpty ( ) )
{
m_HtmlBackground . setFillViewport ( true ) ;
m_HtmlBackground . setBorderArea ( htmlX , htmlY , htmlW , htmlH ) ;
m_HtmlBackground . setPaddingArea ( htmlX , htmlY , htmlW , htmlH ) ;
m_HtmlBackground . setContentArea ( htmlX , htmlY , htmlW , htmlH ) ;
}
if ( ! m_BodyBackground . isEmpty ( ) )
{
// TODO: html padding + html border
m_BodyBackground . setBorderArea ( htmlX , htmlY , htmlW , htmlH ) ;
// TODO: html padding + html border + body border
m_BodyBackground . setPaddingArea ( htmlX , htmlY , htmlW , htmlH ) ;
// TODO: html padding + html_border + body padding
m_BodyBackground . setContentArea ( htmlX , htmlY , htmlW , htmlH ) ;
}
}
}
}
// ***************************************************************************
bool CGroupHTML : : translateChar ( u32char & output , u32char input , u32char lastCharParam ) const
{
// Keep this char ?
bool keep = true ;
// char is between table elements
// TODO: only whitespace is handled, text is added to either TD, or after TABLE (should be before)
bool tableWhitespace = getTable ( ) & & ( _Cells . empty ( ) | | _Cells . back ( ) = = NULL ) ;
switch ( input )
{
// Return / tab only in <PRE> mode
case ' \t ' :
case ' \n ' :
{
if ( tableWhitespace )
{
keep = false ;
}
else
{
// Get the last char
u32char lastChar = lastCharParam ;
if ( lastChar = = 0 )
lastChar = getLastChar ( ) ;
keep = ( ( lastChar ! = ( u32char ) ' ' ) & &
( lastChar ! = 0 ) ) | | getPRE ( ) | | ( _CurrentViewImage & & ( lastChar = = 0 ) ) ;
if ( ! getPRE ( ) )
input = ( u32char ) ' ' ;
}
}
break ;
case ' ' :
{
if ( tableWhitespace )
{
keep = false ;
}
else
{
// Get the last char
u32char lastChar = lastCharParam ;
if ( lastChar = = 0 )
lastChar = getLastChar ( ) ;
keep = ( ( lastChar ! = ( u32char ) ' ' ) & &
( lastChar ! = ( u32char ) ' \n ' ) & &
( lastChar ! = 0 ) ) | | getPRE ( ) | | ( _CurrentViewImage & & ( lastChar = = 0 ) ) ;
}
}
break ;
case 0xd :
keep = false ;
break ;
}
if ( keep )
{
output = input ;
}
return keep ;
}
// ***************************************************************************
void CGroupHTML : : registerAnchor ( CInterfaceElement * elm )
{
if ( ! _AnchorName . empty ( ) )
{
for ( uint32 i = 0 ; i < _AnchorName . size ( ) ; + + i )
{
// filter out duplicates and register only first
if ( ! _AnchorName [ i ] . empty ( ) & & _Anchors . count ( _AnchorName [ i ] ) = = 0 )
{
_Anchors [ _AnchorName [ i ] ] = elm ;
}
}
_AnchorName . clear ( ) ;
}
}
// ***************************************************************************
bool CGroupHTML : : isSameStyle ( CViewLink * text , const CStyleParams & style ) const
{
if ( ! text ) return false ;
bool embolden = style . FontWeight > = FONT_WEIGHT_BOLD ;
bool sameShadow = style . TextShadow . Enabled & & text - > getShadow ( ) ;
if ( sameShadow & & style . TextShadow . Enabled )
{
sint sx , sy ;
text - > getShadowOffset ( sx , sy ) ;
sameShadow = ( style . TextShadow . Color = = text - > getShadowColor ( ) ) ;
sameShadow = sameShadow & & ( style . TextShadow . Outline = = text - > getShadowOutline ( ) ) ;
sameShadow = sameShadow & & ( style . TextShadow . X = = sx ) & & ( style . TextShadow . Y = = sy ) ;
}
// Compatible with current parameters ?
return sameShadow & &
( style . TextColor = = text - > getColor ( ) ) & &
( style . FontFamily = = text - > getFontName ( ) ) & &
( style . FontSize = = ( uint ) text - > getFontSize ( ) ) & &
( style . Underlined = = text - > getUnderlined ( ) ) & &
( style . StrikeThrough = = text - > getStrikeThrough ( ) ) & &
( embolden = = text - > getEmbolden ( ) ) & &
( style . FontOblique = = text - > getOblique ( ) ) & &
( getLink ( ) = = text - > Link ) & &
( style . GlobalColorText = = text - > getModulateGlobalColor ( ) ) ;
}
// ***************************************************************************
void CGroupHTML : : newTextButton ( const std : : string & text , const std : : string & tpl )
{
_CurrentViewLink = NULL ;
_CurrentViewImage = NULL ;
// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
string param = " name= " + this - > _Id + " |url= " + getLink ( ) ;
string name ;
if ( ! _AnchorName . empty ( ) )
name = _AnchorName . back ( ) ;
typedef pair < string , string > TTmplParam ;
vector < TTmplParam > tmplParams ;
tmplParams . push_back ( TTmplParam ( " id " , " " ) ) ;
tmplParams . push_back ( TTmplParam ( " onclick " , " browse " ) ) ;
tmplParams . push_back ( TTmplParam ( " onclick_param " , param ) ) ;
tmplParams . push_back ( TTmplParam ( " active " , " true " ) ) ;
CInterfaceGroup * buttonGroup = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( tpl , getId ( ) + " : " + name , tmplParams ) ;
if ( ! buttonGroup )
{
nlinfo ( " Text button template '%s' not found " , tpl . c_str ( ) ) ;
return ;
}
buttonGroup - > setId ( getId ( ) + " : " + name ) ;
// Add the ctrl button
CCtrlTextButton * ctrlButton = dynamic_cast < CCtrlTextButton * > ( buttonGroup - > getCtrl ( " button " ) ) ;
if ( ! ctrlButton ) ctrlButton = dynamic_cast < CCtrlTextButton * > ( buttonGroup - > getCtrl ( " b " ) ) ;
if ( ! ctrlButton )
{
nlinfo ( " Text button template '%s' is missing :button or :b text element " , tpl . c_str ( ) ) ;
return ;
}
ctrlButton - > setModulateGlobalColorAll ( _Style . Current . GlobalColor ) ;
ctrlButton - > setTextModulateGlobalColorNormal ( _Style . Current . GlobalColorText ) ;
ctrlButton - > setTextModulateGlobalColorOver ( _Style . Current . GlobalColorText ) ;
ctrlButton - > setTextModulateGlobalColorPushed ( _Style . Current . GlobalColorText ) ;
// Translate the tooltip
ctrlButton - > setText ( text ) ;
ctrlButton - > setDefaultContextHelp ( std : : string ( getLinkTitle ( ) ) ) ;
// empty url / button disabled
ctrlButton - > setFrozen ( * getLink ( ) = = ' \0 ' ) ;
setTextButtonStyle ( ctrlButton , _Style . Current ) ;
_Paragraph - > addChild ( buttonGroup ) ;
}
// ***************************************************************************
void CGroupHTML : : newTextLink ( const std : : string & text )
{
CViewLink * newLink = new CViewLink ( CViewBase : : TCtorParam ( ) ) ;
if ( getA ( ) )
{
newLink - > Link = getLink ( ) ;
newLink - > LinkTitle = getLinkTitle ( ) ;
if ( ! newLink - > Link . empty ( ) )
{
newLink - > setHTMLView ( this ) ;
newLink - > setActionOnLeftClick ( " browse " ) ;
newLink - > setParamsOnLeftClick ( " name= " + getId ( ) + " |url= " + newLink - > Link ) ;
}
}
newLink - > setText ( text ) ;
newLink - > setMultiLineSpace ( ( uint ) ( ( float ) ( _Style . Current . FontSize ) * LineSpaceFontFactor ) ) ;
newLink - > setMultiLine ( true ) ;
newLink - > setModulateGlobalColor ( _Style . Current . GlobalColorText ) ;
setTextStyle ( newLink , _Style . Current ) ;
registerAnchor ( newLink ) ;
if ( getA ( ) & & ! newLink - > Link . empty ( ) )
getParagraph ( ) - > addChildLink ( newLink ) ;
else
getParagraph ( ) - > addChild ( newLink ) ;
_CurrentViewLink = newLink ;
_CurrentViewImage = NULL ;
}
// ***************************************************************************
void CGroupHTML : : addString ( const std : : string & str )
{
string tmpStr = str ;
if ( _Localize )
{
string _str = tmpStr ;
string : : size_type p = _str . find ( ' # ' ) ;
if ( p = = string : : npos )
{
tmpStr = CI18N : : get ( _str ) ;
}
else
{
string cmd = _str . substr ( 0 , p ) ;
string arg = _str . substr ( p + 1 ) ;
if ( cmd = = " date " )
{
uint year , month , day ;
sscanf ( arg . c_str ( ) , " %d/%d/%d " , & year , & month , & day ) ;
tmpStr = CI18N : : get ( " uiMFIDate " ) ;
year + = ( year > 70 ? 1900 : 2000 ) ;
strFindReplace ( tmpStr , " %year " , toString ( " %d " , year ) ) ;
strFindReplace ( tmpStr , " %month " , CI18N : : get ( toString ( " uiMonth%02d " , month ) ) ) ;
strFindReplace ( tmpStr , " %day " , toString ( " %d " , day ) ) ;
}
else
{
tmpStr = arg ;
}
}
}
// In title ?
if ( _Object )
{
_ObjectScript + = tmpStr ;
}
else if ( _SelectOption )
{
if ( ! ( _Forms . empty ( ) ) )
{
if ( ! _Forms . back ( ) . Entries . empty ( ) )
{
_SelectOptionStr + = tmpStr ;
}
}
}
else
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
CStyleParams & style = _Style . Current ;
// Text added ?
bool added = false ;
if ( _CurrentViewLink )
{
bool skipLine = ! _CurrentViewLink - > getText ( ) . empty ( ) & & * ( _CurrentViewLink - > getText ( ) . rbegin ( ) ) = = ' \n ' ;
if ( ! skipLine & & isSameStyle ( _CurrentViewLink , style ) )
{
// Concat the text
_CurrentViewLink - > setText ( _CurrentViewLink - > getText ( ) + tmpStr ) ;
_CurrentViewLink - > invalidateContent ( ) ;
added = true ;
}
}
// Not added ?
if ( ! added )
{
if ( getA ( ) & & string ( getLinkClass ( ) ) = = " ryzom-ui-button " )
newTextButton ( tmpStr , DefaultButtonGroup ) ;
else
newTextLink ( tmpStr ) ;
}
}
}
// ***************************************************************************
void CGroupHTML : : addImage ( const std : : string & id , const std : : string & img , bool reloadImg , const CStyleParams & style )
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
// No more text in this text view
_CurrentViewLink = NULL ;
// Not added ?
CViewBitmap * newImage = new CViewBitmap ( TCtorParam ( ) ) ;
newImage - > setId ( id ) ;
addImageDownload ( img , newImage , style , NormalImage ) ;
newImage - > setRenderLayer ( getRenderLayer ( ) + 1 ) ;
getParagraph ( ) - > addChild ( newImage ) ;
paragraphChange ( ) ;
setImageSize ( newImage , style ) ;
}
// ***************************************************************************
CInterfaceGroup * CGroupHTML : : addTextArea ( const std : : string & templateName , const char * name , uint rows , uint cols , bool multiLine , const std : : string & content , uint maxlength )
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
// No more text in this text view
_CurrentViewLink = NULL ;
CStyleParams & style = _Style . Current ;
{
// override cols/rows values from style
if ( style . Width > 0 ) cols = style . Width / style . FontSize ;
if ( style . Height > 0 ) rows = style . Height / style . FontSize ;
// Not added ?
std : : vector < std : : pair < std : : string , std : : string > > templateParams ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " w " , toString ( cols * style . FontSize ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " id " , name ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " prompt " , " " ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " multiline " , multiLine ? " true " : " false " ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " fontsize " , toString ( style . FontSize ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " color " , style . TextColor . toString ( ) ) ) ;
if ( style . FontWeight > = FONT_WEIGHT_BOLD )
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " fontweight " , " bold " ) ) ;
if ( style . FontOblique )
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " fontstyle " , " oblique " ) ) ;
if ( multiLine )
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " multi_min_line " , toString ( rows ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " want_return " , multiLine ? " true " : " false " ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " onenter " , " " ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " enter_recover_focus " , " false " ) ) ;
if ( maxlength > 0 )
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " max_num_chars " , toString ( maxlength ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " shadow " , toString ( style . TextShadow . Enabled ) ) ) ;
if ( style . TextShadow . Enabled )
{
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " shadow_x " , toString ( style . TextShadow . X ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " shadow_y " , toString ( style . TextShadow . Y ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " shadow_color " , style . TextShadow . Color . toString ( ) ) ) ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " shadow_outline " , toString ( style . TextShadow . Outline ) ) ) ;
}
CInterfaceGroup * textArea = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( templateName . c_str ( ) ,
getParagraph ( ) - > getId ( ) , templateParams . empty ( ) ? NULL : & ( templateParams [ 0 ] ) , ( uint ) templateParams . size ( ) ) ;
// Group created ?
if ( textArea )
{
// Set the content
CGroupEditBox * eb = dynamic_cast < CGroupEditBox * > ( textArea - > getGroup ( " eb " ) ) ;
if ( eb )
{
eb - > setInputString ( decodeHTMLEntities ( content ) ) ;
if ( style . hasStyle ( " background-color " ) )
{
CViewBitmap * bg = dynamic_cast < CViewBitmap * > ( eb - > getView ( " bg " ) ) ;
if ( bg )
{
bg - > setTexture ( " blank.tga " ) ;
bg - > setColor ( style . Background . color ) ;
}
}
}
textArea - > invalidateCoords ( ) ;
getParagraph ( ) - > addChild ( textArea ) ;
paragraphChange ( ) ;
return textArea ;
}
}
// Not group created
return NULL ;
}
// ***************************************************************************
CDBGroupComboBox * CGroupHTML : : addComboBox ( const std : : string & templateName , const char * name )
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
{
// Not added ?
std : : vector < std : : pair < std : : string , std : : string > > templateParams ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " id " , name ) ) ;
CInterfaceGroup * group = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( templateName . c_str ( ) ,
getParagraph ( ) - > getId ( ) , templateParams . empty ( ) ? NULL : & ( templateParams [ 0 ] ) , ( uint ) templateParams . size ( ) ) ;
// Group created ?
if ( group )
{
// Set the content
CDBGroupComboBox * cb = dynamic_cast < CDBGroupComboBox * > ( group ) ;
if ( ! cb )
{
nlwarning ( " '%s' template has bad type, combo box expected " , templateName . c_str ( ) ) ;
delete cb ;
return NULL ;
}
else
{
getParagraph ( ) - > addChild ( cb ) ;
paragraphChange ( ) ;
return cb ;
}
}
}
// Not group created
return NULL ;
}
// ***************************************************************************
CGroupMenu * CGroupHTML : : addSelectBox ( const std : : string & templateName , const char * name )
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
// Not added ?
std : : vector < std : : pair < std : : string , std : : string > > templateParams ;
templateParams . push_back ( std : : pair < std : : string , std : : string > ( " id " , name ) ) ;
CInterfaceGroup * group = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( templateName . c_str ( ) ,
getParagraph ( ) - > getId ( ) , & ( templateParams [ 0 ] ) , ( uint ) templateParams . size ( ) ) ;
// Group created ?
if ( group )
{
// Set the content
CGroupMenu * sb = dynamic_cast < CGroupMenu * > ( group ) ;
if ( ! sb )
{
nlwarning ( " '%s' template has bad type, CGroupMenu expected " , templateName . c_str ( ) ) ;
delete sb ;
return NULL ;
}
else
{
getParagraph ( ) - > addChild ( sb ) ;
paragraphChange ( ) ;
return sb ;
}
}
// No group created
return NULL ;
}
// ***************************************************************************
CCtrlButton * CGroupHTML : : addButton ( CCtrlButton : : EType type , const std : : string & name , const std : : string & normalBitmap , const std : : string & pushedBitmap ,
const std : : string & overBitmap , const char * actionHandler , const char * actionHandlerParams ,
const std : : string & tooltip , const CStyleParams & style )
{
// In a paragraph ?
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
// Add the ctrl button
CCtrlButton * ctrlButton = new CCtrlButton ( TCtorParam ( ) ) ;
if ( ! name . empty ( ) )
{
ctrlButton - > setId ( name ) ;
}
std : : string normal ;
if ( startsWith ( normalBitmap , " data:image/ " ) )
{
normal = decodeURIComponent ( normalBitmap ) ;
}
else
{
// Load only tga files.. (conversion in dds filename is done in the lookup procedure)
normal = normalBitmap . empty ( ) ? " " : CFile : : getPath ( normalBitmap ) + CFile : : getFilenameWithoutExtension ( normalBitmap ) + " .tga " ;
// if the image doesn't exist on local, we check in the cache
if ( ! CPath : : exists ( normal ) )
{
// search in the compressed texture
CViewRenderer & rVR = * CViewRenderer : : getInstance ( ) ;
sint32 id = rVR . getTextureIdFromName ( normal ) ;
if ( id = = - 1 )
{
normal = localImageName ( normalBitmap ) ;
addImageDownload ( normalBitmap , ctrlButton , style ) ;
}
}
}
std : : string pushed ;
if ( startsWith ( pushedBitmap , " data:image/ " ) )
{
pushed = decodeURIComponent ( pushedBitmap ) ;
}
else
{
pushed = pushedBitmap . empty ( ) ? " " : CFile : : getPath ( pushedBitmap ) + CFile : : getFilenameWithoutExtension ( pushedBitmap ) + " .tga " ;
// if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it
if ( ! CPath : : exists ( pushed ) )
{
// search in the compressed texture
CViewRenderer & rVR = * CViewRenderer : : getInstance ( ) ;
sint32 id = rVR . getTextureIdFromName ( pushed ) ;
if ( id = = - 1 )
{
pushed = localImageName ( pushedBitmap ) ;
}
}
}
std : : string over ;
if ( startsWith ( overBitmap , " data:image/ " ) )
{
over = decodeURIComponent ( overBitmap ) ;
}
else
{
over = overBitmap . empty ( ) ? " " : CFile : : getPath ( overBitmap ) + CFile : : getFilenameWithoutExtension ( overBitmap ) + " .tga " ;
// schedule mouseover bitmap for download if its different from normal
if ( ! over . empty ( ) & & ! CPath : : exists ( over ) )
{
if ( overBitmap ! = normalBitmap )
{
over = localImageName ( overBitmap ) ;
addImageDownload ( overBitmap , ctrlButton , style , OverImage ) ;
}
}
}
ctrlButton - > setType ( type ) ;
if ( ! normal . empty ( ) )
ctrlButton - > setTexture ( normal ) ;
if ( ! pushed . empty ( ) )
ctrlButton - > setTexturePushed ( pushed ) ;
if ( ! over . empty ( ) )
ctrlButton - > setTextureOver ( over ) ;
ctrlButton - > setModulateGlobalColorAll ( style . GlobalColor ) ;
ctrlButton - > setActionOnLeftClick ( actionHandler ) ;
ctrlButton - > setParamsOnLeftClick ( actionHandlerParams ) ;
// Translate the tooltip or display raw text (tooltip from webig)
if ( ! tooltip . empty ( ) )
{
if ( CI18N : : hasTranslation ( tooltip ) )
{
ctrlButton - > setDefaultContextHelp ( CI18N : : get ( tooltip ) ) ;
//ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
}
else
{
ctrlButton - > setDefaultContextHelp ( tooltip ) ;
//ctrlButton->setOnContextHelp(string(tooltip));
}
ctrlButton - > setInstantContextHelp ( true ) ;
ctrlButton - > setToolTipParent ( TTMouse ) ;
ctrlButton - > setToolTipParentPosRef ( Hotspot_TTAuto ) ;
ctrlButton - > setToolTipPosRef ( Hotspot_TTAuto ) ;
}
getParagraph ( ) - > addChild ( ctrlButton ) ;
paragraphChange ( ) ;
setImageSize ( ctrlButton , style ) ;
return ctrlButton ;
}
// ***************************************************************************
void CGroupHTML : : flushString ( )
{
_CurrentViewLink = NULL ;
}
// ***************************************************************************
void CGroupHTML : : clearContext ( )
{
_Paragraph = NULL ;
_PRE . clear ( ) ;
_Indent . clear ( ) ;
_LI = false ;
_UL . clear ( ) ;
_DL . clear ( ) ;
_A . clear ( ) ;
_Link . clear ( ) ;
_LinkTitle . clear ( ) ;
_Tables . clear ( ) ;
_Cells . clear ( ) ;
_TR . clear ( ) ;
_Forms . clear ( ) ;
_FormOpen = false ;
_FormSubmit . clear ( ) ;
_Groups . clear ( ) ;
_Divs . clear ( ) ;
_Anchors . clear ( ) ;
_AnchorName . clear ( ) ;
_CellParams . clear ( ) ;
_Object = false ;
_Localize = false ;
_ReadingHeadTag = false ;
_IgnoreHeadTag = false ;
_IgnoreBaseUrlTag = false ;
_AutoIdSeq = 0 ;
m_TableRowBackgroundColor . clear ( ) ;
paragraphChange ( ) ;
releaseDataDownloads ( ) ;
}
// ***************************************************************************
u32char CGroupHTML : : getLastChar ( ) const
{
if ( _CurrentViewLink )
{
: : u32string str = CUtfStringView ( _CurrentViewLink - > getText ( ) ) . toUtf32 ( ) ; // FIXME: Optimize reverse UTF iteration
if ( ! str . empty ( ) )
return str [ str . length ( ) - 1 ] ;
}
return 0 ;
}
// ***************************************************************************
void CGroupHTML : : paragraphChange ( )
{
_CurrentViewLink = NULL ;
_CurrentViewImage = NULL ;
CGroupParagraph * paragraph = getParagraph ( ) ;
if ( paragraph )
{
// Number of child in this paragraph
uint numChild = paragraph - > getNumChildren ( ) ;
if ( numChild )
{
// Get the last child
CViewBase * child = paragraph - > getChild ( numChild - 1 ) ;
// Is this a string view ?
_CurrentViewLink = dynamic_cast < CViewLink * > ( child ) ;
_CurrentViewImage = dynamic_cast < CViewBitmap * > ( child ) ;
}
}
}
// ***************************************************************************
CInterfaceGroup * CGroupHTML : : getCurrentGroup ( )
{
if ( ! _Cells . empty ( ) & & _Cells . back ( ) )
return _Cells . back ( ) - > Group ;
else
return _GroupListAdaptor ;
}
// ***************************************************************************
void CGroupHTML : : addHtmlGroup ( CInterfaceGroup * group , uint beginSpace )
{
if ( ! group )
return ;
registerAnchor ( group ) ;
if ( ! _DivName . empty ( ) )
{
group - > setName ( _DivName ) ;
_Groups . push_back ( group ) ;
}
group - > setSizeRef ( CInterfaceElement : : width ) ;
// Compute begin space between paragraph and tables
// * If first in group, no begin space
// Pointer on the current paragraph (can be a table too)
CGroupParagraph * p = dynamic_cast < CGroupParagraph * > ( group ) ;
CInterfaceGroup * parentGroup = CGroupHTML : : getCurrentGroup ( ) ;
const std : : vector < CInterfaceGroup * > & groups = parentGroup - > getGroups ( ) ;
group - > setParent ( parentGroup ) ;
group - > setParentSize ( parentGroup ) ;
if ( groups . empty ( ) )
{
group - > setParentPos ( parentGroup ) ;
group - > setPosRef ( Hotspot_TL ) ;
group - > setParentPosRef ( Hotspot_TL ) ;
beginSpace = 0 ;
}
else
{
// Last is a paragraph ?
group - > setParentPos ( groups . back ( ) ) ;
group - > setPosRef ( Hotspot_TL ) ;
group - > setParentPosRef ( Hotspot_BL ) ;
}
// Set the begin space
if ( p )
p - > setTopSpace ( beginSpace ) ;
else
group - > setY ( - ( sint32 ) beginSpace ) ;
parentGroup - > addGroup ( group ) ;
}
// ***************************************************************************
void CGroupHTML : : setContainerTitle ( const std : : string & title )
{
CInterfaceElement * parent = getParent ( ) ;
if ( parent )
{
if ( ( parent = parent - > getParent ( ) ) )
{
CGroupContainer * container = dynamic_cast < CGroupContainer * > ( parent ) ;
if ( container )
{
container - > setTitle ( title ) ;
}
}
}
}
void CGroupHTML : : setTitle ( const std : : string & title )
{
if ( _TitlePrefix . empty ( ) )
_TitleString = title ;
else
_TitleString = _TitlePrefix + " - " + title ;
setContainerTitle ( _TitleString ) ;
}
std : : string CGroupHTML : : getTitle ( ) const {
return _TitleString ;
} ;
// ***************************************************************************
bool CGroupHTML : : lookupLocalFile ( string & result , const char * url , bool isUrl )
{
result = url ;
string tmp ;
if ( toLowerAscii ( result ) . find ( " file: " ) = = 0 & & result . size ( ) > 5 )
{
result = result . substr ( 5 , result . size ( ) - 5 ) ;
}
else if ( result . find ( " :// " ) ! = string : : npos | | result . find ( " // " ) = = 0 )
{
// http://, https://, etc or protocol-less url "//domain.com/image.png"
return false ;
}
tmp = CPath : : lookup ( CFile : : getFilename ( result ) , false , false , false ) ;
if ( tmp . empty ( ) )
{
// try to find in local directory
tmp = CPath : : lookup ( result , false , false , true ) ;
}
if ( ! tmp . empty ( ) )
{
// Normalize the path
if ( isUrl )
//result = "file:"+toLowerAscii(CPath::standardizePath (CPath::getFullPath (CFile::getPath(result)))+CFile::getFilename(result));*/
result = " file:/ " + tmp ;
else
result = tmp ;
return true ;
}
else
{
// Is it a texture in the big texture ?
if ( CViewRenderer : : getInstance ( ) - > getTextureIdFromName ( result ) > = 0 )
{
return true ;
}
else
{
// This is not a file in the CPath, let libwww open this URL
result = url ;
return false ;
}
}
}
// ***************************************************************************
void CGroupHTML : : submitForm ( uint button , sint32 x , sint32 y )
{
if ( button > = _FormSubmit . size ( ) )
return ;
for ( uint formId = 0 ; formId < _Forms . size ( ) ; formId + + )
{
// case sensitive search (user id is lowecase, auto id is uppercase)
if ( _Forms [ formId ] . id = = _FormSubmit [ button ] . form )
{
_PostNextTime = true ;
_PostFormId = formId ;
_PostFormAction = _FormSubmit [ button ] . formAction ;
_PostFormSubmitType = _FormSubmit [ button ] . type ;
_PostFormSubmitButton = _FormSubmit [ button ] . name ;
_PostFormSubmitValue = _FormSubmit [ button ] . value ;
_PostFormSubmitX = x ;
_PostFormSubmitY = y ;
return ;
}
}
nlwarning ( " Unable to find form '%s' to submit (button '%s') " , _FormSubmit [ button ] . form . c_str ( ) , _FormSubmit [ button ] . name . c_str ( ) ) ;
}
// ***************************************************************************
void CGroupHTML : : setupBackground ( CSSBackgroundRenderer * bg )
{
if ( ! bg ) return ;
bg - > setModulateGlobalColor ( _Style . Current . GlobalColor ) ;
bg - > setBackground ( _Style . Current . Background ) ;
bg - > setFontSize ( _Style . Root . FontSize , _Style . Current . FontSize ) ;
bg - > setViewport ( getList ( ) - > getParentPos ( ) ? getList ( ) - > getParentPos ( ) : this ) ;
if ( ! _Style . Current . Background . image . empty ( ) )
addTextureDownload ( _Style . Current . Background . image , bg - > TextureId , this ) ;
}
// ***************************************************************************
void CGroupHTML : : setBackgroundColor ( const CRGBA & bgcolor )
{
// TODO: DefaultBackgroundBitmapView should be removed from interface xml
CViewBase * view = getView ( DefaultBackgroundBitmapView ) ;
if ( view )
view - > setActive ( false ) ;
m_HtmlBackground . setColor ( bgcolor ) ;
}
// ***************************************************************************
void CGroupHTML : : setBackground ( const string & bgtex , bool scale , bool tile )
{
// TODO: DefaultBackgroundBitmapView should be removed from interface xml
CViewBase * view = getView ( DefaultBackgroundBitmapView ) ;
if ( view )
view - > setActive ( false ) ;
m_HtmlBackground . setImage ( bgtex ) ;
m_HtmlBackground . setImageRepeat ( tile ) ;
m_HtmlBackground . setImageCover ( scale ) ;
if ( ! bgtex . empty ( ) )
addTextureDownload ( bgtex , m_HtmlBackground . TextureId , this ) ;
}
struct CButtonFreezer : public CInterfaceElementVisitor
{
virtual void visitCtrl ( CCtrlBase * ctrl )
{
CCtrlBaseButton * textButt = dynamic_cast < CCtrlTextButton * > ( ctrl ) ;
if ( textButt )
{
textButt - > setFrozen ( true ) ;
}
}
} ;
// ***************************************************************************
void CGroupHTML : : handle ( )
{
H_AUTO ( RZ_Interface_Html_handle )
const CWidgetManager : : SInterfaceTimes & times = CWidgetManager : : getInstance ( ) - > getInterfaceTimes ( ) ;
// handle curl downloads
checkDownloads ( ) ;
// handle refresh timer
if ( _NextRefreshTime > 0 & & _NextRefreshTime < = ( times . thisFrameMs / 1000.0f ) )
{
// there might be valid uses for 0sec refresh, but two in a row is probably a mistake
if ( _NextRefreshTime - _LastRefreshTime > = 1.0 )
{
_LastRefreshTime = _NextRefreshTime ;
doBrowse ( _RefreshUrl . c_str ( ) ) ;
}
else
nlwarning ( " Ignore second 0sec http-equiv refresh in a row (url '%s') " , _URL . c_str ( ) ) ;
_NextRefreshTime = 0 ;
}
if ( _CurlWWW )
{
// still transfering html page
if ( _TimeoutValue ! = 0 & & _ConnectingTimeout < = ( times . thisFrameMs / 1000.0f ) )
{
browseError ( ( " Connection timeout : " + _URL ) . c_str ( ) ) ;
}
}
else
if ( _RenderNextTime )
{
_RenderNextTime = false ;
renderHtmlString ( _DocumentHtml ) ;
}
else
if ( _WaitingForStylesheet )
{
renderDocument ( ) ;
}
else
if ( _BrowseNextTime | | _PostNextTime )
{
// Set timeout
_ConnectingTimeout = ( times . thisFrameMs / 1000.0f ) + _TimeoutValue ;
// freeze form buttons
CButtonFreezer freezer ;
this - > visit ( & freezer ) ;
// Home ?
if ( _URL = = " home " )
_URL = home ( ) ;
string finalUrl ;
bool isLocal = lookupLocalFile ( finalUrl , _URL . c_str ( ) , true ) ;
if ( ! isLocal & & _URL . c_str ( ) [ 0 ] = = ' / ' )
{
if ( options . webServer . empty ( ) )
{
// Try again later
return ;
}
finalUrl = options . webServer + finalUrl ;
}
_URL = finalUrl ;
CUrlParser uri ( _URL ) ;
_TrustedDomain = isTrustedDomain ( uri . host ) ;
_DocumentDomain = uri . host ;
// file is probably from bnp (ingame help)
if ( isLocal )
{
doBrowseLocalFile ( finalUrl ) ;
}
else
{
SFormFields formfields ;
if ( _PostNextTime )
{
buildHTTPPostParams ( formfields ) ;
// _URL is set from form.Action
finalUrl = _URL ;
}
else
{
// Add custom get params from child classes
addHTTPGetParams ( finalUrl , _TrustedDomain ) ;
}
doBrowseRemoteUrl ( finalUrl , " " , _PostNextTime , formfields ) ;
}
_BrowseNextTime = false ;
_PostNextTime = false ;
}
}
// ***************************************************************************
void CGroupHTML : : buildHTTPPostParams ( SFormFields & formfields )
{
// Add text area text
uint i ;
if ( _PostFormId > = _Forms . size ( ) )
{
nlwarning ( " (%s) invalid form index %d, _Forms %d " , _Id . c_str ( ) , _PostFormId , _Forms . size ( ) ) ;
return ;
}
// Ref the form
CForm & form = _Forms [ _PostFormId ] ;
// button can override form action url (and methor, but we only do POST)
_URL = _PostFormAction . empty ( ) ? form . Action : _PostFormAction ;
CUrlParser uri ( _URL ) ;
_TrustedDomain = isTrustedDomain ( uri . host ) ;
_DocumentDomain = uri . host ;
for ( i = 0 ; i < form . Entries . size ( ) ; i + + )
{
// Text area ?
bool addEntry = false ;
string entryData ;
if ( form . Entries [ i ] . TextArea )
{
// Get the edit box view
CInterfaceGroup * group = form . Entries [ i ] . TextArea - > getGroup ( " eb " ) ;
if ( group )
{
// Should be a CGroupEditBox
CGroupEditBox * editBox = dynamic_cast < CGroupEditBox * > ( group ) ;
if ( editBox )
{
entryData = editBox - > getViewText ( ) - > getText ( ) ;
addEntry = true ;
}
}
}
else if ( form . Entries [ i ] . Checkbox )
{
// todo handle unicode POST here
if ( form . Entries [ i ] . Checkbox - > getPushed ( ) )
{
entryData = form . Entries [ i ] . Value ;
addEntry = true ;
}
}
else if ( form . Entries [ i ] . ComboBox )
{
CDBGroupComboBox * cb = form . Entries [ i ] . ComboBox ;
entryData = form . Entries [ i ] . SelectValues [ cb - > getSelection ( ) ] ;
addEntry = true ;
}
else if ( form . Entries [ i ] . SelectBox )
{
CGroupMenu * sb = form . Entries [ i ] . SelectBox ;
CGroupSubMenu * rootMenu = sb - > getRootMenu ( ) ;
if ( rootMenu )
{
for ( uint j = 0 ; j < rootMenu - > getNumLine ( ) ; + + j )
{
CInterfaceGroup * ig = rootMenu - > getUserGroupLeft ( j ) ;
if ( ig )
{
CCtrlBaseButton * cb = dynamic_cast < CCtrlBaseButton * > ( ig - > getCtrl ( " b " ) ) ;
if ( cb & & cb - > getPushed ( ) )
formfields . add ( form . Entries [ i ] . Name , form . Entries [ i ] . SelectValues [ j ] ) ;
}
}
}
}
// This is a hidden value
else
{
entryData = form . Entries [ i ] . Value ;
addEntry = true ;
}
// Add this entry
if ( addEntry )
{
formfields . add ( form . Entries [ i ] . Name , entryData ) ;
}
}
if ( _PostFormSubmitType = = " image " )
{
// Add the button coordinates
if ( _PostFormSubmitButton . find_first_of ( " [ " ) = = string : : npos )
{
formfields . add ( _PostFormSubmitButton + " _x " , NLMISC : : toString ( _PostFormSubmitX ) ) ;
formfields . add ( _PostFormSubmitButton + " _y " , NLMISC : : toString ( _PostFormSubmitY ) ) ;
}
else
{
formfields . add ( _PostFormSubmitButton , NLMISC : : toString ( _PostFormSubmitX ) ) ;
formfields . add ( _PostFormSubmitButton , NLMISC : : toString ( _PostFormSubmitY ) ) ;
}
}
else
formfields . add ( _PostFormSubmitButton , _PostFormSubmitValue ) ;
// Add custom params from child classes
addHTTPPostParams ( formfields , _TrustedDomain ) ;
}
// ***************************************************************************
void CGroupHTML : : doBrowseLocalFile ( const std : : string & uri )
{
releaseDownloads ( ) ;
updateRefreshButton ( ) ;
std : : string filename ;
if ( toLowerAscii ( uri ) . find ( " file:/ " ) = = 0 )
{
filename = uri . substr ( 6 , uri . size ( ) - 6 ) ;
}
else
{
filename = uri ;
}
LOG_DL ( " browse local file '%s' " , filename . c_str ( ) ) ;
_TrustedDomain = true ;
_DocumentDomain = " localhost " ;
CIFile in ;
if ( in . open ( filename ) )
{
std : : string html ;
while ( ! in . eof ( ) )
{
char buf [ 1024 ] ;
in . getline ( buf , 1024 ) ;
html + = std : : string ( buf ) + " \n " ;
}
in . close ( ) ;
if ( ! renderHtmlString ( html ) )
{
browseError ( ( string ( " Failed to parse html from file : " ) + filename ) . c_str ( ) ) ;
}
}
else
{
browseError ( ( string ( " The page address is malformed : " ) + filename ) . c_str ( ) ) ;
}
}
// ***************************************************************************
void CGroupHTML : : doBrowseRemoteUrl ( std : : string url , const std : : string & referer , bool doPost , const SFormFields & formfields )
{
// stop all downloads from previous page
releaseDownloads ( ) ;
updateRefreshButton ( ) ;
// Reset the title
if ( _TitlePrefix . empty ( ) )
setTitle ( CI18N : : get ( " uiPleaseWait " ) ) ;
else
setTitle ( _TitlePrefix + " - " + CI18N : : get ( " uiPleaseWait " ) ) ;
url = upgradeInsecureUrl ( url ) ;
LOG_DL ( " (%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d " ,
_Id . c_str ( ) , ( _TrustedDomain ? " true " : " false " ) , url . c_str ( ) , referer . c_str ( ) , ( doPost ? " true " : " false " ) , formfields . Values . size ( ) ) ;
if ( ! MultiCurl )
{
browseError ( string ( " Invalid MultCurl handle, loading url failed : " + url ) . c_str ( ) ) ;
return ;
}
CURL * curl = curl_easy_init ( ) ;
if ( ! curl )
{
nlwarning ( " (%s) failed to create curl handle " , _Id . c_str ( ) ) ;
browseError ( string ( " Failed to create cURL handle : " + url ) . c_str ( ) ) ;
return ;
}
// https://
if ( toLowerAscii ( url . substr ( 0 , 8 ) ) = = " https:// " )
{
// if supported, use custom SSL context function to load certificates
NLWEB : : CCurlCertificates : : useCertificates ( curl ) ;
}
// do not follow redirects, we have own handler
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 0 ) ;
// after redirect
curl_easy_setopt ( curl , CURLOPT_FRESH_CONNECT , 1 ) ;
// tell curl to use compression if possible (gzip, deflate)
// leaving this empty allows all encodings that curl supports
//curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
// limit curl to HTTP and HTTPS protocols only
curl_easy_setopt ( curl , CURLOPT_PROTOCOLS , CURLPROTO_HTTP | CURLPROTO_HTTPS ) ;
curl_easy_setopt ( curl , CURLOPT_REDIR_PROTOCOLS , CURLPROTO_HTTP | CURLPROTO_HTTPS ) ;
// Destination
curl_easy_setopt ( curl , CURLOPT_URL , url . c_str ( ) ) ;
// User-Agent:
std : : string userAgent = options . appName + " / " + options . appVersion ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , userAgent . c_str ( ) ) ;
// Cookies
sendCookies ( curl , _DocumentDomain , _TrustedDomain ) ;
// Referer
if ( ! referer . empty ( ) )
{
curl_easy_setopt ( curl , CURLOPT_REFERER , referer . c_str ( ) ) ;
LOG_DL ( " (%s) set referer '%s' " , _Id . c_str ( ) , referer . c_str ( ) ) ;
}
if ( doPost )
{
// serialize form data and add it to curl
std : : string data ;
for ( uint i = 0 ; i < formfields . Values . size ( ) ; + + i )
{
char * escapedName = curl_easy_escape ( curl , formfields . Values [ i ] . name . c_str ( ) , formfields . Values [ i ] . name . size ( ) ) ;
char * escapedValue = curl_easy_escape ( curl , formfields . Values [ i ] . value . c_str ( ) , formfields . Values [ i ] . value . size ( ) ) ;
if ( i > 0 )
data + = " & " ;
data + = std : : string ( escapedName ) + " = " + escapedValue ;
curl_free ( escapedName ) ;
curl_free ( escapedValue ) ;
}
curl_easy_setopt ( curl , CURLOPT_POST , 1 ) ;
curl_easy_setopt ( curl , CURLOPT_POSTFIELDSIZE , data . size ( ) ) ;
curl_easy_setopt ( curl , CURLOPT_COPYPOSTFIELDS , data . c_str ( ) ) ;
}
else
{
curl_easy_setopt ( curl , CURLOPT_HTTPGET , 1 ) ;
}
// transfer handle
_CurlWWW = new CCurlWWWData ( curl , url ) ;
// set the language code used by the client
std : : vector < std : : string > headers ;
headers . push_back ( " Accept-Language: " + options . languageCode ) ;
headers . push_back ( " Accept-Charset: utf-8 " ) ;
_CurlWWW - > sendHeaders ( headers ) ;
// catch headers for redirect
curl_easy_setopt ( curl , CURLOPT_HEADERFUNCTION , NLGUI : : curlHeaderCallback ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEHEADER , _CurlWWW ) ;
// catch body
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , NLGUI : : curlDataCallback ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , _CurlWWW ) ;
# ifdef LOG_CURL_PROGRESS
// progress callback
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 0 ) ;
curl_easy_setopt ( curl , CURLOPT_XFERINFOFUNCTION , NLGUI : : curlProgressCallback ) ;
curl_easy_setopt ( curl , CURLOPT_XFERINFODATA , _CurlWWW ) ;
# else
// progress off
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 1 ) ;
# endif
//
curl_multi_add_handle ( MultiCurl , curl ) ;
// start the transfer
int NewRunningCurls = 0 ;
curl_multi_perform ( MultiCurl , & NewRunningCurls ) ;
RunningCurls + + ;
_RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT ;
}
// ***************************************************************************
void CGroupHTML : : htmlDownloadFinished ( bool success , const std : : string & error )
{
if ( ! success )
{
CUrlParser uri ( _CurlWWW - > Url ) ;
// potentially unwanted chars
std : : string url = _CurlWWW - > Url ;
url = strFindReplaceAll ( url , string ( " < " ) , string ( " %3C " ) ) ;
url = strFindReplaceAll ( url , string ( " > " ) , string ( " %3E " ) ) ;
url = strFindReplaceAll ( url , string ( " \" " ) , string ( " %22 " ) ) ;
url = strFindReplaceAll ( url , string ( " ' " ) , string ( " %27 " ) ) ;
std : : string err ;
err = " <html><head><title>cURL error</title></head><body> " ;
err + = " <h1>Connection failed with cURL error</h1> " ;
err + = error ;
err + = " <hr>( " + uri . scheme + " :// " + uri . host + " ) <a href= \" " + url + " \" >reload</a> " ;
err + = " </body></html> " ;
browseErrorHtml ( err ) ;
return ;
}
// received content from remote
std : : string content = trim ( _CurlWWW - > Content ) ;
// save HSTS header from all requests regardless of HTTP code
if ( _CurlWWW - > hasHSTSHeader ( ) )
{
CUrlParser uri ( _CurlWWW - > Url ) ;
CStrictTransportSecurity : : getInstance ( ) - > setFromHeader ( uri . host , _CurlWWW - > getHSTSHeader ( ) ) ;
}
receiveCookies ( _CurlWWW - > Request , _DocumentDomain , _TrustedDomain ) ;
long code ;
curl_easy_getinfo ( _CurlWWW - > Request , CURLINFO_RESPONSE_CODE , & code ) ;
LOG_DL ( " (%s) web transfer '%p' completed with http code %d, url (len %d) '%s' " , _Id . c_str ( ) , _CurlWWW - > Request , code , _CurlWWW - > Url . size ( ) , _CurlWWW - > Url . c_str ( ) ) ;
if ( ( code > = 301 & & code < = 303 ) | | code = = 307 | | code = = 308 )
{
if ( _RedirectsRemaining < 0 )
{
browseError ( string ( " Redirect limit reached : " + _URL ) . c_str ( ) ) ;
return ;
}
// redirect, get the location and try browse again
// we cant use curl redirection because 'addHTTPGetParams()' must be called on new destination
std : : string location ( _CurlWWW - > getLocationHeader ( ) ) ;
if ( location . empty ( ) )
{
browseError ( string ( " Request was redirected, but location was not set : " + _URL ) . c_str ( ) ) ;
return ;
}
LOG_DL ( " (%s) request (%d) redirected to (len %d) '%s' " , _Id . c_str ( ) , _RedirectsRemaining , location . size ( ) , location . c_str ( ) ) ;
location = getAbsoluteUrl ( location ) ;
_PostNextTime = false ;
_RedirectsRemaining - - ;
doBrowse ( location . c_str ( ) ) ;
}
else if ( ( code < 200 | | code > = 300 ) )
{
// catches 304 not modified, but html is not in cache anyway
// if server did not send any error back
if ( content . empty ( ) )
{
content = string ( " <html><head><title>ERROR</title></head><body><h1>Connection failed</h1><p>HTTP code ' " + toString ( ( sint32 ) code ) + " '</p><p>URL ' " + _CurlWWW - > Url + " '</p></body></html> " ) ;
}
}
char * ch ;
std : : string contentType ;
CURLcode res = curl_easy_getinfo ( _CurlWWW - > Request , CURLINFO_CONTENT_TYPE , & ch ) ;
if ( res = = CURLE_OK & & ch ! = NULL )
{
contentType = ch ;
}
htmlDownloadFinished ( content , contentType , code ) ;
// clear curl handler
if ( MultiCurl )
{
curl_multi_remove_handle ( MultiCurl , _CurlWWW - > Request ) ;
}
delete _CurlWWW ;
_CurlWWW = NULL ;
// refresh button uses _CurlWWW. refresh button may stay disabled if
// there is no css files to download and page is rendered before _CurlWWW is freed
updateRefreshButton ( ) ;
}
void CGroupHTML : : dataDownloadFinished ( bool success , const std : : string & error , CDataDownload * data )
{
fclose ( data - > fp ) ;
CUrlParser uri ( data - > url ) ;
if ( ! uri . host . empty ( ) )
{
receiveCookies ( data - > data - > Request , uri . host , isTrustedDomain ( uri . host ) ) ;
}
long code = - 1 ;
curl_easy_getinfo ( data - > data - > Request , CURLINFO_RESPONSE_CODE , & code ) ;
LOG_DL ( " (%s) transfer '%p' completed with http code %d, url (len %d) '%s' " , _Id . c_str ( ) , data - > data - > Request , code , data - > url . size ( ) , data - > url . c_str ( ) ) ;
curl_multi_remove_handle ( MultiCurl , data - > data - > Request ) ;
// save HSTS header from all requests regardless of HTTP code
if ( success )
{
if ( data - > data - > hasHSTSHeader ( ) )
{
CStrictTransportSecurity : : getInstance ( ) - > setFromHeader ( uri . host , data - > data - > getHSTSHeader ( ) ) ;
}
// 2XX success, 304 Not Modified
if ( ( code > = 200 & & code < = 204 ) | | code = = 304 )
{
CHttpCacheObject obj ;
obj . Expires = data - > data - > getExpires ( ) ;
obj . Etag = data - > data - > getEtag ( ) ;
obj . LastModified = data - > data - > getLastModified ( ) ;
CHttpCache : : getInstance ( ) - > store ( data - > dest , obj ) ;
if ( code = = 304 & & CFile : : fileExists ( data - > tmpdest ) )
{
CFile : : deleteFile ( data - > tmpdest ) ;
}
}
else if ( ( code > = 301 & & code < = 303 ) | | code = = 307 | | code = = 308 )
{
if ( data - > redirects < DEFAULT_RYZOM_REDIRECT_LIMIT )
{
std : : string location ( data - > data - > getLocationHeader ( ) ) ;
if ( ! location . empty ( ) )
{
CUrlParser uri ( location ) ;
if ( ! uri . isAbsolute ( ) )
{
uri . inherit ( data - > url ) ;
location = uri . toString ( ) ;
}
// clear old request state, and curl easy handle
delete data - > data ;
data - > data = NULL ;
data - > fp = NULL ;
data - > url = location ;
data - > redirects + + ;
// push same request in the front of the queue
// cache filename is based of original url
Curls . push_front ( data ) ;
LOG_DL ( " Redirect '%s' " , location . c_str ( ) ) ;
// no finished callback called, so cleanup old temp
if ( CFile : : fileExists ( data - > tmpdest ) )
{
CFile : : deleteFile ( data - > tmpdest ) ;
}
return ;
}
nlwarning ( " Redirected to empty url '%s' " , data - > url . c_str ( ) ) ;
}
else
{
nlwarning ( " Redirect limit reached for '%s' " , data - > url . c_str ( ) ) ;
}
}
else
{
nlwarning ( " HTTP request failed with code [%d] for '%s' \n " , code , data - > url . c_str ( ) ) ;
// 404, 500, etc
if ( CFile : : fileExists ( data - > dest ) )
{
CFile : : deleteFile ( data - > dest ) ;
}
}
}
else
{
nlwarning ( " DATA download failed '%s', error '%s' " , data - > url . c_str ( ) , error . c_str ( ) ) ;
}
finishCurlDownload ( data ) ;
}
void CGroupHTML : : htmlDownloadFinished ( const std : : string & content , const std : : string & type , long code )
{
LOG_DL ( " (%s) HTML download finished, content length %d, type '%s', code %d " , _Id . c_str ( ) , content . size ( ) , type . c_str ( ) , code ) ;
// create <html> markup for image downloads
if ( type . find ( " image/ " ) = = 0 & & ! content . empty ( ) )
{
try
{
std : : string dest = localImageName ( _URL ) ;
COFile out ;
out . open ( dest ) ;
out . serialBuffer ( ( uint8 * ) ( content . c_str ( ) ) , content . size ( ) ) ;
out . close ( ) ;
LOG_DL ( " (%s) image saved to '%s', url '%s' " , _Id . c_str ( ) , dest . c_str ( ) , _URL . c_str ( ) ) ;
}
catch ( . . . ) { }
// create html code with image url inside and do the request again
renderHtmlString ( " <html><head><title> " + _URL + " </title></head><body><img src= \" " + _URL + " \" ></body></html> " ) ;
}
else if ( _TrustedDomain & & type . find ( " text/lua " ) = = 0 )
{
setTitle ( _TitleString ) ;
_LuaScript = " \n local __CURRENT_WINDOW__= \" " + this - > _Id + " \" \n " + content ;
CLuaManager : : getInstance ( ) . executeLuaScript ( _LuaScript , true ) ;
_LuaScript . clear ( ) ;
// disable refresh button
clearRefresh ( ) ;
// disable redo into this url
_AskedUrl . clear ( ) ;
}
else
{
// Sanitize downloaded HTML UTF-8 encoding, and render
renderHtmlString ( CUtfStringView ( content ) . toUtf8 ( true ) ) ;
}
}
// ***************************************************************************
void CGroupHTML : : cssDownloadFinished ( const std : : string & url , const std : : string & local )
{
for ( std : : vector < CHtmlParser : : StyleLink > : : iterator it = _StylesheetQueue . begin ( ) ;
it ! = _StylesheetQueue . end ( ) ; + + it )
{
if ( it - > Url = = url )
{
// read downloaded file into HtmlStyles
if ( CFile : : fileExists ( local ) & & it - > Index < _HtmlStyles . size ( ) )
{
CIFile in ;
if ( in . open ( local ) )
{
if ( ! in . readAll ( _HtmlStyles [ it - > Index ] ) )
{
nlwarning ( " Failed to read downloaded css file(%s), url(%s) " , local . c_str ( ) , url . c_str ( ) ) ;
}
}
}
_StylesheetQueue . erase ( it ) ;
break ;
}
}
}
void CGroupHTML : : renderDocument ( )
{
if ( ! Curls . empty ( ) & & ! _StylesheetQueue . empty ( ) )
{
// waiting for stylesheets to finish downloading
return ;
}
_WaitingForStylesheet = false ;
//TGameTime renderStart = CTime::getLocalTime();
// clear previous state and page
beginBuild ( ) ;
removeContent ( ) ;
// process all <style> and <link rel=stylesheet> elements
for ( uint i = 0 ; i < _HtmlStyles . size ( ) ; + + i )
{
if ( ! _HtmlStyles [ i ] . empty ( ) )
{
_Style . parseStylesheet ( _HtmlStyles [ i ] ) ;
}
}
_HtmlStyles . clear ( ) ;
std : : list < CHtmlElement > : : iterator it = _HtmlDOM . Children . begin ( ) ;
while ( it ! = _HtmlDOM . Children . end ( ) )
{
renderDOM ( * it ) ;
+ + it ;
}
endBuild ( ) ;
//TGameTime renderStop = CTime::getLocalTime();
//nlwarning("[%s] render: %.1fms (%s)\n", _Id.c_str(), (renderStop - renderStart), _URL.c_str());
}
// ***************************************************************************
bool CGroupHTML : : renderHtmlString ( const std : : string & html )
{
bool success ;
// if we are already rendering, then queue up the next page
if ( _Browsing )
{
_DocumentHtml = html ;
_RenderNextTime = true ;
return true ;
}
//
_DocumentUrl = _URL ;
_DocumentHtml = html ;
_NextRefreshTime = 0 ;
_RefreshUrl . clear ( ) ;
if ( trim ( html ) . empty ( ) )
{
// clear the page
beginBuild ( ) ;
// clear previous page and state
removeContent ( ) ;
endBuild ( ) ;
success = false ;
}
else
{
// browser.css
resetCssStyle ( ) ;
// start new rendering
_HtmlDOM = CHtmlElement ( CHtmlElement : : NONE , " <root> " ) ;
_CurrentHTMLElement = NULL ;
success = parseHtml ( html ) ;
if ( success )
{
_WaitingForStylesheet = ! _StylesheetQueue . empty ( ) ;
renderDocument ( ) ;
}
else
{
std : : string error = " ERROR: HTML parse failed. " ;
error + = toString ( " \n size %d bytes " , html . size ( ) ) ;
error + = toString ( " \n ---start--- \n %s \n ---end--- \n " , html . c_str ( ) ) ;
browseError ( error . c_str ( ) ) ;
}
}
return success ;
}
// ***************************************************************************
void CGroupHTML : : doBrowseAnchor ( const std : : string & anchor )
{
if ( _Anchors . count ( anchor ) = = 0 )
{
return ;
}
CInterfaceElement * pIE = _Anchors . find ( anchor ) - > second ;
if ( pIE )
{
// hotspot depends on vertical/horizontal scrollbar
CCtrlScroll * pSB = getScrollBar ( ) ;
if ( pSB )
{
pSB - > ensureVisible ( pIE , Hotspot_Tx , Hotspot_Tx ) ;
}
}
}
// ***************************************************************************
void CGroupHTML : : draw ( )
{
uint8 CurrentAlpha = 255 ;
// search a parent container
CInterfaceGroup * gr = getParent ( ) ;
while ( gr )
{
if ( gr - > isGroupContainer ( ) )
{
CGroupContainer * gc = static_cast < CGroupContainer * > ( gr ) ;
CurrentAlpha = gc - > getCurrentContainerAlpha ( ) ;
break ;
}
gr = gr - > getParent ( ) ;
}
m_HtmlBackground . CurrentAlpha = CurrentAlpha ;
m_BodyBackground . CurrentAlpha = CurrentAlpha ;
m_HtmlBackground . draw ( ) ;
m_BodyBackground . draw ( ) ;
CGroupScrollText : : draw ( ) ;
}
// ***************************************************************************
void CGroupHTML : : beginBuild ( )
{
_Browsing = true ;
}
void CGroupHTML : : endBuild ( )
{
// set the browser as complete
_Browsing = false ;
updateRefreshButton ( ) ;
// check that the title is set, or reset it (in the case the page
// does not provide a title)
if ( _TitleString . empty ( ) )
{
setTitle ( _TitlePrefix ) ;
}
invalidateCoords ( ) ;
}
// ***************************************************************************
void CGroupHTML : : addHTTPGetParams ( string & /* url */ , bool /*trustedDomain*/ )
{
}
// ***************************************************************************
void CGroupHTML : : addHTTPPostParams ( SFormFields & /* formfields */ , bool /*trustedDomain*/ )
{
}
// ***************************************************************************
string CGroupHTML : : home ( ) const
{
return Home ;
}
// ***************************************************************************
void CGroupHTML : : removeContent ( )
{
// Remove old document
if ( ! _GroupListAdaptor )
{
_GroupListAdaptor = new CGroupListAdaptor ( CViewBase : : TCtorParam ( ) ) ; // deleted by the list
_GroupListAdaptor - > setId ( getList ( ) - > getId ( ) + " :GLA " ) ;
_GroupListAdaptor - > setResizeFromChildH ( true ) ;
getList ( ) - > addChild ( _GroupListAdaptor , true ) ;
}
// Group list adaptor not exist ?
_GroupListAdaptor - > clearGroups ( ) ;
_GroupListAdaptor - > clearControls ( ) ;
_GroupListAdaptor - > clearViews ( ) ;
CWidgetManager : : getInstance ( ) - > clearViewUnders ( ) ;
CWidgetManager : : getInstance ( ) - > clearCtrlsUnders ( ) ;
// Clear all the context
clearContext ( ) ;
// Reset default background
m_HtmlBackground . clear ( ) ;
m_BodyBackground . clear ( ) ;
// TODO: DefaultBackgroundBitmapView should be removed from interface xml
CViewBase * view = getView ( DefaultBackgroundBitmapView ) ;
if ( view )
view - > setActive ( false ) ;
paragraphChange ( ) ;
}
// ***************************************************************************
const std : : string & CGroupHTML : : selectTreeNodeRecurs ( CGroupTree : : SNode * node , const std : : string & url )
{
static std : : string emptyString ;
if ( ! node )
{
return emptyString ;
}
// if this node match
if ( actionLaunchUrlRecurs ( node - > AHName , node - > AHParams , url ) )
{
return node - > Id ;
}
// fails => look into children
else
{
for ( uint i = 0 ; i < node - > Children . size ( ) ; i + + )
{
const string & childRes = selectTreeNodeRecurs ( node - > Children [ i ] , url ) ;
if ( ! childRes . empty ( ) )
return childRes ;
}
// none match...
return emptyString ;
}
}
// ***************************************************************************
bool CGroupHTML : : actionLaunchUrlRecurs ( const std : : string & ah , const std : : string & params , const std : : string & url )
{
// check if this action match
if ( ( ah = = " launch_help " | | ah = = " browse " ) & & IActionHandler : : getParam ( params , " url " ) = = url )
{
return true ;
}
// can be a proc that contains launch_help/browse => look recurs
else if ( ah = = " proc " )
{
const std : : string & procName = params ;
// look into this proc
uint numActions = CWidgetManager : : getInstance ( ) - > getParser ( ) - > getProcedureNumActions ( procName ) ;
for ( uint i = 0 ; i < numActions ; i + + )
{
string procAh , procParams ;
if ( CWidgetManager : : getInstance ( ) - > getParser ( ) - > getProcedureAction ( procName , i , procAh , procParams ) )
{
// recurs proc if needed!
if ( actionLaunchUrlRecurs ( procAh , procParams , url ) )
return true ;
}
}
}
return false ;
}
// ***************************************************************************
void CGroupHTML : : clearRefresh ( )
{
_URL . clear ( ) ;
updateRefreshButton ( ) ;
}
// ***************************************************************************
void CGroupHTML : : clearUndoRedo ( )
{
// erase any undo/redo
_BrowseUndo . clear ( ) ;
_BrowseRedo . clear ( ) ;
// update buttons validation
updateUndoRedoButtons ( ) ;
}
// ***************************************************************************
void CGroupHTML : : pushUrlUndoRedo ( const std : : string & url )
{
// if same url, no op
if ( url = = _AskedUrl )
return ;
// erase any redo, push undo, set current
_BrowseRedo . clear ( ) ;
if ( ! _AskedUrl . empty ( ) )
_BrowseUndo . push_back ( _AskedUrl ) ;
_AskedUrl = url ;
// limit undo
while ( _BrowseUndo . size ( ) > MaxUrlUndoRedo )
_BrowseUndo . pop_front ( ) ;
// update buttons validation
updateUndoRedoButtons ( ) ;
}
// ***************************************************************************
void CGroupHTML : : browseUndo ( )
{
if ( _BrowseUndo . empty ( ) )
return ;
// push to redo, pop undo, and set current
if ( ! _AskedUrl . empty ( ) )
_BrowseRedo . push_front ( _AskedUrl ) ;
_AskedUrl = _BrowseUndo . back ( ) ;
_BrowseUndo . pop_back ( ) ;
// update buttons validation
updateUndoRedoButtons ( ) ;
// and then browse the undoed url, with no undo/redo
doBrowse ( _AskedUrl . c_str ( ) ) ;
}
// ***************************************************************************
void CGroupHTML : : browseRedo ( )
{
if ( _BrowseRedo . empty ( ) )
return ;
// push to undo, pop redo, and set current
_BrowseUndo . push_back ( _AskedUrl ) ;
_AskedUrl = _BrowseRedo . front ( ) ;
_BrowseRedo . pop_front ( ) ;
// update buttons validation
updateUndoRedoButtons ( ) ;
// and then browse the redoed url, with no undo/redo
doBrowse ( _AskedUrl . c_str ( ) ) ;
}
// ***************************************************************************
void CGroupHTML : : updateUndoRedoButtons ( )
{
CCtrlBaseButton * butUndo = dynamic_cast < CCtrlBaseButton * > ( CWidgetManager : : getInstance ( ) - > getElementFromId ( _BrowseUndoButton ) ) ;
CCtrlBaseButton * butRedo = dynamic_cast < CCtrlBaseButton * > ( CWidgetManager : : getInstance ( ) - > getElementFromId ( _BrowseRedoButton ) ) ;
// gray according to list size
if ( butUndo )
butUndo - > setFrozen ( _BrowseUndo . empty ( ) ) ;
if ( butRedo )
butRedo - > setFrozen ( _BrowseRedo . empty ( ) ) ;
}
// ***************************************************************************
void CGroupHTML : : updateRefreshButton ( )
{
CCtrlBaseButton * butRefresh = dynamic_cast < CCtrlBaseButton * > ( CWidgetManager : : getInstance ( ) - > getElementFromId ( _BrowseRefreshButton ) ) ;
if ( butRefresh )
{
// connecting, rendering, or is missing url
bool frozen = _CurlWWW | | _Browsing | | _URL . empty ( ) ;
butRefresh - > setFrozen ( frozen ) ;
}
}
// ***************************************************************************
NLMISC_REGISTER_OBJECT ( CViewBase , CGroupHTMLInputOffset , std : : string , " html_input_offset " ) ;
CGroupHTMLInputOffset : : CGroupHTMLInputOffset ( const TCtorParam & param )
: CInterfaceGroup ( param ) ,
Offset ( 0 )
{
}
xmlNodePtr CGroupHTMLInputOffset : : serialize ( xmlNodePtr parentNode , const char * type ) const
{
xmlNodePtr node = CInterfaceGroup : : serialize ( parentNode , type ) ;
if ( node = = NULL )
return NULL ;
xmlSetProp ( node , BAD_CAST " type " , BAD_CAST " html_input_offset " ) ;
xmlSetProp ( node , BAD_CAST " y_offset " , BAD_CAST toString ( Offset ) . c_str ( ) ) ;
return node ;
}
// ***************************************************************************
bool CGroupHTMLInputOffset : : parse ( xmlNodePtr cur , CInterfaceGroup * parentGroup )
{
if ( ! CInterfaceGroup : : parse ( cur , parentGroup ) ) return false ;
CXMLAutoPtr ptr ;
// Get the url
ptr = xmlGetProp ( cur , ( xmlChar * ) " y_offset " ) ;
if ( ptr )
fromString ( ( const char * ) ptr , Offset ) ;
return true ;
}
// ***************************************************************************
int CGroupHTML : : luaParseHtml ( CLuaState & ls )
{
const char * funcName = " parseHtml " ;
CLuaIHM : : checkArgCount ( ls , funcName , 1 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
std : : string html = ls . toString ( 1 ) ;
parseHtml ( html ) ;
return 0 ;
}
int CGroupHTML : : luaClearRefresh ( CLuaState & ls )
{
const char * funcName = " clearRefresh " ;
CLuaIHM : : checkArgCount ( ls , funcName , 0 ) ;
clearRefresh ( ) ;
return 0 ;
}
int CGroupHTML : : luaClearUndoRedo ( CLuaState & ls )
{
const char * funcName = " clearUndoRedo " ;
CLuaIHM : : checkArgCount ( ls , funcName , 0 ) ;
clearUndoRedo ( ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaBrowse ( CLuaState & ls )
{
const char * funcName = " browse " ;
CLuaIHM : : checkArgCount ( ls , funcName , 1 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
browse ( ls . toString ( 1 ) ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaRefresh ( CLuaState & ls )
{
const char * funcName = " refresh " ;
CLuaIHM : : checkArgCount ( ls , funcName , 0 ) ;
refresh ( ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaRemoveContent ( CLuaState & ls )
{
const char * funcName = " removeContent " ;
CLuaIHM : : checkArgCount ( ls , funcName , 0 ) ;
removeContent ( ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaRenderHtml ( CLuaState & ls )
{
const char * funcName = " renderHtml " ;
CLuaIHM : : checkArgCount ( ls , funcName , 1 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
std : : string html = ls . toString ( 1 ) ;
// Always trust domain if rendered from lua
_TrustedDomain = true ;
renderHtmlString ( html ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaSetBackground ( CLuaState & ls )
{
const char * funcName = " setBackground " ;
CLuaIHM : : checkArgCount ( ls , funcName , 3 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
CLuaIHM : : checkArgType ( ls , funcName , 2 , LUA_TBOOLEAN ) ;
CLuaIHM : : checkArgType ( ls , funcName , 3 , LUA_TBOOLEAN ) ;
std : : string image = ls . toString ( 1 ) ;
bool scale = ls . toBoolean ( 2 ) ;
bool repeat = ls . toBoolean ( 3 ) ;
setBackground ( image , scale , repeat ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaInsertText ( CLuaState & ls )
{
const char * funcName = " insertText " ;
CLuaIHM : : checkArgCount ( ls , funcName , 3 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
CLuaIHM : : checkArgType ( ls , funcName , 2 , LUA_TSTRING ) ;
CLuaIHM : : checkArgType ( ls , funcName , 3 , LUA_TBOOLEAN ) ;
string name = ls . toString ( 1 ) ;
string text = ls . toString ( 2 ) ;
if ( ! _Forms . empty ( ) )
{
for ( uint i = 0 ; i < _Forms . back ( ) . Entries . size ( ) ; i + + )
{
if ( _Forms . back ( ) . Entries [ i ] . TextArea & & _Forms . back ( ) . Entries [ i ] . Name = = name )
{
// Get the edit box view
CInterfaceGroup * group = _Forms . back ( ) . Entries [ i ] . TextArea - > getGroup ( " eb " ) ;
if ( group )
{
// Should be a CGroupEditBox
CGroupEditBox * editBox = dynamic_cast < CGroupEditBox * > ( group ) ;
if ( editBox )
editBox - > writeString ( text , false , ls . toBoolean ( 3 ) ) ;
}
}
}
}
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaAddString ( CLuaState & ls )
{
const char * funcName = " addString " ;
CLuaIHM : : checkArgCount ( ls , funcName , 1 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
addString ( ls . toString ( 1 ) ) ;
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaAddImage ( CLuaState & ls )
{
const char * funcName = " addImage " ;
CLuaIHM : : checkArgCount ( ls , funcName , 2 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
CLuaIHM : : checkArgType ( ls , funcName , 2 , LUA_TBOOLEAN ) ;
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
CStyleParams style ;
style . GlobalColor = ls . toBoolean ( 2 ) ;
string url = getLink ( ) ;
if ( ! url . empty ( ) )
{
string params = " name= " + getId ( ) + " |url= " + getLink ( ) ;
addButton ( CCtrlButton : : PushButton , " " , ls . toString ( 1 ) , ls . toString ( 1 ) ,
" " , " browse " , params . c_str ( ) , " " , style ) ;
}
else
{
addImage ( " " , ls . toString ( 1 ) , false , style ) ;
}
return 0 ;
}
// ***************************************************************************
int CGroupHTML : : luaShowDiv ( CLuaState & ls )
{
const char * funcName = " showDiv " ;
CLuaIHM : : checkArgCount ( ls , funcName , 2 ) ;
CLuaIHM : : checkArgType ( ls , funcName , 1 , LUA_TSTRING ) ;
CLuaIHM : : checkArgType ( ls , funcName , 2 , LUA_TBOOLEAN ) ;
if ( ! _Groups . empty ( ) )
{
for ( uint i = 0 ; i < _Groups . size ( ) ; i + + )
{
CInterfaceGroup * group = _Groups [ i ] ;
if ( group - > getName ( ) = = ls . toString ( 1 ) )
{
group - > setActive ( ls . toBoolean ( 2 ) ) ;
}
}
}
return 0 ;
}
// ***************************************************************************
void CGroupHTML : : setURL ( const std : : string & url )
{
browse ( url . c_str ( ) ) ;
}
void CGroupHTML : : setHTML ( const std : : string & html )
{
renderHtmlString ( html ) ;
}
void CGroupHTML : : setHome ( const std : : string & home )
{
Home = home ;
}
// ***************************************************************************
void CGroupHTML : : parseStylesheetFile ( const std : : string & fname )
{
CIFile css ;
if ( css . open ( fname ) )
{
uint32 remaining = css . getFileSize ( ) ;
std : : string content ;
try {
while ( ! css . eof ( ) & & remaining > 0 )
{
const uint BUF_SIZE = 4096 ;
char buf [ BUF_SIZE ] ;
uint32 readJustNow = std : : min ( remaining , BUF_SIZE ) ;
css . serialBuffer ( ( uint8 * ) & buf , readJustNow ) ;
content . append ( buf , readJustNow ) ;
remaining - = readJustNow ;
}
_Style . parseStylesheet ( content ) ;
}
catch ( const Exception & e )
{
nlwarning ( " exception while reading css file '%s' " , e . what ( ) ) ;
}
}
else
{
nlwarning ( " Stylesheet file '%s' not found (%s) " , fname . c_str ( ) , _URL . c_str ( ) ) ;
}
}
// ***************************************************************************
bool CGroupHTML : : parseHtml ( const std : : string & htmlString )
{
CHtmlElement * parsedDOM ;
if ( _CurrentHTMLElement = = NULL )
{
// parse under <root> element (clean dom)
parsedDOM = & _HtmlDOM ;
}
else
{
// parse under currently rendered <lua> element
parsedDOM = _CurrentHTMLElement ;
}
std : : vector < CHtmlParser : : StyleLink > links ;
CHtmlParser parser ;
parser . getDOM ( htmlString , * parsedDOM , _HtmlStyles , links ) ;
// <link> elements inserted from lua::parseHtml are ignored
if ( _CurrentHTMLElement = = NULL & & ! links . empty ( ) )
{
addStylesheetDownload ( links ) ;
}
else if ( _CurrentHTMLElement ! = NULL )
{
// Called from active element (lua)
// <style> order is not preserved as document is already being rendered
for ( uint i = 0 ; i < _HtmlStyles . size ( ) ; + + i )
{
if ( ! _HtmlStyles [ i ] . empty ( ) )
{
_Style . parseStylesheet ( _HtmlStyles [ i ] ) ;
}
}
_HtmlStyles . clear ( ) ;
}
// this should rarely fail as first element should be <html>
bool success = parsedDOM - > Children . size ( ) > 0 ;
std : : list < CHtmlElement > : : iterator it = parsedDOM - > Children . begin ( ) ;
while ( it ! = parsedDOM - > Children . end ( ) )
{
if ( it - > Type = = CHtmlElement : : ELEMENT_NODE & & it - > Value = = " html " )
{
// move newly parsed childs from <body> into siblings
if ( _CurrentHTMLElement ) {
std : : list < CHtmlElement > : : iterator it2 = it - > Children . begin ( ) ;
while ( it2 ! = it - > Children . end ( ) )
{
if ( it2 - > Type = = CHtmlElement : : ELEMENT_NODE & & it2 - > Value = = " body " )
{
spliceFragment ( it2 ) ;
break ;
}
+ + it2 ;
}
// remove <html> fragment from current element child
it = parsedDOM - > Children . erase ( it ) ;
}
else
{
// remove link to <root> (html->parent == '<root>') or css selector matching will break
it - > parent = NULL ;
+ + it ;
}
continue ;
}
// skip over other non-handled element
+ + it ;
}
return success ;
}
void CGroupHTML : : spliceFragment ( std : : list < CHtmlElement > : : iterator src )
{
if ( ! _CurrentHTMLElement - > parent )
{
nlwarning ( " BUG: Current node is missing parent element. unable to splice fragment " ) ;
return ;
}
// get the iterators for current element (<lua>) and next sibling
std : : list < CHtmlElement > : : iterator currentElement ;
currentElement = std : : find ( _CurrentHTMLElement - > parent - > Children . begin ( ) , _CurrentHTMLElement - > parent - > Children . end ( ) , * _CurrentHTMLElement ) ;
if ( currentElement = = _CurrentHTMLElement - > parent - > Children . end ( ) )
{
nlwarning ( " BUG: unable to find current element iterator from parent " ) ;
return ;
}
// where fragment should be moved
std : : list < CHtmlElement > : : iterator insertBefore ;
if ( _CurrentHTMLNextSibling = = NULL )
{
insertBefore = _CurrentHTMLElement - > parent - > Children . end ( ) ;
} else {
// get iterator for nextSibling
insertBefore = std : : find ( _CurrentHTMLElement - > parent - > Children . begin ( ) , _CurrentHTMLElement - > parent - > Children . end ( ) , * _CurrentHTMLNextSibling ) ;
}
_CurrentHTMLElement - > parent - > Children . splice ( insertBefore , src - > Children ) ;
// reindex moved elements
CHtmlElement * prev = NULL ;
uint childIndex = _CurrentHTMLElement - > childIndex ;
while ( currentElement ! = _CurrentHTMLElement - > parent - > Children . end ( ) )
{
if ( currentElement - > Type = = CHtmlElement : : ELEMENT_NODE )
{
if ( prev ! = NULL )
{
currentElement - > parent = _CurrentHTMLElement - > parent ;
currentElement - > childIndex = childIndex ;
currentElement - > previousSibling = prev ;
prev - > nextSibling = & ( * currentElement ) ;
}
childIndex + + ;
prev = & ( * currentElement ) ;
}
+ + currentElement ;
}
}
// ***************************************************************************
inline bool isDigit ( char c , uint base = 16 )
{
if ( c > = ' 0 ' & & c < = ' 9 ' ) return true ;
if ( base ! = 16 ) return false ;
if ( c > = ' A ' & & c < = ' F ' ) return true ;
if ( c > = ' a ' & & c < = ' f ' ) return true ;
return false ;
}
// ***************************************************************************
inline char convertHexDigit ( char c )
{
if ( c > = ' 0 ' & & c < = ' 9 ' ) return c - ' 0 ' ;
if ( c > = ' A ' & & c < = ' F ' ) return c - ' A ' + 10 ;
if ( c > = ' a ' & & c < = ' f ' ) return c - ' a ' + 10 ;
return 0 ;
}
// ***************************************************************************
std : : string CGroupHTML : : decodeHTMLEntities ( const std : : string & str )
{
std : : string result ;
result . reserve ( str . size ( ) + ( str . size ( ) > > 2 ) ) ;
uint last , pos ;
for ( uint i = 0 ; i < str . length ( ) ; + + i )
{
// HTML entity
if ( str [ i ] = = ' & ' & & ( str . length ( ) - i ) > = 4 )
{
pos = i + 1 ;
// unicode character
if ( str [ pos ] = = ' # ' )
{
+ + pos ;
// using decimal by default
uint base = 10 ;
// using hexadecimal if &#x
if ( str [ pos ] = = ' x ' )
{
base = 16 ;
+ + pos ;
}
// setup "last" to point at the first character following "&#x?[0-9a-f]+"
for ( last = pos ; last < str . length ( ) ; + + last ) if ( ! isDigit ( str [ last ] , base ) ) break ;
// make sure that at least 1 digit was found
// and have the terminating ';' to complete the token: "&#x?[0-9a-f]+;"
if ( last = = pos | | str [ last ] ! = ' ; ' )
{
result + = str [ i ] ;
continue ;
}
u32char c = 0 ;
// convert digits to unicode character
while ( pos < last ) c = convertHexDigit ( str [ pos + + ] ) + ( c * u32char ( base ) ) ;
// append our new character to the result string
CUtfStringView : : append ( result , c ) ;
// move 'i' forward to point at the ';' .. the for(...) will increment i to point to next char
i = last ;
continue ;
}
// special xml characters
if ( str . substr ( i + 1 , 5 ) = = " quot; " ) { i + = 5 ; result + = ' \" ' ; continue ; }
if ( str . substr ( i + 1 , 4 ) = = " amp; " ) { i + = 4 ; result + = ' & ' ; continue ; }
if ( str . substr ( i + 1 , 3 ) = = " lt; " ) { i + = 3 ; result + = ' < ' ; continue ; }
if ( str . substr ( i + 1 , 3 ) = = " gt; " ) { i + = 3 ; result + = ' > ' ; continue ; }
}
// all the special cases are catered for... treat this as a normal character
result + = str [ i ] ;
}
return result ;
}
// ***************************************************************************
std : : string CGroupHTML : : getAbsoluteUrl ( const std : : string & url )
{
CUrlParser uri ( url ) ;
if ( uri . isAbsolute ( ) )
return url ;
uri . inherit ( _URL ) ;
return uri . toString ( ) ;
}
// ***************************************************************************
void CGroupHTML : : resetCssStyle ( )
{
_WaitingForStylesheet = false ;
_StylesheetQueue . clear ( ) ;
_Style . reset ( ) ;
_Style = _BrowserStyle ;
}
// ***************************************************************************
std : : string CGroupHTML : : HTMLOListElement : : getListMarkerText ( ) const
{
std : : string ret ;
sint32 number = Value ;
if ( Type = = " disc " )
{
// (ucchar)0x2219;
ret = " \xe2 \x88 \x99 " ;
}
else if ( Type = = " circle " )
{
// (uchar)0x26AA;
ret = " \xe2 \x9a \xaa " ;
}
else if ( Type = = " square " )
{
// (ucchar)0x25AA;
ret = " \xe2 \x96 \xaa " ;
}
else if ( Type = = " a " | | Type = = " A " )
{
// @see toAlphabeticOrNumeric in WebKit
static const char lower [ 26 ] = { ' a ' , ' b ' , ' c ' , ' d ' , ' e ' , ' f ' , ' g ' , ' h ' , ' i ' , ' j ' , ' k ' , ' l ' , ' m ' ,
' n ' , ' o ' , ' p ' , ' q ' , ' r ' , ' s ' , ' t ' , ' u ' , ' v ' , ' w ' , ' x ' , ' y ' , ' z ' } ;
static const char upper [ 26 ] = { ' A ' , ' B ' , ' C ' , ' D ' , ' E ' , ' F ' , ' G ' , ' H ' , ' I ' , ' J ' , ' K ' , ' L ' , ' M ' ,
' N ' , ' O ' , ' P ' , ' Q ' , ' R ' , ' S ' , ' T ' , ' U ' , ' V ' , ' W ' , ' X ' , ' Y ' , ' Z ' } ;
uint size = 26 ;
if ( number < 1 )
{
ret = toString ( number ) ;
}
else
{
const char * digits = ( Type = = " A " ? upper : lower ) ;
while ( number > 0 )
{
- - number ;
ret . insert ( ret . begin ( ) , digits [ number % size ] ) ;
number / = size ;
}
}
ret + = " . " ;
}
else if ( Type = = " i " | | Type = = " I " )
{
// @see toRoman in WebKit
static const char lower [ 7 ] = { ' i ' , ' v ' , ' x ' , ' l ' , ' c ' , ' d ' , ' m ' } ;
static const char upper [ 7 ] = { ' I ' , ' V ' , ' X ' , ' L ' , ' C ' , ' D ' , ' M ' } ;
if ( number < 1 | | number > 3999 )
{
ret = toString ( number ) ;
}
else
{
const char * digits = ( Type = = " I " ? upper : lower ) ;
uint8 i , d = 0 ;
do
{
uint32 num = number % 10 ;
if ( num % 5 < 4 )
{
for ( i = num % 5 ; i > 0 ; i - - )
{
ret . insert ( ret . begin ( ) , digits [ d ] ) ;
}
}
if ( num > = 4 & & num < = 8 )
{
ret . insert ( ret . begin ( ) , digits [ d + 1 ] ) ;
}
if ( num = = 9 )
{
ret . insert ( ret . begin ( ) , digits [ d + 2 ] ) ;
}
if ( num % 5 = = 4 )
{
ret . insert ( ret . begin ( ) , digits [ d ] ) ;
}
number / = 10 ;
d + = 2 ;
}
while ( number > 0 ) ;
if ( Type = = " I " )
{
ret = toUpper ( ret ) ;
}
}
ret + = " . " ;
}
else
{
ret = toString ( Value ) + " . " ;
}
return ret ;
}
void CGroupHTML : : HTMLMeterElement : : readValues ( const CHtmlElement & elm )
{
if ( ! elm . hasAttribute ( " value " ) | | ! fromString ( elm . getAttribute ( " value " ) , value ) )
value = 0.f ;
if ( ! elm . hasAttribute ( " min " ) | | ! fromString ( elm . getAttribute ( " min " ) , min ) )
min = 0.f ;
if ( ! elm . hasAttribute ( " max " ) | | ! fromString ( elm . getAttribute ( " max " ) , max ) )
max = 1.f ;
// ensure min < max
if ( max < min )
std : : swap ( min , max ) ;
if ( ! elm . hasAttribute ( " low " ) | | ! fromString ( elm . getAttribute ( " low " ) , low ) )
low = min ;
if ( ! elm . hasAttribute ( " high " ) | | ! fromString ( elm . getAttribute ( " high " ) , high ) )
high = max ;
if ( ! elm . hasAttribute ( " optimum " ) | | ! fromString ( elm . getAttribute ( " optimum " ) , optimum ) )
optimum = ( max - min ) / 2.f ;
// ensure low < high
if ( high < low )
std : : swap ( low , high ) ;
if ( low < min )
low = min ;
if ( high > max )
high = max ;
}
float CGroupHTML : : HTMLMeterElement : : getValueRatio ( ) const
{
if ( max < = min )
return 0.f ;
return ( value - min ) / ( max - min ) ;
}
CGroupHTML : : HTMLMeterElement : : EValueRegion CGroupHTML : : HTMLMeterElement : : getValueRegion ( ) const
{
if ( optimum < = low )
{
// low region is optimum
if ( value < = low )
return VALUE_OPTIMUM ;
else if ( value < = high )
return VALUE_SUB_OPTIMAL ;
return VALUE_EVEN_LESS_GOOD ;
}
else if ( optimum > = high )
{
// high region is optimum
if ( value > = high )
return VALUE_OPTIMUM ;
else if ( value > = low )
return VALUE_SUB_OPTIMAL ;
return VALUE_EVEN_LESS_GOOD ;
}
// middle region is optimum
if ( value > = low & & value < = high )
return VALUE_OPTIMUM ;
return VALUE_SUB_OPTIMAL ;
}
NLMISC : : CRGBA CGroupHTML : : HTMLMeterElement : : getBarColor ( const CHtmlElement & elm , CCssStyle & style ) const
{
// color meter (inactive) bar segment
// firefox:: meter { background:none; background-color: #555; },
// webkit:: meter::-webkit-meter-bar { background:none; background-color: #555; }
// webkit makes background color visible when padding is added
CRGBA color ( 150 , 150 , 150 , 255 ) ;
// use webkit pseudo elements as thats easier than firefox pseudo classes
// background-color is expected to be set from browser.css
style . pushStyle ( ) ;
style . applyStyle ( elm . getPseudo ( " :-webkit-meter-bar " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
style . popStyle ( ) ;
return color ;
}
NLMISC : : CRGBA CGroupHTML : : HTMLMeterElement : : getValueColor ( const CHtmlElement & elm , CCssStyle & style ) const
{
// background-color is expected to be set from browser.css
CRGBA color ;
style . pushStyle ( ) ;
switch ( getValueRegion ( ) )
{
case VALUE_OPTIMUM :
{
style . applyStyle ( elm . getPseudo ( " :-webkit-meter-optimum-value " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
break ;
}
case VALUE_SUB_OPTIMAL :
{
style . applyStyle ( elm . getPseudo ( " :-webkit-meter-suboptimum-value " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
break ;
}
case VALUE_EVEN_LESS_GOOD : // fall through
default :
{
style . applyStyle ( elm . getPseudo ( " :-webkit-meter-even-less-good-value " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
break ;
}
} //switch
style . popStyle ( ) ;
return color ;
}
// ****************************************************************************
void CGroupHTML : : HTMLProgressElement : : readValues ( const CHtmlElement & elm )
{
if ( ! elm . hasAttribute ( " value " ) | | ! fromString ( elm . getAttribute ( " value " ) , value ) )
value = 0.f ;
if ( ! elm . hasAttribute ( " max " ) | | ! fromString ( elm . getAttribute ( " max " ) , max ) )
max = 1.f ;
if ( value > max )
value = max ;
}
// ****************************************************************************
float CGroupHTML : : HTMLProgressElement : : getValueRatio ( ) const
{
if ( max > 0.f )
return value / max ;
return 0.f ;
}
// ****************************************************************************
NLMISC : : CRGBA CGroupHTML : : HTMLProgressElement : : getBarColor ( const CHtmlElement & elm , CCssStyle & style ) const
{
CRGBA color ;
style . pushStyle ( ) ;
style . applyStyle ( elm . getPseudo ( " :-webkit-progress-bar " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
style . popStyle ( ) ;
return color ;
}
// ****************************************************************************
NLMISC : : CRGBA CGroupHTML : : HTMLProgressElement : : getValueColor ( const CHtmlElement & elm , CCssStyle & style ) const
{
CRGBA color ;
style . pushStyle ( ) ;
style . applyStyle ( elm . getPseudo ( " :-webkit-progress-value " ) ) ;
if ( style . hasStyle ( " background-color " ) )
color = style . Current . Background . color ;
style . popStyle ( ) ;
return color ;
}
// ****************************************************************************
void CGroupHTML : : getCellsParameters ( const CHtmlElement & elm , bool inherit )
{
CGroupHTML : : CCellParams cellParams ;
if ( ! _CellParams . empty ( ) & & inherit )
cellParams = _CellParams . back ( ) ;
if ( ! _Style . hasStyle ( " background-color " ) & & elm . hasNonEmptyAttribute ( " bgcolor " ) )
{
CRGBA c ;
if ( scanHTMLColor ( elm . getAttribute ( " bgcolor " ) . c_str ( ) , c ) )
_Style . Current . Background . color = c ;
}
cellParams . BgColor = _Style . Current . Background . color ;
if ( elm . hasAttribute ( " nowrap " ) | | _Style . Current . WhiteSpace = = " nowrap " )
cellParams . NoWrap = true ;
if ( elm . hasNonEmptyAttribute ( " l_margin " ) )
fromString ( elm . getAttribute ( " l_margin " ) , cellParams . LeftMargin ) ;
if ( _Style . hasStyle ( " height " ) )
cellParams . Height = _Style . Current . Height ;
else if ( elm . hasNonEmptyAttribute ( " height " ) )
fromString ( elm . getAttribute ( " height " ) , cellParams . Height ) ;
{
std : : string align ;
// having text-align on table/tr should not override td align attribute
if ( _Style . hasStyle ( " text-align " ) )
align = _Style . Current . TextAlign ;
else if ( elm . hasNonEmptyAttribute ( " align " ) )
align = toLowerAscii ( elm . getAttribute ( " align " ) ) ;
if ( align = = " left " )
cellParams . Align = CGroupCell : : Left ;
else if ( align = = " center " )
cellParams . Align = CGroupCell : : Center ;
else if ( align = = " right " )
cellParams . Align = CGroupCell : : Right ;
else if ( align ! = " justify " )
align . clear ( ) ;
// copy td align (can be empty) attribute back into css
_Style . Current . TextAlign = align ;
}
{
std : : string valign ;
if ( _Style . hasStyle ( " vertical-align " ) )
valign = _Style . Current . VerticalAlign ;
else if ( elm . hasNonEmptyAttribute ( " valign " ) )
valign = toLowerAscii ( elm . getAttribute ( " valign " ) ) ;
if ( valign = = " top " )
cellParams . VAlign = CGroupCell : : Top ;
else if ( valign = = " middle " )
cellParams . VAlign = CGroupCell : : Middle ;
else if ( valign = = " bottom " )
cellParams . VAlign = CGroupCell : : Bottom ;
}
_CellParams . push_back ( cellParams ) ;
}
// ***************************************************************************
void CGroupHTML : : insertFormImageButton ( const std : : string & name , const std : : string & tooltip , const std : : string & src , const std : : string & over , const std : : string & formId , const std : : string & action , uint32 minWidth , const std : : string & templateName )
{
_FormSubmit . push_back ( SFormSubmitButton ( formId , name , " " , " image " , action ) ) ;
// Action handler parameters
std : : string param = " name= " + getId ( ) + " |button= " + toString ( _FormSubmit . size ( ) - 1 ) ;
// Add the ctrl button
addButton ( CCtrlButton : : PushButton , name , src , src , over , " html_submit_form " , param . c_str ( ) , tooltip . c_str ( ) , _Style . Current ) ;
}
// ***************************************************************************
void CGroupHTML : : insertFormTextButton ( const std : : string & name , const std : : string & tooltip , const std : : string & value , const std : : string & formId , const std : : string & formAction , uint32 minWidth , const std : : string & templateName )
{
_FormSubmit . push_back ( SFormSubmitButton ( formId , name , value , " submit " , formAction ) ) ;
// Action handler parameters
string param = " name= " + getId ( ) + " |button= " + toString ( _FormSubmit . size ( ) - 1 ) ;
// Add the ctrl button
if ( ! _Paragraph )
{
newParagraph ( 0 ) ;
paragraphChange ( ) ;
}
string buttonTemplate ( ! templateName . empty ( ) ? templateName : DefaultButtonGroup ) ;
typedef pair < string , string > TTmplParam ;
vector < TTmplParam > tmplParams ;
tmplParams . push_back ( TTmplParam ( " id " , name ) ) ;
tmplParams . push_back ( TTmplParam ( " onclick " , " html_submit_form " ) ) ;
tmplParams . push_back ( TTmplParam ( " onclick_param " , param ) ) ;
tmplParams . push_back ( TTmplParam ( " active " , " true " ) ) ;
if ( minWidth > 0 ) tmplParams . push_back ( TTmplParam ( " wmin " , toString ( minWidth ) ) ) ;
CInterfaceGroup * buttonGroup = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( buttonTemplate , _Paragraph - > getId ( ) , tmplParams ) ;
if ( buttonGroup )
{
// Add the ctrl button
CCtrlTextButton * ctrlButton = dynamic_cast < CCtrlTextButton * > ( buttonGroup - > getCtrl ( " button " ) ) ;
if ( ! ctrlButton ) ctrlButton = dynamic_cast < CCtrlTextButton * > ( buttonGroup - > getCtrl ( " b " ) ) ;
if ( ctrlButton )
{
ctrlButton - > setModulateGlobalColorAll ( _Style . Current . GlobalColor ) ;
ctrlButton - > setTextModulateGlobalColorNormal ( _Style . Current . GlobalColorText ) ;
ctrlButton - > setTextModulateGlobalColorOver ( _Style . Current . GlobalColorText ) ;
ctrlButton - > setTextModulateGlobalColorPushed ( _Style . Current . GlobalColorText ) ;
// Translate the tooltip
if ( ! tooltip . empty ( ) )
{
if ( CI18N : : hasTranslation ( tooltip ) )
{
ctrlButton - > setDefaultContextHelp ( CI18N : : get ( tooltip ) ) ;
}
else
{
ctrlButton - > setDefaultContextHelp ( tooltip ) ;
}
}
ctrlButton - > setText ( value ) ;
setTextButtonStyle ( ctrlButton , _Style . Current ) ;
}
getParagraph ( ) - > addChild ( buttonGroup ) ;
paragraphChange ( ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlA ( const CHtmlElement & elm )
{
_A . push_back ( true ) ;
_Link . push_back ( " " ) ;
_LinkTitle . push_back ( " " ) ;
_LinkClass . push_back ( " " ) ;
if ( elm . hasClass ( " ryzom-ui-button " ) )
_LinkClass . back ( ) = " ryzom-ui-button " ;
// #fragment works with both ID and NAME so register both
if ( elm . hasNonEmptyAttribute ( " name " ) )
_AnchorName . push_back ( elm . getAttribute ( " name " ) ) ;
if ( elm . hasNonEmptyAttribute ( " title " ) )
_LinkTitle . back ( ) = elm . getAttribute ( " title " ) ;
if ( elm . hasNonEmptyAttribute ( " href " ) )
{
string suri = elm . getAttribute ( " href " ) ;
if ( suri . find ( " ah: " ) = = 0 )
{
if ( _TrustedDomain )
_Link . back ( ) = suri ;
}
else
{
// convert href from "?key=val" into "http://domain.com/?key=val"
_Link . back ( ) = getAbsoluteUrl ( suri ) ;
}
}
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlAend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
popIfNotEmpty ( _A ) ;
popIfNotEmpty ( _Link ) ;
popIfNotEmpty ( _LinkTitle ) ;
popIfNotEmpty ( _LinkClass ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlBASE ( const CHtmlElement & elm )
{
if ( ! _ReadingHeadTag | | _IgnoreBaseUrlTag )
return ;
if ( elm . hasNonEmptyAttribute ( " href " ) )
{
CUrlParser uri ( elm . getAttribute ( " href " ) ) ;
if ( uri . isAbsolute ( ) )
{
_URL = uri . toString ( ) ;
_IgnoreBaseUrlTag = true ;
}
}
}
// ***************************************************************************
void CGroupHTML : : htmlBODY ( const CHtmlElement & elm )
{
// override <body> (or <html>) css style attribute
if ( elm . hasNonEmptyAttribute ( " bgcolor " ) )
_Style . applyStyle ( " background-color: " + elm . getAttribute ( " bgcolor " ) ) ;
if ( m_HtmlBackground . isEmpty ( ) )
setupBackground ( & m_HtmlBackground ) ;
else
setupBackground ( & m_BodyBackground ) ;
renderPseudoElement ( " :before " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlBR ( const CHtmlElement & elm )
{
if ( ! _Paragraph | | _Paragraph - > getNumChildren ( ) = = 0 )
{
addString ( " \n " ) ;
}
else
{
endParagraph ( ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlBUTTON ( const CHtmlElement & elm )
{
std : : string name = elm . getAttribute ( " name " ) ;
std : : string value = elm . getAttribute ( " value " ) ;
std : : string formId = elm . getAttribute ( " form " ) ;
std : : string formAction = elm . getAttribute ( " formaction " ) ;
std : : string tooltip = elm . getAttribute ( " tooltip " ) ;
bool disabled = elm . hasAttribute ( " disabled " ) ;
if ( formId . empty ( ) & & _FormOpen )
{
formId = _Forms . back ( ) . id ;
}
if ( ! formAction . empty ( ) )
{
formAction = getAbsoluteUrl ( formAction ) ;
}
_FormSubmit . push_back ( SFormSubmitButton ( formId , name , value , " text " , formAction ) ) ;
// Action handler parameters
std : : string param ;
if ( ! disabled )
{
if ( elm . getAttribute ( " type " ) = = " submit " )
{
param = " ah:html_submit_form&name= " + getId ( ) + " &button= " + toString ( _FormSubmit . size ( ) - 1 ) ;
}
else
{
param = " ah: " ;
}
}
_A . push_back ( true ) ;
_Link . push_back ( param ) ;
_LinkTitle . push_back ( tooltip ) ;
_LinkClass . push_back ( " ryzom-ui-button " ) ;
// TODO: this creates separate button element
//renderPseudoElement(":before", elm);
}
void CGroupHTML : : htmlBUTTONend ( const CHtmlElement & elm )
{
// TODO: this creates separate button element
//renderPseudoElement(":after", elm);
popIfNotEmpty ( _A ) ;
popIfNotEmpty ( _Link ) ;
popIfNotEmpty ( _LinkTitle ) ;
popIfNotEmpty ( _LinkClass ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlDD ( const CHtmlElement & elm )
{
if ( _DL . empty ( ) )
return ;
// if there was no closing tag for <dt>, then remove <dt> style
if ( _DL . back ( ) . DT )
{
nlwarning ( " BUG: nested DT in DD " ) ;
_DL . back ( ) . DT = false ;
}
if ( _DL . back ( ) . DD )
{
nlwarning ( " BUG: nested DD in DD " ) ;
_DL . back ( ) . DD = false ;
popIfNotEmpty ( _Indent ) ;
}
_DL . back ( ) . DD = true ;
_Indent . push_back ( getIndent ( ) + ULIndent ) ;
if ( ! _LI )
{
_LI = true ;
newParagraph ( ULBeginSpace ) ;
}
else
{
newParagraph ( LIBeginSpace ) ;
}
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlDDend ( const CHtmlElement & elm )
{
if ( _DL . empty ( ) )
return ;
renderPseudoElement ( " :after " , elm ) ;
// parser will process two DD in a row as nested when first DD is not closed
if ( _DL . back ( ) . DD )
{
_DL . back ( ) . DD = false ;
popIfNotEmpty ( _Indent ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlDIV ( const CHtmlElement & elm )
{
_DivName = elm . getAttribute ( " name " ) ;
string instClass = elm . getAttribute ( " class " ) ;
// use generic template system
if ( _TrustedDomain & & ! instClass . empty ( ) & & instClass = = " ryzom-ui-grouptemplate " )
{
string style = elm . getAttribute ( " style " ) ;
string id = elm . getAttribute ( " id " ) ;
if ( id . empty ( ) )
id = " DIV " + toString ( getNextAutoIdSeq ( ) ) ;
typedef pair < string , string > TTmplParam ;
vector < TTmplParam > tmplParams ;
string templateName ;
if ( ! style . empty ( ) )
{
TStyle styles = parseStyle ( style ) ;
TStyle : : iterator it ;
for ( it = styles . begin ( ) ; it ! = styles . end ( ) ; it + + )
{
if ( ( * it ) . first = = " template " )
templateName = ( * it ) . second ;
else
tmplParams . push_back ( TTmplParam ( ( * it ) . first , ( * it ) . second ) ) ;
}
}
if ( ! templateName . empty ( ) )
{
string parentId ;
bool haveParentDiv = getDiv ( ) ! = NULL ;
if ( haveParentDiv )
parentId = getDiv ( ) - > getId ( ) ;
else
{
if ( ! _Paragraph )
newParagraph ( 0 ) ;
parentId = _Paragraph - > getId ( ) ;
}
CInterfaceGroup * inst = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( templateName , parentId , tmplParams ) ;
if ( inst )
{
inst - > setId ( parentId + " : " + id ) ;
inst - > updateCoords ( ) ;
if ( haveParentDiv )
{
inst - > setParent ( getDiv ( ) ) ;
inst - > setParentSize ( getDiv ( ) ) ;
inst - > setParentPos ( getDiv ( ) ) ;
inst - > setPosRef ( Hotspot_TL ) ;
inst - > setParentPosRef ( Hotspot_TL ) ;
getDiv ( ) - > addGroup ( inst ) ;
}
else
{
getParagraph ( ) - > addChild ( inst ) ;
paragraphChange ( ) ;
}
_Divs . push_back ( inst ) ;
}
}
}
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlDIVend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
_DivName . clear ( ) ;
popIfNotEmpty ( _Divs ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlDL ( const CHtmlElement & elm )
{
_DL . push_back ( HTMLDListElement ( ) ) ;
_LI = _DL . size ( ) > 1 | | ! _UL . empty ( ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlDLend ( const CHtmlElement & elm )
{
if ( _DL . empty ( ) )
return ;
renderPseudoElement ( " :after " , elm ) ;
// unclosed DT
if ( _DL . back ( ) . DT )
{
nlwarning ( " BUG: unclosed DT in DL " ) ;
}
// unclosed DD
if ( _DL . back ( ) . DD )
{
popIfNotEmpty ( _Indent ) ;
nlwarning ( " BUG: unclosed DD in DL " ) ;
}
popIfNotEmpty ( _DL ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlDT ( const CHtmlElement & elm )
{
if ( _DL . empty ( ) )
return ;
// TODO: check if nested tags still happen and fix it in parser
// : remove special handling for nesting and let it happen
// html parser and libxml2 should prevent nested tags like these
if ( _DL . back ( ) . DD )
{
nlwarning ( " BUG: nested DD in DT " ) ;
_DL . back ( ) . DD = false ;
popIfNotEmpty ( _Indent ) ;
}
// html parser and libxml2 should prevent nested tags like these
if ( _DL . back ( ) . DT )
{
nlwarning ( " BUG: nested DT in DT " ) ;
}
_DL . back ( ) . DT = true ;
if ( ! _LI )
{
_LI = true ;
newParagraph ( ULBeginSpace ) ;
}
else
{
newParagraph ( LIBeginSpace ) ;
}
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlDTend ( const CHtmlElement & elm )
{
if ( _DL . empty ( ) )
return ;
renderPseudoElement ( " :after " , elm ) ;
_DL . back ( ) . DT = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlFONT ( const CHtmlElement & elm )
{
if ( elm . hasNonEmptyAttribute ( " color " ) )
{
CRGBA color ;
if ( scanHTMLColor ( elm . getAttribute ( " color " ) . c_str ( ) , color ) )
_Style . Current . TextColor = color ;
}
if ( elm . hasNonEmptyAttribute ( " size " ) )
{
uint fontsize ;
fromString ( elm . getAttribute ( " size " ) , fontsize ) ;
_Style . Current . FontSize = fontsize ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlFORM ( const CHtmlElement & elm )
{
_FormOpen = true ;
// Build the form
CGroupHTML : : CForm form ;
// id check is case sensitive and auto id's are uppercase
form . id = toLowerAscii ( trim ( elm . getAttribute ( " id " ) ) ) ;
if ( form . id . empty ( ) )
{
form . id = toString ( " FORM%d " , _Forms . size ( ) ) ;
}
// Get the action name
if ( elm . hasNonEmptyAttribute ( " action " ) )
{
form . Action = getAbsoluteUrl ( elm . getAttribute ( " action " ) ) ;
}
else
{
form . Action = _URL ;
}
_Forms . push_back ( form ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlFORMend ( const CHtmlElement & elm )
{
_FormOpen = false ;
renderPseudoElement ( " :after " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlH ( const CHtmlElement & elm )
{
newParagraph ( PBeginSpace ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlHend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlHEAD ( const CHtmlElement & elm )
{
_ReadingHeadTag = ! _IgnoreHeadTag ;
_IgnoreHeadTag = true ;
}
void CGroupHTML : : htmlHEADend ( const CHtmlElement & elm )
{
_ReadingHeadTag = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlHR ( const CHtmlElement & elm )
{
CInterfaceGroup * sep = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( " html_hr " , " " , NULL , 0 ) ;
if ( sep )
{
CViewBitmap * bitmap = dynamic_cast < CViewBitmap * > ( sep - > getView ( " hr " ) ) ;
if ( bitmap )
{
bitmap - > setColor ( _Style . Current . TextColor ) ;
if ( _Style . Current . Width > 0 )
{
clamp ( _Style . Current . Width , 1 , 32000 ) ;
bitmap - > setW ( _Style . Current . Width ) ;
bitmap - > setSizeRef ( CInterfaceElement : : none ) ;
}
if ( _Style . Current . Height > 0 )
{
clamp ( _Style . Current . Height , 1 , 1000 ) ;
bitmap - > setH ( _Style . Current . Height ) ;
}
}
renderPseudoElement ( " :before " , elm ) ;
addHtmlGroup ( sep , 0 ) ;
renderPseudoElement ( " :after " , elm ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlHTML ( const CHtmlElement & elm )
{
if ( elm . hasNonEmptyAttribute ( " style " ) )
_Style . applyStyle ( elm . getAttribute ( " style " ) ) ;
_Style . Root = _Style . Current ;
setupBackground ( & m_HtmlBackground ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlI ( const CHtmlElement & elm )
{
_Localize = true ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlIend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
_Localize = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlIMG ( const CHtmlElement & elm )
{
std : : string src = trim ( elm . getAttribute ( " src " ) ) ;
if ( src . empty ( ) )
{
// no 'src' attribute, or empty
return ;
}
float tmpf ;
std : : string id = elm . getAttribute ( " id " ) ;
if ( elm . hasNonEmptyAttribute ( " width " ) )
getPercentage ( _Style . Current . Width , tmpf , elm . getAttribute ( " width " ) . c_str ( ) ) ;
if ( elm . hasNonEmptyAttribute ( " height " ) )
getPercentage ( _Style . Current . Height , tmpf , elm . getAttribute ( " height " ) . c_str ( ) ) ;
// Get the global color name
if ( elm . hasAttribute ( " global_color " ) )
_Style . Current . GlobalColor = true ;
// Tooltip
// keep "alt" attribute for backward compatibility
std : : string tooltip = elm . getAttribute ( " alt " ) ;
// tooltip
if ( elm . hasNonEmptyAttribute ( " title " ) )
tooltip = elm . getAttribute ( " title " ) ;
// Mouse over image
string overSrc = elm . getAttribute ( " data-over-src " ) ;
// inside a/button with valid url (ie, button is not disabled)
string url = getLink ( ) ;
if ( getA ( ) & & ! url . empty ( ) & & getParent ( ) & & getParent ( ) - > getParent ( ) )
{
string params = " name= " + getId ( ) + " |url= " + url ;
addButton ( CCtrlButton : : PushButton , id , src , src , overSrc , " browse " , params . c_str ( ) , tooltip , _Style . Current ) ;
}
else
if ( ! tooltip . empty ( ) | | ! overSrc . empty ( ) )
{
addButton ( CCtrlButton : : PushButton , id , src , src , overSrc , " " , " " , tooltip , _Style . Current ) ;
}
else
{
// Get the option to reload (class==reload)
bool reloadImg = false ;
if ( elm . hasNonEmptyAttribute ( " style " ) )
{
string styleString = elm . getAttribute ( " style " ) ;
TStyle styles = parseStyle ( styleString ) ;
TStyle : : iterator it ;
it = styles . find ( " reload " ) ;
if ( it ! = styles . end ( ) & & ( * it ) . second = = " 1 " )
reloadImg = true ;
}
addImage ( id , elm . getAttribute ( " src " ) , reloadImg , _Style . Current ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlINPUT ( const CHtmlElement & elm )
{
if ( _Forms . empty ( ) )
return ;
// read general property
string id = elm . getAttribute ( " id " ) ;
// Widget template name (old)
string templateName = elm . getAttribute ( " z_btn_tmpl " ) ;
// Input name is the new
if ( elm . hasNonEmptyAttribute ( " z_input_tmpl " ) )
templateName = elm . getAttribute ( " z_input_tmpl " ) ;
// Widget minimal width
uint32 minWidth = 0 ;
fromString ( elm . getAttribute ( " z_input_width " ) , minWidth ) ;
// <input type="...">
std : : string type = trim ( elm . getAttribute ( " type " ) ) ;
if ( type . empty ( ) )
{
// no 'type' attribute, or empty
return ;
}
// Global color flag
if ( elm . hasAttribute ( " global_color " ) )
_Style . Current . GlobalColor = true ;
// Tooltip
std : : string tooltip = elm . getAttribute ( " alt " ) ;
if ( type = = " image " )
{
string name = elm . getAttribute ( " name " ) ;
string src = elm . getAttribute ( " src " ) ;
string over = elm . getAttribute ( " data-over-src " ) ;
string formId = elm . getAttribute ( " form " ) ;
string formAction = elm . getAttribute ( " formaction " ) ;
if ( formId . empty ( ) & & _FormOpen ) {
formId = _Forms . back ( ) . id ;
}
insertFormImageButton ( name , tooltip , src , over , formId , formAction , minWidth , templateName ) ;
}
else if ( type = = " button " | | type = = " submit " )
{
string name = elm . getAttribute ( " name " ) ;
string value = elm . getAttribute ( " value " ) ;
string formId = elm . getAttribute ( " form " ) ;
string formAction = elm . getAttribute ( " formaction " ) ;
if ( formId . empty ( ) & & _FormOpen ) {
formId = _Forms . back ( ) . id ;
}
insertFormTextButton ( name , tooltip , value , formId , formAction , minWidth , templateName ) ;
}
else if ( type = = " text " )
{
// Get the string name
string name = elm . getAttribute ( " name " ) ;
string ucValue = elm . getAttribute ( " value " ) ;
uint size = 20 ;
uint maxlength = 1024 ;
if ( elm . hasNonEmptyAttribute ( " size " ) )
fromString ( elm . getAttribute ( " size " ) , size ) ;
if ( elm . hasNonEmptyAttribute ( " maxlength " ) )
fromString ( elm . getAttribute ( " maxlength " ) , maxlength ) ;
// ryzom client used to have 'size' attribute in pixels, (12 == was default font size)
if ( _Style . hasStyle ( " -ryzom-input-size-px " ) & & _Style . getStyle ( " -ryzom-input-size-px " ) = = " true " )
size = size / 12 ;
string textTemplate ( ! templateName . empty ( ) ? templateName : DefaultFormTextGroup ) ;
// Add the editbox
CInterfaceGroup * textArea = addTextArea ( textTemplate , name . c_str ( ) , 1 , size , false , ucValue , maxlength ) ;
if ( textArea )
{
// Add the text area to the form
CGroupHTML : : CForm : : CEntry entry ;
entry . Name = name ;
entry . TextArea = textArea ;
_Forms . back ( ) . Entries . push_back ( entry ) ;
}
}
else if ( type = = " checkbox " | | type = = " radio " )
{
renderPseudoElement ( " :before " , elm ) ;
CCtrlButton : : EType btnType ;
string name = elm . getAttribute ( " name " ) ;
string normal = elm . getAttribute ( " src " ) ;
string pushed ;
string over ;
string ucValue = " on " ;
bool checked = elm . hasAttribute ( " checked " ) ;
// TODO: unknown if empty attribute should override or not
if ( elm . hasNonEmptyAttribute ( " value " ) )
ucValue = elm . getAttribute ( " value " ) ;
if ( type = = " radio " )
{
btnType = CCtrlButton : : RadioButton ;
normal = DefaultRadioButtonBitmapNormal ;
pushed = DefaultRadioButtonBitmapPushed ;
over = DefaultRadioButtonBitmapOver ;
}
else
{
btnType = CCtrlButton : : ToggleButton ;
normal = DefaultCheckBoxBitmapNormal ;
pushed = DefaultCheckBoxBitmapPushed ;
over = DefaultCheckBoxBitmapOver ;
}
// Add the ctrl button
CCtrlButton * checkbox = addButton ( btnType , name , normal , pushed , over , " " , " " , tooltip , _Style . Current ) ;
if ( checkbox )
{
if ( btnType = = CCtrlButton : : RadioButton )
{
// override with 'id' because radio buttons share same name
if ( ! id . empty ( ) )
checkbox - > setId ( id ) ;
// group together buttons with same name
CForm & form = _Forms . back ( ) ;
bool notfound = true ;
for ( uint i = 0 ; i < form . Entries . size ( ) ; i + + )
{
if ( form . Entries [ i ] . Name = = name & & form . Entries [ i ] . Checkbox - > getType ( ) = = CCtrlButton : : RadioButton )
{
checkbox - > initRBRefFromRadioButton ( form . Entries [ i ] . Checkbox ) ;
notfound = false ;
break ;
}
}
if ( notfound )
{
// this will start a new group (initRBRef() would take first button in group container otherwise)
checkbox - > initRBRefFromRadioButton ( checkbox ) ;
}
}
checkbox - > setPushed ( checked ) ;
// Add the button to the form
CGroupHTML : : CForm : : CEntry entry ;
entry . Name = name ;
entry . Value = decodeHTMLEntities ( ucValue ) ;
entry . Checkbox = checkbox ;
_Forms . back ( ) . Entries . push_back ( entry ) ;
}
renderPseudoElement ( " :after " , elm ) ;
}
else if ( type = = " hidden " )
{
if ( elm . hasNonEmptyAttribute ( " name " ) )
{
// Get the name
string name = elm . getAttribute ( " name " ) ;
// Get the value
string ucValue = elm . getAttribute ( " value " ) ;
// Add an entry
CGroupHTML : : CForm : : CEntry entry ;
entry . Name = name ;
entry . Value = decodeHTMLEntities ( ucValue ) ;
_Forms . back ( ) . Entries . push_back ( entry ) ;
}
}
}
// ***************************************************************************
void CGroupHTML : : htmlLI ( const CHtmlElement & elm )
{
if ( _UL . empty ( ) )
return ;
// UL, OL top margin if this is the first LI
if ( ! _LI )
{
_LI = true ;
newParagraph ( ULBeginSpace ) ;
}
else
{
newParagraph ( LIBeginSpace ) ;
}
// OL list index can be overridden by <li value="1"> attribute
if ( elm . hasNonEmptyAttribute ( " value " ) )
fromString ( elm . getAttribute ( " value " ) , _UL . back ( ) . Value ) ;
string str = _UL . back ( ) . getListMarkerText ( ) ;
addString ( str ) ;
// list-style-type: outside
if ( _CurrentViewLink )
{
getParagraph ( ) - > setFirstViewIndent ( - _CurrentViewLink - > getMaxUsedW ( ) ) ;
}
flushString ( ) ;
// after marker
renderPseudoElement ( " :before " , elm ) ;
_UL . back ( ) . Value + + ;
}
void CGroupHTML : : htmlLIend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlLUA ( const CHtmlElement & elm )
{
// we receive an embeded lua script
_ParsingLua = _TrustedDomain ; // Only parse lua if TrustedDomain
_LuaScript . clear ( ) ;
}
void CGroupHTML : : htmlLUAend ( const CHtmlElement & elm )
{
if ( _ParsingLua & & _TrustedDomain )
{
_ParsingLua = false ;
// execute the embeded lua script
_LuaScript = " \n local __CURRENT_WINDOW__= \" " + this - > _Id + " \" \n " + _LuaScript ;
CLuaManager : : getInstance ( ) . executeLuaScript ( _LuaScript , true ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlMETA ( const CHtmlElement & elm )
{
if ( ! _ReadingHeadTag )
return ;
std : : string httpEquiv = elm . getAttribute ( " http-equiv " ) ;
std : : string httpContent = elm . getAttribute ( " content " ) ;
if ( httpEquiv . empty ( ) | | httpContent . empty ( ) )
{
return ;
}
// only first http-equiv="refresh" should be handled
if ( _RefreshUrl . empty ( ) & & httpEquiv = = " refresh " )
{
const CWidgetManager : : SInterfaceTimes & times = CWidgetManager : : getInstance ( ) - > getInterfaceTimes ( ) ;
double timeSec = times . thisFrameMs / 1000.0f ;
string : : size_type pos = httpContent . find_first_of ( " ; " ) ;
if ( pos = = string : : npos )
{
fromString ( httpContent , _NextRefreshTime ) ;
_RefreshUrl = _URL ;
}
else
{
fromString ( httpContent . substr ( 0 , pos ) , _NextRefreshTime ) ;
pos = toLowerAscii ( httpContent ) . find ( " url= " ) ;
if ( pos ! = string : : npos )
_RefreshUrl = getAbsoluteUrl ( httpContent . substr ( pos + 4 ) ) ;
}
_NextRefreshTime + = timeSec ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlMETER ( const CHtmlElement & elm )
{
HTMLMeterElement meter ;
meter . readValues ( elm ) ;
std : : string id = " meter " ;
if ( elm . hasAttribute ( " id " ) )
id = elm . getAttribute ( " id " ) ;
// width: 5em, height: 1em
uint32 width = _Style . Current . Width > - 1 ? _Style . Current . Width : _Style . Current . FontSize * 5 ;
uint32 height = _Style . Current . Height > - 1 ? _Style . Current . Height : _Style . Current . FontSize ;
// FIXME: only using border-top
uint32 border = _Style . Current . Border . Top . Width . getValue ( ) > - 1 ? _Style . Current . Border . Top . Width . getValue ( ) : 0 ;
uint barw = ( uint ) ( width * meter . getValueRatio ( ) ) ;
CRGBA bgColor = meter . getBarColor ( elm , _Style ) ;
CRGBA valueColor = meter . getValueColor ( elm , _Style ) ;
typedef pair < string , string > TTmplParam ;
vector < TTmplParam > tmplParams ;
tmplParams . push_back ( TTmplParam ( " id " , id ) ) ;
tmplParams . push_back ( TTmplParam ( " active " , " true " ) ) ;
tmplParams . push_back ( TTmplParam ( " w " , toString ( width ) ) ) ;
tmplParams . push_back ( TTmplParam ( " h " , toString ( height ) ) ) ;
tmplParams . push_back ( TTmplParam ( " border_x2 " , toString ( border * 2 ) ) ) ;
tmplParams . push_back ( TTmplParam ( " bgtexture " , " blank.tga " ) ) ;
tmplParams . push_back ( TTmplParam ( " bgcolor " , bgColor . toString ( ) ) ) ;
tmplParams . push_back ( TTmplParam ( " value_w " , toString ( barw ) ) ) ;
tmplParams . push_back ( TTmplParam ( " value_texture " , " blank.tga " ) ) ;
tmplParams . push_back ( TTmplParam ( " value_color " , valueColor . toString ( ) ) ) ;
CInterfaceGroup * gr = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( " html_meter " , getParagraph ( ) - > getId ( ) , & tmplParams [ 0 ] , ( uint ) tmplParams . size ( ) ) ;
if ( gr )
{
renderPseudoElement ( " :before " , elm ) ;
getParagraph ( ) - > addChild ( gr ) ;
renderPseudoElement ( " :after " , elm ) ;
// ignore any inner elements
_IgnoreChildElements = true ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlOBJECT ( const CHtmlElement & elm )
{
_ObjectType = elm . getAttribute ( " type " ) ;
_ObjectData = elm . getAttribute ( " data " ) ;
_ObjectMD5Sum = elm . getAttribute ( " id " ) ;
_ObjectAction = elm . getAttribute ( " standby " ) ;
_Object = true ;
}
void CGroupHTML : : htmlOBJECTend ( const CHtmlElement & elm )
{
if ( ! _TrustedDomain )
return ;
if ( _ObjectType = = " application/ryzom-data " )
{
if ( ! _ObjectData . empty ( ) )
{
if ( addBnpDownload ( _ObjectData , _ObjectAction , _ObjectScript , _ObjectMD5Sum ) )
{
CLuaManager : : getInstance ( ) . executeLuaScript ( " \n local __ALLREADYDL__=true \n " + _ObjectScript , true ) ;
}
_ObjectScript . clear ( ) ;
}
}
_Object = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlOL ( const CHtmlElement & elm )
{
sint32 start = 1 ;
std : : string type ( " 1 " ) ;
if ( elm . hasNonEmptyAttribute ( " start " ) )
fromString ( elm . getAttribute ( " start " ) , start ) ;
if ( elm . hasNonEmptyAttribute ( " type " ) )
type = elm . getAttribute ( " type " ) ;
_UL . push_back ( HTMLOListElement ( start , type ) ) ;
// if LI is already present
_LI = _UL . size ( ) > 1 | | _DL . size ( ) > 1 ;
_Indent . push_back ( getIndent ( ) + ULIndent ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlOLend ( const CHtmlElement & elm )
{
htmlULend ( elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlOPTION ( const CHtmlElement & elm )
{
_SelectOption = true ;
_SelectOptionStr . clear ( ) ;
// Got one form ?
if ( _Forms . empty ( ) | | _Forms . back ( ) . Entries . empty ( ) )
return ;
_Forms . back ( ) . Entries . back ( ) . SelectValues . push_back ( elm . getAttribute ( " value " ) ) ;
if ( elm . hasAttribute ( " selected " ) )
_Forms . back ( ) . Entries . back ( ) . InitialSelection = ( sint ) _Forms . back ( ) . Entries . back ( ) . SelectValues . size ( ) - 1 ;
if ( elm . hasAttribute ( " disabled " ) )
_Forms . back ( ) . Entries . back ( ) . sbOptionDisabled = ( sint ) _Forms . back ( ) . Entries . back ( ) . SelectValues . size ( ) - 1 ;
}
void CGroupHTML : : htmlOPTIONend ( const CHtmlElement & elm )
{
if ( _Forms . empty ( ) | | _Forms . back ( ) . Entries . empty ( ) )
return ;
// use option text as value
if ( ! elm . hasAttribute ( " value " ) )
{
_Forms . back ( ) . Entries . back ( ) . SelectValues . back ( ) = _SelectOptionStr ;
}
// insert the parsed text into the select control
CDBGroupComboBox * cb = _Forms . back ( ) . Entries . back ( ) . ComboBox ;
if ( cb )
{
uint lineIndex = cb - > getNumTexts ( ) ;
cb - > addText ( _SelectOptionStr ) ;
if ( _Forms . back ( ) . Entries . back ( ) . sbOptionDisabled = = lineIndex )
{
cb - > setGrayed ( lineIndex , true ) ;
}
}
else
{
CGroupMenu * sb = _Forms . back ( ) . Entries . back ( ) . SelectBox ;
if ( sb )
{
uint lineIndex = sb - > getNumLine ( ) ;
sb - > addLine ( _SelectOptionStr , " " , " " ) ;
if ( _Forms . back ( ) . Entries . back ( ) . sbOptionDisabled = = lineIndex )
{
sb - > setGrayedLine ( lineIndex , true ) ;
}
else
{
// create option line checkbox, CGroupMenu is taking ownership of the checbox
CInterfaceGroup * ig = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( " menu_checkbox " , " " , NULL , 0 ) ;
if ( ig )
{
CCtrlButton * cb = dynamic_cast < CCtrlButton * > ( ig - > getCtrl ( " b " ) ) ;
if ( cb )
{
if ( _Forms . back ( ) . Entries . back ( ) . sbMultiple )
{
cb - > setType ( CCtrlButton : : ToggleButton ) ;
cb - > setTexture ( DefaultCheckBoxBitmapNormal ) ;
cb - > setTexturePushed ( DefaultCheckBoxBitmapPushed ) ;
cb - > setTextureOver ( DefaultCheckBoxBitmapOver ) ;
}
else
{
cb - > setType ( CCtrlButton : : RadioButton ) ;
cb - > setTexture ( DefaultRadioButtonBitmapNormal ) ;
cb - > setTexturePushed ( DefaultRadioButtonBitmapPushed ) ;
cb - > setTextureOver ( DefaultRadioButtonBitmapOver ) ;
if ( _Forms . back ( ) . Entries . back ( ) . sbRBRef = = NULL )
_Forms . back ( ) . Entries . back ( ) . sbRBRef = cb ;
cb - > initRBRefFromRadioButton ( _Forms . back ( ) . Entries . back ( ) . sbRBRef ) ;
}
cb - > setPushed ( _Forms . back ( ) . Entries . back ( ) . InitialSelection = = lineIndex ) ;
sb - > setUserGroupLeft ( lineIndex , ig ) ;
}
else
{
nlwarning ( " Failed to get 'b' element from 'menu_checkbox' template " ) ;
delete ig ;
}
}
}
}
}
}
// ***************************************************************************
void CGroupHTML : : htmlP ( const CHtmlElement & elm )
{
newParagraph ( PBeginSpace ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlPend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlPRE ( const CHtmlElement & elm )
{
_PRE . push_back ( true ) ;
newParagraph ( 0 ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlPREend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
popIfNotEmpty ( _PRE ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlPROGRESS ( const CHtmlElement & elm )
{
HTMLProgressElement progress ;
progress . readValues ( elm ) ;
std : : string id = " progress " ;
if ( elm . hasAttribute ( " id " ) )
id = elm . getAttribute ( " id " ) ;
// width: 10em, height: 1em
uint32 width = _Style . Current . Width > - 1 ? _Style . Current . Width : _Style . Current . FontSize * 10 ;
uint32 height = _Style . Current . Height > - 1 ? _Style . Current . Height : _Style . Current . FontSize ;
// FIXME: only using border-top
uint32 border = _Style . Current . Border . Top . Width . getValue ( ) > - 1 ? _Style . Current . Border . Top . Width . getValue ( ) : 0 ;
uint barw = ( uint ) ( width * progress . getValueRatio ( ) ) ;
CRGBA bgColor = progress . getBarColor ( elm , _Style ) ;
CRGBA valueColor = progress . getValueColor ( elm , _Style ) ;
typedef pair < string , string > TTmplParam ;
vector < TTmplParam > tmplParams ;
tmplParams . push_back ( TTmplParam ( " id " , id ) ) ;
tmplParams . push_back ( TTmplParam ( " active " , " true " ) ) ;
tmplParams . push_back ( TTmplParam ( " w " , toString ( width ) ) ) ;
tmplParams . push_back ( TTmplParam ( " h " , toString ( height ) ) ) ;
tmplParams . push_back ( TTmplParam ( " border_x2 " , toString ( border * 2 ) ) ) ;
tmplParams . push_back ( TTmplParam ( " bgtexture " , " blank.tga " ) ) ;
tmplParams . push_back ( TTmplParam ( " bgcolor " , bgColor . toString ( ) ) ) ;
tmplParams . push_back ( TTmplParam ( " value_w " , toString ( barw ) ) ) ;
tmplParams . push_back ( TTmplParam ( " value_texture " , " blank.tga " ) ) ;
tmplParams . push_back ( TTmplParam ( " value_color " , valueColor . toString ( ) ) ) ;
CInterfaceGroup * gr = CWidgetManager : : getInstance ( ) - > getParser ( ) - > createGroupInstance ( " html_progress " , getParagraph ( ) - > getId ( ) , & tmplParams [ 0 ] , ( uint ) tmplParams . size ( ) ) ;
if ( gr )
{
renderPseudoElement ( " :before " , elm ) ;
getParagraph ( ) - > addChild ( gr ) ;
renderPseudoElement ( " :after " , elm ) ;
// ignore any inner elements
_IgnoreChildElements = true ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlSCRIPT ( const CHtmlElement & elm )
{
_IgnoreText = true ;
}
void CGroupHTML : : htmlSCRIPTend ( const CHtmlElement & elm )
{
_IgnoreText = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlSELECT ( const CHtmlElement & elm )
{
if ( _Forms . empty ( ) )
return ;
// A select box
string name = elm . getAttribute ( " name " ) ;
bool multiple = elm . hasAttribute ( " multiple " ) ;
sint32 size = 0 ;
if ( elm . hasNonEmptyAttribute ( " size " ) )
fromString ( elm . getAttribute ( " size " ) , size ) ;
CGroupHTML : : CForm : : CEntry entry ;
entry . Name = name ;
entry . sbMultiple = multiple ;
if ( size > 1 | | multiple )
{
entry . InitialSelection = - 1 ;
CGroupMenu * sb = addSelectBox ( DefaultFormSelectBoxMenuGroup , name . c_str ( ) ) ;
if ( sb )
{
if ( size < 1 )
size = 4 ;
if ( _Style . Current . Width > - 1 )
sb - > setMinW ( _Style . Current . Width ) ;
if ( _Style . Current . Height > - 1 )
sb - > setMinH ( _Style . Current . Height ) ;
sb - > setMaxVisibleLine ( size ) ;
sb - > setFontSize ( _Style . Current . FontSize , false ) ;
}
entry . SelectBox = sb ;
}
else
{
CDBGroupComboBox * cb = addComboBox ( DefaultFormSelectGroup , name . c_str ( ) ) ;
entry . ComboBox = cb ;
if ( cb )
{
// create view text
cb - > updateCoords ( ) ;
setTextStyle ( cb - > getViewText ( ) , _Style . Current ) ;
}
}
_Forms . back ( ) . Entries . push_back ( entry ) ;
}
void CGroupHTML : : htmlSELECTend ( const CHtmlElement & elm )
{
_SelectOption = false ;
if ( _Forms . empty ( ) | | _Forms . back ( ) . Entries . empty ( ) )
return ;
CDBGroupComboBox * cb = _Forms . back ( ) . Entries . back ( ) . ComboBox ;
if ( cb )
{
cb - > setSelectionNoTrigger ( _Forms . back ( ) . Entries . back ( ) . InitialSelection ) ;
// TODO: magic padding
cb - > setW ( cb - > evalContentWidth ( ) + 16 ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlSTYLE ( const CHtmlElement & elm )
{
_IgnoreText = true ;
}
void CGroupHTML : : htmlSTYLEend ( const CHtmlElement & elm )
{
_IgnoreText = false ;
}
// ***************************************************************************
void CGroupHTML : : htmlTABLE ( const CHtmlElement & elm )
{
// Get cells parameters
getCellsParameters ( elm , false ) ;
CGroupTable * table = new CGroupTable ( TCtorParam ( ) ) ;
if ( elm . hasNonEmptyAttribute ( " id " ) )
table - > setId ( getCurrentGroup ( ) - > getId ( ) + " : " + elm . getAttribute ( " id " ) ) ;
else
table - > setId ( getCurrentGroup ( ) - > getId ( ) + " :TABLE " + toString ( getNextAutoIdSeq ( ) ) ) ;
// TODO: border-spacing: 2em;
{
if ( elm . hasNonEmptyAttribute ( " cellspacing " ) )
fromString ( elm . getAttribute ( " cellspacing " ) , table - > CellSpacing ) ;
// TODO: cssLength, horiz/vert values
if ( _Style . hasStyle ( " border-spacing " ) )
fromString ( _Style . getStyle ( " border-spacing " ) , table - > CellSpacing ) ;
// overrides border-spacing if set to 'collapse'
if ( _Style . checkStyle ( " border-collapse " , " collapse " ) )
table - > CellSpacing = 0 ;
}
if ( elm . hasNonEmptyAttribute ( " cellpadding " ) )
fromString ( elm . getAttribute ( " cellpadding " ) , table - > CellPadding ) ;
if ( _Style . hasStyle ( " width " ) )
{
// _Style.Width does not handle '%' unit currently
if ( _Style . Current . Width > 0 )
{
table - > ForceWidthMin = _Style . Current . Width ;
table - > TableRatio = 0 ;
}
else
{
getPercentage ( table - > ForceWidthMin , table - > TableRatio , _Style . getStyle ( " width " ) . c_str ( ) ) ;
}
}
else if ( elm . hasNonEmptyAttribute ( " width " ) )
{
getPercentage ( table - > ForceWidthMin , table - > TableRatio , elm . getAttribute ( " width " ) . c_str ( ) ) ;
}
// border from css or from attribute
{
CSSRect < CSSBorder > border ;
border . Top . Color = _Style . Current . TextColor ;
border . Right . Color = _Style . Current . TextColor ;
border . Bottom . Color = _Style . Current . TextColor ;
border . Left . Color = _Style . Current . TextColor ;
if ( elm . hasAttribute ( " border " ) )
{
uint32 borderWidth = 0 ;
CRGBA borderColor = CRGBA : : Transparent ;
std : : string s = elm . getAttribute ( " border " ) ;
if ( s . empty ( ) )
borderWidth = 1 ;
else
fromString ( elm . getAttribute ( " border " ) , borderWidth ) ;
if ( elm . hasNonEmptyAttribute ( " bordercolor " ) )
scanHTMLColor ( elm . getAttribute ( " bordercolor " ) . c_str ( ) , borderColor ) ;
else
borderColor = CRGBA ( 128 , 128 , 128 , 255 ) ;
table - > CellBorder = ( borderWidth > 0 ) ;
border . Top . set ( borderWidth , CSS_LINE_STYLE_OUTSET , borderColor ) ;
border . Right . set ( borderWidth , CSS_LINE_STYLE_OUTSET , borderColor ) ;
border . Bottom . set ( borderWidth , CSS_LINE_STYLE_OUTSET , borderColor ) ;
border . Left . set ( borderWidth , CSS_LINE_STYLE_OUTSET , borderColor ) ;
}
if ( _Style . hasStyle ( " border-top-width " ) ) border . Top . Width = _Style . Current . Border . Top . Width ;
if ( _Style . hasStyle ( " border-right-width " ) ) border . Right . Width = _Style . Current . Border . Right . Width ;
if ( _Style . hasStyle ( " border-bottom-width " ) ) border . Bottom . Width = _Style . Current . Border . Bottom . Width ;
if ( _Style . hasStyle ( " border-left-width " ) ) border . Left . Width = _Style . Current . Border . Left . Width ;
if ( _Style . hasStyle ( " border-top-color " ) ) border . Top . Color = _Style . Current . Border . Top . Color ;
if ( _Style . hasStyle ( " border-right-color " ) ) border . Right . Color = _Style . Current . Border . Right . Color ;
if ( _Style . hasStyle ( " border-bottom-color " ) ) border . Bottom . Color = _Style . Current . Border . Bottom . Color ;
if ( _Style . hasStyle ( " border-left-color " ) ) border . Left . Color = _Style . Current . Border . Left . Color ;
if ( _Style . hasStyle ( " border-top-style " ) ) border . Top . Style = _Style . Current . Border . Top . Style ;
if ( _Style . hasStyle ( " border-right-style " ) ) border . Right . Style = _Style . Current . Border . Right . Style ;
if ( _Style . hasStyle ( " border-bottom-style " ) ) border . Bottom . Style = _Style . Current . Border . Bottom . Style ;
if ( _Style . hasStyle ( " border-left-style " ) ) border . Left . Style = _Style . Current . Border . Left . Style ;
table - > Border - > setBorder ( border ) ;
table - > Border - > setFontSize ( _Style . Root . FontSize , _Style . Current . FontSize ) ;
table - > Border - > setViewport ( getList ( ) - > getParentPos ( ) ? getList ( ) - > getParentPos ( ) : this ) ;
}
setupBackground ( table - > Background ) ;
table - > setModulateGlobalColor ( _Style . Current . GlobalColor ) ;
table - > setMarginLeft ( getIndent ( ) ) ;
addHtmlGroup ( table , 0 ) ;
renderPseudoElement ( " :before " , elm ) ;
_Tables . push_back ( table ) ;
// Add a cell pointer
_Cells . push_back ( NULL ) ;
_TR . push_back ( false ) ;
_Indent . push_back ( 0 ) ;
}
void CGroupHTML : : htmlTABLEend ( const CHtmlElement & elm )
{
popIfNotEmpty ( _CellParams ) ;
popIfNotEmpty ( _TR ) ;
popIfNotEmpty ( _Cells ) ;
popIfNotEmpty ( _Tables ) ;
popIfNotEmpty ( _Indent ) ;
renderPseudoElement ( " :after " , elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlTD ( const CHtmlElement & elm )
{
// Get cells parameters
getCellsParameters ( elm , true ) ;
if ( ! m_TableRowBackgroundColor . empty ( ) & & m_TableRowBackgroundColor . back ( ) . A > 0 )
_Style . Current . Background . color . blendFromui ( m_TableRowBackgroundColor . back ( ) , _Style . Current . Background . color , _Style . Current . Background . color . A ) ;
if ( elm . ID = = HTML_TH )
{
if ( ! _Style . hasStyle ( " font-weight " ) )
_Style . Current . FontWeight = FONT_WEIGHT_BOLD ;
// center if not specified otherwise.
if ( ! elm . hasNonEmptyAttribute ( " align " ) & & ! _Style . hasStyle ( " text-align " ) )
_CellParams . back ( ) . Align = CGroupCell : : Center ;
}
CGroupTable * table = getTable ( ) ;
if ( ! table )
{
// <td> appears to be outside <table>
return ;
}
if ( _Cells . empty ( ) )
{
// <table> not started
return ;
}
_Cells . back ( ) = new CGroupCell ( CViewBase : : TCtorParam ( ) ) ;
if ( elm . hasNonEmptyAttribute ( " id " ) )
_Cells . back ( ) - > setId ( table - > getId ( ) + " : " + elm . getAttribute ( " id " ) ) ;
else
_Cells . back ( ) - > setId ( table - > getId ( ) + " :TD " + toString ( getNextAutoIdSeq ( ) ) ) ;
// inner cell content
_Cells . back ( ) - > Group - > setId ( _Cells . back ( ) - > getId ( ) + " :CELL " ) ;
setupBackground ( _Cells . back ( ) - > Background ) ;
_Cells . back ( ) - > setModulateGlobalColor ( _Style . Current . GlobalColor ) ;
if ( elm . hasNonEmptyAttribute ( " colspan " ) )
fromString ( elm . getAttribute ( " colspan " ) , _Cells . back ( ) - > ColSpan ) ;
if ( elm . hasNonEmptyAttribute ( " rowspan " ) )
fromString ( elm . getAttribute ( " rowspan " ) , _Cells . back ( ) - > RowSpan ) ;
_Cells . back ( ) - > Align = _CellParams . back ( ) . Align ;
_Cells . back ( ) - > VAlign = _CellParams . back ( ) . VAlign ;
_Cells . back ( ) - > LeftMargin = _CellParams . back ( ) . LeftMargin ;
_Cells . back ( ) - > NoWrap = _CellParams . back ( ) . NoWrap ;
_Cells . back ( ) - > ColSpan = std : : max ( 1 , _Cells . back ( ) - > ColSpan ) ;
_Cells . back ( ) - > RowSpan = std : : max ( 1 , _Cells . back ( ) - > RowSpan ) ;
_Cells . back ( ) - > Height = _CellParams . back ( ) . Height ;
float temp ;
if ( _Style . hasStyle ( " width " ) )
{
// _Style.Width does not handle '%' unit currently
if ( _Style . Current . Width > 0 )
{
_Cells . back ( ) - > WidthWanted = _Style . Current . Width ;
_Cells . back ( ) - > TableRatio = 0 ;
}
else
{
getPercentage ( _Cells . back ( ) - > WidthWanted , _Cells . back ( ) - > TableRatio , _Style . getStyle ( " width " ) . c_str ( ) ) ;
}
}
else if ( elm . hasNonEmptyAttribute ( " width " ) )
{
getPercentage ( _Cells . back ( ) - > WidthWanted , _Cells . back ( ) - > TableRatio , elm . getAttribute ( " width " ) . c_str ( ) ) ;
}
_Cells . back ( ) - > NewLine = getTR ( ) ;
CSSRect < CSSBorder > border ;
border . Top . set ( table - > CellBorder ? 1 : 0 , CSS_LINE_STYLE_INSET , _Style . Current . TextColor ) ;
border . Right . set ( table - > CellBorder ? 1 : 0 , CSS_LINE_STYLE_INSET , _Style . Current . TextColor ) ;
border . Bottom . set ( table - > CellBorder ? 1 : 0 , CSS_LINE_STYLE_INSET , _Style . Current . TextColor ) ;
border . Left . set ( table - > CellBorder ? 1 : 0 , CSS_LINE_STYLE_INSET , _Style . Current . TextColor ) ;
if ( _Style . hasStyle ( " border-top-width " ) ) border . Top . Width = _Style . Current . Border . Top . Width ;
if ( _Style . hasStyle ( " border-right-width " ) ) border . Right . Width = _Style . Current . Border . Right . Width ;
if ( _Style . hasStyle ( " border-bottom-width " ) ) border . Bottom . Width = _Style . Current . Border . Bottom . Width ;
if ( _Style . hasStyle ( " border-left-width " ) ) border . Left . Width = _Style . Current . Border . Left . Width ;
if ( _Style . hasStyle ( " border-top-color " ) ) border . Top . Color = _Style . Current . Border . Top . Color ;
if ( _Style . hasStyle ( " border-right-color " ) ) border . Right . Color = _Style . Current . Border . Right . Color ;
if ( _Style . hasStyle ( " border-bottom-color " ) ) border . Bottom . Color = _Style . Current . Border . Bottom . Color ;
if ( _Style . hasStyle ( " border-left-color " ) ) border . Left . Color = _Style . Current . Border . Left . Color ;
if ( _Style . hasStyle ( " border-top-style " ) ) border . Top . Style = _Style . Current . Border . Top . Style ;
if ( _Style . hasStyle ( " border-right-style " ) ) border . Right . Style = _Style . Current . Border . Right . Style ;
if ( _Style . hasStyle ( " border-bottom-style " ) ) border . Bottom . Style = _Style . Current . Border . Bottom . Style ;
if ( _Style . hasStyle ( " border-left-style " ) ) border . Left . Style = _Style . Current . Border . Left . Style ;
_Cells . back ( ) - > Border - > setBorder ( border ) ;
_Cells . back ( ) - > Border - > setFontSize ( _Style . Root . FontSize , _Style . Current . FontSize ) ;
_Cells . back ( ) - > Border - > setViewport ( getList ( ) - > getParentPos ( ) ? getList ( ) - > getParentPos ( ) : this ) ;
// padding from <table cellpadding="1">
if ( table - > CellPadding )
{
// FIXME: padding is ignored by vertical align
_Cells . back ( ) - > PaddingTop = table - > CellPadding ;
_Cells . back ( ) - > PaddingRight = table - > CellPadding ;
_Cells . back ( ) - > PaddingBottom = table - > CellPadding ;
_Cells . back ( ) - > PaddingLeft = table - > CellPadding ;
}
if ( _Style . hasStyle ( " padding-top " ) ) _Cells . back ( ) - > PaddingTop = _Style . Current . PaddingTop ;
if ( _Style . hasStyle ( " padding-right " ) ) _Cells . back ( ) - > PaddingRight = _Style . Current . PaddingRight ;
if ( _Style . hasStyle ( " padding-bottom " ) ) _Cells . back ( ) - > PaddingBottom = _Style . Current . PaddingBottom ;
if ( _Style . hasStyle ( " padding-left " ) ) _Cells . back ( ) - > PaddingLeft = _Style . Current . PaddingLeft ;
table - > addChild ( _Cells . back ( ) ) ;
// reusing indent pushed by table
_Indent . back ( ) = 0 ;
newParagraph ( TDBeginSpace ) ;
// indent is already 0, getParagraph()->setMarginLeft(0); // maybe setIndent(0) if LI is using one
// Reset TR flag
if ( ! _TR . empty ( ) )
_TR . back ( ) = false ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlTDend ( const CHtmlElement & elm )
{
renderPseudoElement ( " :after " , elm ) ;
popIfNotEmpty ( _CellParams ) ;
if ( ! _Cells . empty ( ) )
_Cells . back ( ) = NULL ;
}
// ***************************************************************************
void CGroupHTML : : htmlTEXTAREA ( const CHtmlElement & elm )
{
_IgnoreChildElements = true ;
// TODO: allow textarea without form
if ( _Forms . empty ( ) )
return ;
// read general property
string templateName ;
// Widget template name
if ( elm . hasNonEmptyAttribute ( " z_input_tmpl " ) )
templateName = elm . getAttribute ( " z_input_tmpl " ) ;
// Get the string name
_TextAreaName . clear ( ) ;
_TextAreaRow = 1 ;
_TextAreaCols = 10 ;
_TextAreaMaxLength = 1024 ;
if ( elm . hasNonEmptyAttribute ( " name " ) )
_TextAreaName = elm . getAttribute ( " name " ) ;
if ( elm . hasNonEmptyAttribute ( " rows " ) )
fromString ( elm . getAttribute ( " rows " ) , _TextAreaRow ) ;
if ( elm . hasNonEmptyAttribute ( " cols " ) )
fromString ( elm . getAttribute ( " cols " ) , _TextAreaCols ) ;
if ( elm . hasNonEmptyAttribute ( " maxlength " ) )
fromString ( elm . getAttribute ( " maxlength " ) , _TextAreaMaxLength ) ;
_TextAreaTemplate = ! templateName . empty ( ) ? templateName : DefaultFormTextAreaGroup ;
std : : string content = strFindReplaceAll ( elm . serializeChilds ( false ) , std : : string ( " \r " ) , std : : string ( " " ) ) ;
CInterfaceGroup * textArea = addTextArea ( _TextAreaTemplate , _TextAreaName . c_str ( ) , _TextAreaRow , _TextAreaCols , true , content , _TextAreaMaxLength ) ;
if ( textArea )
{
// Add the text area to the form
CGroupHTML : : CForm : : CEntry entry ;
entry . Name = _TextAreaName ;
entry . TextArea = textArea ;
_Forms . back ( ) . Entries . push_back ( entry ) ;
}
}
// ***************************************************************************
void CGroupHTML : : htmlTH ( const CHtmlElement & elm )
{
htmlTD ( elm ) ;
}
void CGroupHTML : : htmlTHend ( const CHtmlElement & elm )
{
htmlTDend ( elm ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlTITLE ( const CHtmlElement & elm )
{
_IgnoreChildElements = true ;
// TODO: only from <head>
// if (!_ReadingHeadTag) return;
// consume all child elements
_TitleString = strFindReplaceAll ( elm . serializeChilds ( false ) , std : : string ( " \t " ) , std : : string ( " " ) ) ;
_TitleString = strFindReplaceAll ( _TitleString , std : : string ( " \n " ) , std : : string ( " " ) ) ;
setTitle ( _TitleString ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlTR ( const CHtmlElement & elm )
{
// prevent inheriting from table
if ( ! _CellParams . empty ( ) )
{
_CellParams . back ( ) . BgColor = CRGBA : : Transparent ;
_CellParams . back ( ) . Height = 0 ;
}
// Get cells parameters
getCellsParameters ( elm , true ) ;
m_TableRowBackgroundColor . push_back ( _CellParams . back ( ) . BgColor ) ;
_CellParams . back ( ) . BgColor = CRGBA : : Transparent ;
// TODO: this probably ends up in first cell
renderPseudoElement ( " :before " , elm ) ;
// Set TR flag
if ( ! _TR . empty ( ) )
_TR . back ( ) = true ;
}
void CGroupHTML : : htmlTRend ( const CHtmlElement & elm )
{
// TODO: this probably ends up in last cell
renderPseudoElement ( " :after " , elm ) ;
popIfNotEmpty ( _CellParams ) ;
popIfNotEmpty ( m_TableRowBackgroundColor ) ;
}
// ***************************************************************************
void CGroupHTML : : htmlUL ( const CHtmlElement & elm )
{
if ( _UL . empty ( ) )
_UL . push_back ( HTMLOListElement ( 1 , " disc " ) ) ;
else if ( _UL . size ( ) = = 1 )
_UL . push_back ( HTMLOListElement ( 1 , " circle " ) ) ;
else
_UL . push_back ( HTMLOListElement ( 1 , " square " ) ) ;
// if LI is already present
_LI = _UL . size ( ) > 1 | | _DL . size ( ) > 1 ;
_Indent . push_back ( getIndent ( ) + ULIndent ) ;
renderPseudoElement ( " :before " , elm ) ;
}
void CGroupHTML : : htmlULend ( const CHtmlElement & elm )
{
if ( _UL . empty ( ) )
return ;
renderPseudoElement ( " :after " , elm ) ;
popIfNotEmpty ( _UL ) ;
popIfNotEmpty ( _Indent ) ;
}
}