diff --git a/.hgeol b/.hgeol new file mode 100644 index 000000000..a9766f577 --- /dev/null +++ b/.hgeol @@ -0,0 +1,28 @@ +[patterns] +**.h = native +**.cpp = native + +**/database.xml = BIN +**/msg.xml = BIN + +**.txt = native +**.xml = native + +**.layout = native +**.looknfeel = native +**.imageset = native +**.font = native +**.scheme = native + +**.tpl = native + +**.xsd = native +**.dox = native + +**.py = native +**.lua = native + +**.pkg = native + +[repository] +native = LF diff --git a/.hgignore b/.hgignore new file mode 100644 index 000000000..41a0e58cf --- /dev/null +++ b/.hgignore @@ -0,0 +1,265 @@ +syntax: glob + +# Various build directories +bin +obj +Debug +Release +ReleaseDebug +DebugFast +ReleaseDebugStatic +DebugFastStatic + +# Test and application directories +screenshots +release +test +Temp + +# NeL cache +*.packed_sheets + +# Ryzom save +save_shard +last_loaded_char.bin +*.binprim +*.string_cache +graphs_output +default_c + +# Windows compile +*.exe +*.dll +*.lib +*.obj + +# Linux compile +*.a +*.la +*.lo +*.Po +*.Plo +*.o +*.so +*.so.* +*_debug +*.pc +*.gch + +# Mac OS X compile +*.dylib + +# Log dump files +report_refused +report_failed +exception_catched +*.stat +*.log +log.txt + +# Max plugin extensions +*.dlx +*.dlm +*.dlu + +# makeall build +.mode_static + +# cmake build files & directories +CMakeFiles +CMakeCache.txt +cmake_install.cmake +CTestTestfile.cmake +CPackConfig.cmake +CPackSourceConfig.cmake +.libs + +# Linux garbage +Makefile* +aclocal.m4 +config.guess +config.sub +configure +depcomp +config.h.in +nelconfig.h.in +install-sh +ltmain.sh +missing +ylwrap +*.mk + +# Visual Studio garbage +*.opensdf +UpgradeLog*.XML +_UpgradeReport_Files +BuildLog.htm +mt.dep +ipch +*.suo +*.ncb +*.user +*.ilk +*.pdb +*.aps +*.exp +*.idb +*.sdf + +# Mac OS X garbage +.DS_Store + +# Ryzom server garbage +aes_alias_name.cfg +aes_nagios_report.txt +aes_state.txt +*.launch_ctrl +*.state +*.start_count + +# Vim and kwrite cache +*~ + +# Kdevelop4 garbage +*.kdev4 +.kdev4 + +# intellij project folder +.idea/ + +# Python cache +*.pyd +*.pyc + +# Qt compiler +moc_*.cpp +*.moc + +# Misc garbage +*.rej +*.orig +*.cachefile +*.cache +*.patch +*.7z +3rdParty +.svn +thumbs.db +Thumbs.db +*.tpl.php +.SyncID +.SyncIgnore +.SyncArchive + +# build +code/nel/build/* +code/nelns/build/* +code/snowballs/build/* +code/ryzom/build/* +code/build/* +code/build-2010/* +build/* +install/* +build_vc* +code/nel/tools/build_gamedata/configuration/buildsite.py + +# Linux nel compile +code/nel/build/nel-config +code/nel/config.status +code/nel/include/nelconfig.h +code/nel/include/stamp-h1 +code/nel/libtool +code/nel/nel-config +code/nel/samples/3d/cluster_viewer/cluster_viewer +code/nel/samples/3d/font/font +code/nel/samples/georges/georges +code/nel/samples/misc/command/command +code/nel/samples/misc/configfile/configfile +code/nel/samples/misc/debug/debug +code/nel/samples/misc/i18n/i18n +code/nel/samples/misc/log/log +code/nel/samples/misc/strings/strings +code/nel/samples/net/chat/chatclient +code/nel/samples/net/chat/chatserver +code/nel/samples/net/login_system/nls_frontend_service +code/nel/samples/net/login_system/nls_login_client +code/nel/samples/net/udp/udp_bench_client +code/nel/samples/net/udp/udp_bench_service +code/nel/samples/pacs/pacs_sample +code/nel/tools/3d/build_coarse_mesh/build_coarse_mesh +code/nel/tools/3d/build_far_bank/build_far_bank +code/nel/tools/3d/build_smallbank/build_smallbank +code/nel/tools/3d/ig_lighter/ig_lighter +code/nel/tools/3d/zone_dependencies/zone_dependencies +code/nel/tools/3d/zone_ig_lighter/zone_ig_lighter +code/nel/tools/3d/zone_lighter/zone_lighter +code/nel/tools/3d/zone_welder/zone_welder +code/nel/tools/misc/bnp_make/bnp_make +code/nel/tools/misc/disp_sheet_id/disp_sheet_id +code/nel/tools/misc/make_sheet_id/make_sheet_id +code/nel/tools/misc/xml_packer/xml_packer +code/nel/tools/pacs/build_ig_boxes/build_ig_boxes +code/nel/tools/pacs/build_indoor_rbank/build_indoor_rbank +code/nel/tools/pacs/build_rbank/build_rbank +code/ryzom/common/data_leveldesign/leveldesign/game_element/xp_table/skills.skill_tree +code/ryzom/common/data_leveldesign/leveldesign/game_element/xp_table/xptable.xp_table +code/ryzom/tools/server/sql/ryzom_admin_default_data.sql + + +# Linux server compile +code/ryzom/server/src/entities_game_service/entities_game_service +code/ryzom/server/src/frontend_service/frontend_service +code/ryzom/server/src/gpm_service/gpm_service +code/ryzom/server/src/input_output_service/input_output_service +code/ryzom/server/src/mirror_service/mirror_service +code/ryzom/server/src/ryzom_admin_service/ryzom_admin_service +code/ryzom/server/src/ryzom_naming_service/ryzom_naming_service +code/ryzom/server/src/ryzom_welcome_service/ryzom_welcome_service +code/ryzom/server/src/tick_service/tick_service +# WebTT temp dir +code/ryzom/tools/server/www/webtt/app/tmp + +# AMS ignore +code/web/public_php/ams/is_installed +code/web/docs/ams/html +code/web/public_php/ams/templates_c +code/ryzom/tools/server/ryzom_ams/drupal +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/autoload +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/configs +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/cron +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/img +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/plugins +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/smarty +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/translations +code/ryzom/tools/server/ryzom_ams/drupal_module/ryzommanage/ams_lib/libinclude.php +code/ryzom/tools/server/ryzom_ams/old + + + +#tools and external dir's +external +external_stlport +nel_tools* +ryzom_tools* + +#Dumps +*.dmp + +code/nel/tools/build_gamedata/processes/ai_wmap/ai_build_wmap.cfg +code/nel/tools/build_gamedata/processes/sheets/sheets_packer.cfg +code/nel/tools/build_gamedata/processes/rbank/build_rbank.cfg +code/nel/tools/build_gamedata/processes/zone/debug_zone_dependencies.cfg +code/web/public_php/config.php +code/web/public_php/is_installed +code/web/public_php/ams/files +code/web/public_php/db_version_lib +code/web/public_php/db_version_shard +code/web/public_php/db_version_tool +code/web/public_php/db_version_web +code/web/public_php/role_service +code/web/public_php/role_support +code/web/public_php/role_domain +code/web/public_php/db_version_ring +code/web/public_php/config_user.php +code/nel/tools/build_gamedata/processes/pz/build_world_packed_col.cfg +code/nel/tools/build_gamedata/processes/cartographer/island_screenshots.cfg diff --git a/.hgtags b/.hgtags new file mode 100644 index 000000000..7176f5560 --- /dev/null +++ b/.hgtags @@ -0,0 +1,18 @@ +950d650ca92e6041611258d7e5131ccf661e4ec2 compatibility-merge +4eddbaff0c5e5d685db96ee3e8427aa0fd96ac83 ryzomcore/v0.8.0 +00d9b6e29e95f56785fbf85abe60afd34674f402 ryzomcore/v0.9.0 +79776c337176dd5b02e1a74fe5dfb703b91747aa ryzomcore/v0.9.1 +fedf2aa443d09707beed814b0f499c6a5519cc84 ryzomcore/v0.10.0 +edaa3624a56420b02ccc64c26059801a389927ee ryzomcore/v0.11.0 +e3fe4855f22c3e75722e015dc33c091c340b3ad7 ryzomcore/v0.11.1 +9e583b717fd63be0be9fd60b99087abf1691ea49 ryzomcore/v0.11.2 +bfe5628e14a024ba7ea32e4b326ae433a07856b9 ryzomcore/v0.11.3 +9a6120735daa97c96ac5d85ca35c7f21f607bd87 ryzomcore/v0.12.0 +3e17907af67e8d66d80e6b714707bbf912607f2a ryzom-patch-3.0.0 +153e0b605c9e0c83ba05b6428c62838b49cc84b2 ryzom-patch-3.0.1 +9d41f2994d44b9aad92b83f945f114e4b6bed44a ryzom-patch-3.0.2 +4300cc14aad098b1f86ea4c55577b7fa4a4cb5d2 ryzom-patch-3.1.0 +d4060f217f4f834cc62a33f2f1ccdf3c28298066 ryzom-patch-3.1.0-hotfix +043aaeb3d8a2a54177581b57bda87a9deaad510e ryzom-patch-3.1.0-april_patch +4036ecf59e83960f03acebc2089eb2ff5eeaed0a ryzom-patch-3.2.0 +18403bb9485da3d9742c6f007a16d5619ebfb196 ryzom-patch-3.2.1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c59df07eb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,48 @@ +sudo: false +language: cpp + +compiler: + - gcc + +os: + - linux +matrix: + fast_finish: true +env: + - CMAKE_CONFIGURE_OPTIONS="-DWITH_NEL_TESTS=OFF -DWITH_LUA51=ON" + - CMAKE_CONFIGURE_OPTIONS="-DCPPTEST_LIBRARY_DEBUG:STRING=/usr/lib/libcpptest.so" + CMAKE_BUILD_OPTIONS="--target nel_unit_test -- -j 2" + RUN="build/bin/nel_unit_test" + +addons: + apt: + packages: + - liblua5.1-dev + - libluabind-dev + - libcpptest-dev + - libogg-dev + - libvorbis-dev + - libopenal-dev + - libgif-dev + - libfreetype6-dev + - libxml2-dev + +before_script: + - mkdir build + - cmake --version + - cmake -Hcode -Bbuild $CMAKE_CONFIGURE_OPTIONS + - cat build/CMakeCache.txt + +script: + - cmake --build build $CMAKE_BUILD_OPTIONS + - $RUN + +notifications: + irc: + channels: + - $NOTIFICATION_IRC_CHANNEL + template: + - "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}" + - "Description : %{commit_message}" + - "Change view : %{compare_url}" + - "Build details : %{build_url}" diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4e5d4bfb5..c708b9dda 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -28,10 +28,6 @@ IF(COMMAND cmake_policy) # have absolute paths (e.g. -lpthread) cmake_policy(SET CMP0003 NEW) - # Works around warnings about escaped quotes in ADD_DEFINITIONS - # statements - cmake_policy(SET CMP0005 OLD) - # allow to link to qtmain automatically under Windows IF(POLICY CMP0020) CMAKE_POLICY(SET CMP0020 NEW) @@ -64,8 +60,8 @@ SET(YEAR "2004-${CURRENT_YEAR}") SET(AUTHOR "Winch Gate and The Ryzom Core Community") SET(RYZOM_VERSION_MAJOR 3) -SET(RYZOM_VERSION_MINOR 1) -SET(RYZOM_VERSION_PATCH 0) +SET(RYZOM_VERSION_MINOR 2) +SET(RYZOM_VERSION_PATCH 1) #----------------------------------------------------------------------------- # Redirect output files @@ -168,25 +164,30 @@ IF(WITH_SSE2) ENDIF() ENDIF() -IF(APPLE_CERTIFICATE) - # Find codesign_allocate +IF(APPLE) + FIND_LIBRARY(CARBON_FRAMEWORK Carbon) + FIND_LIBRARY(FOUNDATION_FRAMEWORK Foundation) - # Xcode 7.0 and later versions - SET(CODESIGN_ALLOCATE ${OSX_DEVELOPER_ROOT}/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate) + IF(APPLE_CERTIFICATE) + # Find codesign_allocate - IF(NOT EXISTS "${CODESIGN_ALLOCATE}") - # Xcode 6.4 and previous versions - SET(CODESIGN_ALLOCATE ${CMAKE_OSX_SYSROOT}/usr/bin/codesign_allocate) - ENDIF() + # Xcode 7.0 and later versions + SET(CODESIGN_ALLOCATE ${OSX_DEVELOPER_ROOT}/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate) - IF(NOT EXISTS "${CODESIGN_ALLOCATE}") - # System path - SET(CODESIGN_ALLOCATE /usr/bin/codesign_allocate) - ENDIF() + IF(NOT EXISTS "${CODESIGN_ALLOCATE}") + # Xcode 6.4 and previous versions + SET(CODESIGN_ALLOCATE ${CMAKE_OSX_SYSROOT}/usr/bin/codesign_allocate) + ENDIF() - IF(NOT EXISTS "${CODESIGN_ALLOCATE}") - MESSAGE(WARNING "Unable to find codesign_allocate in standard directories") - SET(CODESIGN_ALLOCATE) + IF(NOT EXISTS "${CODESIGN_ALLOCATE}") + # System path + SET(CODESIGN_ALLOCATE /usr/bin/codesign_allocate) + ENDIF() + + IF(NOT EXISTS "${CODESIGN_ALLOCATE}") + MESSAGE(WARNING "Unable to find codesign_allocate in standard directories") + SET(CODESIGN_ALLOCATE) + ENDIF() ENDIF() ENDIF() @@ -336,7 +337,7 @@ IF(WITH_QT5) ENDIF() # freetype is needed since Qt 5.5 - FIND_PACKAGE(FreeType) + FIND_PACKAGE(Freetype) IF(FREETYPE_FOUND) SET(QT_LIBRARIES ${QT_LIBRARIES} ${FREETYPE_LIBRARIES}) @@ -382,8 +383,6 @@ IF(WITH_QT5) IF(APPLE) FIND_LIBRARY(PCRE_LIBRARY pcre16 pcre) - FIND_LIBRARY(FOUNDATION_FRAMEWORK Foundation) - FIND_LIBRARY(CARBON_FRAMEWORK Carbon) FIND_LIBRARY(SECURITY_FRAMEWORK Security) SET(QT_LIBRARIES ${QT_LIBRARIES} @@ -448,6 +447,8 @@ IF(WITH_NEL) SET(CURL_LIBRARIES ${CURL_LIBRARIES} ${INTL_LIBRARY}) ENDIF() ENDIF() + ELSEIF(WIN32) + SET(CURL_LIBRARIES ${CURL_LIBRARIES} Crypt32.lib) ENDIF() ENDIF() ENDIF() diff --git a/code/CMakeModules/AndroidToolChain.cmake b/code/CMakeModules/AndroidToolChain.cmake index 7135400e2..4ca702295 100644 --- a/code/CMakeModules/AndroidToolChain.cmake +++ b/code/CMakeModules/AndroidToolChain.cmake @@ -28,14 +28,14 @@ IF(TARGET_CPU STREQUAL "armv7") SET(TOOLCHAIN_ARCH "arm") SET(GCC_TOOLCHAIN_PREFIX "arm-linux-androideabi") SET(TOOLCHAIN_BIN_PREFIX "arm-linux-androideabi") - SET(MINIMUM_NDK_TARGET 4) + SET(MINIMUM_NDK_TARGET 9) ELSEIF(TARGET_CPU STREQUAL "armv5") SET(LIBRARY_ARCHITECTURE "armeabi") SET(CMAKE_SYSTEM_PROCESSOR "armv5") SET(TOOLCHAIN_ARCH "arm") SET(GCC_TOOLCHAIN_PREFIX "arm-linux-androideabi") SET(TOOLCHAIN_BIN_PREFIX "arm-linux-androideabi") - SET(MINIMUM_NDK_TARGET 4) + SET(MINIMUM_NDK_TARGET 9) ELSEIF(TARGET_CPU STREQUAL "arm64") SET(LIBRARY_ARCHITECTURE "arm64-v8a") SET(CMAKE_SYSTEM_PROCESSOR "arm64") @@ -76,14 +76,9 @@ ELSE() ENDIF() SET(CLANG_TOOLCHAIN_PREFIX "llvm") -SET(ANDROID_COMPILER "GCC") - -IF(NDK_TOOLCHAIN_VERSION STREQUAL "clang") - SET(ANDROID_COMPILER "clang") - SET(CLANG ON) -ELSE() - SET(GCC_TOOLCHAIN_VERSION ${NDK_TOOLCHAIN_VERSION}) -ENDIF() +SET(ANDROID_COMPILER "clang") +SET(ANDROID_COMPILER "clang") +SET(CLANG ON) IF(NOT NDK_TARGET) SET(NDK_TARGET ${MINIMUM_NDK_TARGET}) @@ -94,88 +89,71 @@ ELSE() ENDIF() IF(CMAKE_HOST_WIN32) - SET(TOOLCHAIN_HOST "windows") + SET(TOOLCHAIN_HOST "windows-x86_64") SET(TOOLCHAIN_BIN_SUFFIX ".exe") ELSEIF(CMAKE_HOST_APPLE) SET(TOOLCHAIN_HOST "apple") SET(TOOLCHAIN_BIN_SUFFIX "") ELSEIF(CMAKE_HOST_UNIX) - SET(TOOLCHAIN_HOST "linux") + SET(TOOLCHAIN_HOST "linux-x86_64") SET(TOOLCHAIN_BIN_SUFFIX "") ENDIF() -MACRO(SEARCH_TOOLCHAIN _COMPILER) - SET(${_COMPILER}_TOOLCHAIN_VERSIONS) - FILE(GLOB _TOOLCHAIN_VERSIONS "${NDK_ROOT}/toolchains/${${_COMPILER}_TOOLCHAIN_PREFIX}-*") - IF(_TOOLCHAIN_VERSIONS) - LIST(SORT _TOOLCHAIN_VERSIONS) - LIST(REVERSE _TOOLCHAIN_VERSIONS) - FOREACH(_TOOLCHAIN_VERSION ${_TOOLCHAIN_VERSIONS}) - STRING(REGEX REPLACE ".+${_PREFIX}-([0-9.]+)" "\\1" _TOOLCHAIN_VERSION "${_TOOLCHAIN_VERSION}") - IF(_TOOLCHAIN_VERSION MATCHES "^([0-9.]+)$") - LIST(APPEND ${_COMPILER}_TOOLCHAIN_VERSIONS ${_TOOLCHAIN_VERSION}) - ENDIF() - ENDFOREACH() - ENDIF() +# clang +SET(CLANG_TOOLCHAIN_ROOT "${NDK_ROOT}/toolchains/${CLANG_TOOLCHAIN_PREFIX}/prebuilt/${TOOLCHAIN_HOST}") - # try prefixes without version - SET(_TOOLCHAIN_WITHOUT_VERSION "${NDK_ROOT}/toolchains/${${_COMPILER}_TOOLCHAIN_PREFIX}") - IF(EXISTS ${_TOOLCHAIN_WITHOUT_VERSION}) - LIST(APPEND ${_COMPILER}_TOOLCHAIN_VERSIONS "default") - ENDIF() +IF(EXISTS ${CLANG_TOOLCHAIN_ROOT}) + MESSAGE(STATUS "Found LLVM toolchain in ${CLANG_TOOLCHAIN_ROOT}") +ELSE() + MESSAGE(FATAL_ERROR "No LLVM toolchain found in default search path ${CLANG_TOOLCHAIN_ROOT}") +ENDIF() - IF(NOT ${_COMPILER}_TOOLCHAIN_VERSIONS) - MESSAGE(FATAL_ERROR "No Android ${_COMPILER} toolchain found in default search path ${NDK_ROOT}/toolchains") - ENDIF() - - IF(${_COMPILER}_TOOLCHAIN_VERSIONS) - LIST(FIND ${_COMPILER}_TOOLCHAIN_VERSIONS "${${_COMPILER}_TOOLCHAIN_VERSION}" _INDEX) - IF(_INDEX EQUAL -1) - LIST(GET ${_COMPILER}_TOOLCHAIN_VERSIONS 0 ${_COMPILER}_TOOLCHAIN_VERSION) +# gcc +SET(GCC_TOOLCHAIN_VERSIONS) +FILE(GLOB _TOOLCHAIN_VERSIONS "${NDK_ROOT}/toolchains/${GCC_TOOLCHAIN_PREFIX}-*") +IF(_TOOLCHAIN_VERSIONS) + LIST(SORT _TOOLCHAIN_VERSIONS) + LIST(REVERSE _TOOLCHAIN_VERSIONS) + FOREACH(_TOOLCHAIN_VERSION ${_TOOLCHAIN_VERSIONS}) + STRING(REGEX REPLACE ".+${_PREFIX}-([0-9.]+)" "\\1" _TOOLCHAIN_VERSION "${_TOOLCHAIN_VERSION}") + IF(_TOOLCHAIN_VERSION MATCHES "^([0-9.]+)$") + LIST(APPEND GCC_TOOLCHAIN_VERSIONS ${_TOOLCHAIN_VERSION}) ENDIF() - ELSE() - LIST(GET ${_COMPILER}_TOOLCHAIN_VERSIONS 0 ${_COMPILER}_TOOLCHAIN_VERSION) - ENDIF() - - MESSAGE(STATUS "TOOLCHAIN_PREFIX = ${${_COMPILER}_TOOLCHAIN_VERSION}") - - IF("${${_COMPILER}_TOOLCHAIN_VERSION}" STREQUAL "default") - MESSAGE(STATUS "default") - SET(${_COMPILER}_TOOLCHAIN_ROOT "${NDK_ROOT}/toolchains/${${_COMPILER}_TOOLCHAIN_PREFIX}/prebuilt/${TOOLCHAIN_HOST}") - ELSE() - MESSAGE(STATUS "not default") - SET(${_COMPILER}_TOOLCHAIN_ROOT "${NDK_ROOT}/toolchains/${${_COMPILER}_TOOLCHAIN_PREFIX}-${${_COMPILER}_TOOLCHAIN_VERSION}/prebuilt/${TOOLCHAIN_HOST}") - ENDIF() - - IF(NOT EXISTS "${${_COMPILER}_TOOLCHAIN_ROOT}") - FILE(GLOB _TOOLCHAIN_PREFIXES "${${_COMPILER}_TOOLCHAIN_ROOT}*") - IF(_TOOLCHAIN_PREFIXES) - LIST(GET _TOOLCHAIN_PREFIXES 0 ${_COMPILER}_TOOLCHAIN_ROOT) - ENDIF() - ENDIF() -ENDMACRO() - -IF(CLANG) - SEARCH_TOOLCHAIN(CLANG) - - MESSAGE(STATUS "Target Android NDK ${NDK_TARGET} and use clang ${CLANG_TOOLCHAIN_VERSION}") + ENDFOREACH() ENDIF() -SEARCH_TOOLCHAIN(GCC) - -MESSAGE(STATUS "Target Android NDK ${NDK_TARGET} and use GCC ${GCC_TOOLCHAIN_VERSION}") - -IF(CLANG_TOOLCHAIN_ROOT) - MESSAGE(STATUS "Found Android LLVM toolchain in ${CLANG_TOOLCHAIN_ROOT}") +IF(NOT GCC_TOOLCHAIN_VERSIONS) + MESSAGE(FATAL_ERROR "No GCC version found in default search path ${NDK_ROOT}/toolchains") ENDIF() -IF(GCC_TOOLCHAIN_ROOT) - MESSAGE(STATUS "Found Android GCC toolchain in ${GCC_TOOLCHAIN_ROOT}") +IF(GCC_TOOLCHAIN_VERSIONS) + LIST(FIND GCC_TOOLCHAIN_VERSIONS "${GCC_TOOLCHAIN_VERSION}" _INDEX) + IF(_INDEX EQUAL -1) + LIST(GET GCC_TOOLCHAIN_VERSIONS 0 GCC_TOOLCHAIN_VERSION) + ENDIF() +ELSE() + LIST(GET GCC_TOOLCHAIN_VERSIONS 0 GCC_TOOLCHAIN_VERSION) ENDIF() +SET(GCC_TOOLCHAIN_ROOT "${NDK_ROOT}/toolchains/${GCC_TOOLCHAIN_PREFIX}-${GCC_TOOLCHAIN_VERSION}/prebuilt/${TOOLCHAIN_HOST}") + +IF(NOT EXISTS "${GCC_TOOLCHAIN_ROOT}") + FILE(GLOB _TOOLCHAIN_PREFIXES "${GCC_TOOLCHAIN_ROOT}*") + IF(_TOOLCHAIN_PREFIXES) + LIST(GET _TOOLCHAIN_PREFIXES 0 GCC_TOOLCHAIN_ROOT) + ENDIF() +ENDIF() + +IF(EXISTS "${GCC_TOOLCHAIN_ROOT}") + MESSAGE(STATUS "Found GCC toolchain in ${GCC_TOOLCHAIN_ROOT}") +ELSE() + MESSAGE(FATAL_ERROR "No GCC toolchain found in default search path ${GCC_TOOLCHAIN_ROOT}") +ENDIF() + +# NDK SET(PLATFORM_ROOT "${NDK_ROOT}/platforms/android-${NDK_TARGET}/arch-${TOOLCHAIN_ARCH}") -MESSAGE(STATUS "Found Android platform in ${PLATFORM_ROOT}") +MESSAGE(STATUS "Target Android NDK ${NDK_TARGET} found in ${PLATFORM_ROOT}") # include dirs SET(PLATFORM_INCLUDE_DIR "${PLATFORM_ROOT}/usr/include") @@ -208,41 +186,29 @@ MACRO(SET_TOOLCHAIN_BINARY_GCC _NAME _BINARY) SET(${_NAME} ${GCC_TOOLCHAIN_ROOT}/bin/${TOOLCHAIN_BIN_PREFIX}-${_BINARY}${TOOLCHAIN_BIN_SUFFIX} CACHE PATH "" FORCE) ENDMACRO() -# Force the compilers to GCC for Android -include (CMakeForceCompiler) +SET(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -IF(CLANG) - MESSAGE(STATUS "Using clang compiler") +SET_TOOLCHAIN_BINARY_LLVM(CMAKE_C_COMPILER clang) +SET_TOOLCHAIN_BINARY_LLVM(CMAKE_CXX_COMPILER clang++) - SET_TOOLCHAIN_BINARY_LLVM(CMAKE_C_COMPILER clang) - SET_TOOLCHAIN_BINARY_LLVM(CMAKE_CXX_COMPILER clang++) +SET(CMAKE_C_COMPILER ${CMAKE_C_COMPILER}) +SET(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN_BIN_PREFIX}) +SET(CMAKE_C_COMPILER_FORCED TRUE) - CMAKE_FORCE_C_COMPILER(${CMAKE_C_COMPILER} clang) - CMAKE_FORCE_CXX_COMPILER(${CMAKE_CXX_COMPILER} clang) +SET(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER}) +SET(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN_BIN_PREFIX}) +SET(CMAKE_CXX_COMPILER_FORCED TRUE) - SET_TOOLCHAIN_BINARY_LLVM(CMAKE_ASM_COMPILER llvm-as) - SET_TOOLCHAIN_BINARY_LLVM(CMAKE_AR llvm-ar) - SET_TOOLCHAIN_BINARY_LLVM(CMAKE_LINKER clang++) - - IF(NOT EXISTS "${CMAKE_ASM_COMPILER}") - SET_TOOLCHAIN_BINARY_GCC(CMAKE_ASM_COMPILER as) - ENDIF() - - IF(NOT EXISTS "${CMAKE_AR}") - SET_TOOLCHAIN_BINARY_GCC(CMAKE_AR ar) - ENDIF() -ELSE() - MESSAGE(STATUS "Using GCC compiler") - - SET_TOOLCHAIN_BINARY_GCC(CMAKE_C_COMPILER gcc) - SET_TOOLCHAIN_BINARY_GCC(CMAKE_CXX_COMPILER g++) - - CMAKE_FORCE_C_COMPILER(${CMAKE_C_COMPILER} GNU) - CMAKE_FORCE_CXX_COMPILER(${CMAKE_CXX_COMPILER} GNU) +SET_TOOLCHAIN_BINARY_LLVM(CMAKE_ASM_COMPILER llvm-as) +SET_TOOLCHAIN_BINARY_LLVM(CMAKE_AR llvm-ar) +SET_TOOLCHAIN_BINARY_LLVM(CMAKE_LINKER clang++) +IF(NOT EXISTS "${CMAKE_ASM_COMPILER}") SET_TOOLCHAIN_BINARY_GCC(CMAKE_ASM_COMPILER as) +ENDIF() + +IF(NOT EXISTS "${CMAKE_AR}") SET_TOOLCHAIN_BINARY_GCC(CMAKE_AR ar) - SET_TOOLCHAIN_BINARY_GCC(CMAKE_LINKER ld) ENDIF() SET_TOOLCHAIN_BINARY_GCC(CMAKE_STRIP strip) diff --git a/code/CMakeModules/FindMSVC.cmake b/code/CMakeModules/FindMSVC.cmake index dceb6f054..af44f6676 100644 --- a/code/CMakeModules/FindMSVC.cmake +++ b/code/CMakeModules/FindMSVC.cmake @@ -1,114 +1,168 @@ -# - Find MS Visual C++ -# -# VC_INCLUDE_DIR - where to find headers -# VC_INCLUDE_DIRS - where to find headers -# VC_LIBRARY_DIR - where to find libraries -# VC_FOUND - True if MSVC found. - -MACRO(DETECT_VC_VERSION_HELPER _ROOT _VERSION) - # Software/Wow6432Node/... - GET_FILENAME_COMPONENT(VC${_VERSION}_DIR "[${_ROOT}\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7;${_VERSION}]" ABSOLUTE) - - IF(VC${_VERSION}_DIR AND VC${_VERSION}_DIR STREQUAL "/registry") - SET(VC${_VERSION}_DIR) - GET_FILENAME_COMPONENT(VC${_VERSION}_DIR "[${_ROOT}\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7;${_VERSION}]" ABSOLUTE) - IF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") - SET(VC${_VERSION}_DIR "${VC${_VERSION}_DIR}VC/") - ENDIF() - ENDIF() - - IF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") - SET(VC${_VERSION}_FOUND ON) - DETECT_EXPRESS_VERSION(${_VERSION}) - IF(NOT MSVC_FIND_QUIETLY) - SET(_VERSION_STR ${_VERSION}) - IF(MSVC_EXPRESS) - SET(_VERSION_STR "${_VERSION_STR} Express") - ENDIF() - MESSAGE(STATUS "Found Visual C++ ${_VERSION_STR} in ${VC${_VERSION}_DIR}") - ENDIF() - ELSEIF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") - SET(VC${_VERSION}_FOUND OFF) - SET(VC${_VERSION}_DIR "") - ENDIF() -ENDMACRO() - -MACRO(DETECT_VC_VERSION _VERSION) - SET(VC${_VERSION}_FOUND OFF) - DETECT_VC_VERSION_HELPER("HKEY_CURRENT_USER" ${_VERSION}) - - IF(NOT VC${_VERSION}_FOUND) - DETECT_VC_VERSION_HELPER("HKEY_LOCAL_MACHINE" ${_VERSION}) - ENDIF() - - IF(VC${_VERSION}_FOUND) - SET(VC_FOUND ON) - SET(VC_DIR "${VC${_VERSION}_DIR}") - ENDIF() -ENDMACRO() - -MACRO(DETECT_EXPRESS_VERSION _VERSION) - GET_FILENAME_COMPONENT(MSVC_EXPRESS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress\\${_VERSION}\\Setup\\VC;ProductDir]" ABSOLUTE) - - IF(MSVC_EXPRESS AND NOT MSVC_EXPRESS STREQUAL "/registry") - SET(MSVC_EXPRESS ON) - ENDIF() -ENDMACRO() - -IF(MSVC14) - DETECT_VC_VERSION("14.0") - SET(MSVC_TOOLSET "140") - - IF(NOT MSVC14_REDIST_DIR) - # If you have VC++ 2015 Express, put x64/Microsoft.VC140.CRT/*.dll in ${EXTERNAL_PATH}/redist - SET(MSVC14_REDIST_DIR "${EXTERNAL_PATH}/redist") - ENDIF() -ELSEIF(MSVC12) - DETECT_VC_VERSION("12.0") - SET(MSVC_TOOLSET "120") - - IF(NOT MSVC12_REDIST_DIR) - # If you have VC++ 2013 Express, put x64/Microsoft.VC120.CRT/*.dll in ${EXTERNAL_PATH}/redist - SET(MSVC12_REDIST_DIR "${EXTERNAL_PATH}/redist") - ENDIF() -ELSEIF(MSVC11) - DETECT_VC_VERSION("11.0") - SET(MSVC_TOOLSET "110") - - IF(NOT MSVC11_REDIST_DIR) - # If you have VC++ 2012 Express, put x64/Microsoft.VC110.CRT/*.dll in ${EXTERNAL_PATH}/redist - SET(MSVC11_REDIST_DIR "${EXTERNAL_PATH}/redist") - ENDIF() -ELSEIF(MSVC10) - DETECT_VC_VERSION("10.0") - SET(MSVC_TOOLSET "100") - - IF(NOT MSVC10_REDIST_DIR) - # If you have VC++ 2010 Express, put x64/Microsoft.VC100.CRT/*.dll in ${EXTERNAL_PATH}/redist - SET(MSVC10_REDIST_DIR "${EXTERNAL_PATH}/redist") - ENDIF() -ELSEIF(MSVC90) - DETECT_VC_VERSION("9.0") - SET(MSVC_TOOLSET "90") -ELSEIF(MSVC80) - DETECT_VC_VERSION("8.0") - SET(MSVC_TOOLSET "80") -ENDIF() - -# If you plan to use VC++ compilers with WINE, set VC_DIR environment variable -IF(NOT VC_DIR) - SET(VC_DIR $ENV{VC_DIR}) -ENDIF() - -IF(NOT VC_DIR) - IF(CMAKE_CXX_COMPILER) - SET(_COMPILER ${CMAKE_CXX_COMPILER}) - ELSE() - SET(_COMPILER ${CMAKE_C_COMPILER}) - ENDIF() - STRING(REGEX REPLACE "/(bin|BIN|Bin)/.+" "" VC_DIR ${_COMPILER}) -ENDIF() - -SET(VC_INCLUDE_DIR "${VC_DIR}/include") -SET(VC_INCLUDE_DIRS ${VC_INCLUDE_DIR}) -INCLUDE_DIRECTORIES(${VC_INCLUDE_DIR}) +# - Find MS Visual C++ +# +# VC_INCLUDE_DIR - where to find headers +# VC_INCLUDE_DIRS - where to find headers +# VC_LIBRARY_DIR - where to find libraries +# VC_FOUND - True if MSVC found. + +MACRO(ADD_TRAILING_SLASH _FILENAME_VAR) + # put content in a new variable + SET(_FILENAME ${${_FILENAME_VAR}}) + # get length of the string + STRING(LENGTH ${_FILENAME} _LEN) + # convert length to last pos + MATH(EXPR _POS "${_LEN}-1") + # get last character of the string + STRING(SUBSTRING ${_FILENAME} ${_POS} 1 _FILENAME_END) + # compare it with a slash + IF(NOT _FILENAME_END STREQUAL "/") + # not a slash, append it + SET(${_FILENAME_VAR} "${_FILENAME}/") + ELSE() + # already a slash + ENDIF() +ENDMACRO() + +MACRO(DETECT_VC_VERSION_HELPER _ROOT _VERSION) + # Software/Wow6432Node/... + GET_FILENAME_COMPONENT(VC${_VERSION}_DIR "[${_ROOT}\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7;${_VERSION}]" ABSOLUTE) + + IF(VC${_VERSION}_DIR AND VC${_VERSION}_DIR STREQUAL "/registry") + SET(VC${_VERSION}_DIR) + GET_FILENAME_COMPONENT(VC${_VERSION}_DIR "[${_ROOT}\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7;${_VERSION}]" ABSOLUTE) + + IF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") + # be sure it's finishing by a / + ADD_TRAILING_SLASH(VC${_VERSION}_DIR) + + SET(VC${_VERSION}_DIR "${VC${_VERSION}_DIR}VC/") + ENDIF() + ENDIF() + + IF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") + SET(VC${_VERSION}_FOUND ON) + DETECT_EXPRESS_VERSION(${_VERSION}) + IF(NOT MSVC_FIND_QUIETLY) + SET(_VERSION_STR ${_VERSION}) + IF(MSVC_EXPRESS) + SET(_VERSION_STR "${_VERSION_STR} Express") + ENDIF() + MESSAGE(STATUS "Found Visual C++ ${_VERSION_STR} in ${VC${_VERSION}_DIR}") + ENDIF() + ELSEIF(VC${_VERSION}_DIR AND NOT VC${_VERSION}_DIR STREQUAL "/registry") + SET(VC${_VERSION}_FOUND OFF) + SET(VC${_VERSION}_DIR "") + ENDIF() +ENDMACRO() + +MACRO(DETECT_VC_VERSION _VERSION) + SET(VC${_VERSION}_FOUND OFF) + DETECT_VC_VERSION_HELPER("HKEY_CURRENT_USER" ${_VERSION}) + + IF(NOT VC${_VERSION}_FOUND) + DETECT_VC_VERSION_HELPER("HKEY_LOCAL_MACHINE" ${_VERSION}) + ENDIF() + + IF(VC${_VERSION}_FOUND) + SET(VC_FOUND ON) + SET(VC_DIR "${VC${_VERSION}_DIR}") + ENDIF() +ENDMACRO() + +MACRO(DETECT_EXPRESS_VERSION _VERSION) + GET_FILENAME_COMPONENT(MSVC_EXPRESS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress\\${_VERSION}\\Setup\\VC;ProductDir]" ABSOLUTE) + + IF(MSVC_EXPRESS AND NOT MSVC_EXPRESS STREQUAL "/registry") + SET(MSVC_EXPRESS ON) + ENDIF() +ENDMACRO() + +IF(MSVC1411 OR MSVC1410) + DETECT_VC_VERSION("15.0") + SET(MSVC_TOOLSET "140") + + SET(VC_DIR "${VC_DIR}Tools/MSVC") + + FILE(GLOB MSVC_TOOLCHAIN_VERSIONS RELATIVE ${VC_DIR} "${VC_DIR}/*") + + IF(MSVC_TOOLCHAIN_VERSIONS) + LIST(SORT MSVC_TOOLCHAIN_VERSIONS) + LIST(REVERSE MSVC_TOOLCHAIN_VERSIONS) + ENDIF() + + IF(NOT MSVC_TOOLCHAIN_VERSIONS) + MESSAGE(FATAL_ERROR "No MSVC version found in default search path ${VC_DIR}") + ENDIF() + + LIST(GET MSVC_TOOLCHAIN_VERSIONS 0 MSVC_TOOLCHAIN_VERSION) + + SET(VC_DIR "${VC_DIR}/${MSVC_TOOLCHAIN_VERSION}") + SET(VC_INCLUDE_DIR "${VC_DIR}/include") + + IF(NOT MSVC14_REDIST_DIR) + # If you have VC++ 2017 Express, put x64/Microsoft.VC141.CRT/*.dll in ${EXTERNAL_PATH}/redist + # original files whould be in ${VC_DIR}/Redist/MSVC/14.11.25325/x64/Microsoft.VC141.CRT + SET(MSVC14_REDIST_DIR "${EXTERNAL_PATH}/redist") + ENDIF() +ELSEIF(MSVC14) + DETECT_VC_VERSION("14.0") + SET(MSVC_TOOLSET "140") + + IF(NOT MSVC14_REDIST_DIR) + # If you have VC++ 2015 Express, put x64/Microsoft.VC140.CRT/*.dll in ${EXTERNAL_PATH}/redist + SET(MSVC14_REDIST_DIR "${EXTERNAL_PATH}/redist") + ENDIF() +ELSEIF(MSVC12) + DETECT_VC_VERSION("12.0") + SET(MSVC_TOOLSET "120") + + IF(NOT MSVC12_REDIST_DIR) + # If you have VC++ 2013 Express, put x64/Microsoft.VC120.CRT/*.dll in ${EXTERNAL_PATH}/redist + SET(MSVC12_REDIST_DIR "${EXTERNAL_PATH}/redist") + ENDIF() +ELSEIF(MSVC11) + DETECT_VC_VERSION("11.0") + SET(MSVC_TOOLSET "110") + + IF(NOT MSVC11_REDIST_DIR) + # If you have VC++ 2012 Express, put x64/Microsoft.VC110.CRT/*.dll in ${EXTERNAL_PATH}/redist + SET(MSVC11_REDIST_DIR "${EXTERNAL_PATH}/redist") + ENDIF() +ELSEIF(MSVC10) + DETECT_VC_VERSION("10.0") + SET(MSVC_TOOLSET "100") + + IF(NOT MSVC10_REDIST_DIR) + # If you have VC++ 2010 Express, put x64/Microsoft.VC100.CRT/*.dll in ${EXTERNAL_PATH}/redist + SET(MSVC10_REDIST_DIR "${EXTERNAL_PATH}/redist") + ENDIF() +ELSEIF(MSVC90) + DETECT_VC_VERSION("9.0") + SET(MSVC_TOOLSET "90") +ELSEIF(MSVC80) + DETECT_VC_VERSION("8.0") + SET(MSVC_TOOLSET "80") +ENDIF() + +# If you plan to use VC++ compilers with WINE, set VC_DIR environment variable +IF(NOT VC_DIR) + SET(VC_DIR $ENV{VC_DIR}) +ENDIF() + +IF(NOT VC_DIR) + IF(CMAKE_CXX_COMPILER) + SET(_COMPILER ${CMAKE_CXX_COMPILER}) + ELSE() + SET(_COMPILER ${CMAKE_C_COMPILER}) + ENDIF() + STRING(REGEX REPLACE "/(bin|BIN|Bin)/.+" "" VC_DIR ${_COMPILER}) +ENDIF() + +IF(NOT VC_INCLUDE_DIR) + SET(VC_INCLUDE_DIR "${VC_DIR}/include") +ENDIF() + +MESSAGE(STATUS "Using headers from ${VC_INCLUDE_DIR}") + +SET(VC_INCLUDE_DIRS ${VC_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${VC_INCLUDE_DIR}) diff --git a/code/CMakeModules/FindWindowsSDK.cmake b/code/CMakeModules/FindWindowsSDK.cmake index d7918816f..0e2735570 100644 --- a/code/CMakeModules/FindWindowsSDK.cmake +++ b/code/CMakeModules/FindWindowsSDK.cmake @@ -35,7 +35,7 @@ MACRO(DETECT_WINKIT_VERSION _VERSION _SUFFIX) SET(WINSDK${_VERSION}_FOUND ON) SET(WINSDK${_VERSION}_VERSION_FULL "${_VERSION}") IF(NOT WindowsSDK_FIND_QUIETLY) - MESSAGE(STATUS "Found Windows SDK ${_VERSION} in ${WINSDK${_VERSION}_DIR}") + MESSAGE(STATUS "Found Windows Kit ${_VERSION} in ${WINSDK${_VERSION}_DIR}") ENDIF() LIST(APPEND WINSDK_DETECTED_VERSIONS ${_VERSION}) ELSE() @@ -240,7 +240,11 @@ MACRO(USE_CURRENT_WINSDK) IF(NOT WINSDK_DIR) # Use Windows SDK versions installed with VC++ when possible - IF(MSVC14) + IF(MSVC1411 OR MSVC1410) + # Special case, use Kits for SDK + SET(WINSDK_VERSION "10.0") + SET(WINSDK_DIR ${WINSDK_UCRT_DIR}) + ELSEIF(MSVC14) SET(WINSDK_VERSION "8.1") ELSEIF(MSVC12) SET(WINSDK_VERSION "8.1") @@ -305,7 +309,7 @@ MACRO(USE_CURRENT_WINSDK) ENDMACRO() IF(MSVC14) - # Under VC++ 2015, stdio.h, stdlib.h, etc... are part of UCRT + # Under VC++ 2015 and 2017, stdio.h, stdlib.h, etc... are part of UCRT SET(WINSDK_UCRT_VERSION "10.0") ENDIF() @@ -314,6 +318,40 @@ IF(WINSDK_UCRT_VERSION AND WINSDK${WINSDK_UCRT_VERSION}_FOUND) SET(WINSDK_UCRT_DIR "${WINSDK${WINSDK_UCRT_VERSION}_DIR}") ENDIF() +IF(WINSDK_UCRT_DIR) + # determine exact UCRT version + SET(WINSDK_UCRT_INCLUDE_ROOT_DIR ${WINSDK_UCRT_DIR}/Include) + SET(WINSDK_UCRT_LIB_ROOT_DIR ${WINSDK_UCRT_DIR}/Lib) + + FILE(GLOB UCRT_SUBDIRS RELATIVE ${WINSDK_UCRT_INCLUDE_ROOT_DIR} ${WINSDK_UCRT_INCLUDE_ROOT_DIR}/*) + SET(UCRT_VERSION) + + FOREACH(UCRT_SUBDIR ${UCRT_SUBDIRS}) + IF(NOT UCRT_VERSION OR UCRT_SUBDIR VERSION_GREATER UCRT_VERSION) + SET(UCRT_VERSION ${UCRT_SUBDIR}) + ENDIF() + ENDFOREACH() + + IF(UCRT_VERSION) + MESSAGE(STATUS "Using Windows UCRT ${UCRT_VERSION}") + + SET(WINSDK10_INCLUDE_DIR ${WINSDK_UCRT_INCLUDE_ROOT_DIR}/${UCRT_VERSION}) + SET(WINSDK10_LIBRARY_DIR ${WINSDK_UCRT_LIB_ROOT_DIR}/${UCRT_VERSION}) + + # directory where UCRT headers are found + FIND_PATH(WINSDK_UCRT_INCLUDE_DIR corecrt.h + HINTS + ${WINSDK10_INCLUDE_DIR}/ucrt + ) + + # directory where UCRT libraries are found + FIND_PATH(WINSDK_UCRT_LIBRARY_DIR ucrt.lib + HINTS + ${WINSDK10_LIBRARY_DIR}/ucrt/${WINSDK8_SUFFIX} + ) + ENDIF() +ENDIF() + IF(WINSDK_VERSION STREQUAL "CURRENT") USE_CURRENT_WINSDK() ELSE() @@ -334,93 +372,87 @@ ENDIF() # directory where Win32 headers are found FIND_PATH(WINSDK_INCLUDE_DIR Windows.h HINTS + ${WINSDK_DIR}/Include/${UCRT_VERSION}/um ${WINSDK_DIR}/Include/um ${WINSDK_DIR}/Include + NO_DEFAULT_PATH ) +MESSAGE(STATUS "Found Windows.h in ${WINSDK_INCLUDE_DIR}") + # directory where WinRT headers are found FIND_PATH(WINSDK_WINRT_INCLUDE_DIR winstring.h HINTS + ${WINSDK_DIR}/Include/${UCRT_VERSION}/winrt ${WINSDK_DIR}/Include/winrt + NO_DEFAULT_PATH ) +MESSAGE(STATUS "Found winstring.h in ${WINSDK_WINRT_INCLUDE_DIR}") + # directory where DirectX headers are found FIND_PATH(WINSDK_SHARED_INCLUDE_DIR d3d9.h HINTS + ${WINSDK_DIR}/Include/${UCRT_VERSION}/shared ${WINSDK_DIR}/Include/shared + NO_DEFAULT_PATH ) +MESSAGE(STATUS "Found d3d9.h in ${WINSDK_SHARED_INCLUDE_DIR}") + # directory where OpenGL headers are found FIND_PATH(WINSDK_OPENGL_INCLUDE_DIR GL.h HINTS + ${WINSDK_INCLUDE_DIR}/gl ${WINSDK_DIR}/Include/um/gl ${WINSDK_DIR}/Include/gl + NO_DEFAULT_PATH ) +MESSAGE(STATUS "Found GL.h in ${WINSDK_OPENGL_INCLUDE_DIR}") + SET(WINSDK_LIBRARY_DIRS + ${WINSDK_DIR}/Lib/${UCRT_VERSION}/um/${WINSDK8_SUFFIX} ${WINSDK_DIR}/Lib/winv6.3/um/${WINSDK8_SUFFIX} ${WINSDK_DIR}/Lib/win8/um/${WINSDK8_SUFFIX} ) IF(WINSDK_SUFFIXES) FOREACH(_SUFFIX ${WINSDK_SUFFIXES}) - SET(WINSDK_LIBRARY_DIRS ${WINSDK_LIBRARY_DIRS} ${WINSDK_DIR}/Lib/${_SUFFIX}) + LIST(APPEND WINSDK_LIBRARY_DIRS ${WINSDK_DIR}/Lib/${_SUFFIX}) ENDFOREACH() ELSE() - SET(WINSDK_LIBRARY_DIRS ${WINSDK_LIBRARY_DIRS} ${WINSDK_DIR}/Lib) + LIST(APPEND WINSDK_LIBRARY_DIRS ${WINSDK_DIR}/Lib) ENDIF() # directory where all libraries are found FIND_PATH(WINSDK_LIBRARY_DIR ComCtl32.lib HINTS ${WINSDK_LIBRARY_DIRS} + NO_DEFAULT_PATH ) -IF(WINSDK_UCRT_DIR) - # determine exact UCRT version - SET(WINSDK_UCRT_INCLUDE_ROOT_DIR ${WINSDK_UCRT_DIR}/Include) - SET(WINSDK_UCRT_LIB_ROOT_DIR ${WINSDK_UCRT_DIR}/Lib) +MESSAGE(STATUS "Found ComCtl32.lib in ${WINSDK_LIBRARY_DIR}") - FILE(GLOB UCRT_SUBDIRS RELATIVE ${WINSDK_UCRT_INCLUDE_ROOT_DIR} ${WINSDK_UCRT_INCLUDE_ROOT_DIR}/*) - SET(UCRT_VERSION) - - FOREACH(UCRT_SUBDIR ${UCRT_SUBDIRS}) - IF(NOT UCRT_VERSION OR UCRT_SUBDIR VERSION_GREATER UCRT_VERSION) - SET(UCRT_VERSION ${UCRT_SUBDIR}) - ENDIF() - ENDFOREACH() - - IF(UCRT_VERSION) - MESSAGE(STATUS "Using Windows UCRT ${UCRT_VERSION}") - - # directory where UCRT headers are found - FIND_PATH(WINSDK_UCRT_INCLUDE_DIR corecrt.h - HINTS - ${WINSDK_UCRT_INCLUDE_ROOT_DIR}/${UCRT_VERSION}/ucrt - ) - - # directory where UCRT libraries are found - FIND_PATH(WINSDK_UCRT_LIBRARY_DIR ucrt.lib - HINTS - ${WINSDK_UCRT_LIB_ROOT_DIR}/${UCRT_VERSION}/ucrt/${WINSDK8_SUFFIX} - ) - ENDIF() -ENDIF() +SET(WINSDK_BINARY_DIRS + ${WINSDK_DIR}/Bin/${UCRT_VERSION}/${WINSDK8_SUFFIX} + ${WINSDK_DIR}/Bin/${WINSDK8_SUFFIX} + ${WINSDK_DIR}/Bin/x86 + ${WINSDK_DIR}/Bin +) # signtool is used to sign executables FIND_PROGRAM(WINSDK_SIGNTOOL signtool HINTS - ${WINSDK_DIR}/Bin/${WINSDK8_SUFFIX} - ${WINSDK_DIR}/Bin/x86 - ${WINSDK_DIR}/Bin + ${WINSDK_BINARY_DIRS} + NO_DEFAULT_PATH ) # midl is used to generate IDL interfaces FIND_PROGRAM(WINSDK_MIDL midl HINTS - ${WINSDK_DIR}/Bin/${WINSDK8_SUFFIX} - ${WINSDK_DIR}/Bin/x86 - ${WINSDK_DIR}/Bin + ${WINSDK_BINARY_DIRS} + NO_DEFAULT_PATH ) IF(WINSDK_INCLUDE_DIR) @@ -444,7 +476,7 @@ IF(WINSDK_INCLUDE_DIR) SET(WINSDK_INCLUDE_DIRS ${WINSDK_INCLUDE_DIRS} ${WINSDK_WINRT_INCLUDE_DIR}) ENDIF() - INCLUDE_DIRECTORIES(${WINSDK_INCLUDE_DIRS}) # TODO: Move this after all other includes somehow... + INCLUDE_DIRECTORIES(${WINSDK_INCLUDE_DIRS}) IF(WINSDK_UCRT_LIBRARY_DIR) SET(CMAKE_LIBRARY_PATH ${WINSDK_UCRT_LIBRARY_DIR} ${CMAKE_LIBRARY_PATH}) diff --git a/code/CMakeModules/OSXToolChain.cmake b/code/CMakeModules/OSXToolChain.cmake new file mode 100644 index 000000000..3036aec01 --- /dev/null +++ b/code/CMakeModules/OSXToolChain.cmake @@ -0,0 +1,115 @@ +# Define OSX_SDK to force a specific version such as : -DOSX_SDK=10.11 +# +# Example: +# cmake ../code -DCMAKE_TOOLCHAIN_FILE=../code/CMakeModules/OSXToolChain.cmake -DWITH_NEL_TESTS=OFF -DWITH_RYZOM_SERVER=OFF -DWITH_NEL_TOOLS=OFF -DWITH_RYZOM_TOOLS=OFF -DWITH_LUA51=OFF -DWITH_LUA53=ON -DCMAKE_BUILD_TYPE=Release -DWITH_RYZOM_INSTALLER=OFF -DWITH_RYZOM_PATCH=ON -DWITH_NEL_TESTS=OFF -DWITH_NEL_TOOLS=OFF -DWITH_TOOLS=OFF -DWITH_NEL_SAMPLES=OFF -DWITH_WARNINGS=OFF -DWITH_QT5=OFF -DWITH_STATIC=ON -DWITH_STATIC_DRIVERS=ON -DWITH_STATIC_EXTERNAL=ON -DWITH_UNIX_STRUCTURE=OFF -DWITH_INSTALL_LIBRARIES=OFF -DWITH_RYZOM_SANDBOX=OFF -DOSX_SDK=10.11 + +# Don't forget to define environment variables: +# +# export MACOSX_DEPLOYMENT_TARGET=10.7 +# export OSXCROSS_GCC_NO_STATIC_RUNTIME=1 +# export PATH=$PATH:/home/src/osxcross/target/bin +# +# ln -s /usr/bin/hg /home/src/osxcross/target/bin/hg +# +# To install all dependencies: +# ./osxcross-macports install libxml2 jpeg curl libogg libvorbis freetype boost openssl zlib lua-5.3 giflib + +# to compile Luabind +# export CMAKE_MODULE_PATH=$HOME/shard/tools/external/cmake/modules +# cmake .. -DCMAKE_TOOLCHAIN_FILE=$HOME/ryzomcore/code/CMakeModules/OSXToolChain.cmake -DWITH_SHARED=OFF -DWITH_STATIC=ON -DWITH_LUA51=OFF -DWITH_LUA53=ON -DCMAKE_INSTALL_PREFIX=$HOME/osxcross/target/external + +IF(DEFINED CMAKE_CROSSCOMPILING) + # subsequent toolchain loading is not really needed + RETURN() +ENDIF() + +# Force the compilers to Clang for OS X +SET(CMAKE_C_COMPILER x86_64-apple-darwin15-clang) +SET(CMAKE_CXX_COMPILER x86_64-apple-darwin15-clang++) +set(CMAKE_CXX_COMPILER_ID "AppleClang") + +# Skip the platform compiler checks for cross compiling. +SET(CMAKE_CXX_COMPILER_FORCED TRUE) +SET(CMAKE_C_COMPILER_FORCED TRUE) + +# Check if osxcross is installed +EXECUTE_PROCESS(COMMAND which ${CMAKE_CXX_COMPILER} OUTPUT_VARIABLE COMPILER_FULLPATH OUTPUT_STRIP_TRAILING_WHITESPACE) + +IF(NOT COMPILER_FULLPATH) + MESSAGE(FATAL_ERROR "Unable to find ${CMAKE_CXX_COMPILER}, are you sure osxcross is installed and is in PATH?") +ENDIF() + +# Default paths +GET_FILENAME_COMPONENT(CMAKE_OSX_TOOLCHAIN_ROOT ${COMPILER_FULLPATH} DIRECTORY) + +# Parent directory +GET_FILENAME_COMPONENT(CMAKE_OSX_TOOLCHAIN_ROOT ${CMAKE_OSX_TOOLCHAIN_ROOT} DIRECTORY) + +SET(CMAKE_OSX_SYSROOT ${CMAKE_OSX_TOOLCHAIN_ROOT}/SDK) +SET(MACPORTS_ROOT_DIR ${CMAKE_OSX_TOOLCHAIN_ROOT}/macports/pkgs/opt/local) +SET(EXTERNAL_OSX_PATH ${CMAKE_OSX_TOOLCHAIN_ROOT}/external) + +# List of all SDKs that have been found +SET(OSX_SDKS) + +FILE(GLOB _CMAKE_OSX_SDKS "${CMAKE_OSX_SYSROOT}/MacOSX*") +IF(_CMAKE_OSX_SDKS) + LIST(SORT _CMAKE_OSX_SDKS) + LIST(REVERSE _CMAKE_OSX_SDKS) + FOREACH(_CMAKE_OSX_SDK ${_CMAKE_OSX_SDKS}) + STRING(REGEX REPLACE ".+MacOSX([0-9.]+)\\.sdk" "\\1" _OSX_SDK "${_CMAKE_OSX_SDK}") + LIST(APPEND OSX_SDKS ${_OSX_SDK}) + ENDFOREACH() +ENDIF() + +# Find and use the most recent OS X sdk +IF(NOT OSX_SDKS) + MESSAGE(FATAL_ERROR "No OS X SDK's found in default search path ${CMAKE_OSX_SYSROOT}.") +ENDIF() + +# if a specific SDK is defined, try to use it +IF(OSX_SDK) + LIST(FIND OSX_SDKS "${OSX_SDK}" _INDEX) + IF(_INDEX EQUAL -1) + # if specified SDK doesn't exist, use the last one + LIST(GET OSX_SDKS 0 OSX_SDK) + ENDIF() +ELSE() + # use the last SDK + LIST(GET OSX_SDKS 0 OSX_SDK) +ENDIF() + +MESSAGE(STATUS "Using OS X SDK ${OSX_SDK}") + +# Define final OS X sysroot +SET(CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT}/MacOSX${OSX_SDK}.sdk) + +# Standard settings +SET(CMAKE_SYSTEM_NAME Darwin) +set(CMAKE_SYSTEM "Darwin-15.0.0") +set(CMAKE_SYSTEM_VERSION "15.0.0") +set(CMAKE_SYSTEM_PROCESSOR "x86_64") +SET(UNIX ON) +SET(APPLE ON) + +# Set the find root to the OS X developer roots and to user defined paths +SET(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_TOOLCHAIN_ROOT} ${CMAKE_OSX_SYSROOT} ${CMAKE_PREFIX_PATH} ${CMAKE_INSTALL_PREFIX} ${MACPORTS_ROOT_DIR} ${EXTERNAL_OSX_PATH} $ENV{EXTERNAL_OSX_PATH} CACHE STRING "OS X find search path root") + +# default to searching for frameworks first +SET(CMAKE_FIND_FRAMEWORK FIRST) + +# set up the default search directories for frameworks +SET(CMAKE_SYSTEM_FRAMEWORK_PATH + ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks + ${CMAKE_OSX_SYSROOT}/System/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT}/Developer/Library/Frameworks +) + +# only search the OS X sdks, not the remainder of the host filesystem +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# determinate location for bin utils based on CMAKE_FIND_ROOT_PATH +INCLUDE(CMakeFindBinUtils) + diff --git a/code/CMakeModules/PCHSupport.cmake b/code/CMakeModules/PCHSupport.cmake index 36d031f10..c396bd6cf 100644 --- a/code/CMakeModules/PCHSupport.cmake +++ b/code/CMakeModules/PCHSupport.cmake @@ -114,12 +114,16 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) GET_TARGET_PROPERTY(oldProps ${_target} COMPILE_FLAGS) IF(oldProps) - LIST(APPEND _FLAGS ${oldProps}) + SET(_FLAG ${oldProps}) + SEPARATE_ARGUMENTS(_FLAG) + LIST(APPEND _FLAGS ${_FLAG}) ENDIF() GET_TARGET_PROPERTY(oldPropsBuild ${_target} COMPILE_FLAGS_${_UPPER_BUILD}) IF(oldPropsBuild) - LIST(APPEND _FLAGS ${oldPropsBuild}) + SET(_FLAG ${oldPropsBuild}) + SEPARATE_ARGUMENTS(_FLAG) + LIST(APPEND _FLAGS ${_FLAG}) ENDIF() GET_TARGET_PROPERTY(DIRINC ${_target} INCLUDE_DIRECTORIES) @@ -205,6 +209,10 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) ENDIF() ENDIF() + IF(USE_CPP0X AND gcc_compiler_version GREATER "6.2.0") + LIST(APPEND _FLAGS "-std=gnu++11") + ENDIF() + # Format definitions IF(MSVC) # Fix path with space @@ -316,7 +324,7 @@ MACRO(PCH_SET_COMPILE_COMMAND _inputcpp _compile_FLAGS) SET(_FLAGS "") IF(APPLE) SET(HEADER_FORMAT "objective-${HEADER_FORMAT}") - SET(_FLAGS -fobjc-abi-version=2 -fobjc-legacy-dispatch) + SET(_FLAGS ${OBJC_FLAGS}) ENDIF() SET(PCH_COMMAND ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} ${_FLAGS} -x ${HEADER_FORMAT} -o ${PCH_OUTPUT} -c ${PCH_INPUT}) ENDIF() @@ -402,7 +410,8 @@ MACRO(ADD_PRECOMPILED_HEADER_TO_TARGET _targetName) ENDIF() IF(APPLE) - SET(PCH_ADDITIONAL_COMPILER_FLAGS "-fobjc-abi-version=2 -fobjc-legacy-dispatch -x objective-c++ ${PCH_ADDITIONAL_COMPILER_FLAGS}") + STRING(REPLACE ";" " " OBJC_FLAGS_STR "${OBJC_FLAGS}") + SET(PCH_ADDITIONAL_COMPILER_FLAGS "${OBJC_FLAGS_STR} -x objective-c++ ${PCH_ADDITIONAL_COMPILER_FLAGS}") ENDIF() IF(WITH_PCH_DEBUG) diff --git a/code/CMakeModules/iOSToolChain.cmake b/code/CMakeModules/iOSToolChain.cmake index 610045e33..7bcb57227 100644 --- a/code/CMakeModules/iOSToolChain.cmake +++ b/code/CMakeModules/iOSToolChain.cmake @@ -35,23 +35,6 @@ SET(UNIX ON) SET(APPLE ON) SET(IOS ON) -# Force the compilers to Clang for iOS -include (CMakeForceCompiler) -CMAKE_FORCE_C_COMPILER (clang Clang) -CMAKE_FORCE_CXX_COMPILER (clang++ Clang) - -IF(CMAKE_CXX_COMPILER) - EXECUTE_PROCESS(COMMAND ${CMAKE_CXX_COMPILER} --version - OUTPUT_VARIABLE CLANG_VERSION_RAW - OUTPUT_STRIP_TRAILING_WHITESPACE) - - STRING(REGEX REPLACE "Apple LLVM version ([\\.0-9]+).*" - "\\1" CMAKE_CXX_COMPILER_VERSION "${CLANG_VERSION_RAW}") - - SET(CMAKE_C_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) - SET(CMAKE_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) -ENDIF() - # Setup iOS platform IF(NOT DEFINED IOS_PLATFORM) SET(IOS_PLATFORM "OS") @@ -162,14 +145,14 @@ IF(CMAKE_GENERATOR MATCHES Xcode) ENDIF() ELSE() IF(${IOS_PLATFORM} STREQUAL "OS") - SET(ARCHS "armv7;arm64") + SET(ARCHS armv7 arm64) SET(CMAKE_SYSTEM_PROCESSOR "armv7") ELSEIF(${IOS_PLATFORM} STREQUAL "SIMULATOR") # iPhone simulator targets i386 SET(ARCHS "i386") SET(CMAKE_SYSTEM_PROCESSOR "x86") ELSEIF(${IOS_PLATFORM} STREQUAL "ALL") - SET(ARCHS "armv7;arm64;i386;x86_64") + SET(ARCHS armv7 arm64 i386 x86_64) SET(CMAKE_SYSTEM_PROCESSOR "armv7") ENDIF() ENDIF() @@ -198,5 +181,13 @@ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +# Force the compilers to Clang for iOS +SET(CMAKE_C_COMPILER clang) +SET(CMAKE_CXX_COMPILER clang++) + +# Skip the platform compiler checks for cross compiling. +SET(CMAKE_CXX_COMPILER_FORCED TRUE) +SET(CMAKE_C_COMPILER_FORCED TRUE) + # determinate location for bin utils based on CMAKE_FIND_ROOT_PATH -include(CMakeFindBinUtils) +INCLUDE(CMakeFindBinUtils) diff --git a/code/CMakeModules/nel.cmake b/code/CMakeModules/nel.cmake index ed0847015..95b28bb8a 100644 --- a/code/CMakeModules/nel.cmake +++ b/code/CMakeModules/nel.cmake @@ -565,11 +565,18 @@ MACRO(NL_SETUP_BUILD) ADD_PLATFORM_FLAGS("/X") IF(MSVC14) - ADD_PLATFORM_FLAGS("/Gy- /MP") - # /Ox is working with VC++ 2015, but custom optimizations don't exist + ADD_PLATFORM_FLAGS("/Gy-") + # /Ox is working with VC++ 2015 and 2017, but custom optimizations don't exist SET(RELEASE_CFLAGS "/Ox /GF /GS- ${RELEASE_CFLAGS}") # without inlining it's unusable, use custom optimizations again SET(DEBUG_CFLAGS "/Od /Ob1 /GF- ${DEBUG_CFLAGS}") + + # Special cases for VC++ 2017 + IF(MSVC_VERSION EQUAL "1911") + SET(MSVC1411 ON) + ELSEIF(MSVC_VERSION EQUAL "1910") + SET(MSVC1410 ON) + ENDIF() ELSEIF(MSVC12) ADD_PLATFORM_FLAGS("/Gy-") # /Ox is working with VC++ 2013, but custom optimizations don't exist @@ -657,6 +664,8 @@ MACRO(NL_SETUP_BUILD) ENDIF() IF(APPLE) + SET(OBJC_FLAGS -fobjc-abi-version=2 -fobjc-legacy-dispatch -fobjc-weak) + IF(NOT XCODE) IF(CMAKE_OSX_ARCHITECTURES) SET(TARGETS_COUNT 0) @@ -1160,6 +1169,7 @@ MACRO(SETUP_EXTERNAL) IF(APPLE) IF(WITH_STATIC_EXTERNAL) + # Look only for static libraries because systems libraries are using Frameworks SET(CMAKE_FIND_LIBRARY_SUFFIXES .a) ELSE() SET(CMAKE_FIND_LIBRARY_SUFFIXES .dylib .so .a) @@ -1173,12 +1183,13 @@ MACRO(SETUP_EXTERNAL) ENDIF() ENDIF() - # Android and iOS have pthread - IF(ANDROID OR IOS) + # Android, iOS and Mac OS X have pthread, but no need to link to libpthread + IF(ANDROID OR APPLE) SET(CMAKE_USE_PTHREADS_INIT 1) SET(Threads_FOUND TRUE) ELSE() - FIND_PACKAGE(Threads REQUIRED) + SET(THREADS_HAVE_PTHREAD_ARG ON) + FIND_PACKAGE(Threads) # TODO: replace all -l by absolute path to in CMAKE_THREAD_LIBS_INIT ENDIF() diff --git a/code/nel/include/nel/gui/curl_certificates.h b/code/nel/include/nel/gui/curl_certificates.h new file mode 100644 index 000000000..021b13360 --- /dev/null +++ b/code/nel/include/nel/gui/curl_certificates.h @@ -0,0 +1,35 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// 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 . + +#ifndef CL_CURL_CERTIFICATES_HTML_H +#define CL_CURL_CERTIFICATES_HTML_H + +#include + +#include "nel/misc/types_nl.h" + +namespace NLGUI +{ +#if defined(NL_OS_WINDOWS) + class CCurlCertificates { + public: + // cURL SSL certificate loading + static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm); + }; +#endif // NL_OS_WINDOWS + +} // namespace +#endif diff --git a/code/nel/include/nel/gui/group_editbox.h b/code/nel/include/nel/gui/group_editbox.h index 771bdc482..14d9d55a5 100644 --- a/code/nel/include/nel/gui/group_editbox.h +++ b/code/nel/include/nel/gui/group_editbox.h @@ -264,6 +264,7 @@ namespace NLGUI // because of multiline, thz parent container will be moved to top // The good position can be restored by a press on enter then bool _WantReturn : 1; // Want return char, don't call the enter action handler + bool _ClearOnEscape : 1; // clear content when ESC is pressed? bool _Savable : 1; // should content be saved ? bool _DefaultInputString : 1; // Is the current input string the default one (should not be edited) bool _Frozen : 1; // is the control frozen? (cannot edit in it) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 479fc7384..8d3478579 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -84,6 +84,7 @@ namespace NLGUI FontOblique=false; Underlined=false; StrikeThrough=false; + GlobalColor=false; Width=-1; Height=-1; MaxWidth=-1; @@ -94,6 +95,7 @@ namespace NLGUI bool FontOblique; std::string FontFamily; NLMISC::CRGBA TextColor; + bool GlobalColor; bool Underlined; bool StrikeThrough; sint32 Width; @@ -339,7 +341,7 @@ namespace NLGUI void addString(const ucstring &str); // Add an image in the current paragraph - void addImage(const char *image, bool globalColor, bool reloadImg=false, const CStyleParams &style = CStyleParams()); + void addImage(const char *image, bool reloadImg=false, const CStyleParams &style = CStyleParams()); // Add a text area in the current paragraph CInterfaceGroup *addTextArea (const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength); @@ -350,7 +352,7 @@ namespace NLGUI // Add a button in the current paragraph. actionHandler, actionHandlerParams and tooltip can be NULL. CCtrlButton *addButton(CCtrlButton::EType type, const std::string &name, const std::string &normalBitmap, const std::string &pushedBitmap, - const std::string &overBitmap, bool useGlobalColor, const char *actionHandler, const char *actionHandlerParams, const char *tooltip, + const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams, const char *tooltip, const CStyleParams &style = CStyleParams()); // Set the background color @@ -712,7 +714,12 @@ namespace NLGUI std::vector _CellParams; // Indentation - uint _Indent; + std::vector _Indent; + inline uint getIndent() const { + if (_Indent.empty()) + return 0; + return _Indent.back(); + } // Current node is a title bool _Title; @@ -791,7 +798,7 @@ namespace NLGUI void doBrowseLocalFile(const std::string &filename); // load remote content using either GET or POST - void doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields()); + void doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields()); // render html string as new browser page bool renderHtmlString(const std::string &html); @@ -805,33 +812,37 @@ namespace NLGUI // ImageDownload system enum TDataType {ImgType= 0, BnpType}; + enum TImageType {NormalImage=0, OverImage}; struct CDataImageDownload { public: - CDataImageDownload(CViewBase *img, CStyleParams style): Image(img), Style(style) + CDataImageDownload(CViewBase *img, CStyleParams style, TImageType type): Image(img), Style(style), Type(type) { } public: CViewBase * Image; CStyleParams Style; + TImageType Type; }; struct CDataDownload { public: - CDataDownload(CURL *c, const std::string &u, const std::string &d, FILE *f, TDataType t, CViewBase *i, const std::string &s, const std::string &m, const CStyleParams &style = CStyleParams()) : curl(c), url(u), dest(d), luaScript(s), md5sum(m), type(t), fp(f) + CDataDownload(const std::string &u, const std::string &d, TDataType t, CViewBase *i, const std::string &s, const std::string &m, const CStyleParams &style = CStyleParams(), const TImageType imagetype = NormalImage) + : data(NULL), fp(NULL), url(u), dest(d), type(t), luaScript(s), md5sum(m), redirects(0) { - if (t == ImgType) imgs.push_back(CDataImageDownload(i, style)); + if (t == ImgType) imgs.push_back(CDataImageDownload(i, style, imagetype)); } public: - CURL *curl; + CCurlWWWData *data; std::string url; std::string dest; std::string luaScript; std::string md5sum; TDataType type; + uint32 redirects; FILE *fp; std::vector imgs; }; @@ -840,20 +851,22 @@ namespace NLGUI CURLM *MultiCurl; int RunningCurls; + bool startCurlDownload(CDataDownload &download); + void initImageDownload(); void checkImageDownload(); - void addImageDownload(const std::string &url, CViewBase *img, const CStyleParams &style = CStyleParams()); + void addImageDownload(const std::string &url, CViewBase *img, const CStyleParams &style = CStyleParams(), const TImageType type = NormalImage); std::string localImageName(const std::string &url); std::string getAbsoluteUrl(const std::string &url); bool isTrustedDomain(const std::string &domain); - void setImage(CViewBase *view, const std::string &file); + void setImage(CViewBase *view, const std::string &file, const TImageType type); void setImageSize(CViewBase *view, const CStyleParams &style = CStyleParams()); // BnpDownload system void initBnpDownload(); void checkBnpDownload(); - bool addBnpDownload(const std::string &url, const std::string &action, const std::string &script, const std::string &md5sum); + bool addBnpDownload(std::string url, const std::string &action, const std::string &script, const std::string &md5sum); std::string localBnpName(const std::string &url); void releaseDownloads(); diff --git a/code/nel/include/nel/gui/group_tree.h b/code/nel/include/nel/gui/group_tree.h index 1b64122f3..ae4bcb3d0 100644 --- a/code/nel/include/nel/gui/group_tree.h +++ b/code/nel/include/nel/gui/group_tree.h @@ -97,6 +97,7 @@ namespace NLGUI void addChildSortedByBitmap(SNode *pNode); void setParentTree(CGroupTree *parent); void setFather(SNode *father); + void openAll(); void closeAll(); void makeOrphan(); bool parse (xmlNodePtr cur, CGroupTree *parentGroup); diff --git a/code/nel/include/nel/gui/http_cache.h b/code/nel/include/nel/gui/http_cache.h new file mode 100644 index 000000000..0921b2f64 --- /dev/null +++ b/code/nel/include/nel/gui/http_cache.h @@ -0,0 +1,77 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// 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 . + +#ifndef CL_HTTP_CACHE_H +#define CL_HTTP_CACHE_H + +#include "nel/misc/types_nl.h" + +namespace NLGUI +{ + struct CHttpCacheObject + { + CHttpCacheObject(uint32 expires = 0, const std::string& lastModified = "", const std::string& etag = "") + : Expires(expires) + , LastModified(lastModified) + , Etag(etag){}; + + uint32 Expires; + std::string LastModified; + std::string Etag; + + void serial(NLMISC::IStream& f); + }; + + /** + * Keeping track of downloaded files cache related headers + * \author Meelis Mägi (nimetu) + * \date 2017 + */ + class CHttpCache + { + typedef std::map THttpCacheMap; + + public: + static CHttpCache* getInstance(); + static void release(); + + public: + void setCacheIndex(const std::string& fname); + void init(); + + CHttpCacheObject lookup(const std::string& fname); + void store(const std::string& fname, const CHttpCacheObject& data); + + void flushCache(); + + void serial(NLMISC::IStream& f); + + private: + CHttpCache(); + ~CHttpCache(); + + void pruneCache(); + + static CHttpCache* instance; + + THttpCacheMap _List; + + std::string _IndexFilename; + bool _Initialized; + size_t _MaxObjects; + }; +} +#endif diff --git a/code/nel/include/nel/gui/http_hsts.h b/code/nel/include/nel/gui/http_hsts.h new file mode 100644 index 000000000..2693461cd --- /dev/null +++ b/code/nel/include/nel/gui/http_hsts.h @@ -0,0 +1,73 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// 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 . + +#ifndef CL_HTTP_HSTS_H +#define CL_HTTP_HSTS_H + +#include "nel/misc/types_nl.h" + +namespace NLGUI +{ + // ******************************************************************************** + struct SHSTSObject + { + public: + SHSTSObject(uint64 expires = 0, bool includeSubDomains = false) + : Expires(expires) + , IncludeSubDomains(includeSubDomains) + { } + + uint64 Expires; + bool IncludeSubDomains; + }; + + /** + * Keeping track of HSTS header + * \author Meelis Mägi (nimetu) + * \date 2017 + */ + class CStrictTransportSecurity + { + public: + typedef std::map THSTSObject; + + static CStrictTransportSecurity* getInstance(); + static void release(); + + public: + bool isSecureHost(const std::string &domain) const; + + // ************************************************************************ + void init(const std::string& fname); + void save(); + + void erase(const std::string &domain); + void set(const std::string &domain, uint64 expires, bool includeSubDomains); + bool get(const std::string &domain, SHSTSObject &hsts) const; + void setFromHeader(const std::string &domain, const std::string &header); + + void serial(NLMISC::IStream& f); + private: + static CStrictTransportSecurity* instance; + + ~CStrictTransportSecurity(); + + std::string _Filename; + THSTSObject _Domains; + }; + +} +#endif diff --git a/code/nel/include/nel/gui/interface_element.h b/code/nel/include/nel/gui/interface_element.h index 65a0fcaff..cd82417d6 100644 --- a/code/nel/include/nel/gui/interface_element.h +++ b/code/nel/include/nel/gui/interface_element.h @@ -94,6 +94,7 @@ namespace NLGUI _XReal = _YReal = _WReal = _HReal = 0; _X = _Y = _W = _H = 0; //_Snap = 1; + _MarginLeft = 0; _PosRef = Hotspot_BL; _ParentPosRef = Hotspot_BL; @@ -178,6 +179,9 @@ namespace NLGUI sint32 getH() const { return (_Active?_H:0); } sint32 getH(bool bTestActive) const { return (bTestActive?(_Active?_H:0):_H); } + void setMarginLeft(sint32 m) { _MarginLeft = m; } + sint32 getMarginLeft() const { return _MarginLeft; } + /** * Get the max width used by the window. * @@ -568,6 +572,8 @@ namespace NLGUI sint32 _W; sint32 _H; + sint32 _MarginLeft; + //sint32 _Snap; // position references e.g. : _PosRef=BL, _ParentPosref=MM : the bottom left corner of the element diff --git a/code/nel/include/nel/gui/libwww.h b/code/nel/include/nel/gui/libwww.h index 22eba2f02..0825779f2 100644 --- a/code/nel/include/nel/gui/libwww.h +++ b/code/nel/include/nel/gui/libwww.h @@ -149,6 +149,8 @@ namespace NLGUI HTML_ATTR(IMG,USEMAP), HTML_ATTR(IMG,VSPACE), HTML_ATTR(IMG,WIDTH), + // not sorted to keep enum values + HTML_ATTR(IMG,DATA_OVER_SRC), }; enum diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index a00932667..8b8e1a7cf 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -93,7 +93,7 @@ namespace NLGUI void setMultiLineSpace (sint nMultiLineSpace); void setMultiLineMaxWOnly (bool state); void setMultiLineClipEndSpace (bool state); // use it for multiline edit box for instance - void setFirstLineX (uint firstLineX); + void setFirstLineX (sint firstLineX); void setMultiMaxLine(uint l) { _MultiMaxLine = l; } void setMultiMinLine(uint l) { _MultiMinLine = l; } @@ -398,7 +398,7 @@ namespace NLGUI uint _TextSelectionEnd; // First line X coordinate - uint _FirstLineX; + sint _FirstLineX; /// Dynamic tooltips std::vector _Tooltips; diff --git a/code/nel/include/nel/misc/i_xml.h b/code/nel/include/nel/misc/i_xml.h index f750c3265..75d4e9bde 100644 --- a/code/nel/include/nel/misc/i_xml.h +++ b/code/nel/include/nel/misc/i_xml.h @@ -110,6 +110,11 @@ public: */ bool init (IStream &stream); + /** Return the error string. + * if not empty, something wrong appends + */ + static std::string getErrorString(); + /** Release the resources used by the stream. */ void release (); @@ -172,7 +177,12 @@ public: static bool getContentString (std::string &result, xmlNodePtr node); /** - * Release meory used by libxml2, to only call before exit. + * Init all structures used by libxml2, to only call once. + */ + static void initLibXml(); + + /** + * Release memory used by libxml2, to only call before exit. */ static void releaseLibXml(); @@ -234,13 +244,16 @@ private: uint _ContentStringIndex; // Error message - std::string _ErrorString; + static std::string _ErrorString; // Try binary mode bool _TryBinaryMode; // If not NULL, binary mode detected, use this stream in serials IStream *_BinaryStream; + + // LibXml has been initialized + static bool _LibXmlIntialized; }; diff --git a/code/nel/include/nel/misc/o_xml.h b/code/nel/include/nel/misc/o_xml.h index 179534bb9..80b60149d 100644 --- a/code/nel/include/nel/misc/o_xml.h +++ b/code/nel/include/nel/misc/o_xml.h @@ -80,7 +80,6 @@ class COXml : public IStream { friend int xmlOutputWriteCallbackForNeL ( void *context, const char *buffer, int len ); friend int xmlOutputCloseCallbackForNeL ( void *context ); - friend void xmlGenericErrorFuncWrite (void *ctx, const char *msg, ...); public: /** Stream ctor @@ -100,7 +99,7 @@ public: /** Return the error string. * if not empty, something wrong appends */ - const char *getErrorString () const; + static std::string getErrorString (); /** Default dstor * @@ -178,9 +177,6 @@ private: // Current content string std::string _ContentString; - - // Error message - std::string _ErrorString; }; diff --git a/code/nel/include/nel/misc/types_nl.h b/code/nel/include/nel/misc/types_nl.h index a532a47fb..71702bce5 100644 --- a/code/nel/include/nel/misc/types_nl.h +++ b/code/nel/include/nel/misc/types_nl.h @@ -166,6 +166,10 @@ # define NL_ISO_CPP0X_AVAILABLE #endif +#if defined(NL_COMP_GCC) && (__cplusplus >= 201103L) +# define NL_NO_EXCEPTION_SPECS +#endif + // gcc 3.4 introduced ISO C++ with tough template rules // // NL_ISO_SYNTAX can be used using #if NL_ISO_SYNTAX or #if !NL_ISO_SYNTAX @@ -407,12 +411,21 @@ typedef unsigned int uint; // at least 32bits (depend of processor) #ifndef NL_CPU_X86_64 // on x86_64, new and delete are already aligned on 16 bytes + +#ifdef NL_NO_EXCEPTION_SPECS +extern void *operator new(size_t size); +extern void *operator new[](size_t size); +extern void operator delete(void *p) noexcept; +extern void operator delete[](void *p) noexcept; +#else extern void *operator new(size_t size) throw(std::bad_alloc); extern void *operator new[](size_t size) throw(std::bad_alloc); extern void operator delete(void *p) throw(); extern void operator delete[](void *p) throw(); #endif +#endif + #else /* NL_HAS_SSE2 */ #define NL_DEFAULT_MEMORY_ALIGNMENT 4 diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp index 5bd0e2cbf..be7a74722 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp @@ -32,6 +32,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d.h b/code/nel/src/3d/driver/direct3d/driver_direct3d.h index c497230aa..a4d9dbaeb 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d.h +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d.h @@ -480,12 +480,21 @@ public: virtual ~CStateRecord() {} // use STL allocator for fast alloc. this works because objects are small ( < 128 bytes) void *operator new(size_t size) { return CStateRecord::Allocator.allocate(size); } + void *operator new(size_t size, int /* blockUse */, char const * /* fileName */, int /* lineNumber */) + { + // TODO: add memory leaks detector + return CStateRecord::Allocator.allocate(size); + } void operator delete(void *block) { CStateRecord::Allocator.deallocate((uint8 *) block, 1); } + void operator delete(void *block, int /* blockUse */, char const* /* fileName */, int /* lineNumber */) + { + // TODO: add memory leaks detector + CStateRecord::Allocator.deallocate((uint8 *)block, 1); + } static std::allocator Allocator; }; - // record of a single .fx pass class CFXPassRecord { diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_index.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_index.cpp index dfdc48670..d7ee84303 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_index.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_index.cpp @@ -25,6 +25,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_inputs.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_inputs.cpp index f754b6357..d26741faf 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_inputs.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_inputs.cpp @@ -17,6 +17,10 @@ #include "stddirect3d.h" #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_light.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_light.cpp index d3bb62e64..f68caab3a 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_light.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_light.cpp @@ -26,6 +26,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_material.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_material.cpp index cc54d6305..e6622f61d 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_material.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_material.cpp @@ -27,6 +27,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_matrix.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_matrix.cpp index c5b479de0..6e7520fb7 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_matrix.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_matrix.cpp @@ -26,6 +26,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_pixel_program.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_pixel_program.cpp index e0a2cd4a4..7d79eb1a7 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_pixel_program.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_pixel_program.cpp @@ -29,6 +29,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_profile.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_profile.cpp index 4ff29481c..b574fff54 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_profile.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_profile.cpp @@ -19,6 +19,10 @@ #include "driver_direct3d.h" #include "nel/misc/hierarchical_timer.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_render.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_render.cpp index 939dc1dc9..6b9d2ce0c 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_render.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_render.cpp @@ -27,6 +27,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_shader.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_shader.cpp index 6f48cc2a4..3f11f8bce 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_shader.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_shader.cpp @@ -20,6 +20,10 @@ #include "nel/misc/path.h" #include "nel/misc/file.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp index d9ba5a526..a10732c64 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp @@ -26,6 +26,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_uniform.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_uniform.cpp index e44780e89..a936d5b46 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_uniform.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_uniform.cpp @@ -18,6 +18,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex.cpp index 2b7fa8fa9..ba726fcc9 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex.cpp @@ -26,6 +26,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex_program.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex_program.cpp index 69ae49c4f..265468125 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex_program.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_vertex_program.cpp @@ -18,6 +18,10 @@ #include "driver_direct3d.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace std; using namespace NLMISC; diff --git a/code/nel/src/3d/driver/opengl/CMakeLists.txt b/code/nel/src/3d/driver/opengl/CMakeLists.txt index 21d790668..8b5aabeb1 100644 --- a/code/nel/src/3d/driver/opengl/CMakeLists.txt +++ b/code/nel/src/3d/driver/opengl/CMakeLists.txt @@ -30,6 +30,14 @@ ELSE() SET(NLDRV_OGL_LIB "nel_drv_opengl") ENDIF() +# This helps to debug issue 310 +IF(DEBUG_OGL_SPECULAR_FALLBACK) + ADD_DEFINITIONS(-DDEBUG_OGL_SPECULAR_FALLBACK) +ENDIF() +IF(DEBUG_OGL_COMBINE43_DISABLE) + ADD_DEFINITIONS(-DDEBUG_OGL_COMBINE43_DISABLE) +ENDIF() + NL_TARGET_DRIVER(${NLDRV_OGL_LIB} ${SRC}) INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/code/nel/src/3d/driver/opengl/driver_opengl_extension.cpp b/code/nel/src/3d/driver/opengl/driver_opengl_extension.cpp index 91a2fcab3..d386dd85f 100644 --- a/code/nel/src/3d/driver/opengl/driver_opengl_extension.cpp +++ b/code/nel/src/3d/driver/opengl/driver_opengl_extension.cpp @@ -786,7 +786,13 @@ static bool setupNVTextureEnvCombine4(const char *glext) { H_AUTO_OGL(setupNVTextureEnvCombine4); CHECK_EXT("GL_NV_texture_env_combine4"); +#ifdef DEBUG_OGL_COMBINE43_DISABLE + // issue 310: disable extension to debug bug around CDriverGL::setupSpecularPass() + nlwarning("GL_NV_texture_env_combine4 disabled by request (DEBUG_OGL_COMBINE43_DISABLE)"); + return false; +#else return true; +#endif } // ********************************* @@ -802,7 +808,13 @@ static bool setupATITextureEnvCombine3(const char *glext) // #endif CHECK_EXT("GL_ATI_texture_env_combine3"); +#ifdef DEBUG_OGL_COMBINE43_DISABLE + // issue 310: disable extension to debug bug around CDriverGL::setupSpecularPass() + nlwarning("GL_ATI_texture_env_combine3 disabled by request (DEBUG_OGL_COMBINE43_DISABLE)"); + return false; +#else return true; +#endif } // ********************************* diff --git a/code/nel/src/3d/driver/opengl/driver_opengl_material.cpp b/code/nel/src/3d/driver/opengl/driver_opengl_material.cpp index bf9845103..e28521310 100644 --- a/code/nel/src/3d/driver/opengl/driver_opengl_material.cpp +++ b/code/nel/src/3d/driver/opengl/driver_opengl_material.cpp @@ -1425,6 +1425,11 @@ void CDriverGL::setupSpecularPass(uint pass) } else { +// Disabled because of Intel GPU texture bug (issue 310) +// CMake options to debug +// -DDEBUG_OGL_SPECULAR_FALLBACK=ON enables this +// -DDEBUG_OGL_COMBINE43_DISABLE=ON disables GL_NV_texture_env_combine4/GL_ATI_texture_env_combine3 +#ifdef DEBUG_OGL_SPECULAR_FALLBACK // Multiply texture1 by alpha_texture0 and display with add _DriverGLStates.enableBlend(true); _DriverGLStates.blendFunc(GL_ONE, GL_ONE); @@ -1457,6 +1462,7 @@ void CDriverGL::setupSpecularPass(uint pass) } activateTexEnvMode(1, env); +#endif // DEBUG_OGL_SPECULAR_FALLBACK } } } diff --git a/code/nel/src/3d/meshvp_wind_tree.cpp b/code/nel/src/3d/meshvp_wind_tree.cpp index 1b81af721..ba27216f0 100644 --- a/code/nel/src/3d/meshvp_wind_tree.cpp +++ b/code/nel/src/3d/meshvp_wind_tree.cpp @@ -226,7 +226,8 @@ void CMeshVPWindTree::initVertexPrograms() { // setup of the VPLight fragment uint numPls= i/4; - bool normalize= (i&1)!=0; + // FIXME: normalize=true makes trees dance, workaround for issue #160 + bool normalize= false; //(i&1)!=0; bool specular= (i&2)!=0; // combine diff --git a/code/nel/src/georges/form.cpp b/code/nel/src/georges/form.cpp index 22ed15a2e..f5cbcc3a1 100644 --- a/code/nel/src/georges/form.cpp +++ b/code/nel/src/georges/form.cpp @@ -24,6 +24,10 @@ #include "nel/georges/form.h" #include "nel/georges/form_loader.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; namespace NLGEORGES diff --git a/code/nel/src/georges/form_dfn.cpp b/code/nel/src/georges/form_dfn.cpp index 3f271b976..629c17f86 100644 --- a/code/nel/src/georges/form_dfn.cpp +++ b/code/nel/src/georges/form_dfn.cpp @@ -24,6 +24,10 @@ #include "nel/georges/form_loader.h" #include "nel/georges/form_elm.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; using namespace std; diff --git a/code/nel/src/georges/form_elm.cpp b/code/nel/src/georges/form_elm.cpp index a28fe2e9c..48368cc38 100644 --- a/code/nel/src/georges/form_elm.cpp +++ b/code/nel/src/georges/form_elm.cpp @@ -25,6 +25,10 @@ #include "nel/georges/form_loader.h" #include "nel/georges/type.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; using namespace std; diff --git a/code/nel/src/georges/form_loader.cpp b/code/nel/src/georges/form_loader.cpp index b56ed23ef..877c587ed 100644 --- a/code/nel/src/georges/form_loader.cpp +++ b/code/nel/src/georges/form_loader.cpp @@ -27,6 +27,10 @@ #include "nel/georges/form.h" #include "nel/georges/form_dfn.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; using namespace std; diff --git a/code/nel/src/georges/header.cpp b/code/nel/src/georges/header.cpp index f814d5802..40617e6d5 100644 --- a/code/nel/src/georges/header.cpp +++ b/code/nel/src/georges/header.cpp @@ -22,6 +22,10 @@ #include "nel/georges/header.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; namespace NLGEORGES diff --git a/code/nel/src/georges/stdgeorges.h b/code/nel/src/georges/stdgeorges.h index a11169d27..fa0bf0819 100644 --- a/code/nel/src/georges/stdgeorges.h +++ b/code/nel/src/georges/stdgeorges.h @@ -14,6 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#ifndef STDGEORGES_H +#define STDGEORGES_H + +#if defined(_MSC_VER) && defined(_DEBUG) +#define _CRTDBG_MAP_ALLOC +#include +#include +#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) +#endif + #include "nel/misc/types_nl.h" #include #include @@ -27,3 +37,5 @@ // Include from libxml2 #include + +#endif diff --git a/code/nel/src/georges/type.cpp b/code/nel/src/georges/type.cpp index cc3c44d6e..0dd2b9457 100644 --- a/code/nel/src/georges/type.cpp +++ b/code/nel/src/georges/type.cpp @@ -27,6 +27,10 @@ #include "nel/georges/form_loader.h" #include "nel/georges/type.h" +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + using namespace NLMISC; using namespace std; diff --git a/code/nel/src/gui/curl_certificates.cpp b/code/nel/src/gui/curl_certificates.cpp new file mode 100644 index 000000000..6cf34c8b8 --- /dev/null +++ b/code/nel/src/gui/curl_certificates.cpp @@ -0,0 +1,123 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// 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 . + +//#include + +#include "stdpch.h" +#include "nel/gui/curl_certificates.h" + +#include +#include + +#if defined(NL_OS_WINDOWS) +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif + +using namespace std; +using namespace NLMISC; + +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + +namespace NLGUI +{ +#if defined(NL_OS_WINDOWS) + static std::vector x509CertList; + + // + // x509CertList lifetime manager + // + class SX509Certificates { + public: + SX509Certificates() + { + curl_version_info_data *data; + data = curl_version_info(CURLVERSION_NOW); + if (!(data && data->features & CURL_VERSION_SSPI)) + { + addCertificatesFrom("CA"); + addCertificatesFrom("AuthRoot"); + addCertificatesFrom("ROOT"); + } + } + + ~SX509Certificates() + { + for (uint i = 0; i < x509CertList.size(); ++i) + { + X509_free(x509CertList[i]); + } + + x509CertList.clear(); + } + + void addCertificatesFrom(LPCSTR root) + { + HCERTSTORE hStore; + PCCERT_CONTEXT pContext = NULL; + X509 *x509; + hStore = CertOpenSystemStore(NULL, root); + if (hStore) + { + while (pContext = CertEnumCertificatesInStore(hStore, pContext)) + { + x509 = NULL; + x509 = d2i_X509(NULL, (const unsigned char **)&pContext->pbCertEncoded, pContext->cbCertEncoded); + if (x509) + { + x509CertList.push_back(x509); + } + } + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + } + + // this is called before debug context is set and log ends up in log.log + //nlinfo("Loaded %d certificates from '%s' certificate store", List.size(), root); + } + }; + + /// this will be initialized on startup and cleared on exit + static SX509Certificates x509CertListManager; + + // *************************************************************************** + // static + CURLcode CCurlCertificates::sslCtxFunction(CURL *curl, void *sslctx, void *parm) + { + if (x509CertList.size() > 0) + { + SSL_CTX *ctx = (SSL_CTX*)sslctx; + X509_STORE *x509store = SSL_CTX_get_cert_store(ctx); + if (x509store) + { + for (uint i = 0; i < x509CertList.size(); ++i) + { + X509_STORE_add_cert(x509store, x509CertList[i]); + } + } + else + { + nlwarning("SSL_CTX_get_cert_store returned NULL"); + } + } + return CURLE_OK; + } +#endif // NL_OS_WINDOWS + +}// namespace + diff --git a/code/nel/src/gui/group_editbox.cpp b/code/nel/src/gui/group_editbox.cpp index f0a41c8a0..e7ed22583 100644 --- a/code/nel/src/gui/group_editbox.cpp +++ b/code/nel/src/gui/group_editbox.cpp @@ -77,6 +77,7 @@ namespace NLGUI _ResetFocusOnHide(false), _BackupFatherContainerPos(false), _WantReturn(false), + _ClearOnEscape(false), _Savable(true), _DefaultInputString(false), _Frozen(false), @@ -239,6 +240,11 @@ namespace NLGUI return toString( _WantReturn ); } else + if( name == "clear_on_escape" ) + { + return toString( _ClearOnEscape ); + } + else if( name == "savable" ) { return toString( _Savable ); @@ -413,6 +419,14 @@ namespace NLGUI return; } else + if( name == "clear_on_escape" ) + { + bool b; + if( fromString( value, b ) ) + _ClearOnEscape = b; + return; + } + else if( name == "savable" ) { bool b; @@ -514,6 +528,7 @@ namespace NLGUI xmlSetProp( node, BAD_CAST "backup_father_container_pos", BAD_CAST toString( _BackupFatherContainerPos ).c_str() ); xmlSetProp( node, BAD_CAST "want_return", BAD_CAST toString( _WantReturn ).c_str() ); + xmlSetProp( node, BAD_CAST "clear_on_escape", BAD_CAST toString( _ClearOnEscape ).c_str() ); xmlSetProp( node, BAD_CAST "savable", BAD_CAST toString( _Savable ).c_str() ); xmlSetProp( node, BAD_CAST "max_float_prec", BAD_CAST toString( _MaxFloatPrec ).c_str() ); @@ -620,6 +635,9 @@ namespace NLGUI prop = (char*) xmlGetProp( cur, (xmlChar*)"want_return" ); if (prop) _WantReturn = convertBool(prop); + prop = (char*) xmlGetProp( cur, (xmlChar*)"clear_on_escape" ); + if (prop) _ClearOnEscape = convertBool(prop); + prop = (char*) xmlGetProp( cur, (xmlChar*)"savable" ); if (prop) _Savable = convertBool(prop); @@ -991,6 +1009,11 @@ namespace NLGUI // stop selection _CurrSelection = NULL; _CursorAtPreviousLineEnd = false; + if (_ClearOnEscape) + { + setInputString(ucstring("")); + triggerOnChangeAH(); + } break; case KeyTAB: makeTopWindow(); diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index a3cfd514f..c9977589f 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -45,6 +45,9 @@ #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/gui/curl_certificates.h" using namespace std; using namespace NLMISC; @@ -69,6 +72,25 @@ namespace NLGUI CGroupHTML::SWebOptions CGroupHTML::options; + // Return URL with https is host is in HSTS list + static std::string upgradeInsecureUrl(const std::string &url) + { + if (toLower(url.substr(0, 7)) != "http://") { + return url; + } + + CUrlParser uri(url); + if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){ + return url; + } + + #ifdef LOG_DL + nlwarning("HSTS url : '%s', using https", url.c_str()); + #endif + uri.scheme = "https"; + + return uri.toString(); + } // Active cURL www transfer class CCurlWWWData @@ -87,6 +109,15 @@ namespace NLGUI curl_slist_free_all(HeadersSent); } + void sendHeaders(const std::vector 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(": "); @@ -110,12 +141,63 @@ namespace NLGUI 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 (toLower(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; @@ -132,18 +214,23 @@ namespace NLGUI } // Update view after download has finished - void CGroupHTML::setImage(CViewBase * view, const string &file) + void CGroupHTML::setImage(CViewBase * view, const string &file, const TImageType type) { CCtrlButton *btn = dynamic_cast(view); if(btn) { - btn->setTexture (file); - btn->setTexturePushed(file); - btn->invalidateCoords(); - btn->invalidateContent(); - btn->resetInvalidCoords(); - btn->updateCoords(); - paragraphChange(); + if (type == NormalImage) + { + btn->setTexture (file); + btn->setTexturePushed(file); + btn->invalidateCoords(); + btn->invalidateContent(); + paragraphChange(); + } + else + { + btn->setTextureOver(file); + } } else { @@ -153,8 +240,6 @@ namespace NLGUI btm->setTexture (file); btm->invalidateCoords(); btm->invalidateContent(); - btm->resetInvalidCoords(); - btm->updateCoords(); paragraphChange(); } else @@ -165,14 +250,12 @@ namespace NLGUI btgc->setTexture (file); btgc->invalidateCoords(); btgc->invalidateContent(); - btgc->resetInvalidCoords(); - btgc->updateCoords(); paragraphChange(); } } } } - + // Force image width, height void CGroupHTML::setImageSize(CViewBase *view, const CStyleParams &style) { @@ -265,10 +348,117 @@ namespace NLGUI return dest; } - // Add a image download request in the multi_curl - void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style) + // Add url to MultiCurl queue and return cURL handle + bool CGroupHTML::startCurlDownload(CDataDownload &download) { - string finalUrl = getAbsoluteUrl(url); + if (!MultiCurl) + { + nlwarning("Invalid MultiCurl handle, unable to download '%s'", download.url.c_str()); + return false; + } + + time_t currentTime; + time(¤tTime); + + CHttpCacheObject cache; + if (CFile::fileExists(download.dest)) + cache = CHttpCache::getInstance()->lookup(download.dest); + + if (cache.Expires > currentTime) + { + #ifdef LOG_DL + nlwarning("Cache for (%s) is not expired (%s, expires:%d)", download.url.c_str(), download.dest.c_str(), cache.Expires - currentTime); + #endif + return false; + } + + string tmpdest = download.dest + ".tmp"; + + // erase the tmp file if exists + if (CFile::fileExists(tmpdest)) + CFile::deleteFile(tmpdest); + + FILE *fp = nlfopen (tmpdest, "wb"); + if (fp == NULL) + { + nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); + return false; + } + + CURL *curl = curl_easy_init(); + if (!curl) + { + fclose(fp); + CFile::deleteFile(tmpdest); + + nlwarning("Creating cURL handle failed, unable to download '%s'", download.url.c_str()); + return false; + } + +#if defined(NL_OS_WINDOWS) + // https:// + if (toLower(download.url.substr(0, 8)) == "https://") + { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); + } +#endif + + download.data = new CCurlWWWData(curl, download.url); + download.fp = fp; + + 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 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, 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); + + curl_multi_add_handle(MultiCurl, curl); + + return true; + } + + // Add a image download request in the multi_curl + void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type) + { + string finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url)); + + // use requested url for local name (cache) + string dest = localImageName(url); + #ifdef LOG_DL + nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img); + #endif + + // Display cached image while downloading new + if (type != TImageType::OverImage && CFile::fileExists(dest)) + { + setImage(img, dest, type); + setImageSize(img, style); + } // Search if we are not already downloading this url. for(uint i = 0; i < Curls.size(); i++) @@ -278,67 +468,27 @@ namespace NLGUI #ifdef LOG_DL nlwarning("already downloading '%s' img %p", finalUrl.c_str(), img); #endif - Curls[i].imgs.push_back(CDataImageDownload(img, style)); + Curls[i].imgs.push_back(CDataImageDownload(img, style, type)); return; } } - // use requested url for local name - string dest = localImageName(url); - string tmpdest = localImageName(url)+".tmp"; - #ifdef LOG_DL - nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img); - #endif - - // erase the tmp file if exists - if (NLMISC::CFile::fileExists(tmpdest)) - NLMISC::CFile::deleteFile(tmpdest); - - if (!NLMISC::CFile::fileExists(dest)) - { - if (!MultiCurl) + Curls.push_back(CDataDownload(finalUrl, dest, ImgType, img, "", "", style, type)); + if (Curls.size() < options.curlMaxConnections) { + if (!startCurlDownload(Curls.back())) { - nlwarning("Invalid MultiCurl handle, unable to download '%s'", finalUrl.c_str()); + Curls.pop_back(); return; } - CURL *curl = curl_easy_init(); - if (!curl) - { - nlwarning("Creating cURL handle failed, unable to download '%s'", finalUrl.c_str()); - return; - } - - FILE *fp = nlfopen(tmpdest, "wb"); - if (fp == NULL) - { - curl_easy_cleanup(curl); - - nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); - return; - } - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true); - curl_easy_setopt(curl, CURLOPT_URL, finalUrl.c_str()); - - std::string userAgent = options.appName + "/" + options.appVersion; - curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); - - sendCookies(curl, _DocumentDomain, _TrustedDomain); - - curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - - curl_multi_add_handle(MultiCurl, curl); - Curls.push_back(CDataDownload(curl, finalUrl, dest, fp, ImgType, img, "", "", style)); - #ifdef LOG_DL - nlwarning("adding handle %x, %d curls", curl, Curls.size()); - #endif RunningCurls++; + #ifdef LOG_DL + nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size()); } else { - setImage(img, dest); - setImageSize(img, style); + nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size()); + #endif } } @@ -363,8 +513,10 @@ namespace NLGUI } // Add a bnp download request in the multi_curl, return true if already downloaded - bool CGroupHTML::addBnpDownload(const string &url, const string &action, const string &script, const string &md5sum) + 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(uint i = 0; i < Curls.size(); i++) { @@ -378,14 +530,9 @@ namespace NLGUI } string dest = localBnpName(url); - string tmpdest = localBnpName(url)+".tmp"; #ifdef LOG_DL nlwarning("add to download '%s' dest '%s'", url.c_str(), dest.c_str()); #endif - - // erase the tmp file if exists - if (NLMISC::CFile::fileExists(tmpdest)) - NLMISC::CFile::deleteFile(tmpdest); // create/delete the local file if (NLMISC::CFile::fileExists(dest)) @@ -402,39 +549,23 @@ namespace NLGUI } if (action != "delete") { - if (!MultiCurl) + Curls.push_back(CDataDownload(url, dest, BnpType, NULL, script, md5sum)); + if (Curls.size() < options.curlMaxConnections) { - nlwarning("Invalid MultiCurl handle, unable to download '%s'", url.c_str()); - return false; - } - - CURL *curl = curl_easy_init(); - if (!curl) - { - nlwarning("Creating cURL handle failed, unable to download '%s'", url.c_str()); - return false; - } - - FILE *fp = nlfopen (tmpdest, "wb"); - if (fp == NULL) - { - curl_easy_cleanup(curl); - nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); - return false; - } - - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true); - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - - curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - - curl_multi_add_handle(MultiCurl, curl); - Curls.push_back(CDataDownload(curl, url, dest, fp, BnpType, NULL, script, md5sum)); + if (!startCurlDownload(Curls.back())) + { + Curls.pop_back(); + return false; + } + RunningCurls++; #ifdef LOG_DL - nlwarning("adding handle %x, %d curls", curl, Curls.size()); + nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size()); + } + else + { + nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size()); #endif - RunningCurls++; + } } else return true; @@ -494,6 +625,12 @@ namespace NLGUI #ifdef LOG_DL nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str()); #endif + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && _CurlWWW->hasHSTSHeader()) + { + CUrlParser uri(_CurlWWW->Url); + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader()); + } if (res != CURLE_OK) { @@ -552,7 +689,7 @@ namespace NLGUI char *ch; std::string contentType; res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch); - if (res == CURLE_OK) + if (res == CURLE_OK && ch != NULL) { contentType = ch; } @@ -565,26 +702,87 @@ namespace NLGUI for (vector::iterator it=Curls.begin(); iteasy_handle == it->curl) + if(it->data && it->data->Request == msg->easy_handle) { CURLcode res = msg->data.result; long r; - curl_easy_getinfo(it->curl, CURLINFO_RESPONSE_CODE, &r); + curl_easy_getinfo(it->data->Request, CURLINFO_RESPONSE_CODE, &r); fclose(it->fp); + CUrlParser uri(it->url); + if (!uri.host.empty()) + receiveCookies(it->data->Request, uri.host, isTrustedDomain(uri.host)); #ifdef LOG_DL - nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->curl, res, r, it->url.size(), it->url.c_str()); + nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->data->Request, res, r, it->url.size(), it->url.c_str()); #endif - curl_multi_remove_handle(MultiCurl, it->curl); - curl_easy_cleanup(it->curl); + curl_multi_remove_handle(MultiCurl, it->data->Request); + + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && it->data->hasHSTSHeader()) + { + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, it->data->getHSTSHeader()); + } string tmpfile = it->dest + ".tmp"; if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString()))) { - NLMISC::CFile::deleteFile(tmpfile.c_str()); + if (it->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT && ((r >= 301 && r <= 303) || r == 307 || r == 308)) + { + std::string location(it->data->getLocationHeader()); + if (!location.empty()) + { + CUrlParser uri(location); + if (!uri.isAbsolute()) + { + uri.inherit(it->url); + location = uri.toString(); + } + + it->url = location; + it->fp = NULL; + + // release CCurlWWWData + delete it->data; + it->data = NULL; + + it->redirects++; + #ifdef LOG_DL + nlwarning("Redirect '%s'", location.c_str()); + #endif + // keep the request in queue + continue; + } + else + nlwarning("Redirected to empty url '%s'", it->url.c_str()); + } + else + { + if (it->redirects >= DEFAULT_RYZOM_REDIRECT_LIMIT) + nlwarning("Redirect limit reached for '%s'", it->url.c_str()); + + NLMISC::CFile::deleteFile(tmpfile.c_str()); + + // 304 Not Modified + if (res == CURLE_OK && r == 304) + { + CHttpCacheObject obj; + obj.Expires = it->data->getExpires(); + obj.Etag = it->data->getEtag(); + obj.LastModified = it->data->getLastModified(); + + CHttpCache::getInstance()->store(it->dest, obj); + } + } } else { + CHttpCacheObject obj; + obj.Expires = it->data->getExpires(); + obj.Etag = it->data->getEtag(); + obj.LastModified = it->data->getLastModified(); + + CHttpCache::getInstance()->store(it->dest, obj); + string finalUrl; if (it->type == ImgType) { @@ -599,10 +797,13 @@ namespace NLGUI CBitmap::loadSize(tmpfile, w, h); if (w != 0 && h != 0) { + if (CFile::fileExists(it->dest)) + CFile::deleteFile(it->dest); + CFile::moveFile(it->dest, tmpfile); for(uint i = 0; i < it->imgs.size(); i++) { - setImage(it->imgs[i].Image, it->dest); + setImage(it->imgs[i].Image, it->dest, it->imgs[i].Type); setImageSize(it->imgs[i].Image, it->imgs[i].Style); } } @@ -624,6 +825,9 @@ namespace NLGUI } } + // release CCurlWWWData + delete it->data; + Curls.erase(it); break; } @@ -631,7 +835,30 @@ namespace NLGUI } } } + RunningCurls = NewRunningCurls; + + if (RunningCurls < options.curlMaxConnections) + { + for (vector::iterator it=Curls.begin(); itdata == NULL) { + #ifdef LOG_DL + nlwarning("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str()); + #endif + if (!startCurlDownload(*it)) + { + Curls.erase(it); + break; + } + + RunningCurls++; + if (RunningCurls >= options.curlMaxConnections) + break; + } + } + } + #ifdef LOG_DL if (RunningCurls > 0 || !Curls.empty()) nlwarning("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size()); @@ -1245,6 +1472,7 @@ namespace NLGUI style.TextColor = LinkColor; style.Underlined = true; style.StrikeThrough = getFontStrikeThrough(); + style.GlobalColor = LinkColorGlobalColor; if (present[HTML_A_STYLE] && value[HTML_A_STYLE]) getStyleParams(value[HTML_A_STYLE], style); @@ -1254,7 +1482,7 @@ namespace NLGUI _TextColor.push_back(style.TextColor); _FontUnderlined.push_back(style.Underlined); _FontStrikeThrough.push_back(style.StrikeThrough); - _GlobalColor.push_back(LinkColorGlobalColor); + _GlobalColor.push_back(style.GlobalColor); _A.push_back(true); _Link.push_back (""); _LinkTitle.push_back(""); @@ -1404,7 +1632,14 @@ namespace NLGUI } break; case HTML_BR: - addString(ucstring ("\n")); + { + endParagraph(); + + // insert zero-width-space (0x200B) to prevent removal of empty lines + ucstring tmp; + tmp.fromUtf8("\xe2\x80\x8b"); + addString(tmp); + } break; case HTML_BODY: { @@ -1517,31 +1752,49 @@ namespace NLGUI getPercentage(style.Width, tmpf, value[MY_HTML_IMG_WIDTH]); if (present[MY_HTML_IMG_HEIGHT] && value[MY_HTML_IMG_HEIGHT]) getPercentage(style.Height, tmpf, value[MY_HTML_IMG_HEIGHT]); + + // Get the global color name + if (present[MY_HTML_IMG_GLOBAL_COLOR]) + style.GlobalColor = true; + // width, height from inline css if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) getStyleParams(value[MY_HTML_IMG_STYLE], style); - - // Get the global color name - bool globalColor = false; - if (present[MY_HTML_IMG_GLOBAL_COLOR]) - globalColor = true; + + // Tooltip + const char *tooltip = NULL; + // keep "alt" attribute for backward compatibility + if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT]) + tooltip = value[MY_HTML_IMG_ALT]; + // tooltip + if (present[MY_HTML_IMG_TITLE] && value[MY_HTML_IMG_TITLE]) + tooltip = value[MY_HTML_IMG_TITLE]; + + // Mouse over image + string overSrc; + if (present[MY_HTML_IMG_DATA_OVER_SRC] && value[MY_HTML_IMG_DATA_OVER_SRC]) + { + overSrc = value[MY_HTML_IMG_DATA_OVER_SRC]; + } + if (getA() && getParent () && getParent ()->getParent()) { - // Tooltip - const char *tooltip = NULL; - if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT]) - tooltip = value[MY_HTML_IMG_ALT]; - string params = "name=" + getId() + "|url=" + getLink (); addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], - "", globalColor, "browse", params.c_str(), tooltip, style); + overSrc, "browse", params.c_str(), tooltip, style); + } + else + if (tooltip || !overSrc.empty()) + { + addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + overSrc, "", "", tooltip, style); } else { // Get the option to reload (class==reload) bool reloadImg = false; - + string styleString; if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) styleString = value[MY_HTML_IMG_STYLE]; @@ -1555,8 +1808,8 @@ namespace NLGUI if (it != styles.end() && (*it).second == "1") reloadImg = true; } - - addImage (value[MY_HTML_IMG_SRC], globalColor, reloadImg, style); + + addImage (value[MY_HTML_IMG_SRC], reloadImg, style); } } } @@ -1582,16 +1835,6 @@ namespace NLGUI // Get the type if (present[MY_HTML_INPUT_TYPE] && value[MY_HTML_INPUT_TYPE]) { - // Global color flag - bool globalColor = false; - if (present[MY_HTML_INPUT_GLOBAL_COLOR]) - globalColor = true; - - // Tooltip - const char *tooltip = NULL; - if (present[MY_HTML_INPUT_ALT] && value[MY_HTML_INPUT_ALT]) - tooltip = value[MY_HTML_INPUT_ALT]; - // by default not inherited, font family defaults to system font CStyleParams style; style.TextColor = TextColor; @@ -1599,6 +1842,15 @@ namespace NLGUI style.FontWeight = FONT_WEIGHT_NORMAL; style.FontOblique = false; + // Global color flag + if (present[MY_HTML_INPUT_GLOBAL_COLOR]) + style.GlobalColor = true; + + // Tooltip + const char *tooltip = NULL; + if (present[MY_HTML_INPUT_ALT] && value[MY_HTML_INPUT_ALT]) + tooltip = value[MY_HTML_INPUT_ALT]; + if (present[MY_HTML_INPUT_STYLE] && value[MY_HTML_INPUT_STYLE]) getStyleParams(value[MY_HTML_INPUT_STYLE], style); @@ -1626,7 +1878,7 @@ namespace NLGUI // Add the ctrl button addButton (CCtrlButton::PushButton, name, normal, pushed.empty()?normal:pushed, over, - globalColor, "html_submit_form", param.c_str(), tooltip, style); + "html_submit_form", param.c_str(), tooltip, style); } if (type == "button" || type == "submit") { @@ -1681,7 +1933,7 @@ namespace NLGUI if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b")); if (ctrlButton) { - ctrlButton->setModulateGlobalColorAll (globalColor); + ctrlButton->setModulateGlobalColorAll (style.GlobalColor); // Translate the tooltip if (tooltip) @@ -1764,8 +2016,7 @@ namespace NLGUI checked = (present[MY_HTML_INPUT_CHECKED] && value[MY_HTML_INPUT_CHECKED]); // Add the ctrl button - CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, - globalColor, "", "", tooltip); + CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, style); if (checkbox) { if (btnType == CCtrlButton::RadioButton) @@ -1924,8 +2175,9 @@ namespace NLGUI sint32 indent = LIIndent; // list-style-type: outside if (_CurrentViewLink) - indent -= _CurrentViewLink->getMaxUsedW(); - getParagraph()->setFirstViewIndent(indent); + { + getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW()); + } flushString (); @@ -1981,8 +2233,7 @@ namespace NLGUI if (present[MY_HTML_TABLE_CELLPADDING] && value[MY_HTML_TABLE_CELLPADDING]) fromString(value[MY_HTML_TABLE_CELLPADDING], table->CellPadding); - // Table must fit the container size - + table->setMarginLeft(getIndent()); addHtmlGroup (table, 0); _Tables.push_back(table); @@ -1990,6 +2241,7 @@ namespace NLGUI // Add a cell pointer _Cells.push_back(NULL); _TR.push_back(false); + _Indent.push_back(0); } break; case HTML_TH: @@ -2071,7 +2323,12 @@ namespace NLGUI _Cells.back()->NewLine = getTR(); 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()) @@ -2161,7 +2418,7 @@ namespace NLGUI _UL.push_back(HTMLOListElement(1, "square")); // if LI is already present _LI = _UL.size() > 1 || _DL.size() > 1; - _Indent += ULIndent; + _Indent.push_back(getIndent() + ULIndent); endParagraph(); break; case HTML_OBJECT: @@ -2190,6 +2447,7 @@ namespace NLGUI style.FontOblique = getFontOblique(); style.Underlined = getFontUnderlined(); style.StrikeThrough = getFontStrikeThrough(); + style.GlobalColor = getGlobalColor(); if (present[MY_HTML_SPAN_STYLE] && value[MY_HTML_SPAN_STYLE]) getStyleParams(value[MY_HTML_SPAN_STYLE], style); @@ -2201,6 +2459,7 @@ namespace NLGUI _FontOblique.push_back(style.FontOblique); _FontUnderlined.push_back(style.Underlined); _FontStrikeThrough.push_back(style.StrikeThrough); + _GlobalColor.push_back(style.GlobalColor); } break; case HTML_DEL: @@ -2230,6 +2489,13 @@ namespace NLGUI case HTML_DT: if (!_DL.empty()) { + // close DT if still open + if (_DL.back().DD) + { + _DL.back().DD = false; + popIfNotEmpty(_Indent); + } + // see if this is the first
, closing tag not required if (!_DL.back().DT) { @@ -2260,8 +2526,8 @@ namespace NLGUI if (!_DL.back().DD) { - _Indent += ULIndent; _DL.back().DD = true; + _Indent.push_back(getIndent() + ULIndent); } if (!_LI) @@ -2288,7 +2554,7 @@ namespace NLGUI _UL.push_back(HTMLOListElement(start, type)); // if LI is already present _LI = _UL.size() > 1 || _DL.size() > 1; - _Indent += ULIndent; + _Indent.push_back(getIndent() + ULIndent); endParagraph(); } break; @@ -2400,6 +2666,7 @@ namespace NLGUI popIfNotEmpty (_TR); popIfNotEmpty (_Cells); popIfNotEmpty (_Tables); + popIfNotEmpty (_Indent); endParagraph(); // Add a cell break; @@ -2542,13 +2809,9 @@ namespace NLGUI case HTML_UL: if (!_UL.empty()) { - if (_Indent > ULIndent) - _Indent = _Indent - ULIndent; - else - _Indent = 0; - endParagraph(); popIfNotEmpty(_UL); + popIfNotEmpty(_Indent); } break; case HTML_DL: @@ -2565,10 +2828,7 @@ namespace NLGUI // unclosed DD if (_DL.back().DD) { - if (_Indent > ULIndent) - _Indent = _Indent - ULIndent; - else - _Indent = 0; + popIfNotEmpty(_Indent); } popIfNotEmpty (_DL); @@ -2587,12 +2847,8 @@ namespace NLGUI // parser will process two DD in a row as nested when first DD is not closed if (_DL.back().DD) { - if (_Indent > ULIndent) - _Indent = _Indent - ULIndent; - else - _Indent = 0; - _DL.back().DD = false; + popIfNotEmpty(_Indent); } } break; @@ -2604,6 +2860,7 @@ namespace NLGUI popIfNotEmpty (_TextColor); popIfNotEmpty (_FontUnderlined); popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_GlobalColor); break; case HTML_DEL: popIfNotEmpty (_FontStrikeThrough); @@ -2704,7 +2961,7 @@ namespace NLGUI _Connecting = false; _CurrentViewLink = NULL; _CurrentViewImage = NULL; - _Indent = 0; + _Indent.clear(); _LI = false; _SelectOption = false; _GroupListAdaptor = NULL; @@ -3773,7 +4030,7 @@ namespace NLGUI CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam()); newParagraph->setResizeFromChildH(true); - newParagraph->setIndent(_Indent); + newParagraph->setMarginLeft(getIndent()); // Add to the group addHtmlGroup (newParagraph, beginSpace); @@ -4162,7 +4419,7 @@ namespace NLGUI // *************************************************************************** - void CGroupHTML::addImage(const char *img, bool globalColor, bool reloadImg, const CStyleParams &style) + void CGroupHTML::addImage(const char *img, bool reloadImg, const CStyleParams &style) { // In a paragraph ? if (!_Paragraph) @@ -4194,7 +4451,11 @@ namespace NLGUI // 2/ if it doesn't work, try to load the image in cache // image = localImageName(img); - if (!reloadImg && lookupLocalFile (finalUrl, image.c_str(), false)) + + if (reloadImg && CFile::fileExists(image)) + CFile::deleteFile(image); + + if (lookupLocalFile (finalUrl, image.c_str(), false)) { // don't display image that are not power of 2 try @@ -4212,22 +4473,18 @@ namespace NLGUI } else { - // - // 3/ if it doesn't work, display a placeholder and ask to dl the image into the cache - // - if (reloadImg && CFile::fileExists(image)) - CFile::deleteFile(image); - + // no image in cache image = "web_del.tga"; - addImageDownload(img, newImage, style); } + + addImageDownload(img, newImage, style); } newImage->setTexture (image); - newImage->setModulateGlobalColor(globalColor); + newImage->setModulateGlobalColor(style.GlobalColor); - getParagraph()->addChild(newImage); + getParagraph()->addChild(newImage); paragraphChange (); - + setImageSize(newImage, style); } @@ -4371,7 +4628,7 @@ namespace NLGUI // *************************************************************************** CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &/* name */, const std::string &normalBitmap, const std::string &pushedBitmap, - const std::string &overBitmap, bool useGlobalColor, const char *actionHandler, const char *actionHandlerParams, + const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams, const char *tooltip, const CStyleParams &style) { // In a paragraph ? @@ -4435,6 +4692,18 @@ namespace NLGUI } string 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); + if (!CFile::fileExists(over)) + { + addImageDownload(overBitmap, ctrlButton, style, TImageType::OverImage); + } + } + } ctrlButton->setType (type); if (!normal.empty()) @@ -4443,7 +4712,7 @@ namespace NLGUI ctrlButton->setTexturePushed (pushed); if (!over.empty()) ctrlButton->setTextureOver (over); - ctrlButton->setModulateGlobalColorAll (useGlobalColor); + ctrlButton->setModulateGlobalColorAll (style.GlobalColor); ctrlButton->setActionOnLeftClick (actionHandler); ctrlButton->setParamsOnLeftClick (actionHandlerParams); @@ -4495,7 +4764,7 @@ namespace NLGUI _FontOblique.clear(); _FontUnderlined.clear(); _FontStrikeThrough.clear(); - _Indent = 0; + _Indent.clear(); _LI = false; _UL.clear(); _DL.clear(); @@ -4531,6 +4800,18 @@ namespace NLGUI Curls[i].imgs.clear(); } + // remove download that are still queued + for (vector::iterator it=Curls.begin(); itdata == NULL) { + #ifdef LOG_DL + nlwarning("Remove waiting curl download (%s)", it->url.c_str()); + #endif + it = Curls.erase(it); + } else { + ++it; + } + } } // *************************************************************************** @@ -4985,7 +5266,7 @@ namespace NLGUI } #if LOG_DL - nlwarning("(%s) browse local file '%s'", filename.c_str()); + nlwarning("browse local file '%s'", filename.c_str()); #endif _TrustedDomain = true; @@ -5021,7 +5302,7 @@ namespace NLGUI } // *************************************************************************** - void CGroupHTML::doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost, const SFormFields &formfields) + void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields) { // Stop previous request and remove content stopBrowse (); @@ -5035,6 +5316,8 @@ namespace NLGUI else setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait")); + url = upgradeInsecureUrl(url); + #if LOG_DL nlwarning("(%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()); @@ -5054,6 +5337,14 @@ namespace NLGUI return; } +#if defined(NL_OS_WINDOWS) + // https:// + if (toLower(url.substr(0, 8)) == "https://") + { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); + } +#endif + // do not follow redirects, we have own handler curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); // after redirect @@ -5119,11 +5410,7 @@ namespace NLGUI std::vector headers; headers.push_back("Accept-Language: "+options.languageCode); headers.push_back("Accept-Charset: utf-8"); - for(uint i=0; i< headers.size(); ++i) - { - _CurlWWW->HeadersSent = curl_slist_append(_CurlWWW->HeadersSent, headers[i].c_str()); - } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, _CurlWWW->HeadersSent); + _CurlWWW->sendHeaders(headers); // catch headers for redirect curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderCallback); @@ -5600,16 +5887,20 @@ namespace NLGUI 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), ls.toString(1), - "", ls.toBoolean(2), "browse", params.c_str(), ""); + "", "browse", params.c_str(), "", style); } else { - addImage(ls.toString(1), ls.toBoolean(2)); + addImage(ls.toString(1), false, style); } @@ -5918,6 +6209,16 @@ namespace NLGUI else if (it->first == "max-height") getPercentage(style.MaxHeight, tmpf, it->second.c_str()); + else + if (it->first == "-ryzom-modulate-color") + { + bool b; + if (it->second == "inherit") + style.GlobalColor = getGlobalColor(); + else + if (fromString(it->second, b)) + style.GlobalColor = b; + } } if (inherit) { diff --git a/code/nel/src/gui/group_html_BACKUP_3737.cpp b/code/nel/src/gui/group_html_BACKUP_3737.cpp new file mode 100644 index 000000000..0f1e6fc0f --- /dev/null +++ b/code/nel/src/gui/group_html_BACKUP_3737.cpp @@ -0,0 +1,6886 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// 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 . + +//#include + +#include "stdpch.h" +#include "nel/gui/group_html.h" + +#include +#include "nel/misc/types_nl.h" +#include "nel/misc/rgba.h" +#include "nel/misc/algo.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" +<<<<<<< HEAD +======= +#include "nel/gui/http_cache.h" +#include "nel/gui/http_hsts.h" +#include "nel/gui/curl_certificates.h" +>>>>>>> ryzomcore + +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 to see the log about image download + //#define LOG_DL 1 + + CGroupHTML::SWebOptions CGroupHTML::options; + +<<<<<<< HEAD +======= + // Return URL with https is host is in HSTS list + static std::string upgradeInsecureUrl(const std::string &url) + { + if (toLower(url.substr(0, 7)) != "http://") { + return url; + } + + CUrlParser uri(url); + if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){ + return url; + } + + #ifdef LOG_DL + nlwarning("HSTS url : '%s', using https", url.c_str()); + #endif + uri.scheme = "https"; + + return uri.toString(); + } +>>>>>>> ryzomcore + + // 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); + } + +<<<<<<< HEAD +======= + void sendHeaders(const std::vector 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); + } + +>>>>>>> ryzomcore + void setRecvHeader(const std::string &header) + { + size_t pos = header.find(": "); + if (pos == std::string::npos) + return; + + std::string key = toLower(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: " header or empty string if no header set + const std::string getLocationHeader() + { + if (HeadersRecv.count("location") > 0) + return HeadersRecv["location"]; + + return ""; + } + +<<<<<<< HEAD +======= + 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 (toLower(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 ""; + } + +>>>>>>> ryzomcore + public: + CURL *Request; + + std::string Url; + std::string Content; + +<<<<<<< HEAD +======= + private: +>>>>>>> ryzomcore + // headers sent with curl request, must be released after transfer + curl_slist * HeadersSent; + + // headers received from curl transfer + std::map HeadersRecv; + }; + + // Check if domain is on TrustedDomain + bool CGroupHTML::isTrustedDomain(const string &domain) + { + vector::iterator it; + it = find ( options.trustedDomains.begin(), options.trustedDomains.end(), domain); + return it != options.trustedDomains.end(); + } + + // Update view after download has finished +<<<<<<< HEAD + void CGroupHTML::setImage(CViewBase * view, const string &file) +======= + void CGroupHTML::setImage(CViewBase * view, const string &file, const TImageType type) +>>>>>>> ryzomcore + { + CCtrlButton *btn = dynamic_cast(view); + if(btn) + { +<<<<<<< HEAD + btn->setTexture (file); + btn->setTexturePushed(file); + btn->invalidateCoords(); + btn->invalidateContent(); + btn->resetInvalidCoords(); + btn->updateCoords(); + paragraphChange(); +======= + if (type == NormalImage) + { + btn->setTexture (file); + btn->setTexturePushed(file); + btn->invalidateCoords(); + btn->invalidateContent(); + paragraphChange(); + } + else + { + btn->setTextureOver(file); + } +>>>>>>> ryzomcore + } + else + { + CViewBitmap *btm = dynamic_cast(view); + if(btm) + { + btm->setTexture (file); + btm->invalidateCoords(); + btm->invalidateContent(); +<<<<<<< HEAD + btm->resetInvalidCoords(); + btm->updateCoords(); +======= +>>>>>>> ryzomcore + paragraphChange(); + } + else + { + CGroupCell *btgc = dynamic_cast(view); + if(btgc) + { + btgc->setTexture (file); + btgc->invalidateCoords(); + btgc->invalidateContent(); +<<<<<<< HEAD + btgc->resetInvalidCoords(); + btgc->updateCoords(); +======= +>>>>>>> ryzomcore + paragraphChange(); + } + } + } + } +<<<<<<< HEAD + +======= + +>>>>>>> ryzomcore + // 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(view); + if(btn) + { + btn->fitTexture(); + imageWidth = btn->getW(false); + imageHeight = btn->getH(false); + } + else + { + CViewBitmap *btm = dynamic_cast(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) + { + applyCssMinMax(width, height, 0, 0, maxw, maxh); + changed = true; + } + + if (changed) + { + CCtrlButton *btn = dynamic_cast(view); + if(btn) + { + btn->setScale(true); + btn->setW(width); + btn->setH(height); + } + else + { + CViewBitmap *image = dynamic_cast(view); + if(image) + { + image->setScale(true); + image->setW(width); + image->setH(height); + } + } + } + } + + // 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; + } + +<<<<<<< HEAD + // Add a image download request in the multi_curl + void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style) + { + string finalUrl = getAbsoluteUrl(url); + + // Search if we are not already downloading this url. + for(uint i = 0; i < Curls.size(); i++) + { + if(Curls[i].url == finalUrl) + { + #ifdef LOG_DL + nlwarning("already downloading '%s' img %p", finalUrl.c_str(), img); + #endif + Curls[i].imgs.push_back(CDataImageDownload(img, style)); + return; + } + } + + // use requested url for local name + string dest = localImageName(url); + string tmpdest = localImageName(url)+".tmp"; +======= + // 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(¤tTime); + + CHttpCacheObject cache; + if (CFile::fileExists(download.dest)) + cache = CHttpCache::getInstance()->lookup(download.dest); + + if (cache.Expires > currentTime) + { + #ifdef LOG_DL + nlwarning("Cache for (%s) is not expired (%s, expires:%d)", download.url.c_str(), download.dest.c_str(), cache.Expires - currentTime); + #endif + return false; + } + + string tmpdest = download.dest + ".tmp"; + + // erase the tmp file if exists + if (CFile::fileExists(tmpdest)) + CFile::deleteFile(tmpdest); + + FILE *fp = nlfopen (tmpdest, "wb"); + if (fp == NULL) + { + nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); + return false; + } + + CURL *curl = curl_easy_init(); + if (!curl) + { + fclose(fp); + CFile::deleteFile(tmpdest); + + nlwarning("Creating cURL handle failed, unable to download '%s'", download.url.c_str()); + return false; + } + +#if defined(NL_OS_WINDOWS) + // https:// + if (toLower(download.url.substr(0, 8)) == "https://") + { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); + } +#endif + + download.data = new CCurlWWWData(curl, download.url); + download.fp = fp; + + 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 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, 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); + + curl_multi_add_handle(MultiCurl, curl); + + return true; + } + + // Add a image download request in the multi_curl + void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type) + { + string finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url)); + + // use requested url for local name (cache) + string dest = localImageName(url); +>>>>>>> ryzomcore + #ifdef LOG_DL + nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img); + #endif + +<<<<<<< HEAD + // erase the tmp file if exists + if (NLMISC::CFile::fileExists(tmpdest)) + NLMISC::CFile::deleteFile(tmpdest); + + if (!NLMISC::CFile::fileExists(dest)) + { + if (!MultiCurl) + { + nlwarning("Invalid MultiCurl handle, unable to download '%s'", finalUrl.c_str()); + return; + } + + CURL *curl = curl_easy_init(); + if (!curl) + { + nlwarning("Creating cURL handle failed, unable to download '%s'", finalUrl.c_str()); + return; + } + + FILE *fp = nlfopen(tmpdest, "wb"); + if (fp == NULL) + { + curl_easy_cleanup(curl); + + nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); + return; + } + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true); + curl_easy_setopt(curl, CURLOPT_URL, finalUrl.c_str()); + + std::string userAgent = options.appName + "/" + options.appVersion; + curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); + + sendCookies(curl, _DocumentDomain, _TrustedDomain); + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + + curl_multi_add_handle(MultiCurl, curl); + Curls.push_back(CDataDownload(curl, finalUrl, dest, fp, ImgType, img, "", "", style)); + #ifdef LOG_DL + nlwarning("adding handle %x, %d curls", curl, Curls.size()); + #endif + RunningCurls++; + } + else + { + setImage(img, dest); + setImageSize(img, style); +======= + // Display cached image while downloading new + if (type != TImageType::OverImage && CFile::fileExists(dest)) + { + setImage(img, dest, type); + setImageSize(img, style); + } + + // Search if we are not already downloading this url. + for(uint i = 0; i < Curls.size(); i++) + { + if(Curls[i].url == finalUrl) + { + #ifdef LOG_DL + nlwarning("already downloading '%s' img %p", finalUrl.c_str(), img); + #endif + Curls[i].imgs.push_back(CDataImageDownload(img, style, type)); + return; + } + } + + Curls.push_back(CDataDownload(finalUrl, dest, ImgType, img, "", "", style, type)); + if (Curls.size() < options.curlMaxConnections) { + if (!startCurlDownload(Curls.back())) + { + Curls.pop_back(); + return; + } + + RunningCurls++; + #ifdef LOG_DL + nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size()); + } + else + { + nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size()); + #endif +>>>>>>> ryzomcore + } + } + + void CGroupHTML::initImageDownload() + { + #ifdef LOG_DL + nlwarning("Init Image Download"); + #endif + + 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 +<<<<<<< HEAD + bool CGroupHTML::addBnpDownload(const string &url, const string &action, const string &script, const string &md5sum) + { +======= + bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum) + { + url = upgradeInsecureUrl(getAbsoluteUrl(url)); + +>>>>>>> ryzomcore + // Search if we are not already downloading this url. + for(uint i = 0; i < Curls.size(); i++) + { + if(Curls[i].url == url) + { + #ifdef LOG_DL + nlwarning("already downloading '%s'", url.c_str()); + #endif + return false; + } + } + + string dest = localBnpName(url); +<<<<<<< HEAD + string tmpdest = localBnpName(url)+".tmp"; + #ifdef LOG_DL + nlwarning("add to download '%s' dest '%s'", url.c_str(), dest.c_str()); + #endif + + // erase the tmp file if exists + if (NLMISC::CFile::fileExists(tmpdest)) + NLMISC::CFile::deleteFile(tmpdest); +======= + #ifdef LOG_DL + nlwarning("add to download '%s' dest '%s'", url.c_str(), dest.c_str()); + #endif +>>>>>>> ryzomcore + + // 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") + { +<<<<<<< HEAD + if (!MultiCurl) + { + nlwarning("Invalid MultiCurl handle, unable to download '%s'", url.c_str()); + return false; + } + + CURL *curl = curl_easy_init(); + if (!curl) + { + nlwarning("Creating cURL handle failed, unable to download '%s'", url.c_str()); + return false; + } + + FILE *fp = nlfopen (tmpdest, "wb"); + if (fp == NULL) + { + curl_easy_cleanup(curl); + nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno)); + return false; + } + + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + + curl_multi_add_handle(MultiCurl, curl); + Curls.push_back(CDataDownload(curl, url, dest, fp, BnpType, NULL, script, md5sum)); + #ifdef LOG_DL + nlwarning("adding handle %x, %d curls", curl, Curls.size()); + #endif + RunningCurls++; +======= + Curls.push_back(CDataDownload(url, dest, BnpType, NULL, script, md5sum)); + if (Curls.size() < options.curlMaxConnections) + { + if (!startCurlDownload(Curls.back())) + { + Curls.pop_back(); + return false; + } + RunningCurls++; + #ifdef LOG_DL + nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size()); + } + else + { + nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size()); + #endif + } +>>>>>>> ryzomcore + } + else + return true; + + return false; + } + + void CGroupHTML::initBnpDownload() + { + if (!_TrustedDomain) + return; + + #ifdef LOG_DL + nlwarning("Init Bnp Download"); + #endif + string pathName = "user"; + if ( ! CFile::isExists( pathName ) ) + CFile::createDirectory( pathName ); + } + + // 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)) + { + #ifdef LOG_DL + nlwarning("more to do now %d - %d curls", NewRunningCurls, Curls.size()); + #endif + } + if(NewRunningCurls < RunningCurls) + { + // some download are done, callback them + #ifdef LOG_DL + nlwarning ("new %d old %d", NewRunningCurls, RunningCurls); + #endif + // check msg + CURLMsg *msg; + int msgs_left; + while ((msg = curl_multi_info_read(MultiCurl, &msgs_left))) + { + #ifdef LOG_DL + nlwarning("> (%s) msgs_left %d", _Id.c_str(), msgs_left); + #endif + if (msg->msg == CURLMSG_DONE) + { + if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle) + { + CURLcode res = msg->data.result; + long code; + curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code); + #ifdef LOG_DL + nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str()); + #endif +<<<<<<< HEAD +======= + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && _CurlWWW->hasHSTSHeader()) + { + CUrlParser uri(_CurlWWW->Url); + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader()); + } +>>>>>>> ryzomcore + + if (res != CURLE_OK) + { + std::string err; + err = "Connection failed with cURL error: "; + err += curl_easy_strerror(res); + err += "\nURL '" + _CurlWWW->Url + "'"; + browseError(err.c_str()); + } + else + if ((code >= 301 && code <= 303) || code == 307 || code == 308) + { + if (_RedirectsRemaining < 0) + { + browseError(string("Redirect limit reached : " + _URL).c_str()); + } + else + { + receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain); + + // 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()) + { + #ifdef LOG_DL + nlwarning("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str()); + #endif + location = getAbsoluteUrl(location); + // throw away this handle and start with new one (easier than reusing) + requestTerminated(); + + _PostNextTime = false; + _RedirectsRemaining--; + + doBrowse(location.c_str()); + } + else + { + browseError(string("Request was redirected, but location was not set : "+_URL).c_str()); + } + } + } + else + { + receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain); + + _RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT; + + if ( (code < 200 || code >= 300) ) + { + browseError(string("Connection failed (curl code " + toString((sint32)res) + ")\nhttp code " + toString((sint32)code) + ")\nURL '" + _CurlWWW->Url + "'").c_str()); + } + else + { + char *ch; + std::string contentType; + res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch); +<<<<<<< HEAD + if (res == CURLE_OK) +======= + if (res == CURLE_OK && ch != NULL) +>>>>>>> ryzomcore + { + contentType = ch; + } + + htmlDownloadFinished(_CurlWWW->Content, contentType, code); + } + requestTerminated(); + } + } + + for (vector::iterator it=Curls.begin(); iteasy_handle == it->curl) + { + CURLcode res = msg->data.result; + long r; + curl_easy_getinfo(it->curl, CURLINFO_RESPONSE_CODE, &r); + fclose(it->fp); + + #ifdef LOG_DL + nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->curl, res, r, it->url.size(), it->url.c_str()); + #endif + curl_multi_remove_handle(MultiCurl, it->curl); + curl_easy_cleanup(it->curl); +======= + if(it->data && it->data->Request == msg->easy_handle) + { + CURLcode res = msg->data.result; + long r; + curl_easy_getinfo(it->data->Request, CURLINFO_RESPONSE_CODE, &r); + fclose(it->fp); + + CUrlParser uri(it->url); + if (!uri.host.empty()) + receiveCookies(it->data->Request, uri.host, isTrustedDomain(uri.host)); + #ifdef LOG_DL + nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->data->Request, res, r, it->url.size(), it->url.c_str()); + #endif + curl_multi_remove_handle(MultiCurl, it->data->Request); + + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && it->data->hasHSTSHeader()) + { + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, it->data->getHSTSHeader()); + } +>>>>>>> ryzomcore + + string tmpfile = it->dest + ".tmp"; + if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString()))) + { +<<<<<<< HEAD + NLMISC::CFile::deleteFile(tmpfile.c_str()); + } + else + { +======= + if (it->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT && ((r >= 301 && r <= 303) || r == 307 || r == 308)) + { + std::string location(it->data->getLocationHeader()); + if (!location.empty()) + { + CUrlParser uri(location); + if (!uri.isAbsolute()) + { + uri.inherit(it->url); + location = uri.toString(); + } + + it->url = location; + it->fp = NULL; + + // release CCurlWWWData + delete it->data; + it->data = NULL; + + it->redirects++; + #ifdef LOG_DL + nlwarning("Redirect '%s'", location.c_str()); + #endif + // keep the request in queue + continue; + } + else + nlwarning("Redirected to empty url '%s'", it->url.c_str()); + } + else + { + if (it->redirects >= DEFAULT_RYZOM_REDIRECT_LIMIT) + nlwarning("Redirect limit reached for '%s'", it->url.c_str()); + + NLMISC::CFile::deleteFile(tmpfile.c_str()); + + // 304 Not Modified + if (res == CURLE_OK && r == 304) + { + CHttpCacheObject obj; + obj.Expires = it->data->getExpires(); + obj.Etag = it->data->getEtag(); + obj.LastModified = it->data->getLastModified(); + + CHttpCache::getInstance()->store(it->dest, obj); + } + } + } + else + { + CHttpCacheObject obj; + obj.Expires = it->data->getExpires(); + obj.Etag = it->data->getEtag(); + obj.LastModified = it->data->getLastModified(); + + CHttpCache::getInstance()->store(it->dest, obj); + +>>>>>>> ryzomcore + string finalUrl; + if (it->type == ImgType) + { + // there is race condition if two browser instances are downloading same file + // second instance deletes first tmpfile and creates new file for itself. + if (CFile::getFileSize(tmpfile) > 0) + { + try + { + // verify that image is not corrupted + uint32 w, h; + CBitmap::loadSize(tmpfile, w, h); + if (w != 0 && h != 0) + { +<<<<<<< HEAD + CFile::moveFile(it->dest, tmpfile); + for(uint i = 0; i < it->imgs.size(); i++) + { + setImage(it->imgs[i].Image, it->dest); +======= + if (CFile::fileExists(it->dest)) + CFile::deleteFile(it->dest); + + CFile::moveFile(it->dest, tmpfile); + for(uint i = 0; i < it->imgs.size(); i++) + { + setImage(it->imgs[i].Image, it->dest, it->imgs[i].Type); +>>>>>>> ryzomcore + setImageSize(it->imgs[i].Image, it->imgs[i].Style); + } + } + } + catch(const NLMISC::Exception &e) + { + // exception message has .tmp file name, so keep it for further analysis + nlwarning("Invalid image (%s): %s", it->url.c_str(), e.what()); + } + } + } + else + { + CFile::moveFile(it->dest, tmpfile); + //if (lookupLocalFile (finalUrl, file.c_str(), false)) + { + CLuaManager::getInstance().executeLuaScript( it->luaScript, true ); + } + } + } + +<<<<<<< HEAD +======= + // release CCurlWWWData + delete it->data; + +>>>>>>> ryzomcore + Curls.erase(it); + break; + } + } + } + } + } +<<<<<<< HEAD + RunningCurls = NewRunningCurls; +======= + + RunningCurls = NewRunningCurls; + + if (RunningCurls < options.curlMaxConnections) + { + for (vector::iterator it=Curls.begin(); itdata == NULL) { + #ifdef LOG_DL + nlwarning("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str()); + #endif + if (!startCurlDownload(*it)) + { + Curls.erase(it); + break; + } + + RunningCurls++; + if (RunningCurls >= options.curlMaxConnections) + break; + } + } + } + +>>>>>>> ryzomcore + #ifdef LOG_DL + if (RunningCurls > 0 || !Curls.empty()) + nlwarning("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size()); + #endif + } + + + void CGroupHTML::releaseDownloads() + { + #ifdef LOG_DL + nlwarning("Release Downloads"); + #endif + if(MultiCurl) + curl_multi_cleanup(MultiCurl); + } + + class CGroupListAdaptor : public CInterfaceGroup + { + public: + CGroupListAdaptor(const TCtorParam ¶m) + : 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 void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); } + + // *************************************************************************** + + void CGroupHTML::beginBuild () + { + if (_Browsing) + { + _Connecting = false; + + removeContent (); + } + else + nlwarning("_Browsing = FALSE"); + } + + + TStyle CGroupHTML::parseStyle (const string &str_styles) + { + TStyle styles; + vector elements; + NLMISC::splitString(str_styles, ";", elements); + + for(uint i = 0; i < elements.size(); ++i) + { + vector 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])] = trim(fullstyle); + } + } + + return styles; + } + + // *************************************************************************** + + void CGroupHTML::addText (const char * buf, int len) + { + if (_Browsing) + { + if (_IgnoreText) + return; + + // Build a UTF8 string + string inputString(buf, buf+len); + + if (_ParsingLua && _TrustedDomain) + { + // we are parsing a lua script + _LuaScript += inputString; + // no more to do + return; + } + + // Build a unicode string + ucstring inputUCString; + inputUCString.fromUtf8(inputString); + + // Build the final unicode string + ucstring tmp; + tmp.reserve(len); + uint ucLen = (uint)inputUCString.size(); + for (uint i=0; i= 'a' && tolower(c) <= 'f'); + } + + static uint8 convertHexa(char c) + { + return (uint8) (tolower(c) - (isdigit(c) ? '0' : ('a' - 10))); + } + + // scan a color component, and return pointer to next position + static const char *scanColorComponent(const char *src, uint8 &intensity) + { + if (!src) return NULL; + if (!isHexa(*src)) return NULL; + uint8 value = convertHexa(*src++) << 4; + if (!isHexa(*src)) return NULL; + value += convertHexa(*src++); + intensity = value; + return src; + } + + static float hueToRgb(float m1, float m2, float h) + { + if (h < 0) h += 1.0f; + if (h > 1) h -= 1.0f; + if (h*6 < 1.0f) return m1 + (m2 - m1)*h*6; + if (h*2 < 1.0f) return m2; + if (h*3 < 2.0f) return m1 + (m2 - m1) * (2.0f/3.0f - h)*6; + return m1; + } + + static void hslToRgb(float h, float s, float l, CRGBA &result) + { + float m1, m2; + if (l <= 0.5f) + m2 = l * (s + 1.0f); + else + m2 = l + s - l * s; + m1 = l*2 - m2; + + result.R = 255 * hueToRgb(m1, m2, h + 1.0f/3.0f); + result.G = 255 * hueToRgb(m1, m2, h); + result.B = 255 * hueToRgb(m1, m2, h - 1.0f/3.0f); + result.A = 255; + } + + class CNameToCol + { + public: + const char *Name; + CRGBA Color; + CNameToCol(const char *name, CRGBA color) : Name(name), Color(color) {} + }; + + static CNameToCol htmlColorNameToRGBA[] = + { + CNameToCol("AliceBlue", CRGBA(0xF0, 0xF8, 0xFF)), + CNameToCol("AntiqueWhite", CRGBA(0xFA, 0xEB, 0xD7)), + CNameToCol("Aqua", CRGBA(0x00, 0xFF, 0xFF)), + CNameToCol("Aquamarine", CRGBA(0x7F, 0xFF, 0xD4)), + CNameToCol("Azure", CRGBA(0xF0, 0xFF, 0xFF)), + CNameToCol("Beige", CRGBA(0xF5, 0xF5, 0xDC)), + CNameToCol("Bisque", CRGBA(0xFF, 0xE4, 0xC4)), + CNameToCol("Black", CRGBA(0x00, 0x00, 0x00)), + CNameToCol("BlanchedAlmond", CRGBA(0xFF, 0xEB, 0xCD)), + CNameToCol("Blue", CRGBA(0x00, 0x00, 0xFF)), + CNameToCol("BlueViolet", CRGBA(0x8A, 0x2B, 0xE2)), + CNameToCol("Brown", CRGBA(0xA5, 0x2A, 0x2A)), + CNameToCol("BurlyWood", CRGBA(0xDE, 0xB8, 0x87)), + CNameToCol("CadetBlue", CRGBA(0x5F, 0x9E, 0xA0)), + CNameToCol("Chartreuse", CRGBA(0x7F, 0xFF, 0x00)), + CNameToCol("Chocolate", CRGBA(0xD2, 0x69, 0x1E)), + CNameToCol("Coral", CRGBA(0xFF, 0x7F, 0x50)), + CNameToCol("CornflowerBlue", CRGBA(0x64, 0x95, 0xED)), + CNameToCol("Cornsilk", CRGBA(0xFF, 0xF8, 0xDC)), + CNameToCol("Crimson", CRGBA(0xDC, 0x14, 0x3C)), + CNameToCol("Cyan", CRGBA(0x00, 0xFF, 0xFF)), + CNameToCol("DarkBlue", CRGBA(0x00, 0x00, 0x8B)), + CNameToCol("DarkCyan", CRGBA(0x00, 0x8B, 0x8B)), + CNameToCol("DarkGoldenRod", CRGBA(0xB8, 0x86, 0x0B)), + CNameToCol("DarkGray", CRGBA(0xA9, 0xA9, 0xA9)), + CNameToCol("DarkGreen", CRGBA(0x00, 0x64, 0x00)), + CNameToCol("DarkKhaki", CRGBA(0xBD, 0xB7, 0x6B)), + CNameToCol("DarkMagenta", CRGBA(0x8B, 0x00, 0x8B)), + CNameToCol("DarkOliveGreen", CRGBA(0x55, 0x6B, 0x2F)), + CNameToCol("Darkorange", CRGBA(0xFF, 0x8C, 0x00)), + CNameToCol("DarkOrchid", CRGBA(0x99, 0x32, 0xCC)), + CNameToCol("DarkRed", CRGBA(0x8B, 0x00, 0x00)), + CNameToCol("DarkSalmon", CRGBA(0xE9, 0x96, 0x7A)), + CNameToCol("DarkSeaGreen", CRGBA(0x8F, 0xBC, 0x8F)), + CNameToCol("DarkSlateBlue", CRGBA(0x48, 0x3D, 0x8B)), + CNameToCol("DarkSlateGray", CRGBA(0x2F, 0x4F, 0x4F)), + CNameToCol("DarkTurquoise", CRGBA(0x00, 0xCE, 0xD1)), + CNameToCol("DarkViolet", CRGBA(0x94, 0x00, 0xD3)), + CNameToCol("DeepPink", CRGBA(0xFF, 0x14, 0x93)), + CNameToCol("DeepSkyBlue", CRGBA(0x00, 0xBF, 0xFF)), + CNameToCol("DimGray", CRGBA(0x69, 0x69, 0x69)), + CNameToCol("DodgerBlue", CRGBA(0x1E, 0x90, 0xFF)), + CNameToCol("Feldspar", CRGBA(0xD1, 0x92, 0x75)), + CNameToCol("FireBrick", CRGBA(0xB2, 0x22, 0x22)), + CNameToCol("FloralWhite", CRGBA(0xFF, 0xFA, 0xF0)), + CNameToCol("ForestGreen", CRGBA(0x22, 0x8B, 0x22)), + CNameToCol("Fuchsia", CRGBA(0xFF, 0x00, 0xFF)), + CNameToCol("Gainsboro", CRGBA(0xDC, 0xDC, 0xDC)), + CNameToCol("GhostWhite", CRGBA(0xF8, 0xF8, 0xFF)), + CNameToCol("Gold", CRGBA(0xFF, 0xD7, 0x00)), + CNameToCol("GoldenRod", CRGBA(0xDA, 0xA5, 0x20)), + CNameToCol("Gray", CRGBA(0x80, 0x80, 0x80)), + CNameToCol("Green", CRGBA(0x00, 0x80, 0x00)), + CNameToCol("GreenYellow", CRGBA(0xAD, 0xFF, 0x2F)), + CNameToCol("HoneyDew", CRGBA(0xF0, 0xFF, 0xF0)), + CNameToCol("HotPink", CRGBA(0xFF, 0x69, 0xB4)), + CNameToCol("IndianRed ", CRGBA(0xCD, 0x5C, 0x5C)), + CNameToCol("Indigo ", CRGBA(0x4B, 0x00, 0x82)), + CNameToCol("Ivory", CRGBA(0xFF, 0xFF, 0xF0)), + CNameToCol("Khaki", CRGBA(0xF0, 0xE6, 0x8C)), + CNameToCol("Lavender", CRGBA(0xE6, 0xE6, 0xFA)), + CNameToCol("LavenderBlush", CRGBA(0xFF, 0xF0, 0xF5)), + CNameToCol("LawnGreen", CRGBA(0x7C, 0xFC, 0x00)), + CNameToCol("LemonChiffon", CRGBA(0xFF, 0xFA, 0xCD)), + CNameToCol("LightBlue", CRGBA(0xAD, 0xD8, 0xE6)), + CNameToCol("LightCoral", CRGBA(0xF0, 0x80, 0x80)), + CNameToCol("LightCyan", CRGBA(0xE0, 0xFF, 0xFF)), + CNameToCol("LightGoldenRodYellow", CRGBA(0xFA, 0xFA, 0xD2)), + CNameToCol("LightGrey", CRGBA(0xD3, 0xD3, 0xD3)), + CNameToCol("LightGreen", CRGBA(0x90, 0xEE, 0x90)), + CNameToCol("LightPink", CRGBA(0xFF, 0xB6, 0xC1)), + CNameToCol("LightSalmon", CRGBA(0xFF, 0xA0, 0x7A)), + CNameToCol("LightSeaGreen", CRGBA(0x20, 0xB2, 0xAA)), + CNameToCol("LightSkyBlue", CRGBA(0x87, 0xCE, 0xFA)), + CNameToCol("LightSlateBlue", CRGBA(0x84, 0x70, 0xFF)), + CNameToCol("LightSlateGray", CRGBA(0x77, 0x88, 0x99)), + CNameToCol("LightSteelBlue", CRGBA(0xB0, 0xC4, 0xDE)), + CNameToCol("LightYellow", CRGBA(0xFF, 0xFF, 0xE0)), + CNameToCol("Lime", CRGBA(0x00, 0xFF, 0x00)), + CNameToCol("LimeGreen", CRGBA(0x32, 0xCD, 0x32)), + CNameToCol("Linen", CRGBA(0xFA, 0xF0, 0xE6)), + CNameToCol("Magenta", CRGBA(0xFF, 0x00, 0xFF)), + CNameToCol("Maroon", CRGBA(0x80, 0x00, 0x00)), + CNameToCol("MediumAquaMarine", CRGBA(0x66, 0xCD, 0xAA)), + CNameToCol("MediumBlue", CRGBA(0x00, 0x00, 0xCD)), + CNameToCol("MediumOrchid", CRGBA(0xBA, 0x55, 0xD3)), + CNameToCol("MediumPurple", CRGBA(0x93, 0x70, 0xD8)), + CNameToCol("MediumSeaGreen", CRGBA(0x3C, 0xB3, 0x71)), + CNameToCol("MediumSlateBlue", CRGBA(0x7B, 0x68, 0xEE)), + CNameToCol("MediumSpringGreen", CRGBA(0x00, 0xFA, 0x9A)), + CNameToCol("MediumTurquoise", CRGBA(0x48, 0xD1, 0xCC)), + CNameToCol("MediumVioletRed", CRGBA(0xC7, 0x15, 0x85)), + CNameToCol("MidnightBlue", CRGBA(0x19, 0x19, 0x70)), + CNameToCol("MintCream", CRGBA(0xF5, 0xFF, 0xFA)), + CNameToCol("MistyRose", CRGBA(0xFF, 0xE4, 0xE1)), + CNameToCol("Moccasin", CRGBA(0xFF, 0xE4, 0xB5)), + CNameToCol("NavajoWhite", CRGBA(0xFF, 0xDE, 0xAD)), + CNameToCol("Navy", CRGBA(0x00, 0x00, 0x80)), + CNameToCol("OldLace", CRGBA(0xFD, 0xF5, 0xE6)), + CNameToCol("Olive", CRGBA(0x80, 0x80, 0x00)), + CNameToCol("OliveDrab", CRGBA(0x6B, 0x8E, 0x23)), + CNameToCol("Orange", CRGBA(0xFF, 0xA5, 0x00)), + CNameToCol("OrangeRed", CRGBA(0xFF, 0x45, 0x00)), + CNameToCol("Orchid", CRGBA(0xDA, 0x70, 0xD6)), + CNameToCol("PaleGoldenRod", CRGBA(0xEE, 0xE8, 0xAA)), + CNameToCol("PaleGreen", CRGBA(0x98, 0xFB, 0x98)), + CNameToCol("PaleTurquoise", CRGBA(0xAF, 0xEE, 0xEE)), + CNameToCol("PaleVioletRed", CRGBA(0xD8, 0x70, 0x93)), + CNameToCol("PapayaWhip", CRGBA(0xFF, 0xEF, 0xD5)), + CNameToCol("PeachPuff", CRGBA(0xFF, 0xDA, 0xB9)), + CNameToCol("Peru", CRGBA(0xCD, 0x85, 0x3F)), + CNameToCol("Pink", CRGBA(0xFF, 0xC0, 0xCB)), + CNameToCol("Plum", CRGBA(0xDD, 0xA0, 0xDD)), + CNameToCol("PowderBlue", CRGBA(0xB0, 0xE0, 0xE6)), + CNameToCol("Purple", CRGBA(0x80, 0x00, 0x80)), + CNameToCol("Red", CRGBA(0xFF, 0x00, 0x00)), + CNameToCol("RosyBrown", CRGBA(0xBC, 0x8F, 0x8F)), + CNameToCol("RoyalBlue", CRGBA(0x41, 0x69, 0xE1)), + CNameToCol("SaddleBrown", CRGBA(0x8B, 0x45, 0x13)), + CNameToCol("Salmon", CRGBA(0xFA, 0x80, 0x72)), + CNameToCol("SandyBrown", CRGBA(0xF4, 0xA4, 0x60)), + CNameToCol("SeaGreen", CRGBA(0x2E, 0x8B, 0x57)), + CNameToCol("SeaShell", CRGBA(0xFF, 0xF5, 0xEE)), + CNameToCol("Sienna", CRGBA(0xA0, 0x52, 0x2D)), + CNameToCol("Silver", CRGBA(0xC0, 0xC0, 0xC0)), + CNameToCol("SkyBlue", CRGBA(0x87, 0xCE, 0xEB)), + CNameToCol("SlateBlue", CRGBA(0x6A, 0x5A, 0xCD)), + CNameToCol("SlateGray", CRGBA(0x70, 0x80, 0x90)), + CNameToCol("Snow", CRGBA(0xFF, 0xFA, 0xFA)), + CNameToCol("SpringGreen", CRGBA(0x00, 0xFF, 0x7F)), + CNameToCol("SteelBlue", CRGBA(0x46, 0x82, 0xB4)), + CNameToCol("Tan", CRGBA(0xD2, 0xB4, 0x8C)), + CNameToCol("Teal", CRGBA(0x00, 0x80, 0x80)), + CNameToCol("Thistle", CRGBA(0xD8, 0xBF, 0xD8)), + CNameToCol("Tomato", CRGBA(0xFF, 0x63, 0x47)), + CNameToCol("Turquoise", CRGBA(0x40, 0xE0, 0xD0)), + CNameToCol("Violet", CRGBA(0xEE, 0x82, 0xEE)), + CNameToCol("VioletRed", CRGBA(0xD0, 0x20, 0x90)), + CNameToCol("Wheat", CRGBA(0xF5, 0xDE, 0xB3)), + CNameToCol("White", CRGBA(0xFF, 0xFF, 0xFF)), + CNameToCol("WhiteSmoke", CRGBA(0xF5, 0xF5, 0xF5)), + CNameToCol("Yellow", CRGBA(0xFF, 0xFF, 0x00)), + CNameToCol("YellowGreen", CRGBA(0x9A, 0xCD, 0x32)) + }; + + // scan a color from a HTML form (#rrggbb format) + bool scanHTMLColor(const char *src, CRGBA &dest) + { + if (!src || *src == '\0') return false; + if (*src == '#') + { + ++src; + if (strlen(src) == 3 || strlen(src) == 4) + { + bool hasAlpha = (strlen(src) == 4); + // check RGB for valid hex + if (isHexa(src[0]) && isHexa(src[1]) && isHexa(src[2])) + { + // check optional A for valid hex + if (hasAlpha && !isHexa(src[3])) return false; + + dest.R = convertHexa(src[0]); + dest.G = convertHexa(src[1]); + dest.B = convertHexa(src[2]); + + dest.R = dest.R << 4 | dest.R; + dest.G = dest.G << 4 | dest.G; + dest.B = dest.B << 4 | dest.B; + + if (hasAlpha) + { + dest.A = convertHexa(src[3]); + dest.A = dest.A << 4 | dest.A; + } + else + dest.A = 255; + + return true; + } + + return false; + } + + CRGBA result; + src = scanColorComponent(src, result.R); if (!src) return false; + src = scanColorComponent(src, result.G); if (!src) return false; + src = scanColorComponent(src, result.B); if (!src) return false; + src = scanColorComponent(src, result.A); + if (!src) + { + // Alpha is optional + result.A = 255; + } + dest = result; + return true; + } + + if (strnicmp(src, "rgb(", 4) == 0 || strnicmp(src, "rgba(", 5) == 0) + { + src += 4; + if (*src == '(') src++; + + vector parts; + NLMISC::splitString(src, ",", parts); + if (parts.size() >= 3) + { + CRGBA result; + sint tmpv; + float tmpf; + + // R + if (getPercentage(tmpv, tmpf, parts[0].c_str())) tmpv = 255 * tmpf; + clamp(tmpv, 0, 255); + result.R = tmpv; + + // G + if (getPercentage(tmpv, tmpf, parts[1].c_str())) tmpv = 255 * tmpf; + clamp(tmpv, 0, 255); + result.G = tmpv; + + // B + if (getPercentage(tmpv, tmpf, parts[2].c_str())) tmpv = 255 * tmpf; + clamp(tmpv, 0, 255); + result.B = tmpv; + + // A + if (parts.size() == 4) + { + if (!fromString(parts[3], tmpf)) return false; + if (parts[3].find_first_of("%") != std::string::npos) + tmpf /= 100; + + tmpv = 255 * tmpf; + clamp(tmpv, 0, 255); + result.A = tmpv; + } + else + result.A = 255; + + dest = result; + return true; + } + + return false; + } + + if (strnicmp(src, "hsl(", 4) == 0 || strnicmp(src, "hsla(", 5) == 0) + { + src += 4; + if (*src == '(') src++; + + vector parts; + NLMISC::splitString(src, ",", parts); + if (parts.size() >= 3) + { + sint tmpv; + float h, s, l; + // hue + if (!fromString(parts[0], tmpv)) return false; + tmpv = ((tmpv % 360) + 360) % 360; + h = (float) tmpv / 360.0f; + + // saturation + if (!getPercentage(tmpv, s, parts[1].c_str())) return false; + clamp(s, 0.0f, 1.0f); + + // lightness + if (!getPercentage(tmpv, l, parts[2].c_str())) return false; + clamp(l, 0.0f, 1.0f); + + CRGBA result; + hslToRgb(h, s, l, result); + + // A + if (parts.size() == 4) + { + float tmpf; + if (!fromString(parts[3], tmpf)) return false; + if (parts[3].find_first_of("%") != std::string::npos) + tmpf /= 100; + clamp(tmpf, 0.0f, 1.0f); + result.A = 255 * tmpf; + } + + dest = result; + return true; + } + + return false; + } + + { + // slow but should suffice for now + for(uint k = 0; k < sizeofarray(htmlColorNameToRGBA); ++k) + { + if (nlstricmp(src, htmlColorNameToRGBA[k].Name) == 0) + { + dest = htmlColorNameToRGBA[k].Color; + return true; + } + } + return false; + } + } + + // *************************************************************************** + + void CGroupHTML::beginElement (uint element_number, const std::vector &present, const std::vector &value) + { + if (_Browsing) + { + // Paragraph ? + switch(element_number) + { + case HTML_HEAD: + _ReadingHeadTag = !_IgnoreHeadTag; + _IgnoreHeadTag = true; + break; + case HTML_BASE: + if (_ReadingHeadTag && !_IgnoreBaseUrlTag) + { + if (present[HTML_BASE_HREF] && value[HTML_BASE_HREF]) + { + CUrlParser uri(value[HTML_BASE_HREF]); + if (uri.isAbsolute()) + { + _URL = uri.toString(); + _IgnoreBaseUrlTag = true; + } + } + } + break; + case HTML_META: + if (_ReadingHeadTag) + { + bool httpEquiv = present[HTML_META_HTTP_EQUIV] && value[HTML_META_HTTP_EQUIV]; + bool httpContent = present[HTML_META_CONTENT] && value[HTML_META_CONTENT]; + if (httpEquiv && httpContent) + { + // only first http-equiv="refresh" should be handled + if (_RefreshUrl.empty() && toLower(value[HTML_META_HTTP_EQUIV]) == "refresh") + { + const CWidgetManager::SInterfaceTimes × = CWidgetManager::getInstance()->getInterfaceTimes(); + double timeSec = times.thisFrameMs / 1000.0f; + string content(value[HTML_META_CONTENT]); + + string::size_type pos = content.find_first_of(";"); + if (pos == string::npos) + { + fromString(content, _NextRefreshTime); + _RefreshUrl = _URL; + } + else + { + fromString(content.substr(0, pos), _NextRefreshTime); + + pos = toLower(content).find("url="); + if (pos != string::npos) + _RefreshUrl = getAbsoluteUrl(content.substr(pos + 4)); + } + + _NextRefreshTime += timeSec; + } + } + } + break; + case HTML_A: + { + registerAnchorName(MY_HTML_A); + + CStyleParams style; + style.FontFamily = getFontFamily(); + style.FontSize = getFontSize(); + style.TextColor = LinkColor; + style.Underlined = true; + style.StrikeThrough = getFontStrikeThrough(); +<<<<<<< HEAD +======= + style.GlobalColor = LinkColorGlobalColor; +>>>>>>> ryzomcore + + if (present[HTML_A_STYLE] && value[HTML_A_STYLE]) + getStyleParams(value[HTML_A_STYLE], style); + + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _TextColor.push_back(style.TextColor); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); +<<<<<<< HEAD + _GlobalColor.push_back(LinkColorGlobalColor); +======= + _GlobalColor.push_back(style.GlobalColor); +>>>>>>> ryzomcore + _A.push_back(true); + _Link.push_back (""); + _LinkTitle.push_back(""); + _LinkClass.push_back(""); + + // #fragment works with both ID and NAME so register both + if (present[MY_HTML_A_NAME] && value[MY_HTML_A_NAME]) + _AnchorName.push_back(value[MY_HTML_A_NAME]); + if (present[MY_HTML_A_TITLE] && value[MY_HTML_A_TITLE]) + _LinkTitle.back() = value[MY_HTML_A_TITLE]; + if (present[MY_HTML_A_CLASS] && value[MY_HTML_A_CLASS]) + _LinkClass.back() = value[MY_HTML_A_CLASS]; + if (present[MY_HTML_A_HREF] && value[MY_HTML_A_HREF]) + { + string suri = value[MY_HTML_A_HREF]; + if(suri.find("ah:") == 0) + { + if (_TrustedDomain) + _Link.back() = suri; + } + else if (_TrustedDomain && suri[0] == '#' && _LuaHrefHack) + { + // Direct url (hack for lua beginElement) + _Link.back() = suri.substr(1); + } + else + { + // convert href from "?key=val" into "http://domain.com/?key=val" + _Link.back() = getAbsoluteUrl(suri); + } + } + } + break; + case HTML_DIV: + { + _BlockLevelElement.push_back(true); + registerAnchorName(MY_HTML_DIV); + + if (present[MY_HTML_DIV_NAME] && value[MY_HTML_DIV_NAME]) + _DivName = value[MY_HTML_DIV_NAME]; + + string instClass; + if (present[MY_HTML_DIV_CLASS] && value[MY_HTML_DIV_CLASS]) + instClass = value[MY_HTML_DIV_CLASS]; + + // use generic template system + if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate") + { + string id; + if (present[MY_HTML_DIV_ID] && value[MY_HTML_DIV_ID]) + id = value[MY_HTML_DIV_ID]; + + string style; + if (present[MY_HTML_DIV_STYLE] && value[MY_HTML_DIV_STYLE]) + style = value[MY_HTML_DIV_STYLE]; + + typedef pair TTmplParam; + vector 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+":"+id, 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); + + _BlockLevelElement.back() = false; + } + else + { + getParagraph()->addChild(inst); + paragraphChange(); + } + _Divs.push_back(inst); + } + } + } + + if (isBlockLevelElement()) + { + newParagraph(0); + } + } + break; + case HTML_FONT: + { + bool found = false; + if (present[HTML_FONT_COLOR] && value[HTML_FONT_COLOR]) + { + CRGBA color; + if (scanHTMLColor(value[HTML_FONT_COLOR], color)) + { + _TextColor.push_back(color); + found = true; + } + } + if (!found) + { + _TextColor.push_back(_TextColor.empty() ? CRGBA::White : _TextColor.back()); + } + + if (present[HTML_FONT_SIZE] && value[HTML_FONT_SIZE]) + { + uint fontsize; + fromString(value[HTML_FONT_SIZE], fontsize); + _FontSize.push_back(fontsize); + } + else + { + _FontSize.push_back(_FontSize.empty() ? TextFontSize : _FontSize.back()); + } + } + break; + case HTML_BR: +<<<<<<< HEAD + addString(ucstring ("\n")); +======= + { + endParagraph(); + + // insert zero-width-space (0x200B) to prevent removal of empty lines + ucstring tmp; + tmp.fromUtf8("\xe2\x80\x8b"); + addString(tmp); + } +>>>>>>> ryzomcore + break; + case HTML_BODY: + { + if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR]) + { + CRGBA bgColor; + if (scanHTMLColor(value[HTML_BODY_BGCOLOR], bgColor)) + setBackgroundColor (bgColor); + } + + string style; + if (present[HTML_BODY_STYLE] && value[HTML_BODY_STYLE]) + style = value[HTML_BODY_STYLE]; + + + if (!style.empty()) + { + TStyle styles = parseStyle(style); + TStyle::iterator it; + + it = styles.find("background-repeat"); + bool repeat = (it != styles.end() && it->second == "1"); + + // Webig only + it = styles.find("background-scale"); + bool scale = (it != styles.end() && it->second == "1"); + + it = styles.find("background-image"); + if (it != styles.end()) + { + string image = it->second; + string::size_type texExt = toLower(image).find("url("); + // Url image + if (texExt != string::npos) + // Remove url() + image = image.substr(4, image.size()-5); + setBackground (image, scale, repeat); + } + } + } + break; + case HTML_FORM: + { + // Build the form + CGroupHTML::CForm form; + + // Get the action name + if (present[HTML_FORM_ACTION] && value[HTML_FORM_ACTION]) + { + form.Action = getAbsoluteUrl(string(value[HTML_FORM_ACTION])); + } + else + { + form.Action = _URL; + } + _Forms.push_back(form); + } + break; + case HTML_H1: + registerAnchorName(MY_HTML_H1); + newParagraph(PBeginSpace); + _FontSize.push_back(H1FontSize); + _TextColor.push_back(H1Color); + _GlobalColor.push_back(H1ColorGlobalColor); + break; + case HTML_H2: + registerAnchorName(MY_HTML_H2); + newParagraph(PBeginSpace); + _FontSize.push_back(H2FontSize); + _TextColor.push_back(H2Color); + _GlobalColor.push_back(H2ColorGlobalColor); + break; + case HTML_H3: + registerAnchorName(MY_HTML_H3); + newParagraph(PBeginSpace); + _FontSize.push_back(H3FontSize); + _TextColor.push_back(H3Color); + _GlobalColor.push_back(H3ColorGlobalColor); + break; + case HTML_H4: + registerAnchorName(MY_HTML_H4); + newParagraph(PBeginSpace); + _FontSize.push_back(H4FontSize); + _TextColor.push_back(H4Color); + _GlobalColor.push_back(H4ColorGlobalColor); + break; + case HTML_H5: + registerAnchorName(MY_HTML_H5); + newParagraph(PBeginSpace); + _FontSize.push_back(H5FontSize); + _TextColor.push_back(H5Color); + _GlobalColor.push_back(H5ColorGlobalColor); + break; + case HTML_H6: + registerAnchorName(MY_HTML_H6); + newParagraph(PBeginSpace); + _FontSize.push_back(H6FontSize); + _TextColor.push_back(H6Color); + _GlobalColor.push_back(H6ColorGlobalColor); + break; + case HTML_IMG: + { + // Get the string name + if (present[MY_HTML_IMG_SRC] && value[MY_HTML_IMG_SRC]) + { + CStyleParams style; + float tmpf; + + if (present[MY_HTML_IMG_WIDTH] && value[MY_HTML_IMG_WIDTH]) + getPercentage(style.Width, tmpf, value[MY_HTML_IMG_WIDTH]); + if (present[MY_HTML_IMG_HEIGHT] && value[MY_HTML_IMG_HEIGHT]) + getPercentage(style.Height, tmpf, value[MY_HTML_IMG_HEIGHT]); +<<<<<<< HEAD + // width, height from inline css + if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) + getStyleParams(value[MY_HTML_IMG_STYLE], style); + + // Get the global color name + bool globalColor = false; + if (present[MY_HTML_IMG_GLOBAL_COLOR]) + globalColor = true; + + if (getA() && getParent () && getParent ()->getParent()) + { + // Tooltip + const char *tooltip = NULL; + if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT]) + tooltip = value[MY_HTML_IMG_ALT]; + + string params = "name=" + getId() + "|url=" + getLink (); + addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + "", globalColor, "browse", params.c_str(), tooltip, style); +======= + + // Get the global color name + if (present[MY_HTML_IMG_GLOBAL_COLOR]) + style.GlobalColor = true; + + // width, height from inline css + if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) + getStyleParams(value[MY_HTML_IMG_STYLE], style); + + // Tooltip + const char *tooltip = NULL; + // keep "alt" attribute for backward compatibility + if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT]) + tooltip = value[MY_HTML_IMG_ALT]; + // tooltip + if (present[MY_HTML_IMG_TITLE] && value[MY_HTML_IMG_TITLE]) + tooltip = value[MY_HTML_IMG_TITLE]; + + // Mouse over image + string overSrc; + if (present[MY_HTML_IMG_DATA_OVER_SRC] && value[MY_HTML_IMG_DATA_OVER_SRC]) + { + overSrc = value[MY_HTML_IMG_DATA_OVER_SRC]; + } + + + if (getA() && getParent () && getParent ()->getParent()) + { + string params = "name=" + getId() + "|url=" + getLink (); + addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + overSrc, "browse", params.c_str(), tooltip, style); + } + else + if (tooltip || !overSrc.empty()) + { + addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + overSrc, "", "", tooltip, style); +>>>>>>> ryzomcore + } + else + { + // Get the option to reload (class==reload) + bool reloadImg = false; +<<<<<<< HEAD + +======= + +>>>>>>> ryzomcore + string styleString; + if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) + styleString = value[MY_HTML_IMG_STYLE]; + + if (!styleString.empty()) + { + TStyle styles = parseStyle(styleString); + TStyle::iterator it; + + it = styles.find("reload"); + if (it != styles.end() && (*it).second == "1") + reloadImg = true; + } +<<<<<<< HEAD + + addImage (value[MY_HTML_IMG_SRC], globalColor, reloadImg, style); +======= + + addImage (value[MY_HTML_IMG_SRC], reloadImg, style); +>>>>>>> ryzomcore + } + } + } + break; + case HTML_INPUT: + // Got one form ? + if (!(_Forms.empty())) + { + // read general property + string templateName; + string minWidth; + + // Widget template name + if (present[MY_HTML_INPUT_Z_BTN_TMPL] && value[MY_HTML_INPUT_Z_BTN_TMPL]) + templateName = value[MY_HTML_INPUT_Z_BTN_TMPL]; + // Input name is the new + if (present[MY_HTML_INPUT_Z_INPUT_TMPL] && value[MY_HTML_INPUT_Z_INPUT_TMPL]) + templateName = value[MY_HTML_INPUT_Z_INPUT_TMPL]; + // Widget minimal width + if (present[MY_HTML_INPUT_Z_INPUT_WIDTH] && value[MY_HTML_INPUT_Z_INPUT_WIDTH]) + minWidth = value[MY_HTML_INPUT_Z_INPUT_WIDTH]; + + // Get the type + if (present[MY_HTML_INPUT_TYPE] && value[MY_HTML_INPUT_TYPE]) + { +<<<<<<< HEAD + // Global color flag + bool globalColor = false; + if (present[MY_HTML_INPUT_GLOBAL_COLOR]) + globalColor = true; +======= + // by default not inherited, font family defaults to system font + CStyleParams style; + style.TextColor = TextColor; + style.FontSize = TextFontSize; + style.FontWeight = FONT_WEIGHT_NORMAL; + style.FontOblique = false; + + // Global color flag + if (present[MY_HTML_INPUT_GLOBAL_COLOR]) + style.GlobalColor = true; +>>>>>>> ryzomcore + + // Tooltip + const char *tooltip = NULL; + if (present[MY_HTML_INPUT_ALT] && value[MY_HTML_INPUT_ALT]) + tooltip = value[MY_HTML_INPUT_ALT]; + +<<<<<<< HEAD + // by default not inherited, font family defaults to system font + CStyleParams style; + style.TextColor = TextColor; + style.FontSize = TextFontSize; + style.FontWeight = FONT_WEIGHT_NORMAL; + style.FontOblique = false; + +======= +>>>>>>> ryzomcore + if (present[MY_HTML_INPUT_STYLE] && value[MY_HTML_INPUT_STYLE]) + getStyleParams(value[MY_HTML_INPUT_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + + string type = toLower(value[MY_HTML_INPUT_TYPE]); + if (type == "image") + { + // The submit button + string name; + string normal; + string pushed; + string over; + if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME]) + name = value[MY_HTML_INPUT_NAME]; + if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC]) + normal = value[MY_HTML_INPUT_SRC]; + + // Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name" + string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=image"; + + // Add the ctrl button + addButton (CCtrlButton::PushButton, name, normal, pushed.empty()?normal:pushed, over, +<<<<<<< HEAD + globalColor, "html_submit_form", param.c_str(), tooltip, style); +======= + "html_submit_form", param.c_str(), tooltip, style); +>>>>>>> ryzomcore + } + if (type == "button" || type == "submit") + { + // The submit button + string name; + string text; + string normal; + string pushed; + string over; + + string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup ); + if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME]) + name = value[MY_HTML_INPUT_NAME]; + if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC]) + normal = value[MY_HTML_INPUT_SRC]; + if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE]) + text = value[MY_HTML_INPUT_VALUE]; + + // Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name" + string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=submit"; + if (!text.empty()) + { + // escape AH param separator + string tmp = text; + while(NLMISC::strFindReplace(tmp, "|", "|")) + ; + param = param + "|submit_button_value=" + tmp; + } + + // Add the ctrl button + if (!_Paragraph) + { + newParagraph (0); + paragraphChange (); + } + + typedef pair TTmplParam; + vector 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("text", text)); + tmplParams.push_back(TTmplParam("active", "true")); + if (!minWidth.empty()) + tmplParams.push_back(TTmplParam("wmin", minWidth)); + CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams); + if (buttonGroup) + { + + // Add the ctrl button + CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button")); + if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b")); + if (ctrlButton) + { +<<<<<<< HEAD + ctrlButton->setModulateGlobalColorAll (globalColor); +======= + ctrlButton->setModulateGlobalColorAll (style.GlobalColor); +>>>>>>> ryzomcore + + // Translate the tooltip + if (tooltip) + { + if (CI18N::hasTranslation(tooltip)) + { + ctrlButton->setDefaultContextHelp(CI18N::get(tooltip)); + } + else + { + ctrlButton->setDefaultContextHelp(ucstring(tooltip)); + } + } + + ctrlButton->setText(ucstring::makeFromUtf8(text)); + } + getParagraph()->addChild (buttonGroup); + paragraphChange (); + } + } + else if (type == "text") + { + // Get the string name + string name; + ucstring ucValue; + uint size = 120; + uint maxlength = 1024; + if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME]) + name = value[MY_HTML_INPUT_NAME]; + if (present[MY_HTML_INPUT_SIZE] && value[MY_HTML_INPUT_SIZE]) + fromString(value[MY_HTML_INPUT_SIZE], size); + if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE]) + ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]); + if (present[MY_HTML_INPUT_MAXLENGTH] && value[MY_HTML_INPUT_MAXLENGTH]) + fromString(value[MY_HTML_INPUT_MAXLENGTH], maxlength); + + string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup); + // Add the editbox + CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size/12, 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") + { + CCtrlButton::EType btnType; + string name; + string normal; + string pushed; + string over; + ucstring ucValue = ucstring("on"); + bool checked = false; + + if (type == "radio") + { + btnType = CCtrlButton::RadioButton; + normal = DefaultRadioButtonBitmapNormal; + pushed = DefaultRadioButtonBitmapPushed; + over = DefaultRadioButtonBitmapOver; + } + else + { + btnType = CCtrlButton::ToggleButton; + normal = DefaultCheckBoxBitmapNormal; + pushed = DefaultCheckBoxBitmapPushed; + over = DefaultCheckBoxBitmapOver; + } + + if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME]) + name = value[MY_HTML_INPUT_NAME]; + if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC]) + normal = value[MY_HTML_INPUT_SRC]; + if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE]) + ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]); + checked = (present[MY_HTML_INPUT_CHECKED] && value[MY_HTML_INPUT_CHECKED]); + + // Add the ctrl button +<<<<<<< HEAD + CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, + globalColor, "", "", tooltip); +======= + CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, style); +>>>>>>> ryzomcore + if (checkbox) + { + if (btnType == CCtrlButton::RadioButton) + { + // group together buttons with same name + CForm &form = _Forms.back(); + bool notfound = true; + for (uint i=0; igetType() == 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); + } + } + else if (type == "hidden") + { + if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME]) + { + // Get the name + string name = value[MY_HTML_INPUT_NAME]; + + // Get the value + ucstring ucValue; + if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE]) + ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]); + + // Add an entry + CGroupHTML::CForm::CEntry entry; + entry.Name = name; + entry.Value = decodeHTMLEntities(ucValue); + _Forms.back().Entries.push_back (entry); + } + } + + popIfNotEmpty(_FontFamily); + popIfNotEmpty(_FontSize); + popIfNotEmpty(_TextColor); + popIfNotEmpty(_FontWeight); + popIfNotEmpty(_FontOblique); + } + } + break; + case HTML_SELECT: + if (!(_Forms.empty())) + { + CStyleParams style; + + // A select box + string name; + bool multiple = false; + sint32 size = 0; + + if (present[HTML_SELECT_NAME] && value[HTML_SELECT_NAME]) + name = value[HTML_SELECT_NAME]; + if (present[HTML_SELECT_SIZE] && value[HTML_SELECT_SIZE]) + fromString(value[HTML_SELECT_SIZE], size); + if (present[HTML_SELECT_MULTIPLE] && value[HTML_SELECT_MULTIPLE]) + multiple = true; + if (present[HTML_SELECT_STYLE] && value[HTML_SELECT_STYLE]) + getStyleParams(value[HTML_SELECT_STYLE], style); + + 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.Width > -1) + sb->setMinW(style.Width); + + if (style.Height > -1) + sb->setMinH(style.Height); + + sb->setMaxVisibleLine(size); + } + + entry.SelectBox = sb; + } + else + { + CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str()); + entry.ComboBox = cb; + } + _Forms.back().Entries.push_back (entry); + } + break; + case HTML_OPTION: + // Got one form ? + if (!(_Forms.empty())) + { + if (!_Forms.back().Entries.empty()) + { + // clear the option string + _SelectOptionStr.clear(); + + std::string optionValue; + if (present[HTML_OPTION_VALUE] && value[HTML_OPTION_VALUE]) + optionValue = value[HTML_OPTION_VALUE]; + _Forms.back().Entries.back().SelectValues.push_back(optionValue); + + if (present[HTML_OPTION_SELECTED]) + _Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1; + if (present[HTML_OPTION_DISABLED]) + _Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1; + } + } + _SelectOption = true; + break; + case HTML_LI: + if (!_UL.empty()) + { + // 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
  • attribute + if (present[HTML_LI_VALUE] && value[HTML_LI_VALUE]) + fromString(value[HTML_LI_VALUE], _UL.back().Value); + + ucstring str; + str.fromUtf8(_UL.back().getListMarkerText()); + addString (str); + + sint32 indent = LIIndent; + // list-style-type: outside + if (_CurrentViewLink) +<<<<<<< HEAD + indent -= _CurrentViewLink->getMaxUsedW(); + getParagraph()->setFirstViewIndent(indent); +======= + { + getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW()); + } +>>>>>>> ryzomcore + + flushString (); + + _UL.back().Value++; + } + break; + case HTML_P: + newParagraph(PBeginSpace); + break; + case HTML_PRE: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = "monospace"; + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); + + if (present[HTML_PRE_STYLE] && value[HTML_PRE_STYLE]) + getStyleParams(value[HTML_PRE_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); + + _PRE.push_back(true); + } + break; + case HTML_TABLE: + { + registerAnchorName(MY_HTML_TABLE); + + // Get cells parameters + getCellsParameters (MY_HTML_TABLE, false); + + CGroupTable *table = new CGroupTable(TCtorParam()); + table->BgColor = _CellParams.back().BgColor; + + if (present[MY_HTML_TABLE_WIDTH] && value[MY_HTML_TABLE_WIDTH]) + getPercentage (table->ForceWidthMin, table->TableRatio, value[MY_HTML_TABLE_WIDTH]); + if (present[MY_HTML_TABLE_BORDER] && value[MY_HTML_TABLE_BORDER]) + fromString(value[MY_HTML_TABLE_BORDER], table->Border); + if (present[MY_HTML_TABLE_BORDERCOLOR] && value[MY_HTML_TABLE_BORDERCOLOR]) + scanHTMLColor(value[MY_HTML_TABLE_BORDERCOLOR], table->BorderColor); + if (present[MY_HTML_TABLE_CELLSPACING] && value[MY_HTML_TABLE_CELLSPACING]) + fromString(value[MY_HTML_TABLE_CELLSPACING], table->CellSpacing); + if (present[MY_HTML_TABLE_CELLPADDING] && value[MY_HTML_TABLE_CELLPADDING]) + fromString(value[MY_HTML_TABLE_CELLPADDING], table->CellPadding); + +<<<<<<< HEAD + // Table must fit the container size + +======= + table->setMarginLeft(getIndent()); +>>>>>>> ryzomcore + addHtmlGroup (table, 0); + + _Tables.push_back(table); + + // Add a cell pointer + _Cells.push_back(NULL); + _TR.push_back(false); +<<<<<<< HEAD +======= + _Indent.push_back(0); +>>>>>>> ryzomcore + } + break; + case HTML_TH: + // TH is similar to TD, just different font style + case HTML_TD: + { + // Get cells parameters + getCellsParameters (MY_HTML_TD, true); + + if (element_number == HTML_TH) + { + _FontWeight.push_back(FONT_WEIGHT_BOLD); + // center if not specified otherwise. TD/TH present/value arrays have same indices + if (!(present[MY_HTML_TD_ALIGN] && value[MY_HTML_TD_ALIGN])) + _CellParams.back().Align = CGroupCell::Center; + } + + CGroupTable *table = getTable(); + if (table) + { + if (!_Cells.empty()) + { + _Cells.back() = new CGroupCell(CViewBase::TCtorParam()); + string style; + if (present[MY_HTML_TD_STYLE] && value[MY_HTML_TD_STYLE]) + style = value[MY_HTML_TD_STYLE]; + + // Set the cell parameters + if (!style.empty()) + { + TStyle styles = parseStyle(style); + TStyle::iterator it; + + it = styles.find("background-repeat"); + _Cells.back()->setTextureTile(it != styles.end()); + + // Webig only + it = styles.find("background-scale"); + _Cells.back()->setTextureScale(it != styles.end()); + + it = styles.find("background-image"); + if (it != styles.end()) + { + string image = (*it).second; + string::size_type texExt = toLower(image).find("url("); + // Url image + if (texExt != string::npos) + { + // Remove url() + image = image.substr(4, image.size()-5); + addImageDownload(image, _Cells.back()); + // Image in BNP + } + else + { + _Cells.back()->setTexture(image); + } + } + } + + if (present[MY_HTML_TD_COLSPAN] && value[MY_HTML_TD_COLSPAN]) + fromString(value[MY_HTML_TD_COLSPAN], _Cells.back()->ColSpan); + if (present[MY_HTML_TD_ROWSPAN] && value[MY_HTML_TD_ROWSPAN]) + fromString(value[MY_HTML_TD_ROWSPAN], _Cells.back()->RowSpan); + + _Cells.back()->BgColor = _CellParams.back().BgColor; + _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); + + float temp; + if (present[MY_HTML_TD_WIDTH] && value[MY_HTML_TD_WIDTH]) + getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, value[MY_HTML_TD_WIDTH]); + if (present[MY_HTML_TD_HEIGHT] && value[MY_HTML_TD_HEIGHT]) + getPercentage (_Cells.back()->Height, temp, value[MY_HTML_TD_HEIGHT]); + + _Cells.back()->NewLine = getTR(); + table->addChild (_Cells.back()); +<<<<<<< HEAD + newParagraph(TDBeginSpace); +======= + + // 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 +>>>>>>> ryzomcore + + // Reset TR flag + if (!_TR.empty()) + _TR.back() = false; + } + } + } + break; + case HTML_TEXTAREA: + _PRE.push_back(true); + + // Got one form ? + if (!(_Forms.empty())) + { + // not inherited by default, font family defaults to system font + CStyleParams style; + style.TextColor = TextColor; + style.FontWeight = FONT_WEIGHT_NORMAL; + style.FontOblique = false; + style.FontSize = TextFontSize; + + if (present[MY_HTML_TEXTAREA_STYLE] && value[MY_HTML_TEXTAREA_STYLE]) + getStyleParams(value[MY_HTML_TEXTAREA_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + + // read general property + string templateName; + + // Widget template name + if (present[MY_HTML_TEXTAREA_Z_INPUT_TMPL] && value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]) + templateName = value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]; + + // Get the string name + _TextAreaName.clear(); + _TextAreaRow = 1; + _TextAreaCols = 10; + _TextAreaContent.clear(); + _TextAreaMaxLength = 1024; + if (present[MY_HTML_TEXTAREA_NAME] && value[MY_HTML_TEXTAREA_NAME]) + _TextAreaName = value[MY_HTML_TEXTAREA_NAME]; + if (present[MY_HTML_TEXTAREA_ROWS] && value[MY_HTML_TEXTAREA_ROWS]) + fromString(value[MY_HTML_TEXTAREA_ROWS], _TextAreaRow); + if (present[MY_HTML_TEXTAREA_COLS] && value[MY_HTML_TEXTAREA_COLS]) + fromString(value[MY_HTML_TEXTAREA_COLS], _TextAreaCols); + if (present[MY_HTML_TEXTAREA_MAXLENGTH] && value[MY_HTML_TEXTAREA_MAXLENGTH]) + fromString(value[MY_HTML_TEXTAREA_MAXLENGTH], _TextAreaMaxLength); + + _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup; + _TextArea = true; + } + break; + case HTML_TITLE: + { + if(!_TitlePrefix.empty()) + _TitleString = _TitlePrefix + " - "; + else + _TitleString.clear(); + _Title = true; + } + break; + case HTML_I: + { + _Localize = true; + } + break; + case HTML_TR: + { + // Get cells parameters + getCellsParameters (MY_HTML_TR, true); + + // Set TR flag + if (!_TR.empty()) + _TR.back() = true; + } + break; + case HTML_UL: + 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; +<<<<<<< HEAD + _Indent += ULIndent; +======= + _Indent.push_back(getIndent() + ULIndent); +>>>>>>> ryzomcore + endParagraph(); + break; + case HTML_OBJECT: + _ObjectType.clear(); + _ObjectData.clear(); + _ObjectMD5Sum.clear(); + _ObjectAction.clear(); + if (present[HTML_OBJECT_TYPE] && value[HTML_OBJECT_TYPE]) + _ObjectType = value[HTML_OBJECT_TYPE]; + if (present[HTML_OBJECT_DATA] && value[HTML_OBJECT_DATA]) + _ObjectData = value[HTML_OBJECT_DATA]; + if (present[HTML_OBJECT_ID] && value[HTML_OBJECT_ID]) + _ObjectMD5Sum = value[HTML_OBJECT_ID]; + if (present[HTML_OBJECT_STANDBY] && value[HTML_OBJECT_STANDBY]) + _ObjectAction = value[HTML_OBJECT_STANDBY]; + _Object = true; + + break; + case HTML_SPAN: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = getFontFamily(); + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); +<<<<<<< HEAD +======= + style.GlobalColor = getGlobalColor(); +>>>>>>> ryzomcore + + if (present[MY_HTML_SPAN_STYLE] && value[MY_HTML_SPAN_STYLE]) + getStyleParams(value[MY_HTML_SPAN_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); +<<<<<<< HEAD +======= + _GlobalColor.push_back(style.GlobalColor); +>>>>>>> ryzomcore + } + break; + case HTML_DEL: + _FontStrikeThrough.push_back(true); + break; + case HTML_U: + _FontUnderlined.push_back(true); + break; + case HTML_EM: + _FontOblique.push_back(true); + break; + case HTML_STRONG: + _FontWeight.push_back(FONT_WEIGHT_BOLD); + break; + case HTML_SMALL: + _FontSize.push_back(getFontSizeSmaller()); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = true; + break; + case HTML_DL: + _DL.push_back(HTMLDListElement()); + _LI = _DL.size() > 1 || !_UL.empty(); + endParagraph(); + break; + case HTML_DT: + if (!_DL.empty()) + { +<<<<<<< HEAD +======= + // close DT if still open + if (_DL.back().DD) + { + _DL.back().DD = false; + popIfNotEmpty(_Indent); + } + +>>>>>>> ryzomcore + // see if this is the first
    , closing tag not required + if (!_DL.back().DT) + { + _DL.back().DT = true; + _FontWeight.push_back(FONT_WEIGHT_BOLD); + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // if there was no closing tag for
    , then remove
    style + if (_DL.back().DT) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + + if (!_DL.back().DD) + { +<<<<<<< HEAD + _Indent += ULIndent; + _DL.back().DD = true; +======= + _DL.back().DD = true; + _Indent.push_back(getIndent() + ULIndent); +>>>>>>> ryzomcore + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_OL: + { + sint32 start = 1; + std::string type("1"); + + if (present[HTML_OL_START] && value[HTML_OL_START]) + fromString(value[HTML_OL_START], start); + if (present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) + type = value[HTML_OL_TYPE]; + + _UL.push_back(HTMLOListElement(start, type)); + // if LI is already present + _LI = _UL.size() > 1 || _DL.size() > 1; +<<<<<<< HEAD + _Indent += ULIndent; +======= + _Indent.push_back(getIndent() + ULIndent); +>>>>>>> ryzomcore + endParagraph(); + } + break; + case HTML_HR: + { + newParagraph(0); + + CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0); + if (sep) + { + CStyleParams style; + style.TextColor = CRGBA(120, 120, 120, 255); + style.Height = 0; + style.Width = 0; + + if (present[HTML_HR_STYLE] && value[HTML_HR_STYLE]) + getStyleParams(value[HTML_HR_STYLE], style); + + CViewBitmap *bitmap = dynamic_cast(sep->getView("hr")); + if (bitmap) + { + bitmap->setColor(style.TextColor); + if (style.Width > 0) + { + clamp(style.Width, 1, 32000); + bitmap->setW(style.Width); + bitmap->setSizeRef(CInterfaceElement::none); + } + if (style.Height > 0) + { + clamp(style.Height, 1, 1000); + bitmap->setH(style.Height); + } + } + + getParagraph()->addChild(sep); + endParagraph(); + } + } + break; + } + } + } + + // *************************************************************************** + + void CGroupHTML::endElement (uint element_number) + { + if (_Browsing) + { + // Paragraph ? + switch(element_number) + { + case HTML_HEAD: + _ReadingHeadTag = false; + break; + case HTML_FONT: + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontSize); + break; + case HTML_A: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_GlobalColor); + popIfNotEmpty (_A); + popIfNotEmpty (_Link); + popIfNotEmpty (_LinkTitle); + popIfNotEmpty (_LinkClass); + break; + case HTML_H1: + case HTML_H2: + case HTML_H3: + case HTML_H4: + case HTML_H5: + case HTML_H6: + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_GlobalColor); + endParagraph(); + break; + case HTML_P: + endParagraph(); + break; + case HTML_PRE: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_PRE); + break; + case HTML_DIV: + if (isBlockLevelElement()) + { + endParagraph(); + } + _DivName.clear(); + popIfNotEmpty (_Divs); + popIfNotEmpty (_BlockLevelElement); + break; + + case HTML_TABLE: + popIfNotEmpty (_CellParams); + popIfNotEmpty (_TR); + popIfNotEmpty (_Cells); + popIfNotEmpty (_Tables); +<<<<<<< HEAD +======= + popIfNotEmpty (_Indent); +>>>>>>> ryzomcore + endParagraph(); + // Add a cell + break; + case HTML_TH: + popIfNotEmpty (_FontWeight); + // no break; + case HTML_TD: + popIfNotEmpty (_CellParams); + if (!_Cells.empty()) + _Cells.back() = NULL; + break; + case HTML_TR: + popIfNotEmpty (_CellParams); + break; + case HTML_TEXTAREA: + { + _TextArea = false; + if (!(_Forms.empty())) + { + CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, _TextAreaContent, _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); + } + + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + } + + popIfNotEmpty (_PRE); + } + break; + case HTML_TITLE: + { + _Title = false; + + // Get the parent container + setTitle (_TitleString); + } + break; + case HTML_SELECT: + { + _SelectOption = false; + if (!(_Forms.empty())) + { + if (!_Forms.back().Entries.empty()) + { + CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox; + if (cb) + { + cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection); + cb->setW(cb->evalContentWidth() + 16); + } + } + } + } + break; + case HTML_OPTION: + if (!(_Forms.empty()) && !(_Forms.back().Entries.empty())) + { + // 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(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; + } + } + } + } + } + } + break; + case HTML_I: + { + _Localize = false; + } + break; + case HTML_OL: + case HTML_UL: + if (!_UL.empty()) + { +<<<<<<< HEAD + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; + + endParagraph(); + popIfNotEmpty(_UL); +======= + endParagraph(); + popIfNotEmpty(_UL); + popIfNotEmpty(_Indent); +>>>>>>> ryzomcore + } + break; + case HTML_DL: + if (!_DL.empty()) + { + endParagraph(); + + // unclosed DT + if (_DL.back().DT) + { + popIfNotEmpty (_FontWeight); + } + + // unclosed DD + if (_DL.back().DD) + { +<<<<<<< HEAD + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; +======= + popIfNotEmpty(_Indent); +>>>>>>> ryzomcore + } + + popIfNotEmpty (_DL); + } + break; + case HTML_DT: + if (!_DL.empty()) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // parser will process two DD in a row as nested when first DD is not closed + if (_DL.back().DD) + { +<<<<<<< HEAD + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; + + _DL.back().DD = false; +======= + _DL.back().DD = false; + popIfNotEmpty(_Indent); +>>>>>>> ryzomcore + } + } + break; + case HTML_SPAN: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); +<<<<<<< HEAD +======= + popIfNotEmpty (_GlobalColor); +>>>>>>> ryzomcore + break; + case HTML_DEL: + popIfNotEmpty (_FontStrikeThrough); + break; + case HTML_U: + popIfNotEmpty (_FontUnderlined); + break; + case HTML_EM: + popIfNotEmpty (_FontOblique); + break; + case HTML_STRONG: + popIfNotEmpty (_FontWeight); + break; + case HTML_SMALL: + popIfNotEmpty (_FontSize); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = false; + break; + case HTML_OBJECT: + if (_TrustedDomain) + { + if (_ObjectType=="application/ryzom-data") + { + if (!_ObjectData.empty()) + { + if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum)) + { + CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true); + } + _ObjectScript.clear(); + } + } + _Object = false; + } + break; + } + } + } + + // *************************************************************************** + void CGroupHTML::beginUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + // we receive an embeded lua script + _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain + _LuaScript.clear(); + } + } + + // *************************************************************************** + void CGroupHTML::endUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + if (_ParsingLua && _TrustedDomain) + { + _ParsingLua = false; + // execute the embeded lua script + _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript; + CLuaManager::getInstance().executeLuaScript(_LuaScript, true); + } + } + } + + + // *************************************************************************** + NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html"); + + + // *************************************************************************** + uint32 CGroupHTML::_GroupHtmlUIDPool= 0; + CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID; + + + // *************************************************************************** + CGroupHTML::CGroupHTML(const TCtorParam ¶m) + : CGroupScrollText(param), + _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT), + _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT) + { + // 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; + _BrowseNextTime = false; + _PostNextTime = false; + _Browsing = false; + _Connecting = false; + _CurrentViewLink = NULL; + _CurrentViewImage = NULL; +<<<<<<< HEAD + _Indent = 0; +======= + _Indent.clear(); +>>>>>>> ryzomcore + _LI = false; + _SelectOption = false; + _GroupListAdaptor = NULL; + _UrlFragment.clear(); + _RefreshUrl.clear(); + _NextRefreshTime = 0.0; + _LastRefreshTime = 0.0; + + // Register + CWidgetManager::getInstance()->registerClockMsgTarget(this); + + // HTML parameters + BgColor = CRGBA::Black; + ErrorColor = CRGBA(255, 0, 0); + LinkColor = CRGBA(0, 0, 255); + TextColor = CRGBA(255, 255, 255); + H1Color = CRGBA(255, 255, 255); + H2Color = CRGBA(255, 255, 255); + H3Color = CRGBA(255, 255, 255); + H4Color = CRGBA(255, 255, 255); + H5Color = CRGBA(255, 255, 255); + H6Color = CRGBA(255, 255, 255); + ErrorColorGlobalColor = false; + LinkColorGlobalColor = false; + TextColorGlobalColor = false; + H1ColorGlobalColor = false; + H2ColorGlobalColor = false; + H3ColorGlobalColor = false; + H4ColorGlobalColor = false; + H5ColorGlobalColor = false; + H6ColorGlobalColor = false; + TextFontSize = 9; + H1FontSize = 18; + H2FontSize = 15; + H3FontSize = 12; + H4FontSize = 9; + H5FontSize = 9; + H6FontSize = 9; + LIBeginSpace = 4; + ULBeginSpace = 12; + PBeginSpace = 12; + TDBeginSpace = 0; + LIIndent = -10; + 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(); + initLibWWW(); + } + + // *************************************************************************** + + 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); + + // stop browsing + stopBrowse (); // NB : we don't call updateRefreshButton here, because : + // 1) it is useless, + // 2) it crashed before when it called getElementFromId (that didn't work when a master group was being removed...). Btw it should work now + // this is why the call to 'updateRefreshButton' has been removed from stopBrowse + + clearContext(); + if (_CurlWWW) + delete _CurlWWW; + } + + std::string CGroupHTML::getProperty( const std::string &name ) const + { + if( name == "url" ) + { + return _URL; + } + else + if( name == "title_prefix" ) + { + return _TitlePrefix.toString(); + } + else + if( name == "background_color" ) + { + return toString( BgColor ); + } + else + if( name == "error_color" ) + { + return toString( ErrorColor ); + } + else + if( name == "link_color" ) + { + return toString( LinkColor ); + } + else + if( name == "h1_color" ) + { + return toString( H1Color ); + } + else + if( name == "h2_color" ) + { + return toString( H2Color ); + } + else + if( name == "h3_color" ) + { + return toString( H3Color ); + } + else + if( name == "h4_color" ) + { + return toString( H4Color ); + } + else + if( name == "h5_color" ) + { + return toString( H5Color ); + } + else + if( name == "h6_color" ) + { + return toString( H6Color ); + } + 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 == "h1_color_global_color" ) + { + return toString( H1ColorGlobalColor ); + } + else + if( name == "h2_color_global_color" ) + { + return toString( H2ColorGlobalColor ); + } + else + if( name == "h3_color_global_color" ) + { + return toString( H3ColorGlobalColor ); + } + else + if( name == "h4_color_global_color" ) + { + return toString( H4ColorGlobalColor ); + } + else + if( name == "h5_color_global_color" ) + { + return toString( H5ColorGlobalColor ); + } + else + if( name == "h6_color_global_color" ) + { + return toString( H6ColorGlobalColor ); + } + else + if( name == "text_font_size" ) + { + return toString( TextFontSize ); + } + else + if( name == "h1_font_size" ) + { + return toString( H1FontSize ); + } + else + if( name == "h2_font_size" ) + { + return toString( H2FontSize ); + } + else + if( name == "h3_font_size" ) + { + return toString( H3FontSize ); + } + else + if( name == "h4_font_size" ) + { + return toString( H4FontSize ); + } + else + if( name == "h5_font_size" ) + { + return toString( H5FontSize ); + } + else + if( name == "h6_font_size" ) + { + return toString( H6FontSize ); + } + 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 == "li_indent" ) + { + return toString( LIIndent ); + } + 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 + 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 == "background_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + BgColor = c; + 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 == "h1_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H1Color = c; + return; + } + else + if( name == "h2_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H2Color = c; + return; + } + else + if( name == "h3_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H3Color = c; + return; + } + else + if( name == "h4_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H4Color = c; + return; + } + else + if( name == "h5_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H5Color = c; + return; + } + else + if( name == "h6_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H6Color = 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 == "h1_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H1ColorGlobalColor = b; + return; + } + else + if( name == "h2_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H2ColorGlobalColor = b; + return; + } + else + if( name == "h3_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H3ColorGlobalColor = b; + return; + } + else + if( name == "h4_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H4ColorGlobalColor = b; + return; + } + else + if( name == "h5_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H5ColorGlobalColor = b; + return; + } + else + if( name == "h6_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H6ColorGlobalColor = b; + return; + } + else + if( name == "text_font_size" ) + { + uint i; + if( fromString( value, i ) ) + TextFontSize = i; + return; + } + else + if( name == "h1_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H1FontSize = i; + return; + } + else + if( name == "h2_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H2FontSize = i; + return; + } + else + if( name == "h3_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H3FontSize = i; + return; + } + else + if( name == "h4_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H4FontSize = i; + return; + } + else + if( name == "h5_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H5FontSize = i; + return; + } + else + if( name == "h6_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H6FontSize = i; + 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 == "li_indent" ) + { + uint i; + if( fromString( value, i ) ) + LIIndent = 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 + 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.toString().c_str() ); + xmlSetProp( node, BAD_CAST "background_color", BAD_CAST toString( BgColor ).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 "background_color", BAD_CAST toString( BgColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_color", BAD_CAST toString( H1Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color", BAD_CAST toString( H2Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color", BAD_CAST toString( H3Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color", BAD_CAST toString( H4Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color", BAD_CAST toString( H5Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color", BAD_CAST toString( H6Color ).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 "h1_color_global_color", + BAD_CAST toString( H1ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color_global_color", + BAD_CAST toString( H2ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color_global_color", + BAD_CAST toString( H3ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color_global_color", + BAD_CAST toString( H4ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color_global_color", + BAD_CAST toString( H5ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color_global_color", + BAD_CAST toString( H6ColorGlobalColor ).c_str() ); + + xmlSetProp( node, BAD_CAST "text_font_size", BAD_CAST toString( TextFontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_font_size", BAD_CAST toString( H1FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_font_size", BAD_CAST toString( H2FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_font_size", BAD_CAST toString( H3FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_font_size", BAD_CAST toString( H4FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_font_size", BAD_CAST toString( H5FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_font_size", BAD_CAST toString( H6FontSize ).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 "li_indent", BAD_CAST toString( LIIndent ).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() ); + + 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*)"background_color"); + if (ptr) + BgColor = convertColor(ptr); + 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*)"text_color"); + if (ptr) + TextColor = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h1_color"); + if (ptr) + H1Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color"); + if (ptr) + H2Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color"); + if (ptr) + H3Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color"); + if (ptr) + H4Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color"); + if (ptr) + H5Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color"); + if (ptr) + H6Color = 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*)"h1_color_global_color"); + if (ptr) + H1ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color_global_color"); + if (ptr) + H2ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color_global_color"); + if (ptr) + H3ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color_global_color"); + if (ptr) + H4ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color_global_color"); + if (ptr) + H5ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color_global_color"); + if (ptr) + H6ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"text_font_size"); + if (ptr) + fromString((const char*)ptr, TextFontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h1_font_size"); + if (ptr) + fromString((const char*)ptr, H1FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h2_font_size"); + if (ptr) + fromString((const char*)ptr, H2FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h3_font_size"); + if (ptr) + fromString((const char*)ptr, H3FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h4_font_size"); + if (ptr) + fromString((const char*)ptr, H4FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h5_font_size"); + if (ptr) + fromString((const char*)ptr, H5FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h6_font_size"); + if (ptr) + fromString((const char*)ptr, H6FontSize); + 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*)"li_indent"); + if (ptr) + fromString((const char*)ptr, LIIndent); + 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); + + 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() + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + _Paragraph = NULL; + + paragraphChange (); + } + + // *************************************************************************** + + void CGroupHTML::newParagraph(uint beginSpace) + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + // Add a new paragraph + CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam()); + newParagraph->setResizeFromChildH(true); + +<<<<<<< HEAD + newParagraph->setIndent(_Indent); +======= + newParagraph->setMarginLeft(getIndent()); +>>>>>>> ryzomcore + + // 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) + { + // Stop previous browse + if (_Browsing) + { + // Clear all the context + clearContext(); + + _Browsing = false; + updateRefreshButton(); + + #ifdef LOG_DL + nlwarning("(%s) *** ALREADY BROWSING, break first", _Id.c_str()); + #endif + } + + #ifdef LOG_DL + nlwarning("(%s) Browsing URL : '%s'", _Id.c_str(), url); + #endif + + + 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(); + _Connecting = false; + _BrowseNextTime = true; + + // 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(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) + { + // 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); + + stopBrowse (); + updateRefreshButton(); + } + + // *************************************************************************** + + bool CGroupHTML::isBrowsing() + { + return _Browsing; + } + + + void CGroupHTML::stopBrowse () + { + #ifdef LOG_DL + nlwarning("*** STOP BROWSE (%s)", _Id.c_str()); + #endif + + // Clear all the context + clearContext(); + + _Browsing = false; + + requestTerminated(); + } + + // *************************************************************************** + + 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(); + } + } + + // *************************************************************************** + + bool CGroupHTML::translateChar(ucchar &output, ucchar input, ucchar lastCharParam) const + { + // Keep this char ? + bool keep = true; + + switch (input) + { + // Return / tab only in
     mode
    +		case '\t':
    +		case '\n':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
    +				if(!getPRE())
    +					input = ' ';
    +			}
    +			break;
    +		case ' ':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != (ucchar)'\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();
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addString(const ucstring &str)
    +	{
    +		ucstring tmpStr = str;
    +
    +		if (_Localize)
    +		{
    +			string	_str = tmpStr.toString();
    +			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 (_Title)
    +		{
    +			_TitleString += tmpStr;
    +		}
    +		else if (_TextArea)
    +		{
    +			_TextAreaContent += tmpStr;
    +		}
    +		else if (_Object)
    +		{
    +			_ObjectScript += tmpStr.toString();
    +		}
    +		else if (_SelectOption)
    +		{
    +			if (!(_Forms.empty()))
    +			{
    +				if (!_Forms.back().Entries.empty())
    +				{
    +					_SelectOptionStr += tmpStr;
    +				}
    +			}
    +		}
    +		else
    +		{
    +			// In a paragraph ?
    +			if (!_Paragraph)
    +			{
    +				newParagraph (0);
    +				paragraphChange ();
    +			}
    +
    +			// Text added ?
    +			bool added = false;
    +			bool embolden = getFontWeight() >= FONT_WEIGHT_BOLD;
    +
    +			// Number of child in this paragraph
    +			if (_CurrentViewLink)
    +			{
    +				bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == (ucchar) '\n';
    +				// Compatible with current parameters ?
    +				if (!skipLine &&
    +					(getTextColor() == _CurrentViewLink->getColor()) &&
    +					(getFontFamily() == _CurrentViewLink->getFontName()) &&
    +					(getFontSize() == (uint)_CurrentViewLink->getFontSize()) &&
    +					(getFontUnderlined() == _CurrentViewLink->getUnderlined()) &&
    +					(getFontStrikeThrough() == _CurrentViewLink->getStrikeThrough()) &&
    +					(embolden == _CurrentViewLink->getEmbolden()) &&
    +					(getFontOblique() == _CurrentViewLink->getOblique()) &&
    +					(getLink() == _CurrentViewLink->Link) &&
    +					(getGlobalColor() == _CurrentViewLink->getModulateGlobalColor()))
    +				{
    +					// Concat the text
    +					_CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
    +					_CurrentViewLink->invalidateContent();
    +					added = true;
    +				}
    +			}
    +
    +			// Not added ?
    +			if (!added)
    +			{
    +				if (getA() && string(getLinkClass()) == "ryzom-ui-button")
    +				{
    +					string buttonTemplate = DefaultButtonGroup;
    +					// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +					string param = "name=" + this->_Id + "|url=" + getLink();
    +
    +					typedef pair TTmplParam;
    +					vector 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(buttonTemplate, _Paragraph->getId(), tmplParams);
    +					if (buttonGroup)
    +					{
    +
    +						// Add the ctrl button
    +						CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button"));
    +						if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b"));
    +						if (ctrlButton)
    +						{
    +							ctrlButton->setModulateGlobalColorAll (false);
    +
    +							// Translate the tooltip
    +							ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(getLinkTitle()));
    +							ctrlButton->setText(tmpStr);
    +						}
    +						getParagraph()->addChild (buttonGroup);
    +						paragraphChange ();
    +					}
    +		
    +				}
    +				else
    +				{
    +					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(tmpStr);
    +					newLink->setColor(getTextColor());
    +					newLink->setFontName(getFontFamily());
    +					newLink->setFontSize(getFontSize());
    +					newLink->setEmbolden(embolden);
    +					newLink->setOblique(getFontOblique());
    +					newLink->setUnderlined(getFontUnderlined());
    +					newLink->setStrikeThrough(getFontStrikeThrough());
    +					newLink->setMultiLineSpace((uint)((float)getFontSize()*LineSpaceFontFactor));
    +					newLink->setMultiLine(true);
    +					newLink->setModulateGlobalColor(getGlobalColor());
    +					// newLink->setLineAtBottom (true);
    +
    +					registerAnchor(newLink);
    +
    +					if (getA() && !newLink->Link.empty())
    +					{
    +						getParagraph()->addChildLink(newLink);
    +					}
    +					else
    +					{
    +						getParagraph()->addChild(newLink);
    +					}
    +					paragraphChange ();
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +<<<<<<< HEAD
    +	void CGroupHTML::addImage(const char *img, bool globalColor, bool reloadImg, const CStyleParams &style)
    +=======
    +	void CGroupHTML::addImage(const char *img, bool reloadImg, const CStyleParams &style)
    +>>>>>>> ryzomcore
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		string finalUrl;
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		// Not added ?
    +		CViewBitmap *newImage = new CViewBitmap (TCtorParam());
    +
    +		//
    +		// 1/ try to load the image with the old system (local files in bnp)
    +		//
    +		string image = CFile::getPath(img) + CFile::getFilenameWithoutExtension(img) + ".tga";
    +		if (lookupLocalFile (finalUrl, image.c_str(), false))
    +		{
    +			newImage->setRenderLayer(getRenderLayer()+1);
    +			image = finalUrl;
    +		}
    +		else
    +		{
    +			//
    +			// 2/ if it doesn't work, try to load the image in cache
    +			//
    +			image = localImageName(img);
    +<<<<<<< HEAD
    +			if (!reloadImg && lookupLocalFile (finalUrl, image.c_str(), false))
    +=======
    +
    +			if (reloadImg && CFile::fileExists(image))
    +				CFile::deleteFile(image);
    +
    +			if (lookupLocalFile (finalUrl, image.c_str(), false))
    +>>>>>>> ryzomcore
    +			{
    +				// don't display image that are not power of 2
    +				try
    +				{
    +					uint32 w, h;
    +					CBitmap::loadSize (image, w, h);
    +					if (w == 0 || h == 0 || ((!NLMISC::isPowerOf2(w) || !NLMISC::isPowerOf2(h)) && !NL3D::CTextureFile::supportNonPowerOfTwoTextures()))
    +						image = "web_del.tga";
    +				}
    +				catch(const NLMISC::Exception &e)
    +				{
    +					nlwarning(e.what());
    +					image = "web_del.tga";
    +				}
    +			}
    +			else
    +			{
    +<<<<<<< HEAD
    +				//
    +				// 3/ if it doesn't work, display a placeholder and ask to dl the image into the cache
    +				//
    +				if (reloadImg && CFile::fileExists(image))
    +					CFile::deleteFile(image);
    +
    +				image = "web_del.tga";
    +				addImageDownload(img, newImage, style);
    +			}
    +		}
    +		newImage->setTexture (image);
    +		newImage->setModulateGlobalColor(globalColor);
    +
    +		getParagraph()->addChild(newImage);	
    +		paragraphChange ();
    +		
    +=======
    +				// no image in cache
    +				image = "web_del.tga";
    +			}
    +
    +			addImageDownload(img, newImage, style);
    +		}
    +		newImage->setTexture (image);
    +		newImage->setModulateGlobalColor(style.GlobalColor);
    +
    +		getParagraph()->addChild(newImage);
    +		paragraphChange ();
    +
    +>>>>>>> ryzomcore
    +		setImageSize(newImage, style);
    +	}
    +
    +	// ***************************************************************************
    +
    +	CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		{
    +			// Not added ?
    +			std::vector > templateParams;
    +			templateParams.push_back (std::pair ("w", toString (cols*getFontSize())));
    +			templateParams.push_back (std::pair ("id", name));
    +			templateParams.push_back (std::pair ("prompt", ""));
    +			templateParams.push_back (std::pair ("multiline", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("fontsize", toString (getFontSize())));
    +			templateParams.push_back (std::pair ("color", getTextColor().toString()));
    +			if (getFontWeight() >= FONT_WEIGHT_BOLD)
    +				templateParams.push_back (std::pair ("fontweight", "bold"));
    +			if (getFontOblique())
    +				templateParams.push_back (std::pair ("fontstyle", "oblique"));
    +			if (multiLine)
    +				templateParams.push_back (std::pair ("multi_min_line", toString(rows)));
    +			templateParams.push_back (std::pair ("want_return", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("enter_recover_focus", "false"));
    +			if (maxlength > 0)
    +				templateParams.push_back (std::pair ("max_num_chars", toString(maxlength)));
    +			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(textArea->getGroup("eb"));
    +				if (eb)
    +					eb->setInputString(decodeHTMLEntities(content));
    +
    +				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 > templateParams;
    +			templateParams.push_back (std::pair ("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(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 > templateParams;
    +		templateParams.push_back(std::pair ("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(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,
    +<<<<<<< HEAD
    +									  const std::string &overBitmap, bool useGlobalColor, const char *actionHandler, const char *actionHandlerParams,
    +=======
    +									  const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams,
    +>>>>>>> ryzomcore
    +									  const char *tooltip, const CStyleParams &style)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// Add the ctrl button
    +		CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
    +
    +		// Load only tga files.. (conversion in dds filename is done in the lookup procedure)
    +		string normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
    +
    +		// if the image doesn't exist on local, we check in the cache
    +	//	if(!CFile::fileExists(normal))
    +		if(!CPath::exists(normal))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(normal);
    +			if(id == -1)
    +			{
    +				normal = localImageName(normalBitmap);
    +				if(!CFile::fileExists(normal))
    +				{
    +					normal = "web_del.tga";
    +					addImageDownload(normalBitmap, ctrlButton, style);
    +				}
    +				else
    +				{
    +					try
    +					{
    +						uint32 w, h;
    +						CBitmap::loadSize(normal, w, h);
    +						if (w == 0 || h == 0)
    +							normal = "web_del.tga";
    +					}
    +					catch(const NLMISC::Exception &e)
    +					{
    +						nlwarning(e.what());
    +						normal = "web_del.tga";
    +					}
    +				}
    +			}
    +		}
    +
    +		string 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(!CFile::fileExists(pushed))
    +		if(!CPath::exists(pushed))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(pushed);
    +			if(id == -1)
    +			{
    +				pushed = localImageName(pushedBitmap);
    +			}
    +		}
    +
    +		string over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga";
    +<<<<<<< HEAD
    +=======
    +		// schedule mouseover bitmap for download if its different from normal
    +		if (!over.empty() && !CPath::exists(over))
    +		{
    +			if (overBitmap != normalBitmap)
    +			{
    +				over = localImageName(overBitmap);
    +				if (!CFile::fileExists(over))
    +				{
    +					addImageDownload(overBitmap, ctrlButton, style, TImageType::OverImage);
    +				}
    +			}
    +		}
    +>>>>>>> ryzomcore
    +
    +		ctrlButton->setType (type);
    +		if (!normal.empty())
    +			ctrlButton->setTexture (normal);
    +		if (!pushed.empty())
    +			ctrlButton->setTexturePushed (pushed);
    +		if (!over.empty())
    +			ctrlButton->setTextureOver (over);
    +<<<<<<< HEAD
    +		ctrlButton->setModulateGlobalColorAll (useGlobalColor);
    +=======
    +		ctrlButton->setModulateGlobalColorAll (style.GlobalColor);
    +>>>>>>> ryzomcore
    +		ctrlButton->setActionOnLeftClick (actionHandler);
    +		ctrlButton->setParamsOnLeftClick (actionHandlerParams);
    +
    +		// Translate the tooltip or display raw text (tooltip from webig)
    +		if (tooltip)
    +		{
    +			if (CI18N::hasTranslation(tooltip))
    +			{
    +				ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
    +				//ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
    +			}
    +			else
    +			{
    +				ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(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();
    +		_TextColor.clear();
    +		_GlobalColor.clear();
    +		_FontSize.clear();
    +		_FontWeight.clear();
    +		_FontOblique.clear();
    +		_FontUnderlined.clear();
    +		_FontStrikeThrough.clear();
    +<<<<<<< HEAD
    +		_Indent = 0;
    +=======
    +		_Indent.clear();
    +>>>>>>> ryzomcore
    +		_LI = false;
    +		_UL.clear();
    +		_DL.clear();
    +		_A.clear();
    +		_Link.clear();
    +		_LinkTitle.clear();
    +		_Tables.clear();
    +		_Cells.clear();
    +		_TR.clear();
    +		_Forms.clear();
    +		_Groups.clear();
    +		_Anchors.clear();
    +		_AnchorName.clear();
    +		_CellParams.clear();
    +		_Title = false;
    +		_TextArea = false;
    +		_Object = false;
    +		_Localize = false;
    +		_ReadingHeadTag = false;
    +		_IgnoreHeadTag = false;
    +		_IgnoreBaseUrlTag = false;
    +
    +		// TR
    +
    +		paragraphChange ();
    +
    +		// clear the pointer to the current image download since all the button are deleted
    +	#ifdef LOG_DL
    +		nlwarning("Clear pointers to %d curls", Curls.size());
    +	#endif
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			Curls[i].imgs.clear();
    +		}
    +
    +<<<<<<< HEAD
    +=======
    +		// remove download that are still queued
    +		for (vector::iterator it=Curls.begin(); itdata == NULL) {
    +	#ifdef LOG_DL
    +		nlwarning("Remove waiting curl download (%s)", it->url.c_str());
    +	#endif
    +				it = Curls.erase(it);
    +			} else {
    +				++it;
    +			}
    +		}
    +>>>>>>> ryzomcore
    +	}
    +
    +	// ***************************************************************************
    +
    +	ucchar CGroupHTML::getLastChar() const
    +	{
    +		if (_CurrentViewLink)
    +		{
    +			const ucstring &str = _CurrentViewLink->getText();
    +			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(child);
    +				_CurrentViewImage = dynamic_cast(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;
    +
    +		// Remove previous paragraph if empty
    +		if (_Paragraph && (_Paragraph->getNumChildren() == 0))
    +		{
    +			_Paragraph->getParent ()->delGroup(_Paragraph);
    +			_Paragraph = NULL;
    +		}
    +
    +		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(group);
    +
    +		CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
    +		const std::vector &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::setTitle (const ucstring &title)
    +	{
    +		CInterfaceElement *parent = getParent();
    +		if (parent)
    +		{
    +			if ((parent = parent->getParent()))
    +			{
    +				CGroupContainer *container = dynamic_cast(parent);
    +				if (container)
    +				{
    +					container->setUCTitle (title);
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
    +	{
    +		result = url;
    +		string tmp;
    +
    +		if (toLower(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:"+toLower(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 formId, const char *submitButtonType, const char *submitButtonName, const char *submitButtonValue, sint32 x, sint32 y)
    +	{
    +		// Form id valid ?
    +		if (formId < _Forms.size())
    +		{
    +			_PostNextTime = true;
    +			_PostFormId = formId;
    +			_PostFormSubmitType = submitButtonType;
    +			_PostFormSubmitButton = submitButtonName;
    +			_PostFormSubmitValue = submitButtonValue;
    +			_PostFormSubmitX = x;
    +			_PostFormSubmitY = y;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				// Change the background color
    +				bitmap->setColor (bgcolor);
    +				bitmap->setModulateGlobalColor(false);
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				bitmap->setParentPosRef(Hotspot_TL);
    +				bitmap->setPosRef(Hotspot_TL);
    +				bitmap->setX(0);
    +				bitmap->setY(0);
    +				bitmap->setRenderLayer(-2);
    +				bitmap->setScale(scale);
    +				bitmap->setTile(tile);
    +				addImageDownload(bgtex, view);
    +			}
    +		}
    +	}
    +
    +
    +	struct CButtonFreezer : public CInterfaceElementVisitor
    +	{
    +		virtual void visitCtrl(CCtrlBase *ctrl)
    +		{
    +			CCtrlBaseButton		*textButt = dynamic_cast(ctrl);
    +			if (textButt)
    +			{
    +				textButt->setFrozen(true);
    +			}
    +		}
    +	};
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::handle ()
    +	{
    +		H_AUTO(RZ_Interface_Html_handle)
    +
    +		const CWidgetManager::SInterfaceTimes × = 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 (_Connecting)
    +		{
    +			// Check timeout if needed
    +			if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
    +			{
    +				browseError(("Connection timeout : "+_URL).c_str());
    +
    +				_Connecting = false;
    +			}
    +		}
    +		else
    +		if (_BrowseNextTime || _PostNextTime)
    +		{
    +			// Set timeout
    +			_Connecting = true;
    +			_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);
    +
    +			_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];
    +
    +		_URL = form.Action;
    +
    +		CUrlParser uri(_URL);
    +		_TrustedDomain = isTrustedDomain(uri.host);
    +		_DocumentDomain = uri.host;
    +
    +		for (i=0; igetGroup ("eb");
    +				if (group)
    +				{
    +					// Should be a CGroupEditBox
    +					CGroupEditBox *editBox = dynamic_cast(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.fromUtf8(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; jgetNumLine(); ++j)
    +					{
    +						CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
    +						if (ig)
    +						{
    +							CCtrlBaseButton *cb = dynamic_cast(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, CI18N::encodeUTF8(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)
    +	{
    +		std::string filename;
    +		if (toLower(uri).find("file:/") == 0)
    +		{
    +			filename = uri.substr(6, uri.size() - 6);
    +		}
    +		else
    +		{
    +			filename = uri;
    +		}
    +
    +	#if LOG_DL
    +<<<<<<< HEAD
    +		nlwarning("(%s) browse local file '%s'", filename.c_str());
    +=======
    +		nlwarning("browse local file '%s'", filename.c_str());
    +>>>>>>> ryzomcore
    +	#endif
    +
    +		_TrustedDomain = true;
    +		_DocumentDomain = "localhost";
    +
    +		// Stop previous browse, remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		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());
    +		}
    +	}
    +
    +	// ***************************************************************************
    +<<<<<<< HEAD
    +	void CGroupHTML::doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost, const SFormFields &formfields)
    +=======
    +	void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
    +>>>>>>> ryzomcore
    +	{
    +		// Stop previous request and remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		// Reset the title
    +		if(_TitlePrefix.empty())
    +			setTitle (CI18N::get("uiPleaseWait"));
    +		else
    +			setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
    +
    +<<<<<<< HEAD
    +=======
    +		url = upgradeInsecureUrl(url);
    +
    +>>>>>>> ryzomcore
    +	#if LOG_DL
    +		nlwarning("(%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());
    +	#endif
    +
    +		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;
    +		}
    +
    +<<<<<<< HEAD
    +=======
    +#if defined(NL_OS_WINDOWS)
    +		// https://
    +		if (toLower(url.substr(0, 8)) == "https://")
    +		{
    +			curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction);
    +		}
    +#endif
    +
    +>>>>>>> ryzomcore
    +		// 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());
    +	#ifdef LOG_DL
    +			nlwarning("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
    +	#endif
    +		}
    +
    +		if (doPost)
    +		{
    +			// serialize form data and add it to curl
    +			std::string data;
    +			for(uint i=0; i0)
    +					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 headers;
    +		headers.push_back("Accept-Language: "+options.languageCode);
    +		headers.push_back("Accept-Charset: utf-8");
    +<<<<<<< HEAD
    +		for(uint i=0; i< headers.size(); ++i)
    +		{
    +			_CurlWWW->HeadersSent = curl_slist_append(_CurlWWW->HeadersSent, headers[i].c_str());
    +		}
    +		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, _CurlWWW->HeadersSent);
    +=======
    +		_CurlWWW->sendHeaders(headers);
    +>>>>>>> ryzomcore
    +
    +		// catch headers for redirect
    +		curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
    +
    +		// catch body
    +		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDataCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
    +
    +	#if LOG_DL
    +		// progress callback
    +		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
    +		curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, 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++;
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
    +	#endif
    +
    +		// create  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();
    +	#ifdef LOG_DL
    +				nlwarning("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
    +	#endif
    +			}
    +			catch(...) { }
    +
    +			// create html code with image url inside and do the request again
    +			renderHtmlString(""+_URL+"");
    +		}
    +		else
    +		{
    +			renderHtmlString(content);
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::renderHtmlString(const std::string &html)
    +	{
    +		bool success;
    +
    +		//
    +		_Browsing = true;
    +		_DocumentUrl = _URL;
    +		_NextRefreshTime = 0;
    +		_RefreshUrl.clear();
    +
    +		// clear content
    +		beginBuild();
    +
    +		success = parseHtml(html);
    +
    +		// invalidate coords
    +		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);
    +		}
    +
    +		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 ()
    +	{
    +		CGroupScrollText::draw ();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::endBuild ()
    +	{
    +		invalidateCoords();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::requestTerminated()
    +	{
    +		if (_CurlWWW)
    +		{
    +	#if LOG_DL
    +			nlwarning("(%s) stop curl, url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
    +	#endif
    +			if (MultiCurl)
    +				curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
    +
    +			delete _CurlWWW;
    +
    +			_CurlWWW = NULL;
    +			_Connecting = false;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	string	CGroupHTML::home ()
    +	{
    +		return Home;
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::removeContent ()
    +	{
    +		// Remove old document
    +		if (!_GroupListAdaptor)
    +		{
    +			_GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
    +			_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 color
    +		setBackgroundColor (BgColor);
    +
    +		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;iChildren.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 ¶ms, 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;igetParser()->getProcedureAction(procName, i, procAh, procParams))
    +				{
    +					// recurs proc if needed!
    +					if (actionLaunchUrlRecurs(procAh, procParams, url))
    +						return true;
    +				}
    +			}
    +		}
    +
    +		return false;
    +	}
    +
    +	// ***************************************************************************
    +	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
    +		_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(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
    +		CCtrlBaseButton		*butRedo= dynamic_cast(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(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
    +
    +		bool enabled = !_Browsing && !_Connecting;
    +		if(butRefresh)
    +			butRefresh->setFrozen(!enabled);
    +	}
    +
    +	// ***************************************************************************
    +
    +	NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
    +
    +	CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam ¶m)
    +		: 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::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);
    +
    +		renderHtmlString(html);
    +
    +		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);
    +
    +		ucstring text;
    +		text.fromUtf8(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(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(ucstring(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();
    +		}
    +<<<<<<< HEAD
    +=======
    +
    +		CStyleParams style;
    +		style.GlobalColor = ls.toBoolean(2);
    +
    +>>>>>>> ryzomcore
    +		string url = getLink();
    +		if (!url.empty())
    +		{
    +			string params = "name=" + getId() + "|url=" + getLink ();
    +			addButton(CCtrlButton::PushButton, ls.toString(1), ls.toString(1), ls.toString(1),
    +<<<<<<< HEAD
    +								"", ls.toBoolean(2), "browse", params.c_str(), "");
    +		}
    +		else
    +		{
    +			addImage(ls.toString(1), ls.toBoolean(2));
    +=======
    +								"", "browse", params.c_str(), "", style);
    +		}
    +		else
    +		{
    +			addImage(ls.toString(1), false, style);
    +>>>>>>> ryzomcore
    +		}
    +
    +
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaBeginElement(CLuaState &ls)
    +	{
    +		const char *funcName = "beginElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 2);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +		CLuaIHM::checkArgType(ls, funcName, 2, LUA_TTABLE);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		std::vector present;
    +		std::vector value;
    +		present.resize(30, false);
    +		value.resize(30);
    +
    +		CLuaObject params;
    +		params.pop(ls);
    +		uint max_idx = 0;
    +
    +
    +		ENUM_LUA_TABLE(params, it)
    +		{
    +			if (!it.nextKey().isInteger())
    +			{
    +				nlwarning("%s : bad key encountered with type %s, integer expected.", funcName, it.nextKey().getTypename());
    +				continue;
    +			}
    +			if (!it.nextValue().isString())
    +			{
    +				nlwarning("%s : bad value encountered with type %s for key %s, string expected.", funcName, it.nextValue().getTypename(), it.nextKey().toString().c_str());
    +				continue;
    +			}
    +			uint idx = (uint)it.nextKey().toInteger();
    +
    +			present.insert(present.begin() + (uint)it.nextKey().toInteger(), true);
    +
    +			string str = it.nextValue().toString();
    +			size_t size = str.size() + 1;
    +			char * buffer = new char[ size ];
    +			strncpy(buffer, str.c_str(), size );
    +
    +			value.insert(value.begin() + (uint)it.nextKey().toInteger(), buffer);
    +		}
    +
    +		// ingame lua scripts from browser are using  url scheme
    +		// reason unknown
    +		_LuaHrefHack = true;
    +		beginElement(element_number, present, value);
    +		_LuaHrefHack = false;
    +
    +		return 0;
    +	}
    +
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaEndElement(CLuaState &ls)
    +	{
    +		const char *funcName = "endElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 1);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		endElement(element_number);
    +
    +		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());
    +	}
    +
    +	// ***************************************************************************
    +	inline bool isDigit(ucchar 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 ucchar convertHexDigit(ucchar 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;
    +	}
    +
    +	// ***************************************************************************
    +	ucstring CGroupHTML::decodeHTMLEntities(const ucstring &str)
    +	{
    +		ucstring result;
    +		uint last, pos;
    +
    +		for (uint i=0; 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;
    +					}
    +
    +					ucchar c = 0;
    +
    +					// convert digits to unicode character
    +					while (posfirst == "font-size")
    +			{
    +				if (it->second == "inherit")
    +					style.FontSize = getFontSize();
    +				else
    +				{
    +					float tmp;
    +					sint size = 0;
    +					getPercentage (size, tmp, it->second.c_str());
    +					if (size > 0)
    +						style.FontSize = size;
    +				}
    +			}
    +			else
    +			if (it->first == "font-style")
    +			{
    +				if (it->second == "inherit")
    +					style.FontOblique = getFontOblique();
    +				else
    +				if (it->second == "italic" || it->second == "oblique")
    +					style.FontOblique = true;
    +			}
    +			else
    +			if (it->first == "font-family")
    +			{
    +				if (it->second == "inherit")
    +					style.FontFamily = getFontFamily();
    +				else
    +				if (it->second == "monospace")
    +					style.FontFamily = "monospace";
    +				else
    +					style.FontFamily.clear();
    +			}
    +			else
    +			if (it->first == "font-weight")
    +			{
    +				// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
    +				uint weight = 400;
    +				if (it->second == "inherit")
    +					weight = getFontWeight();
    +				else
    +				if (it->second == "normal")
    +					weight = 400;
    +				else
    +				if (it->second == "bold")
    +					weight = 700;
    +				else
    +				if (it->second == "lighter")
    +				{
    +					const uint lighter[] = {100, 100, 100, 100, 100, 400, 400, 700, 700};
    +					uint index = getFontWeight() / 100 - 1;
    +					clamp(index, 1u, 9u);
    +					weight = lighter[index-1];
    +				}
    +				else
    +				if (it->second == "bolder")
    +				{
    +					const uint bolder[] =  {400, 400, 400, 700, 700, 900, 900, 900, 900};
    +					uint index = getFontWeight() / 100 + 1;
    +					clamp(index, 1u, 9u);
    +					weight = bolder[index-1];
    +				}
    +				else
    +				if (fromString(it->second, weight))
    +				{
    +					weight = (weight / 100);
    +					clamp(weight, 1u, 9u);
    +					weight *= 100;
    +				}
    +				style.FontWeight = weight;
    +			}
    +			else
    +			if (it->first == "color")
    +				if (it->second == "inherit")
    +					style.TextColor = getTextColor();
    +				else
    +					scanHTMLColor(it->second.c_str(), style.TextColor);
    +			else
    +			if (it->first == "text-decoration" || it->first == "text-decoration-line")
    +			{
    +				std::string prop(toLower(it->second));
    +				style.Underlined = (prop.find("underline") != std::string::npos);
    +				style.StrikeThrough = (prop.find("line-through") != std::string::npos);
    +			}
    +			else
    +			if (it->first == "width")
    +				getPercentage(style.Width, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "height")
    +				getPercentage(style.Height, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-width")
    +				getPercentage(style.MaxWidth, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-height")
    +				getPercentage(style.MaxHeight, tmpf, it->second.c_str());
    +<<<<<<< HEAD
    +=======
    +			else
    +			if (it->first == "-ryzom-modulate-color")
    +			{
    +				bool b;
    +				if (it->second == "inherit")
    +					style.GlobalColor = getGlobalColor();
    +				else
    +				if (fromString(it->second, b))
    +					style.GlobalColor = b;
    +			}
    +>>>>>>> ryzomcore
    +		}
    +		if (inherit)
    +		{
    +			style.Underlined = getFontUnderlined() || style.Underlined;
    +			style.StrikeThrough = getFontStrikeThrough() || style.StrikeThrough;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh)
    +	{
    +		if (maxw <= 0) maxw = width;
    +		if (maxh <= 0) maxh = height;
    +
    +		maxw = std::max(minw, maxw);
    +		maxh = std::max(minh, maxh);
    +		
    +		float ratio = (float) width / std::max(1, height);
    +		if (width > maxw)
    +		{
    +			width = maxw;
    +			height = std::max((sint32)(maxw /ratio), minh);
    +		}
    +		if (width < minw)
    +		{
    +			width = minw;
    +			height = std::min((sint32)(minw / ratio), maxh);
    +		}
    +		if (height > maxh)
    +		{
    +			width = std::max((sint32)(maxh * ratio), minw);
    +			height = maxh;
    +		}
    +		if (height < minh)
    +		{
    +			width = std::min((sint32)(minh * ratio), maxw);
    +			height = minh;
    +		}
    +		if (width > maxw && height > maxh)
    +		{
    +			if (maxw/width <= maxh/height)
    +			{
    +				width = maxw;
    +				height = std::max(minh, (sint32)(maxw / ratio));
    +			}
    +			else
    +			{
    +				width = std::max(minw, (sint32)(maxh * ratio));
    +				height = maxh;
    +			}
    +		}
    +		if (width < minw && height < minh)
    +		{
    +			if (minw / width <= minh / height)
    +			{
    +				width = std::min(maxw, (sint32)(minh * ratio));
    +				height = minh;
    +			}
    +			else
    +			{
    +				width = minw;
    +				height = std::min(maxh, (sint32)(minw / ratio));
    +			}
    +		}
    +		if (width < minw && height > maxh)
    +		{
    +			width = minw;
    +			height = maxh;
    +		}
    +		if (width > maxw && height < minh)
    +		{
    +			width = maxw;
    +			height = minh;
    +		}
    +	}
    +	
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlHeaderCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(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;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlDataCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +			me->Content.append(buffer, size * nmemb);
    +
    +		return size * nmemb;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlProgressCallback(void *pCCurlWWWData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +		{
    +			if (dltotal > 0 || dlnow > 0 || ultotal > 0 || ulnow > 0)
    +			{
    +				nlwarning("> dltotal %d, dlnow %d, ultotal %d, ulnow %d, url '%s'", dltotal, dlnow, ultotal, ulnow, me->Url.c_str());
    +			}
    +		}
    +
    +		// return 1 to cancel download
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	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;
    +	}
    +
    +}
    +
    diff --git a/code/nel/src/gui/group_html_BASE_3737.cpp b/code/nel/src/gui/group_html_BASE_3737.cpp
    new file mode 100644
    index 000000000..e69de29bb
    diff --git a/code/nel/src/gui/group_html_LOCAL_3737.cpp b/code/nel/src/gui/group_html_LOCAL_3737.cpp
    new file mode 100644
    index 000000000..a3cfd514f
    --- /dev/null
    +++ b/code/nel/src/gui/group_html_LOCAL_3737.cpp
    @@ -0,0 +1,6139 @@
    +// Ryzom - MMORPG Framework 
    +// Copyright (C) 2010  Winch Gate Property Limited
    +//
    +// 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 .
    +
    +//#include 
    +
    +#include "stdpch.h"
    +#include "nel/gui/group_html.h"
    +
    +#include 
    +#include "nel/misc/types_nl.h"
    +#include "nel/misc/rgba.h"
    +#include "nel/misc/algo.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"
    +
    +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 to see the log about image download
    +	//#define LOG_DL 1
    +
    +	CGroupHTML::SWebOptions CGroupHTML::options;
    +
    +
    +	// 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 setRecvHeader(const std::string &header)
    +			{
    +				size_t pos = header.find(": ");
    +				if (pos == std::string::npos)
    +					return;
    +
    +				std::string key = toLower(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: " header or empty string if no header set
    +			const std::string getLocationHeader()
    +			{
    +				if (HeadersRecv.count("location") > 0)
    +					return HeadersRecv["location"];
    +
    +				return "";
    +			}
    +
    +		public:
    +			CURL *Request;
    +
    +			std::string Url;
    +			std::string Content;
    +
    +			// headers sent with curl request, must be released after transfer
    +			curl_slist * HeadersSent;
    +
    +			// headers received from curl transfer
    +			std::map HeadersRecv;
    +	};
    +
    +	// Check if domain is on TrustedDomain
    +	bool CGroupHTML::isTrustedDomain(const string &domain)
    +	{
    +		vector::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)
    +	{
    +		CCtrlButton *btn = dynamic_cast(view);
    +		if(btn)
    +		{
    +			btn->setTexture (file);
    +			btn->setTexturePushed(file);
    +			btn->invalidateCoords();
    +			btn->invalidateContent();
    +			btn->resetInvalidCoords();
    +			btn->updateCoords();
    +			paragraphChange();
    +		}
    +		else
    +		{
    +			CViewBitmap *btm = dynamic_cast(view);
    +			if(btm)
    +			{
    +				btm->setTexture (file);
    +				btm->invalidateCoords();
    +				btm->invalidateContent();
    +				btm->resetInvalidCoords();
    +				btm->updateCoords();
    +				paragraphChange();
    +			}
    +			else
    +			{
    +				CGroupCell *btgc = dynamic_cast(view);
    +				if(btgc)
    +				{
    +					btgc->setTexture (file);
    +					btgc->invalidateCoords();
    +					btgc->invalidateContent();
    +					btgc->resetInvalidCoords();
    +					btgc->updateCoords();
    +					paragraphChange();
    +				}
    +			}
    +		}
    +	}
    +	
    +	// 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(view);
    +		if(btn)
    +		{
    +			btn->fitTexture();
    +			imageWidth = btn->getW(false);
    +			imageHeight = btn->getH(false);
    +		}
    +		else
    +		{
    +			CViewBitmap *btm = dynamic_cast(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)
    +		{
    +			applyCssMinMax(width, height, 0, 0, maxw, maxh);
    +			changed = true;
    +		}
    +
    +		if (changed)
    +		{
    +			CCtrlButton *btn = dynamic_cast(view);
    +			if(btn)
    +			{
    +				btn->setScale(true);
    +				btn->setW(width);
    +				btn->setH(height);
    +			}
    +			else
    +			{
    +				CViewBitmap *image = dynamic_cast(view);
    +				if(image)
    +				{
    +					image->setScale(true);
    +					image->setW(width);
    +					image->setH(height);
    +				}
    +			}
    +		}
    +	}
    +	
    +	// 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;
    +	}
    +
    +	// Add a image download request in the multi_curl
    +	void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style)
    +	{
    +		string finalUrl = getAbsoluteUrl(url);
    +
    +		// Search if we are not already downloading this url.
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			if(Curls[i].url == finalUrl)
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("already downloading '%s' img %p", finalUrl.c_str(), img);
    +	#endif
    +				Curls[i].imgs.push_back(CDataImageDownload(img, style));
    +				return;
    +			}
    +		}
    +
    +		// use requested url for local name
    +		string dest = localImageName(url);
    +		string tmpdest = localImageName(url)+".tmp";
    +	#ifdef LOG_DL
    +		nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
    +	#endif
    +
    +		// erase the tmp file if exists
    +		if (NLMISC::CFile::fileExists(tmpdest))
    +			NLMISC::CFile::deleteFile(tmpdest);
    +
    +		if (!NLMISC::CFile::fileExists(dest))
    +		{
    +			if (!MultiCurl)
    +			{
    +				nlwarning("Invalid MultiCurl handle, unable to download '%s'", finalUrl.c_str());
    +				return;
    +			}
    +
    +			CURL *curl = curl_easy_init();
    +			if (!curl)
    +			{
    +				nlwarning("Creating cURL handle failed, unable to download '%s'", finalUrl.c_str());
    +				return;
    +			}
    +
    +			FILE *fp = nlfopen(tmpdest, "wb");
    +			if (fp == NULL)
    +			{
    +				curl_easy_cleanup(curl);
    +
    +				nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno));
    +				return;
    +			}
    +			curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true);
    +			curl_easy_setopt(curl, CURLOPT_URL, finalUrl.c_str());
    +
    +			std::string userAgent = options.appName + "/" + options.appVersion;
    +			curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
    +
    +			sendCookies(curl, _DocumentDomain, _TrustedDomain);
    +
    +			curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
    +			curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
    +
    +			curl_multi_add_handle(MultiCurl, curl);
    +			Curls.push_back(CDataDownload(curl, finalUrl, dest, fp, ImgType, img, "", "", style));
    +		#ifdef LOG_DL
    +			nlwarning("adding handle %x, %d curls", curl, Curls.size());
    +		#endif
    +			RunningCurls++;
    +		}
    +		else
    +		{
    +			setImage(img, dest);
    +			setImageSize(img, style);
    +		}
    +	}
    +
    +	void CGroupHTML::initImageDownload()
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("Init Image Download");
    +	#endif
    +
    +		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(const string &url, const string &action, const string &script, const string &md5sum)
    +	{
    +		// Search if we are not already downloading this url.
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			if(Curls[i].url == url)
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("already downloading '%s'", url.c_str());
    +	#endif
    +				return false;
    +			}
    +		}
    +
    +		string dest = localBnpName(url);
    +		string tmpdest = localBnpName(url)+".tmp";
    +	#ifdef LOG_DL
    +		nlwarning("add to download '%s' dest '%s'", url.c_str(), dest.c_str());
    +	#endif
    +		
    +		// erase the tmp file if exists
    +		if (NLMISC::CFile::fileExists(tmpdest))
    +			NLMISC::CFile::deleteFile(tmpdest);
    +
    +		// 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")
    +		{
    +			if (!MultiCurl)
    +			{
    +				nlwarning("Invalid MultiCurl handle, unable to download '%s'", url.c_str());
    +				return false;
    +			}
    +
    +			CURL *curl = curl_easy_init();
    +			if (!curl)
    +			{
    +				nlwarning("Creating cURL handle failed, unable to download '%s'", url.c_str());
    +				return false;
    +			}
    +
    +			FILE *fp = nlfopen (tmpdest, "wb");
    +			if (fp == NULL)
    +			{
    +				curl_easy_cleanup(curl);
    +				nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno));
    +				return false;
    +			}
    +
    +			curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true);
    +			curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    +
    +			curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
    +			curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
    +
    +			curl_multi_add_handle(MultiCurl, curl);
    +			Curls.push_back(CDataDownload(curl, url, dest, fp, BnpType, NULL, script, md5sum));
    +	#ifdef LOG_DL
    +			nlwarning("adding handle %x, %d curls", curl, Curls.size());
    +	#endif
    +			RunningCurls++;
    +		}
    +		else
    +			return true;
    +
    +		return false;
    +	}
    +
    +	void CGroupHTML::initBnpDownload()
    +	{
    +		if (!_TrustedDomain)
    +			return;
    +
    +	#ifdef LOG_DL
    +		nlwarning("Init Bnp Download");
    +	#endif
    +		string pathName = "user";
    +		if ( ! CFile::isExists( pathName ) )
    +			CFile::createDirectory( pathName );
    +	}
    +
    +	// 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))
    +		{
    +	#ifdef LOG_DL
    +			nlwarning("more to do now %d - %d curls", NewRunningCurls, Curls.size());
    +	#endif
    +		}
    +		if(NewRunningCurls < RunningCurls)
    +		{
    +			// some download are done, callback them
    +	#ifdef LOG_DL
    +			nlwarning ("new %d old %d", NewRunningCurls, RunningCurls);
    +	#endif
    +			// check msg
    +			CURLMsg *msg;
    +			int msgs_left;
    +			while ((msg = curl_multi_info_read(MultiCurl, &msgs_left)))
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("> (%s) msgs_left %d", _Id.c_str(), msgs_left);
    +	#endif
    +				if (msg->msg == CURLMSG_DONE)
    +				{
    +					if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle)
    +					{
    +						CURLcode res = msg->data.result;
    +						long code;
    +						curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
    +	#ifdef LOG_DL
    +						nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str());
    +	#endif
    +
    +						if (res != CURLE_OK)
    +						{
    +							std::string err;
    +							err = "Connection failed with cURL error: ";
    +							err += curl_easy_strerror(res);
    +							err += "\nURL '" + _CurlWWW->Url + "'";
    +							browseError(err.c_str());
    +						}
    +						else
    +						if ((code >= 301 && code <= 303) || code == 307 || code == 308)
    +						{
    +							if (_RedirectsRemaining < 0)
    +							{
    +								browseError(string("Redirect limit reached : " + _URL).c_str());
    +							}
    +							else
    +							{
    +								receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
    +
    +								// 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())
    +								{
    +	#ifdef LOG_DL
    +									nlwarning("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
    +	#endif
    +									location = getAbsoluteUrl(location);
    +									// throw away this handle and start with new one (easier than reusing)
    +									requestTerminated();
    +
    +									_PostNextTime = false;
    +									_RedirectsRemaining--;
    +
    +									doBrowse(location.c_str());
    +								}
    +								else
    +								{
    +									browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
    +								}
    +							}
    +						}
    +						else
    +						{
    +							receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
    +
    +							_RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
    +
    +							if ( (code < 200 || code >= 300) )
    +							{
    +								browseError(string("Connection failed (curl code " + toString((sint32)res) + ")\nhttp code " + toString((sint32)code) + ")\nURL '" + _CurlWWW->Url + "'").c_str());
    +							}
    +							else
    +							{
    +								char *ch;
    +								std::string contentType;
    +								res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
    +								if (res == CURLE_OK)
    +								{
    +									contentType = ch;
    +								}
    +
    +								htmlDownloadFinished(_CurlWWW->Content, contentType, code);
    +							}
    +							requestTerminated();
    +						}
    +					}
    +
    +					for (vector::iterator it=Curls.begin(); iteasy_handle == it->curl)
    +						{
    +							CURLcode res = msg->data.result;
    +							long r;
    +							curl_easy_getinfo(it->curl, CURLINFO_RESPONSE_CODE, &r);
    +							fclose(it->fp);
    +
    +	#ifdef LOG_DL
    +							nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->curl, res, r, it->url.size(), it->url.c_str());
    +	#endif
    +							curl_multi_remove_handle(MultiCurl, it->curl);
    +							curl_easy_cleanup(it->curl);
    +
    +							string tmpfile = it->dest + ".tmp";
    +							if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString())))
    +							{
    +								NLMISC::CFile::deleteFile(tmpfile.c_str());
    +							}
    +							else
    +							{
    +								string finalUrl;
    +								if (it->type == ImgType)
    +								{
    +									// there is race condition if two browser instances are downloading same file
    +									// second instance deletes first tmpfile and creates new file for itself.
    +									if (CFile::getFileSize(tmpfile) > 0)
    +									{
    +										try
    +										{
    +											// verify that image is not corrupted
    +											uint32 w, h;
    +											CBitmap::loadSize(tmpfile, w, h);
    +											if (w != 0 && h != 0)
    +											{
    +												CFile::moveFile(it->dest, tmpfile);
    +												for(uint i = 0; i < it->imgs.size(); i++)
    +												{
    +													setImage(it->imgs[i].Image, it->dest);
    +													setImageSize(it->imgs[i].Image, it->imgs[i].Style);
    +												}
    +											}
    +										}
    +										catch(const NLMISC::Exception &e)
    +										{
    +											// exception message has .tmp file name, so keep it for further analysis
    +											nlwarning("Invalid image (%s): %s", it->url.c_str(), e.what());
    +										}
    +									}
    +								}
    +								else
    +								{
    +									CFile::moveFile(it->dest, tmpfile);
    +									//if (lookupLocalFile (finalUrl, file.c_str(), false))
    +									{
    +										CLuaManager::getInstance().executeLuaScript( it->luaScript, true );
    +									}
    +								}
    +							}
    +
    +							Curls.erase(it);
    +							break;
    +						}
    +					}
    +				}
    +			}
    +		}
    +		RunningCurls = NewRunningCurls;
    +	#ifdef LOG_DL
    +		if (RunningCurls > 0 || !Curls.empty())
    +			nlwarning("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size());
    +	#endif
    +	}
    +
    +
    +	void CGroupHTML::releaseDownloads()
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("Release Downloads");
    +	#endif
    +		if(MultiCurl)
    +			curl_multi_cleanup(MultiCurl);
    +	}
    +
    +	class CGroupListAdaptor : public CInterfaceGroup
    +	{
    +	public:
    +		CGroupListAdaptor(const TCtorParam ¶m)
    +			: 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 void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); }
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::beginBuild ()
    +	{
    +		if (_Browsing)
    +		{
    +			_Connecting = false;
    +
    +			removeContent ();
    +		}
    +		else
    +			nlwarning("_Browsing = FALSE");
    +	}
    +
    +
    +	TStyle CGroupHTML::parseStyle (const string &str_styles)
    +	{
    +		TStyle	styles;
    +		vector elements;
    +		NLMISC::splitString(str_styles, ";", elements);
    +
    +		for(uint i = 0; i < elements.size(); ++i)
    +		{
    +			vector 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])] = trim(fullstyle);
    +			}
    +		}
    +
    +		return styles;
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addText (const char * buf, int len)
    +	{
    +		if (_Browsing)
    +		{
    +			if (_IgnoreText)
    +				return;
    +
    +			// Build a UTF8 string
    +			string inputString(buf, buf+len);
    +
    +			if (_ParsingLua && _TrustedDomain)
    +			{
    +				// we are parsing a lua script
    +				_LuaScript += inputString;
    +				// no more to do
    +				return;
    +			}
    +
    +			// Build a unicode string
    +			ucstring inputUCString;
    +			inputUCString.fromUtf8(inputString);
    +
    +			// Build the final unicode string
    +			ucstring tmp;
    +			tmp.reserve(len);
    +			uint ucLen = (uint)inputUCString.size();
    +			for (uint i=0; i= 'a' && tolower(c) <= 'f');
    +	}
    +
    +	static uint8 convertHexa(char c)
    +	{
    +		return (uint8) (tolower(c) - (isdigit(c) ? '0' : ('a' - 10)));
    +	}
    +
    +	// scan a color component, and return pointer to next position
    +	static const char *scanColorComponent(const char *src, uint8 &intensity)
    +	{
    +		if (!src) return NULL;
    +		if (!isHexa(*src)) return NULL;
    +		uint8 value = convertHexa(*src++) << 4;
    +		if (!isHexa(*src)) return NULL;
    +		value += convertHexa(*src++);
    +		intensity = value;
    +		return src;
    +	}
    +
    +	static float hueToRgb(float m1, float m2, float h)
    +	{
    +		if (h < 0) h += 1.0f;
    +		if (h > 1) h -= 1.0f;
    +		if (h*6 < 1.0f) return m1 + (m2 - m1)*h*6;
    +		if (h*2 < 1.0f) return m2;
    +		if (h*3 < 2.0f) return m1 + (m2 - m1) * (2.0f/3.0f - h)*6;
    +		return m1;
    +	}
    +
    +	static void hslToRgb(float h, float s, float l, CRGBA &result)
    +	{
    +		float m1, m2;
    +		if (l <= 0.5f)
    +			m2 = l * (s + 1.0f);
    +		else
    +			m2 = l + s - l * s;
    +		m1 = l*2 - m2;
    +
    +		result.R = 255 * hueToRgb(m1, m2, h + 1.0f/3.0f);
    +		result.G = 255 * hueToRgb(m1, m2, h);
    +		result.B = 255 * hueToRgb(m1, m2, h - 1.0f/3.0f);
    +		result.A = 255;
    +	}
    +
    +	class CNameToCol
    +	{
    +	public:
    +		const char *Name;
    +		CRGBA Color;
    +		CNameToCol(const char *name, CRGBA color) : Name(name), Color(color) {}
    +	};
    +
    +	static CNameToCol htmlColorNameToRGBA[] =
    +	{
    +		CNameToCol("AliceBlue", CRGBA(0xF0, 0xF8, 0xFF)),
    +		CNameToCol("AntiqueWhite", CRGBA(0xFA, 0xEB, 0xD7)),
    +		CNameToCol("Aqua", CRGBA(0x00, 0xFF, 0xFF)),
    +		CNameToCol("Aquamarine", CRGBA(0x7F, 0xFF, 0xD4)),
    +		CNameToCol("Azure", CRGBA(0xF0, 0xFF, 0xFF)),
    +		CNameToCol("Beige", CRGBA(0xF5, 0xF5, 0xDC)),
    +		CNameToCol("Bisque", CRGBA(0xFF, 0xE4, 0xC4)),
    +		CNameToCol("Black", CRGBA(0x00, 0x00, 0x00)),
    +		CNameToCol("BlanchedAlmond", CRGBA(0xFF, 0xEB, 0xCD)),
    +		CNameToCol("Blue", CRGBA(0x00, 0x00, 0xFF)),
    +		CNameToCol("BlueViolet", CRGBA(0x8A, 0x2B, 0xE2)),
    +		CNameToCol("Brown", CRGBA(0xA5, 0x2A, 0x2A)),
    +		CNameToCol("BurlyWood", CRGBA(0xDE, 0xB8, 0x87)),
    +		CNameToCol("CadetBlue", CRGBA(0x5F, 0x9E, 0xA0)),
    +		CNameToCol("Chartreuse", CRGBA(0x7F, 0xFF, 0x00)),
    +		CNameToCol("Chocolate", CRGBA(0xD2, 0x69, 0x1E)),
    +		CNameToCol("Coral", CRGBA(0xFF, 0x7F, 0x50)),
    +		CNameToCol("CornflowerBlue", CRGBA(0x64, 0x95, 0xED)),
    +		CNameToCol("Cornsilk", CRGBA(0xFF, 0xF8, 0xDC)),
    +		CNameToCol("Crimson", CRGBA(0xDC, 0x14, 0x3C)),
    +		CNameToCol("Cyan", CRGBA(0x00, 0xFF, 0xFF)),
    +		CNameToCol("DarkBlue", CRGBA(0x00, 0x00, 0x8B)),
    +		CNameToCol("DarkCyan", CRGBA(0x00, 0x8B, 0x8B)),
    +		CNameToCol("DarkGoldenRod", CRGBA(0xB8, 0x86, 0x0B)),
    +		CNameToCol("DarkGray", CRGBA(0xA9, 0xA9, 0xA9)),
    +		CNameToCol("DarkGreen", CRGBA(0x00, 0x64, 0x00)),
    +		CNameToCol("DarkKhaki", CRGBA(0xBD, 0xB7, 0x6B)),
    +		CNameToCol("DarkMagenta", CRGBA(0x8B, 0x00, 0x8B)),
    +		CNameToCol("DarkOliveGreen", CRGBA(0x55, 0x6B, 0x2F)),
    +		CNameToCol("Darkorange", CRGBA(0xFF, 0x8C, 0x00)),
    +		CNameToCol("DarkOrchid", CRGBA(0x99, 0x32, 0xCC)),
    +		CNameToCol("DarkRed", CRGBA(0x8B, 0x00, 0x00)),
    +		CNameToCol("DarkSalmon", CRGBA(0xE9, 0x96, 0x7A)),
    +		CNameToCol("DarkSeaGreen", CRGBA(0x8F, 0xBC, 0x8F)),
    +		CNameToCol("DarkSlateBlue", CRGBA(0x48, 0x3D, 0x8B)),
    +		CNameToCol("DarkSlateGray", CRGBA(0x2F, 0x4F, 0x4F)),
    +		CNameToCol("DarkTurquoise", CRGBA(0x00, 0xCE, 0xD1)),
    +		CNameToCol("DarkViolet", CRGBA(0x94, 0x00, 0xD3)),
    +		CNameToCol("DeepPink", CRGBA(0xFF, 0x14, 0x93)),
    +		CNameToCol("DeepSkyBlue", CRGBA(0x00, 0xBF, 0xFF)),
    +		CNameToCol("DimGray", CRGBA(0x69, 0x69, 0x69)),
    +		CNameToCol("DodgerBlue", CRGBA(0x1E, 0x90, 0xFF)),
    +		CNameToCol("Feldspar", CRGBA(0xD1, 0x92, 0x75)),
    +		CNameToCol("FireBrick", CRGBA(0xB2, 0x22, 0x22)),
    +		CNameToCol("FloralWhite", CRGBA(0xFF, 0xFA, 0xF0)),
    +		CNameToCol("ForestGreen", CRGBA(0x22, 0x8B, 0x22)),
    +		CNameToCol("Fuchsia", CRGBA(0xFF, 0x00, 0xFF)),
    +		CNameToCol("Gainsboro", CRGBA(0xDC, 0xDC, 0xDC)),
    +		CNameToCol("GhostWhite", CRGBA(0xF8, 0xF8, 0xFF)),
    +		CNameToCol("Gold", CRGBA(0xFF, 0xD7, 0x00)),
    +		CNameToCol("GoldenRod", CRGBA(0xDA, 0xA5, 0x20)),
    +		CNameToCol("Gray", CRGBA(0x80, 0x80, 0x80)),
    +		CNameToCol("Green", CRGBA(0x00, 0x80, 0x00)),
    +		CNameToCol("GreenYellow", CRGBA(0xAD, 0xFF, 0x2F)),
    +		CNameToCol("HoneyDew", CRGBA(0xF0, 0xFF, 0xF0)),
    +		CNameToCol("HotPink", CRGBA(0xFF, 0x69, 0xB4)),
    +		CNameToCol("IndianRed ", CRGBA(0xCD, 0x5C, 0x5C)),
    +		CNameToCol("Indigo  ", CRGBA(0x4B, 0x00, 0x82)),
    +		CNameToCol("Ivory", CRGBA(0xFF, 0xFF, 0xF0)),
    +		CNameToCol("Khaki", CRGBA(0xF0, 0xE6, 0x8C)),
    +		CNameToCol("Lavender", CRGBA(0xE6, 0xE6, 0xFA)),
    +		CNameToCol("LavenderBlush", CRGBA(0xFF, 0xF0, 0xF5)),
    +		CNameToCol("LawnGreen", CRGBA(0x7C, 0xFC, 0x00)),
    +		CNameToCol("LemonChiffon", CRGBA(0xFF, 0xFA, 0xCD)),
    +		CNameToCol("LightBlue", CRGBA(0xAD, 0xD8, 0xE6)),
    +		CNameToCol("LightCoral", CRGBA(0xF0, 0x80, 0x80)),
    +		CNameToCol("LightCyan", CRGBA(0xE0, 0xFF, 0xFF)),
    +		CNameToCol("LightGoldenRodYellow", CRGBA(0xFA, 0xFA, 0xD2)),
    +		CNameToCol("LightGrey", CRGBA(0xD3, 0xD3, 0xD3)),
    +		CNameToCol("LightGreen", CRGBA(0x90, 0xEE, 0x90)),
    +		CNameToCol("LightPink", CRGBA(0xFF, 0xB6, 0xC1)),
    +		CNameToCol("LightSalmon", CRGBA(0xFF, 0xA0, 0x7A)),
    +		CNameToCol("LightSeaGreen", CRGBA(0x20, 0xB2, 0xAA)),
    +		CNameToCol("LightSkyBlue", CRGBA(0x87, 0xCE, 0xFA)),
    +		CNameToCol("LightSlateBlue", CRGBA(0x84, 0x70, 0xFF)),
    +		CNameToCol("LightSlateGray", CRGBA(0x77, 0x88, 0x99)),
    +		CNameToCol("LightSteelBlue", CRGBA(0xB0, 0xC4, 0xDE)),
    +		CNameToCol("LightYellow", CRGBA(0xFF, 0xFF, 0xE0)),
    +		CNameToCol("Lime", CRGBA(0x00, 0xFF, 0x00)),
    +		CNameToCol("LimeGreen", CRGBA(0x32, 0xCD, 0x32)),
    +		CNameToCol("Linen", CRGBA(0xFA, 0xF0, 0xE6)),
    +		CNameToCol("Magenta", CRGBA(0xFF, 0x00, 0xFF)),
    +		CNameToCol("Maroon", CRGBA(0x80, 0x00, 0x00)),
    +		CNameToCol("MediumAquaMarine", CRGBA(0x66, 0xCD, 0xAA)),
    +		CNameToCol("MediumBlue", CRGBA(0x00, 0x00, 0xCD)),
    +		CNameToCol("MediumOrchid", CRGBA(0xBA, 0x55, 0xD3)),
    +		CNameToCol("MediumPurple", CRGBA(0x93, 0x70, 0xD8)),
    +		CNameToCol("MediumSeaGreen", CRGBA(0x3C, 0xB3, 0x71)),
    +		CNameToCol("MediumSlateBlue", CRGBA(0x7B, 0x68, 0xEE)),
    +		CNameToCol("MediumSpringGreen", CRGBA(0x00, 0xFA, 0x9A)),
    +		CNameToCol("MediumTurquoise", CRGBA(0x48, 0xD1, 0xCC)),
    +		CNameToCol("MediumVioletRed", CRGBA(0xC7, 0x15, 0x85)),
    +		CNameToCol("MidnightBlue", CRGBA(0x19, 0x19, 0x70)),
    +		CNameToCol("MintCream", CRGBA(0xF5, 0xFF, 0xFA)),
    +		CNameToCol("MistyRose", CRGBA(0xFF, 0xE4, 0xE1)),
    +		CNameToCol("Moccasin", CRGBA(0xFF, 0xE4, 0xB5)),
    +		CNameToCol("NavajoWhite", CRGBA(0xFF, 0xDE, 0xAD)),
    +		CNameToCol("Navy", CRGBA(0x00, 0x00, 0x80)),
    +		CNameToCol("OldLace", CRGBA(0xFD, 0xF5, 0xE6)),
    +		CNameToCol("Olive", CRGBA(0x80, 0x80, 0x00)),
    +		CNameToCol("OliveDrab", CRGBA(0x6B, 0x8E, 0x23)),
    +		CNameToCol("Orange", CRGBA(0xFF, 0xA5, 0x00)),
    +		CNameToCol("OrangeRed", CRGBA(0xFF, 0x45, 0x00)),
    +		CNameToCol("Orchid", CRGBA(0xDA, 0x70, 0xD6)),
    +		CNameToCol("PaleGoldenRod", CRGBA(0xEE, 0xE8, 0xAA)),
    +		CNameToCol("PaleGreen", CRGBA(0x98, 0xFB, 0x98)),
    +		CNameToCol("PaleTurquoise", CRGBA(0xAF, 0xEE, 0xEE)),
    +		CNameToCol("PaleVioletRed", CRGBA(0xD8, 0x70, 0x93)),
    +		CNameToCol("PapayaWhip", CRGBA(0xFF, 0xEF, 0xD5)),
    +		CNameToCol("PeachPuff", CRGBA(0xFF, 0xDA, 0xB9)),
    +		CNameToCol("Peru", CRGBA(0xCD, 0x85, 0x3F)),
    +		CNameToCol("Pink", CRGBA(0xFF, 0xC0, 0xCB)),
    +		CNameToCol("Plum", CRGBA(0xDD, 0xA0, 0xDD)),
    +		CNameToCol("PowderBlue", CRGBA(0xB0, 0xE0, 0xE6)),
    +		CNameToCol("Purple", CRGBA(0x80, 0x00, 0x80)),
    +		CNameToCol("Red", CRGBA(0xFF, 0x00, 0x00)),
    +		CNameToCol("RosyBrown", CRGBA(0xBC, 0x8F, 0x8F)),
    +		CNameToCol("RoyalBlue", CRGBA(0x41, 0x69, 0xE1)),
    +		CNameToCol("SaddleBrown", CRGBA(0x8B, 0x45, 0x13)),
    +		CNameToCol("Salmon", CRGBA(0xFA, 0x80, 0x72)),
    +		CNameToCol("SandyBrown", CRGBA(0xF4, 0xA4, 0x60)),
    +		CNameToCol("SeaGreen", CRGBA(0x2E, 0x8B, 0x57)),
    +		CNameToCol("SeaShell", CRGBA(0xFF, 0xF5, 0xEE)),
    +		CNameToCol("Sienna", CRGBA(0xA0, 0x52, 0x2D)),
    +		CNameToCol("Silver", CRGBA(0xC0, 0xC0, 0xC0)),
    +		CNameToCol("SkyBlue", CRGBA(0x87, 0xCE, 0xEB)),
    +		CNameToCol("SlateBlue", CRGBA(0x6A, 0x5A, 0xCD)),
    +		CNameToCol("SlateGray", CRGBA(0x70, 0x80, 0x90)),
    +		CNameToCol("Snow", CRGBA(0xFF, 0xFA, 0xFA)),
    +		CNameToCol("SpringGreen", CRGBA(0x00, 0xFF, 0x7F)),
    +		CNameToCol("SteelBlue", CRGBA(0x46, 0x82, 0xB4)),
    +		CNameToCol("Tan", CRGBA(0xD2, 0xB4, 0x8C)),
    +		CNameToCol("Teal", CRGBA(0x00, 0x80, 0x80)),
    +		CNameToCol("Thistle", CRGBA(0xD8, 0xBF, 0xD8)),
    +		CNameToCol("Tomato", CRGBA(0xFF, 0x63, 0x47)),
    +		CNameToCol("Turquoise", CRGBA(0x40, 0xE0, 0xD0)),
    +		CNameToCol("Violet", CRGBA(0xEE, 0x82, 0xEE)),
    +		CNameToCol("VioletRed", CRGBA(0xD0, 0x20, 0x90)),
    +		CNameToCol("Wheat", CRGBA(0xF5, 0xDE, 0xB3)),
    +		CNameToCol("White", CRGBA(0xFF, 0xFF, 0xFF)),
    +		CNameToCol("WhiteSmoke", CRGBA(0xF5, 0xF5, 0xF5)),
    +		CNameToCol("Yellow", CRGBA(0xFF, 0xFF, 0x00)),
    +		CNameToCol("YellowGreen", CRGBA(0x9A, 0xCD, 0x32))
    +	};
    +
    +	// scan a color from a HTML form (#rrggbb format)
    +	bool scanHTMLColor(const char *src, CRGBA &dest)
    +	{
    +		if (!src || *src == '\0') return false;
    +		if (*src == '#')
    +		{
    +			++src;
    +			if (strlen(src) == 3 || strlen(src) == 4)
    +			{
    +				bool hasAlpha = (strlen(src) == 4);
    +				// check RGB for valid hex
    +				if (isHexa(src[0]) && isHexa(src[1]) && isHexa(src[2]))
    +				{
    +					// check optional A for valid hex
    +					if (hasAlpha && !isHexa(src[3])) return false;
    +
    +					dest.R = convertHexa(src[0]);
    +					dest.G = convertHexa(src[1]);
    +					dest.B = convertHexa(src[2]);
    +
    +					dest.R = dest.R << 4 | dest.R;
    +					dest.G = dest.G << 4 | dest.G;
    +					dest.B = dest.B << 4 | dest.B;
    +
    +					if (hasAlpha)
    +					{
    +						dest.A = convertHexa(src[3]);
    +						dest.A = dest.A << 4 | dest.A;
    +					}
    +					else
    +						dest.A = 255;
    +
    +					return true;
    +				}
    +
    +				return false;
    +			}
    +
    +			CRGBA result;
    +			src = scanColorComponent(src, result.R); if (!src) return false;
    +			src = scanColorComponent(src, result.G); if (!src) return false;
    +			src = scanColorComponent(src, result.B); if (!src) return false;
    +			src = scanColorComponent(src, result.A);
    +			if (!src)
    +			{
    +				// Alpha is optional
    +				result.A = 255;
    +			}
    +			dest = result;
    +			return true;
    +		}
    +
    +		if (strnicmp(src, "rgb(", 4) == 0 || strnicmp(src, "rgba(", 5) == 0)
    +		{
    +			src += 4;
    +			if (*src == '(') src++;
    +
    +			vector parts;
    +			NLMISC::splitString(src, ",", parts);
    +			if (parts.size() >= 3)
    +			{
    +				CRGBA result;
    +				sint tmpv;
    +				float tmpf;
    +
    +				// R
    +				if (getPercentage(tmpv, tmpf, parts[0].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.R = tmpv;
    +
    +				// G
    +				if (getPercentage(tmpv, tmpf, parts[1].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.G = tmpv;
    +
    +				// B
    +				if (getPercentage(tmpv, tmpf, parts[2].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.B = tmpv;
    +
    +				// A
    +				if (parts.size() == 4)
    +				{
    +					if (!fromString(parts[3], tmpf)) return false;
    +					if (parts[3].find_first_of("%") != std::string::npos)
    +						tmpf /= 100;
    +
    +					tmpv = 255 * tmpf;
    +					clamp(tmpv, 0, 255);
    +					result.A = tmpv;
    +				}
    +				else
    +					result.A = 255;
    +
    +				dest = result;
    +				return true;
    +			}
    +
    +			return false;
    +		}
    +
    +		if (strnicmp(src, "hsl(", 4) == 0 || strnicmp(src, "hsla(", 5) == 0)
    +		{
    +			src += 4;
    +			if (*src == '(') src++;
    +
    +			vector parts;
    +			NLMISC::splitString(src, ",", parts);
    +			if (parts.size() >= 3)
    +			{
    +				sint tmpv;
    +				float h, s, l;
    +				// hue
    +				if (!fromString(parts[0], tmpv)) return false;
    +				tmpv = ((tmpv % 360) + 360) % 360;
    +				h = (float) tmpv / 360.0f;
    +
    +				// saturation
    +				if (!getPercentage(tmpv, s, parts[1].c_str())) return false;
    +				clamp(s, 0.0f, 1.0f);
    +
    +				// lightness
    +				if (!getPercentage(tmpv, l, parts[2].c_str())) return false;
    +				clamp(l, 0.0f, 1.0f);
    +
    +				CRGBA result;
    +				hslToRgb(h, s, l, result);
    +
    +				// A
    +				if (parts.size() == 4)
    +				{
    +					float tmpf;
    +					if (!fromString(parts[3], tmpf)) return false;
    +					if (parts[3].find_first_of("%") != std::string::npos)
    +						tmpf /= 100;
    +					clamp(tmpf, 0.0f, 1.0f);
    +					result.A = 255 * tmpf;
    +				}
    +
    +				dest = result;
    +				return true;
    +			}
    +
    +			return false;
    +		}
    +
    +		{
    +			// slow but should suffice for now
    +			for(uint k = 0; k < sizeofarray(htmlColorNameToRGBA); ++k)
    +			{
    +				if (nlstricmp(src, htmlColorNameToRGBA[k].Name) == 0)
    +				{
    +					dest = htmlColorNameToRGBA[k].Color;
    +					return true;
    +				}
    +			}
    +			return false;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::beginElement (uint element_number, const std::vector &present, const std::vector &value)
    +	{
    +		if (_Browsing)
    +		{
    +			// Paragraph ?
    +			switch(element_number)
    +			{
    +			case HTML_HEAD:
    +				_ReadingHeadTag = !_IgnoreHeadTag;
    +				_IgnoreHeadTag = true;
    +				break;
    +			case HTML_BASE:
    +				if (_ReadingHeadTag && !_IgnoreBaseUrlTag)
    +				{
    +					if (present[HTML_BASE_HREF] && value[HTML_BASE_HREF])
    +					{
    +						CUrlParser uri(value[HTML_BASE_HREF]);
    +						if (uri.isAbsolute())
    +						{
    +							_URL = uri.toString();
    +							_IgnoreBaseUrlTag = true;
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_META:
    +				if (_ReadingHeadTag)
    +				{
    +					bool httpEquiv = present[HTML_META_HTTP_EQUIV] && value[HTML_META_HTTP_EQUIV];
    +					bool httpContent = present[HTML_META_CONTENT] && value[HTML_META_CONTENT];
    +					if (httpEquiv && httpContent)
    +					{
    +						// only first http-equiv="refresh" should be handled
    +						if (_RefreshUrl.empty() && toLower(value[HTML_META_HTTP_EQUIV]) == "refresh")
    +						{
    +							const CWidgetManager::SInterfaceTimes × = CWidgetManager::getInstance()->getInterfaceTimes();
    +							double timeSec = times.thisFrameMs / 1000.0f;
    +							string content(value[HTML_META_CONTENT]);
    +
    +							string::size_type pos = content.find_first_of(";");
    +							if (pos == string::npos)
    +							{
    +								fromString(content, _NextRefreshTime);
    +								_RefreshUrl = _URL;
    +							}
    +							else
    +							{
    +								fromString(content.substr(0, pos), _NextRefreshTime);
    +
    +								pos = toLower(content).find("url=");
    +								if (pos != string::npos)
    +									_RefreshUrl = getAbsoluteUrl(content.substr(pos + 4));
    +							}
    +
    +							_NextRefreshTime += timeSec;
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_A:
    +			{
    +				registerAnchorName(MY_HTML_A);
    +
    +				CStyleParams style;
    +				style.FontFamily = getFontFamily();
    +				style.FontSize = getFontSize();
    +				style.TextColor = LinkColor;
    +				style.Underlined = true;
    +				style.StrikeThrough = getFontStrikeThrough();
    +
    +				if (present[HTML_A_STYLE] && value[HTML_A_STYLE])
    +					getStyleParams(value[HTML_A_STYLE], style);
    +
    +				_FontFamily.push_back(style.FontFamily);
    +				_FontSize.push_back(style.FontSize);
    +				_TextColor.push_back(style.TextColor);
    +				_FontUnderlined.push_back(style.Underlined);
    +				_FontStrikeThrough.push_back(style.StrikeThrough);
    +				_GlobalColor.push_back(LinkColorGlobalColor);
    +				_A.push_back(true);
    +				_Link.push_back ("");
    +				_LinkTitle.push_back("");
    +				_LinkClass.push_back("");
    +
    +				// #fragment works with both ID and NAME so register both
    +				if (present[MY_HTML_A_NAME] && value[MY_HTML_A_NAME])
    +					_AnchorName.push_back(value[MY_HTML_A_NAME]);
    +				if (present[MY_HTML_A_TITLE] && value[MY_HTML_A_TITLE])
    +					_LinkTitle.back() = value[MY_HTML_A_TITLE];
    +				if (present[MY_HTML_A_CLASS] && value[MY_HTML_A_CLASS])
    +					_LinkClass.back() = value[MY_HTML_A_CLASS];
    +				if (present[MY_HTML_A_HREF] && value[MY_HTML_A_HREF])
    +				{
    +					string suri = value[MY_HTML_A_HREF];
    +					if(suri.find("ah:") == 0)
    +					{
    +						if (_TrustedDomain)
    +							_Link.back() = suri;
    +					}
    +					else if (_TrustedDomain && suri[0] == '#' && _LuaHrefHack)
    +					{
    +						// Direct url (hack for lua beginElement)
    +						_Link.back() = suri.substr(1);
    +					}
    +					else
    +					{
    +						// convert href from "?key=val" into "http://domain.com/?key=val"
    +						_Link.back() = getAbsoluteUrl(suri);
    +					}
    +				}
    +			}
    +				break;
    +			case HTML_DIV:
    +			{
    +				_BlockLevelElement.push_back(true);
    +				registerAnchorName(MY_HTML_DIV);
    +
    +				if (present[MY_HTML_DIV_NAME] && value[MY_HTML_DIV_NAME])
    +					_DivName = value[MY_HTML_DIV_NAME];
    +
    +				string instClass;
    +				if (present[MY_HTML_DIV_CLASS] && value[MY_HTML_DIV_CLASS])
    +					instClass = value[MY_HTML_DIV_CLASS];
    +
    +				// use generic template system
    +				if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
    +				{
    +					string id;
    +					if (present[MY_HTML_DIV_ID] && value[MY_HTML_DIV_ID])
    +						id = value[MY_HTML_DIV_ID];
    +
    +					string style;
    +					if (present[MY_HTML_DIV_STYLE] && value[MY_HTML_DIV_STYLE])
    +						style = value[MY_HTML_DIV_STYLE];
    +
    +					typedef pair TTmplParam;
    +					vector 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+":"+id, 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);
    +
    +									_BlockLevelElement.back() = false;
    +							}
    +							else
    +							{
    +								getParagraph()->addChild(inst);
    +								paragraphChange();
    +							}
    +							_Divs.push_back(inst);
    +						}
    +					}
    +				}
    +
    +				if (isBlockLevelElement())
    +				{
    +					newParagraph(0);
    +				}
    +			}
    +				break;
    +			case HTML_FONT:
    +			{
    +				bool found = false;
    +				if (present[HTML_FONT_COLOR] && value[HTML_FONT_COLOR])
    +				{
    +					CRGBA color;
    +					if (scanHTMLColor(value[HTML_FONT_COLOR], color))
    +					{
    +						_TextColor.push_back(color);
    +						found = true;
    +					}
    +				}
    +				if (!found)
    +				{
    +					_TextColor.push_back(_TextColor.empty() ? CRGBA::White : _TextColor.back());
    +				}
    +
    +				if (present[HTML_FONT_SIZE] && value[HTML_FONT_SIZE])
    +				{
    +					uint fontsize;
    +					fromString(value[HTML_FONT_SIZE], fontsize);
    +					_FontSize.push_back(fontsize);
    +				}
    +				else
    +				{
    +					_FontSize.push_back(_FontSize.empty() ? TextFontSize : _FontSize.back());
    +				}
    +			}
    +				break;
    +			case HTML_BR:
    +				addString(ucstring ("\n"));
    +				break;
    +			case HTML_BODY:
    +				{
    +					if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR])
    +					{
    +						CRGBA bgColor;
    +						if (scanHTMLColor(value[HTML_BODY_BGCOLOR], bgColor))
    +							setBackgroundColor (bgColor);
    +					}
    +					
    +					string style;
    +					if (present[HTML_BODY_STYLE] && value[HTML_BODY_STYLE])
    +						style = value[HTML_BODY_STYLE];
    +					
    +					
    +					if (!style.empty())
    +					{
    +						TStyle styles = parseStyle(style);
    +						TStyle::iterator	it;
    +
    +						it = styles.find("background-repeat");
    +						bool repeat = (it != styles.end() && it->second == "1");
    +						
    +						// Webig only
    +						it = styles.find("background-scale");
    +						bool scale = (it != styles.end() && it->second == "1");
    +
    +						it = styles.find("background-image");
    +						if (it != styles.end())
    +						{
    +							string image = it->second;
    +							string::size_type texExt = toLower(image).find("url(");
    +							// Url image
    +							if (texExt != string::npos)
    +								// Remove url()
    +								image = image.substr(4, image.size()-5);
    +							setBackground (image, scale, repeat);
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_FORM:
    +				{
    +					// Build the form
    +					CGroupHTML::CForm form;
    +
    +					// Get the action name
    +					if (present[HTML_FORM_ACTION] && value[HTML_FORM_ACTION])
    +					{
    +						form.Action = getAbsoluteUrl(string(value[HTML_FORM_ACTION]));
    +					}
    +					else
    +					{
    +						form.Action = _URL;
    +					}
    +					_Forms.push_back(form);
    +				}
    +				break;
    +			case HTML_H1:
    +				registerAnchorName(MY_HTML_H1);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H1FontSize);
    +				_TextColor.push_back(H1Color);
    +				_GlobalColor.push_back(H1ColorGlobalColor);
    +				break;
    +			case HTML_H2:
    +				registerAnchorName(MY_HTML_H2);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H2FontSize);
    +				_TextColor.push_back(H2Color);
    +				_GlobalColor.push_back(H2ColorGlobalColor);
    +				break;
    +			case HTML_H3:
    +				registerAnchorName(MY_HTML_H3);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H3FontSize);
    +				_TextColor.push_back(H3Color);
    +				_GlobalColor.push_back(H3ColorGlobalColor);
    +				break;
    +			case HTML_H4:
    +				registerAnchorName(MY_HTML_H4);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H4FontSize);
    +				_TextColor.push_back(H4Color);
    +				_GlobalColor.push_back(H4ColorGlobalColor);
    +				break;
    +			case HTML_H5:
    +				registerAnchorName(MY_HTML_H5);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H5FontSize);
    +				_TextColor.push_back(H5Color);
    +				_GlobalColor.push_back(H5ColorGlobalColor);
    +				break;
    +			case HTML_H6:
    +				registerAnchorName(MY_HTML_H6);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H6FontSize);
    +				_TextColor.push_back(H6Color);
    +				_GlobalColor.push_back(H6ColorGlobalColor);
    +				break;
    +			case HTML_IMG:
    +				{
    +					// Get the string name
    +					if (present[MY_HTML_IMG_SRC] && value[MY_HTML_IMG_SRC])
    +					{
    +						CStyleParams style;
    +						float tmpf;
    +						
    +						if (present[MY_HTML_IMG_WIDTH] && value[MY_HTML_IMG_WIDTH])
    +							getPercentage(style.Width, tmpf, value[MY_HTML_IMG_WIDTH]);
    +						if (present[MY_HTML_IMG_HEIGHT] && value[MY_HTML_IMG_HEIGHT])
    +							getPercentage(style.Height, tmpf, value[MY_HTML_IMG_HEIGHT]);
    +						// width, height from inline css
    +						if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE])
    +							getStyleParams(value[MY_HTML_IMG_STYLE], style);
    +						
    +						// Get the global color name
    +						bool globalColor = false;
    +						if (present[MY_HTML_IMG_GLOBAL_COLOR])
    +							globalColor = true;
    +
    +						if (getA() && getParent () && getParent ()->getParent())
    +						{
    +							// Tooltip
    +							const char *tooltip = NULL;
    +							if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT])
    +								tooltip = value[MY_HTML_IMG_ALT];
    +
    +							string params = "name=" + getId() + "|url=" + getLink ();
    +							addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC],
    +								"", globalColor, "browse", params.c_str(), tooltip, style);
    +						}
    +						else
    +						{
    +							// Get the option to reload (class==reload)
    +							bool reloadImg = false;
    +							
    +							string styleString;
    +							if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE])
    +								styleString = value[MY_HTML_IMG_STYLE];
    +
    +							if (!styleString.empty())
    +							{
    +								TStyle styles = parseStyle(styleString);
    +								TStyle::iterator	it;
    +
    +								it = styles.find("reload");
    +								if (it != styles.end() && (*it).second == "1")
    +									reloadImg = true;
    +							}
    +							
    +							addImage (value[MY_HTML_IMG_SRC], globalColor, reloadImg, style);
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_INPUT:
    +				// Got one form ?
    +				if (!(_Forms.empty()))
    +				{
    +					// read general property
    +					string templateName;
    +					string minWidth;
    +
    +					// Widget template name
    +					if (present[MY_HTML_INPUT_Z_BTN_TMPL] && value[MY_HTML_INPUT_Z_BTN_TMPL])
    +						templateName = value[MY_HTML_INPUT_Z_BTN_TMPL];
    +					// Input name is the new
    +					if (present[MY_HTML_INPUT_Z_INPUT_TMPL] && value[MY_HTML_INPUT_Z_INPUT_TMPL])
    +						templateName = value[MY_HTML_INPUT_Z_INPUT_TMPL];
    +					// Widget minimal width
    +					if (present[MY_HTML_INPUT_Z_INPUT_WIDTH] && value[MY_HTML_INPUT_Z_INPUT_WIDTH])
    +						minWidth = value[MY_HTML_INPUT_Z_INPUT_WIDTH];
    +
    +					// Get the type
    +					if (present[MY_HTML_INPUT_TYPE] && value[MY_HTML_INPUT_TYPE])
    +					{
    +						// Global color flag
    +						bool globalColor = false;
    +						if (present[MY_HTML_INPUT_GLOBAL_COLOR])
    +							globalColor = true;
    +
    +						// Tooltip
    +						const char *tooltip = NULL;
    +						if (present[MY_HTML_INPUT_ALT] && value[MY_HTML_INPUT_ALT])
    +							tooltip = value[MY_HTML_INPUT_ALT];
    +
    +						// by default not inherited, font family defaults to system font
    +						CStyleParams style;
    +						style.TextColor = TextColor;
    +						style.FontSize = TextFontSize;
    +						style.FontWeight = FONT_WEIGHT_NORMAL;
    +						style.FontOblique = false;
    +
    +						if (present[MY_HTML_INPUT_STYLE] && value[MY_HTML_INPUT_STYLE])
    +							getStyleParams(value[MY_HTML_INPUT_STYLE], style);
    +
    +						_TextColor.push_back(style.TextColor);
    +						_FontFamily.push_back(style.FontFamily);
    +						_FontSize.push_back(style.FontSize);
    +						_FontWeight.push_back(style.FontWeight);
    +						_FontOblique.push_back(style.FontOblique);
    +
    +						string type = toLower(value[MY_HTML_INPUT_TYPE]);
    +						if (type == "image")
    +						{
    +							// The submit button
    +							string name;
    +							string normal;
    +							string pushed;
    +							string over;
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +
    +							// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +							string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=image";
    +
    +							// Add the ctrl button
    +							addButton (CCtrlButton::PushButton, name, normal, pushed.empty()?normal:pushed, over,
    +								globalColor, "html_submit_form", param.c_str(), tooltip, style);
    +						}
    +						if (type == "button" || type == "submit")
    +						{
    +							// The submit button
    +							string name;
    +							string text;
    +							string normal;
    +							string pushed;
    +							string over;
    +
    +							string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup );
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								text = value[MY_HTML_INPUT_VALUE];
    +
    +							// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +							string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=submit";
    +							if (!text.empty())
    +							{
    +								// escape AH param separator
    +								string tmp = text;
    +								while(NLMISC::strFindReplace(tmp, "|", "|"))
    +									;
    +								param = param + "|submit_button_value=" + tmp;
    +							}
    +
    +							// Add the ctrl button
    +							if (!_Paragraph)
    +							{
    +								newParagraph (0);
    +								paragraphChange ();
    +							}
    +
    +							typedef pair TTmplParam;
    +							vector 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("text", text));
    +							tmplParams.push_back(TTmplParam("active", "true"));
    +							if (!minWidth.empty())
    +								tmplParams.push_back(TTmplParam("wmin", minWidth));
    +							CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
    +							if (buttonGroup)
    +							{
    +
    +								// Add the ctrl button
    +								CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button"));
    +								if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b"));
    +								if (ctrlButton)
    +								{
    +									ctrlButton->setModulateGlobalColorAll (globalColor);
    +
    +									// Translate the tooltip
    +									if (tooltip)
    +									{
    +										if (CI18N::hasTranslation(tooltip))
    +										{
    +											ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
    +										}
    +										else
    +										{
    +											ctrlButton->setDefaultContextHelp(ucstring(tooltip));
    +										}
    +									}
    +
    +									ctrlButton->setText(ucstring::makeFromUtf8(text));
    +								}
    +								getParagraph()->addChild (buttonGroup);
    +								paragraphChange ();
    +							}
    +						}
    +						else if (type == "text")
    +						{
    +							// Get the string name
    +							string name;
    +							ucstring ucValue;
    +							uint size = 120;
    +							uint maxlength = 1024;
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SIZE] && value[MY_HTML_INPUT_SIZE])
    +								fromString(value[MY_HTML_INPUT_SIZE], size);
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +							if (present[MY_HTML_INPUT_MAXLENGTH] && value[MY_HTML_INPUT_MAXLENGTH])
    +								fromString(value[MY_HTML_INPUT_MAXLENGTH], maxlength);
    +
    +							string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
    +							// Add the editbox
    +							CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size/12, 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")
    +						{
    +							CCtrlButton::EType btnType;
    +							string name;
    +							string normal;
    +							string pushed;
    +							string over;
    +							ucstring ucValue = ucstring("on");
    +							bool checked = false;
    +
    +							if (type == "radio")
    +							{
    +								btnType = CCtrlButton::RadioButton;
    +								normal = DefaultRadioButtonBitmapNormal;
    +								pushed = DefaultRadioButtonBitmapPushed;
    +								over = DefaultRadioButtonBitmapOver;
    +							}
    +							else
    +							{
    +								btnType = CCtrlButton::ToggleButton;
    +								normal = DefaultCheckBoxBitmapNormal;
    +								pushed = DefaultCheckBoxBitmapPushed;
    +								over = DefaultCheckBoxBitmapOver;
    +							}
    +
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +							checked = (present[MY_HTML_INPUT_CHECKED] && value[MY_HTML_INPUT_CHECKED]);
    +
    +							// Add the ctrl button
    +							CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over,
    +								globalColor, "", "", tooltip);
    +							if (checkbox)
    +							{
    +								if (btnType == CCtrlButton::RadioButton)
    +								{
    +									// group together buttons with same name
    +									CForm &form = _Forms.back();
    +									bool notfound = true;
    +									for (uint i=0; igetType() == 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);
    +							}
    +						}
    +						else if (type == "hidden")
    +						{
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +							{
    +								// Get the name
    +								string name = value[MY_HTML_INPUT_NAME];
    +
    +								// Get the value
    +								ucstring ucValue;
    +								if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +									ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +
    +								// Add an entry
    +								CGroupHTML::CForm::CEntry entry;
    +								entry.Name = name;
    +								entry.Value = decodeHTMLEntities(ucValue);
    +								_Forms.back().Entries.push_back (entry);
    +							}
    +						}
    +
    +						popIfNotEmpty(_FontFamily);
    +						popIfNotEmpty(_FontSize);
    +						popIfNotEmpty(_TextColor);
    +						popIfNotEmpty(_FontWeight);
    +						popIfNotEmpty(_FontOblique);
    +					}
    +				}
    +				break;
    +			case HTML_SELECT:
    +				if (!(_Forms.empty()))
    +				{
    +					CStyleParams style;
    +
    +					// A select box
    +					string name;
    +					bool multiple = false;
    +					sint32 size = 0;
    +
    +					if (present[HTML_SELECT_NAME] && value[HTML_SELECT_NAME])
    +						name = value[HTML_SELECT_NAME];
    +					if (present[HTML_SELECT_SIZE] && value[HTML_SELECT_SIZE])
    +						fromString(value[HTML_SELECT_SIZE], size);
    +					if (present[HTML_SELECT_MULTIPLE] && value[HTML_SELECT_MULTIPLE])
    +						multiple = true;
    +					if (present[HTML_SELECT_STYLE] && value[HTML_SELECT_STYLE])
    +						getStyleParams(value[HTML_SELECT_STYLE], style);
    +
    +					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.Width > -1)
    +								sb->setMinW(style.Width);
    +
    +							if (style.Height > -1)
    +								sb->setMinH(style.Height);
    +
    +							sb->setMaxVisibleLine(size);
    +						}
    +
    +						entry.SelectBox = sb;
    +					}
    +					else
    +					{
    +						CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
    +						entry.ComboBox = cb;
    +					}
    +					_Forms.back().Entries.push_back (entry);
    +				}
    +			break;
    +			case HTML_OPTION:
    +				// Got one form ?
    +				if (!(_Forms.empty()))
    +				{
    +					if (!_Forms.back().Entries.empty())
    +					{
    +						// clear the option string
    +						_SelectOptionStr.clear();
    +
    +						std::string optionValue;
    +						if (present[HTML_OPTION_VALUE] && value[HTML_OPTION_VALUE])
    +							optionValue = value[HTML_OPTION_VALUE];
    +						_Forms.back().Entries.back().SelectValues.push_back(optionValue);
    +
    +						if (present[HTML_OPTION_SELECTED])
    +							_Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
    +						if (present[HTML_OPTION_DISABLED])
    +							_Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
    +					}
    +				}
    +				_SelectOption = true;
    +			break;
    +			case HTML_LI:
    +				if (!_UL.empty())
    +				{
    +					// 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 
  • attribute + if (present[HTML_LI_VALUE] && value[HTML_LI_VALUE]) + fromString(value[HTML_LI_VALUE], _UL.back().Value); + + ucstring str; + str.fromUtf8(_UL.back().getListMarkerText()); + addString (str); + + sint32 indent = LIIndent; + // list-style-type: outside + if (_CurrentViewLink) + indent -= _CurrentViewLink->getMaxUsedW(); + getParagraph()->setFirstViewIndent(indent); + + flushString (); + + _UL.back().Value++; + } + break; + case HTML_P: + newParagraph(PBeginSpace); + break; + case HTML_PRE: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = "monospace"; + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); + + if (present[HTML_PRE_STYLE] && value[HTML_PRE_STYLE]) + getStyleParams(value[HTML_PRE_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); + + _PRE.push_back(true); + } + break; + case HTML_TABLE: + { + registerAnchorName(MY_HTML_TABLE); + + // Get cells parameters + getCellsParameters (MY_HTML_TABLE, false); + + CGroupTable *table = new CGroupTable(TCtorParam()); + table->BgColor = _CellParams.back().BgColor; + + if (present[MY_HTML_TABLE_WIDTH] && value[MY_HTML_TABLE_WIDTH]) + getPercentage (table->ForceWidthMin, table->TableRatio, value[MY_HTML_TABLE_WIDTH]); + if (present[MY_HTML_TABLE_BORDER] && value[MY_HTML_TABLE_BORDER]) + fromString(value[MY_HTML_TABLE_BORDER], table->Border); + if (present[MY_HTML_TABLE_BORDERCOLOR] && value[MY_HTML_TABLE_BORDERCOLOR]) + scanHTMLColor(value[MY_HTML_TABLE_BORDERCOLOR], table->BorderColor); + if (present[MY_HTML_TABLE_CELLSPACING] && value[MY_HTML_TABLE_CELLSPACING]) + fromString(value[MY_HTML_TABLE_CELLSPACING], table->CellSpacing); + if (present[MY_HTML_TABLE_CELLPADDING] && value[MY_HTML_TABLE_CELLPADDING]) + fromString(value[MY_HTML_TABLE_CELLPADDING], table->CellPadding); + + // Table must fit the container size + + addHtmlGroup (table, 0); + + _Tables.push_back(table); + + // Add a cell pointer + _Cells.push_back(NULL); + _TR.push_back(false); + } + break; + case HTML_TH: + // TH is similar to TD, just different font style + case HTML_TD: + { + // Get cells parameters + getCellsParameters (MY_HTML_TD, true); + + if (element_number == HTML_TH) + { + _FontWeight.push_back(FONT_WEIGHT_BOLD); + // center if not specified otherwise. TD/TH present/value arrays have same indices + if (!(present[MY_HTML_TD_ALIGN] && value[MY_HTML_TD_ALIGN])) + _CellParams.back().Align = CGroupCell::Center; + } + + CGroupTable *table = getTable(); + if (table) + { + if (!_Cells.empty()) + { + _Cells.back() = new CGroupCell(CViewBase::TCtorParam()); + string style; + if (present[MY_HTML_TD_STYLE] && value[MY_HTML_TD_STYLE]) + style = value[MY_HTML_TD_STYLE]; + + // Set the cell parameters + if (!style.empty()) + { + TStyle styles = parseStyle(style); + TStyle::iterator it; + + it = styles.find("background-repeat"); + _Cells.back()->setTextureTile(it != styles.end()); + + // Webig only + it = styles.find("background-scale"); + _Cells.back()->setTextureScale(it != styles.end()); + + it = styles.find("background-image"); + if (it != styles.end()) + { + string image = (*it).second; + string::size_type texExt = toLower(image).find("url("); + // Url image + if (texExt != string::npos) + { + // Remove url() + image = image.substr(4, image.size()-5); + addImageDownload(image, _Cells.back()); + // Image in BNP + } + else + { + _Cells.back()->setTexture(image); + } + } + } + + if (present[MY_HTML_TD_COLSPAN] && value[MY_HTML_TD_COLSPAN]) + fromString(value[MY_HTML_TD_COLSPAN], _Cells.back()->ColSpan); + if (present[MY_HTML_TD_ROWSPAN] && value[MY_HTML_TD_ROWSPAN]) + fromString(value[MY_HTML_TD_ROWSPAN], _Cells.back()->RowSpan); + + _Cells.back()->BgColor = _CellParams.back().BgColor; + _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); + + float temp; + if (present[MY_HTML_TD_WIDTH] && value[MY_HTML_TD_WIDTH]) + getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, value[MY_HTML_TD_WIDTH]); + if (present[MY_HTML_TD_HEIGHT] && value[MY_HTML_TD_HEIGHT]) + getPercentage (_Cells.back()->Height, temp, value[MY_HTML_TD_HEIGHT]); + + _Cells.back()->NewLine = getTR(); + table->addChild (_Cells.back()); + newParagraph(TDBeginSpace); + + // Reset TR flag + if (!_TR.empty()) + _TR.back() = false; + } + } + } + break; + case HTML_TEXTAREA: + _PRE.push_back(true); + + // Got one form ? + if (!(_Forms.empty())) + { + // not inherited by default, font family defaults to system font + CStyleParams style; + style.TextColor = TextColor; + style.FontWeight = FONT_WEIGHT_NORMAL; + style.FontOblique = false; + style.FontSize = TextFontSize; + + if (present[MY_HTML_TEXTAREA_STYLE] && value[MY_HTML_TEXTAREA_STYLE]) + getStyleParams(value[MY_HTML_TEXTAREA_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + + // read general property + string templateName; + + // Widget template name + if (present[MY_HTML_TEXTAREA_Z_INPUT_TMPL] && value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]) + templateName = value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]; + + // Get the string name + _TextAreaName.clear(); + _TextAreaRow = 1; + _TextAreaCols = 10; + _TextAreaContent.clear(); + _TextAreaMaxLength = 1024; + if (present[MY_HTML_TEXTAREA_NAME] && value[MY_HTML_TEXTAREA_NAME]) + _TextAreaName = value[MY_HTML_TEXTAREA_NAME]; + if (present[MY_HTML_TEXTAREA_ROWS] && value[MY_HTML_TEXTAREA_ROWS]) + fromString(value[MY_HTML_TEXTAREA_ROWS], _TextAreaRow); + if (present[MY_HTML_TEXTAREA_COLS] && value[MY_HTML_TEXTAREA_COLS]) + fromString(value[MY_HTML_TEXTAREA_COLS], _TextAreaCols); + if (present[MY_HTML_TEXTAREA_MAXLENGTH] && value[MY_HTML_TEXTAREA_MAXLENGTH]) + fromString(value[MY_HTML_TEXTAREA_MAXLENGTH], _TextAreaMaxLength); + + _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup; + _TextArea = true; + } + break; + case HTML_TITLE: + { + if(!_TitlePrefix.empty()) + _TitleString = _TitlePrefix + " - "; + else + _TitleString.clear(); + _Title = true; + } + break; + case HTML_I: + { + _Localize = true; + } + break; + case HTML_TR: + { + // Get cells parameters + getCellsParameters (MY_HTML_TR, true); + + // Set TR flag + if (!_TR.empty()) + _TR.back() = true; + } + break; + case HTML_UL: + 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 += ULIndent; + endParagraph(); + break; + case HTML_OBJECT: + _ObjectType.clear(); + _ObjectData.clear(); + _ObjectMD5Sum.clear(); + _ObjectAction.clear(); + if (present[HTML_OBJECT_TYPE] && value[HTML_OBJECT_TYPE]) + _ObjectType = value[HTML_OBJECT_TYPE]; + if (present[HTML_OBJECT_DATA] && value[HTML_OBJECT_DATA]) + _ObjectData = value[HTML_OBJECT_DATA]; + if (present[HTML_OBJECT_ID] && value[HTML_OBJECT_ID]) + _ObjectMD5Sum = value[HTML_OBJECT_ID]; + if (present[HTML_OBJECT_STANDBY] && value[HTML_OBJECT_STANDBY]) + _ObjectAction = value[HTML_OBJECT_STANDBY]; + _Object = true; + + break; + case HTML_SPAN: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = getFontFamily(); + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); + + if (present[MY_HTML_SPAN_STYLE] && value[MY_HTML_SPAN_STYLE]) + getStyleParams(value[MY_HTML_SPAN_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); + } + break; + case HTML_DEL: + _FontStrikeThrough.push_back(true); + break; + case HTML_U: + _FontUnderlined.push_back(true); + break; + case HTML_EM: + _FontOblique.push_back(true); + break; + case HTML_STRONG: + _FontWeight.push_back(FONT_WEIGHT_BOLD); + break; + case HTML_SMALL: + _FontSize.push_back(getFontSizeSmaller()); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = true; + break; + case HTML_DL: + _DL.push_back(HTMLDListElement()); + _LI = _DL.size() > 1 || !_UL.empty(); + endParagraph(); + break; + case HTML_DT: + if (!_DL.empty()) + { + // see if this is the first
    , closing tag not required + if (!_DL.back().DT) + { + _DL.back().DT = true; + _FontWeight.push_back(FONT_WEIGHT_BOLD); + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // if there was no closing tag for
    , then remove
    style + if (_DL.back().DT) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + + if (!_DL.back().DD) + { + _Indent += ULIndent; + _DL.back().DD = true; + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_OL: + { + sint32 start = 1; + std::string type("1"); + + if (present[HTML_OL_START] && value[HTML_OL_START]) + fromString(value[HTML_OL_START], start); + if (present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) + type = value[HTML_OL_TYPE]; + + _UL.push_back(HTMLOListElement(start, type)); + // if LI is already present + _LI = _UL.size() > 1 || _DL.size() > 1; + _Indent += ULIndent; + endParagraph(); + } + break; + case HTML_HR: + { + newParagraph(0); + + CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0); + if (sep) + { + CStyleParams style; + style.TextColor = CRGBA(120, 120, 120, 255); + style.Height = 0; + style.Width = 0; + + if (present[HTML_HR_STYLE] && value[HTML_HR_STYLE]) + getStyleParams(value[HTML_HR_STYLE], style); + + CViewBitmap *bitmap = dynamic_cast(sep->getView("hr")); + if (bitmap) + { + bitmap->setColor(style.TextColor); + if (style.Width > 0) + { + clamp(style.Width, 1, 32000); + bitmap->setW(style.Width); + bitmap->setSizeRef(CInterfaceElement::none); + } + if (style.Height > 0) + { + clamp(style.Height, 1, 1000); + bitmap->setH(style.Height); + } + } + + getParagraph()->addChild(sep); + endParagraph(); + } + } + break; + } + } + } + + // *************************************************************************** + + void CGroupHTML::endElement (uint element_number) + { + if (_Browsing) + { + // Paragraph ? + switch(element_number) + { + case HTML_HEAD: + _ReadingHeadTag = false; + break; + case HTML_FONT: + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontSize); + break; + case HTML_A: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_GlobalColor); + popIfNotEmpty (_A); + popIfNotEmpty (_Link); + popIfNotEmpty (_LinkTitle); + popIfNotEmpty (_LinkClass); + break; + case HTML_H1: + case HTML_H2: + case HTML_H3: + case HTML_H4: + case HTML_H5: + case HTML_H6: + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_GlobalColor); + endParagraph(); + break; + case HTML_P: + endParagraph(); + break; + case HTML_PRE: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_PRE); + break; + case HTML_DIV: + if (isBlockLevelElement()) + { + endParagraph(); + } + _DivName.clear(); + popIfNotEmpty (_Divs); + popIfNotEmpty (_BlockLevelElement); + break; + + case HTML_TABLE: + popIfNotEmpty (_CellParams); + popIfNotEmpty (_TR); + popIfNotEmpty (_Cells); + popIfNotEmpty (_Tables); + endParagraph(); + // Add a cell + break; + case HTML_TH: + popIfNotEmpty (_FontWeight); + // no break; + case HTML_TD: + popIfNotEmpty (_CellParams); + if (!_Cells.empty()) + _Cells.back() = NULL; + break; + case HTML_TR: + popIfNotEmpty (_CellParams); + break; + case HTML_TEXTAREA: + { + _TextArea = false; + if (!(_Forms.empty())) + { + CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, _TextAreaContent, _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); + } + + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + } + + popIfNotEmpty (_PRE); + } + break; + case HTML_TITLE: + { + _Title = false; + + // Get the parent container + setTitle (_TitleString); + } + break; + case HTML_SELECT: + { + _SelectOption = false; + if (!(_Forms.empty())) + { + if (!_Forms.back().Entries.empty()) + { + CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox; + if (cb) + { + cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection); + cb->setW(cb->evalContentWidth() + 16); + } + } + } + } + break; + case HTML_OPTION: + if (!(_Forms.empty()) && !(_Forms.back().Entries.empty())) + { + // 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(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; + } + } + } + } + } + } + break; + case HTML_I: + { + _Localize = false; + } + break; + case HTML_OL: + case HTML_UL: + if (!_UL.empty()) + { + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; + + endParagraph(); + popIfNotEmpty(_UL); + } + break; + case HTML_DL: + if (!_DL.empty()) + { + endParagraph(); + + // unclosed DT + if (_DL.back().DT) + { + popIfNotEmpty (_FontWeight); + } + + // unclosed DD + if (_DL.back().DD) + { + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; + } + + popIfNotEmpty (_DL); + } + break; + case HTML_DT: + if (!_DL.empty()) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // parser will process two DD in a row as nested when first DD is not closed + if (_DL.back().DD) + { + if (_Indent > ULIndent) + _Indent = _Indent - ULIndent; + else + _Indent = 0; + + _DL.back().DD = false; + } + } + break; + case HTML_SPAN: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + break; + case HTML_DEL: + popIfNotEmpty (_FontStrikeThrough); + break; + case HTML_U: + popIfNotEmpty (_FontUnderlined); + break; + case HTML_EM: + popIfNotEmpty (_FontOblique); + break; + case HTML_STRONG: + popIfNotEmpty (_FontWeight); + break; + case HTML_SMALL: + popIfNotEmpty (_FontSize); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = false; + break; + case HTML_OBJECT: + if (_TrustedDomain) + { + if (_ObjectType=="application/ryzom-data") + { + if (!_ObjectData.empty()) + { + if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum)) + { + CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true); + } + _ObjectScript.clear(); + } + } + _Object = false; + } + break; + } + } + } + + // *************************************************************************** + void CGroupHTML::beginUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + // we receive an embeded lua script + _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain + _LuaScript.clear(); + } + } + + // *************************************************************************** + void CGroupHTML::endUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + if (_ParsingLua && _TrustedDomain) + { + _ParsingLua = false; + // execute the embeded lua script + _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript; + CLuaManager::getInstance().executeLuaScript(_LuaScript, true); + } + } + } + + + // *************************************************************************** + NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html"); + + + // *************************************************************************** + uint32 CGroupHTML::_GroupHtmlUIDPool= 0; + CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID; + + + // *************************************************************************** + CGroupHTML::CGroupHTML(const TCtorParam ¶m) + : CGroupScrollText(param), + _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT), + _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT) + { + // 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; + _BrowseNextTime = false; + _PostNextTime = false; + _Browsing = false; + _Connecting = false; + _CurrentViewLink = NULL; + _CurrentViewImage = NULL; + _Indent = 0; + _LI = false; + _SelectOption = false; + _GroupListAdaptor = NULL; + _UrlFragment.clear(); + _RefreshUrl.clear(); + _NextRefreshTime = 0.0; + _LastRefreshTime = 0.0; + + // Register + CWidgetManager::getInstance()->registerClockMsgTarget(this); + + // HTML parameters + BgColor = CRGBA::Black; + ErrorColor = CRGBA(255, 0, 0); + LinkColor = CRGBA(0, 0, 255); + TextColor = CRGBA(255, 255, 255); + H1Color = CRGBA(255, 255, 255); + H2Color = CRGBA(255, 255, 255); + H3Color = CRGBA(255, 255, 255); + H4Color = CRGBA(255, 255, 255); + H5Color = CRGBA(255, 255, 255); + H6Color = CRGBA(255, 255, 255); + ErrorColorGlobalColor = false; + LinkColorGlobalColor = false; + TextColorGlobalColor = false; + H1ColorGlobalColor = false; + H2ColorGlobalColor = false; + H3ColorGlobalColor = false; + H4ColorGlobalColor = false; + H5ColorGlobalColor = false; + H6ColorGlobalColor = false; + TextFontSize = 9; + H1FontSize = 18; + H2FontSize = 15; + H3FontSize = 12; + H4FontSize = 9; + H5FontSize = 9; + H6FontSize = 9; + LIBeginSpace = 4; + ULBeginSpace = 12; + PBeginSpace = 12; + TDBeginSpace = 0; + LIIndent = -10; + 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(); + initLibWWW(); + } + + // *************************************************************************** + + 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); + + // stop browsing + stopBrowse (); // NB : we don't call updateRefreshButton here, because : + // 1) it is useless, + // 2) it crashed before when it called getElementFromId (that didn't work when a master group was being removed...). Btw it should work now + // this is why the call to 'updateRefreshButton' has been removed from stopBrowse + + clearContext(); + if (_CurlWWW) + delete _CurlWWW; + } + + std::string CGroupHTML::getProperty( const std::string &name ) const + { + if( name == "url" ) + { + return _URL; + } + else + if( name == "title_prefix" ) + { + return _TitlePrefix.toString(); + } + else + if( name == "background_color" ) + { + return toString( BgColor ); + } + else + if( name == "error_color" ) + { + return toString( ErrorColor ); + } + else + if( name == "link_color" ) + { + return toString( LinkColor ); + } + else + if( name == "h1_color" ) + { + return toString( H1Color ); + } + else + if( name == "h2_color" ) + { + return toString( H2Color ); + } + else + if( name == "h3_color" ) + { + return toString( H3Color ); + } + else + if( name == "h4_color" ) + { + return toString( H4Color ); + } + else + if( name == "h5_color" ) + { + return toString( H5Color ); + } + else + if( name == "h6_color" ) + { + return toString( H6Color ); + } + 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 == "h1_color_global_color" ) + { + return toString( H1ColorGlobalColor ); + } + else + if( name == "h2_color_global_color" ) + { + return toString( H2ColorGlobalColor ); + } + else + if( name == "h3_color_global_color" ) + { + return toString( H3ColorGlobalColor ); + } + else + if( name == "h4_color_global_color" ) + { + return toString( H4ColorGlobalColor ); + } + else + if( name == "h5_color_global_color" ) + { + return toString( H5ColorGlobalColor ); + } + else + if( name == "h6_color_global_color" ) + { + return toString( H6ColorGlobalColor ); + } + else + if( name == "text_font_size" ) + { + return toString( TextFontSize ); + } + else + if( name == "h1_font_size" ) + { + return toString( H1FontSize ); + } + else + if( name == "h2_font_size" ) + { + return toString( H2FontSize ); + } + else + if( name == "h3_font_size" ) + { + return toString( H3FontSize ); + } + else + if( name == "h4_font_size" ) + { + return toString( H4FontSize ); + } + else + if( name == "h5_font_size" ) + { + return toString( H5FontSize ); + } + else + if( name == "h6_font_size" ) + { + return toString( H6FontSize ); + } + 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 == "li_indent" ) + { + return toString( LIIndent ); + } + 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 + 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 == "background_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + BgColor = c; + 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 == "h1_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H1Color = c; + return; + } + else + if( name == "h2_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H2Color = c; + return; + } + else + if( name == "h3_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H3Color = c; + return; + } + else + if( name == "h4_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H4Color = c; + return; + } + else + if( name == "h5_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H5Color = c; + return; + } + else + if( name == "h6_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H6Color = 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 == "h1_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H1ColorGlobalColor = b; + return; + } + else + if( name == "h2_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H2ColorGlobalColor = b; + return; + } + else + if( name == "h3_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H3ColorGlobalColor = b; + return; + } + else + if( name == "h4_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H4ColorGlobalColor = b; + return; + } + else + if( name == "h5_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H5ColorGlobalColor = b; + return; + } + else + if( name == "h6_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H6ColorGlobalColor = b; + return; + } + else + if( name == "text_font_size" ) + { + uint i; + if( fromString( value, i ) ) + TextFontSize = i; + return; + } + else + if( name == "h1_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H1FontSize = i; + return; + } + else + if( name == "h2_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H2FontSize = i; + return; + } + else + if( name == "h3_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H3FontSize = i; + return; + } + else + if( name == "h4_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H4FontSize = i; + return; + } + else + if( name == "h5_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H5FontSize = i; + return; + } + else + if( name == "h6_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H6FontSize = i; + 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 == "li_indent" ) + { + uint i; + if( fromString( value, i ) ) + LIIndent = 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 + 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.toString().c_str() ); + xmlSetProp( node, BAD_CAST "background_color", BAD_CAST toString( BgColor ).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 "background_color", BAD_CAST toString( BgColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_color", BAD_CAST toString( H1Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color", BAD_CAST toString( H2Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color", BAD_CAST toString( H3Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color", BAD_CAST toString( H4Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color", BAD_CAST toString( H5Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color", BAD_CAST toString( H6Color ).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 "h1_color_global_color", + BAD_CAST toString( H1ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color_global_color", + BAD_CAST toString( H2ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color_global_color", + BAD_CAST toString( H3ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color_global_color", + BAD_CAST toString( H4ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color_global_color", + BAD_CAST toString( H5ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color_global_color", + BAD_CAST toString( H6ColorGlobalColor ).c_str() ); + + xmlSetProp( node, BAD_CAST "text_font_size", BAD_CAST toString( TextFontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_font_size", BAD_CAST toString( H1FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_font_size", BAD_CAST toString( H2FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_font_size", BAD_CAST toString( H3FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_font_size", BAD_CAST toString( H4FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_font_size", BAD_CAST toString( H5FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_font_size", BAD_CAST toString( H6FontSize ).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 "li_indent", BAD_CAST toString( LIIndent ).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() ); + + 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*)"background_color"); + if (ptr) + BgColor = convertColor(ptr); + 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*)"text_color"); + if (ptr) + TextColor = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h1_color"); + if (ptr) + H1Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color"); + if (ptr) + H2Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color"); + if (ptr) + H3Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color"); + if (ptr) + H4Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color"); + if (ptr) + H5Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color"); + if (ptr) + H6Color = 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*)"h1_color_global_color"); + if (ptr) + H1ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color_global_color"); + if (ptr) + H2ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color_global_color"); + if (ptr) + H3ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color_global_color"); + if (ptr) + H4ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color_global_color"); + if (ptr) + H5ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color_global_color"); + if (ptr) + H6ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"text_font_size"); + if (ptr) + fromString((const char*)ptr, TextFontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h1_font_size"); + if (ptr) + fromString((const char*)ptr, H1FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h2_font_size"); + if (ptr) + fromString((const char*)ptr, H2FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h3_font_size"); + if (ptr) + fromString((const char*)ptr, H3FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h4_font_size"); + if (ptr) + fromString((const char*)ptr, H4FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h5_font_size"); + if (ptr) + fromString((const char*)ptr, H5FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h6_font_size"); + if (ptr) + fromString((const char*)ptr, H6FontSize); + 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*)"li_indent"); + if (ptr) + fromString((const char*)ptr, LIIndent); + 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); + + 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() + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + _Paragraph = NULL; + + paragraphChange (); + } + + // *************************************************************************** + + void CGroupHTML::newParagraph(uint beginSpace) + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + // Add a new paragraph + CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam()); + newParagraph->setResizeFromChildH(true); + + newParagraph->setIndent(_Indent); + + // 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) + { + // Stop previous browse + if (_Browsing) + { + // Clear all the context + clearContext(); + + _Browsing = false; + updateRefreshButton(); + + #ifdef LOG_DL + nlwarning("(%s) *** ALREADY BROWSING, break first", _Id.c_str()); + #endif + } + + #ifdef LOG_DL + nlwarning("(%s) Browsing URL : '%s'", _Id.c_str(), url); + #endif + + + 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(); + _Connecting = false; + _BrowseNextTime = true; + + // 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(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) + { + // 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); + + stopBrowse (); + updateRefreshButton(); + } + + // *************************************************************************** + + bool CGroupHTML::isBrowsing() + { + return _Browsing; + } + + + void CGroupHTML::stopBrowse () + { + #ifdef LOG_DL + nlwarning("*** STOP BROWSE (%s)", _Id.c_str()); + #endif + + // Clear all the context + clearContext(); + + _Browsing = false; + + requestTerminated(); + } + + // *************************************************************************** + + 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(); + } + } + + // *************************************************************************** + + bool CGroupHTML::translateChar(ucchar &output, ucchar input, ucchar lastCharParam) const + { + // Keep this char ? + bool keep = true; + + switch (input) + { + // Return / tab only in
     mode
    +		case '\t':
    +		case '\n':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
    +				if(!getPRE())
    +					input = ' ';
    +			}
    +			break;
    +		case ' ':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != (ucchar)'\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();
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addString(const ucstring &str)
    +	{
    +		ucstring tmpStr = str;
    +
    +		if (_Localize)
    +		{
    +			string	_str = tmpStr.toString();
    +			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 (_Title)
    +		{
    +			_TitleString += tmpStr;
    +		}
    +		else if (_TextArea)
    +		{
    +			_TextAreaContent += tmpStr;
    +		}
    +		else if (_Object)
    +		{
    +			_ObjectScript += tmpStr.toString();
    +		}
    +		else if (_SelectOption)
    +		{
    +			if (!(_Forms.empty()))
    +			{
    +				if (!_Forms.back().Entries.empty())
    +				{
    +					_SelectOptionStr += tmpStr;
    +				}
    +			}
    +		}
    +		else
    +		{
    +			// In a paragraph ?
    +			if (!_Paragraph)
    +			{
    +				newParagraph (0);
    +				paragraphChange ();
    +			}
    +
    +			// Text added ?
    +			bool added = false;
    +			bool embolden = getFontWeight() >= FONT_WEIGHT_BOLD;
    +
    +			// Number of child in this paragraph
    +			if (_CurrentViewLink)
    +			{
    +				bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == (ucchar) '\n';
    +				// Compatible with current parameters ?
    +				if (!skipLine &&
    +					(getTextColor() == _CurrentViewLink->getColor()) &&
    +					(getFontFamily() == _CurrentViewLink->getFontName()) &&
    +					(getFontSize() == (uint)_CurrentViewLink->getFontSize()) &&
    +					(getFontUnderlined() == _CurrentViewLink->getUnderlined()) &&
    +					(getFontStrikeThrough() == _CurrentViewLink->getStrikeThrough()) &&
    +					(embolden == _CurrentViewLink->getEmbolden()) &&
    +					(getFontOblique() == _CurrentViewLink->getOblique()) &&
    +					(getLink() == _CurrentViewLink->Link) &&
    +					(getGlobalColor() == _CurrentViewLink->getModulateGlobalColor()))
    +				{
    +					// Concat the text
    +					_CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
    +					_CurrentViewLink->invalidateContent();
    +					added = true;
    +				}
    +			}
    +
    +			// Not added ?
    +			if (!added)
    +			{
    +				if (getA() && string(getLinkClass()) == "ryzom-ui-button")
    +				{
    +					string buttonTemplate = DefaultButtonGroup;
    +					// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +					string param = "name=" + this->_Id + "|url=" + getLink();
    +
    +					typedef pair TTmplParam;
    +					vector 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(buttonTemplate, _Paragraph->getId(), tmplParams);
    +					if (buttonGroup)
    +					{
    +
    +						// Add the ctrl button
    +						CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button"));
    +						if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b"));
    +						if (ctrlButton)
    +						{
    +							ctrlButton->setModulateGlobalColorAll (false);
    +
    +							// Translate the tooltip
    +							ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(getLinkTitle()));
    +							ctrlButton->setText(tmpStr);
    +						}
    +						getParagraph()->addChild (buttonGroup);
    +						paragraphChange ();
    +					}
    +		
    +				}
    +				else
    +				{
    +					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(tmpStr);
    +					newLink->setColor(getTextColor());
    +					newLink->setFontName(getFontFamily());
    +					newLink->setFontSize(getFontSize());
    +					newLink->setEmbolden(embolden);
    +					newLink->setOblique(getFontOblique());
    +					newLink->setUnderlined(getFontUnderlined());
    +					newLink->setStrikeThrough(getFontStrikeThrough());
    +					newLink->setMultiLineSpace((uint)((float)getFontSize()*LineSpaceFontFactor));
    +					newLink->setMultiLine(true);
    +					newLink->setModulateGlobalColor(getGlobalColor());
    +					// newLink->setLineAtBottom (true);
    +
    +					registerAnchor(newLink);
    +
    +					if (getA() && !newLink->Link.empty())
    +					{
    +						getParagraph()->addChildLink(newLink);
    +					}
    +					else
    +					{
    +						getParagraph()->addChild(newLink);
    +					}
    +					paragraphChange ();
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addImage(const char *img, bool globalColor, bool reloadImg, const CStyleParams &style)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		string finalUrl;
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		// Not added ?
    +		CViewBitmap *newImage = new CViewBitmap (TCtorParam());
    +
    +		//
    +		// 1/ try to load the image with the old system (local files in bnp)
    +		//
    +		string image = CFile::getPath(img) + CFile::getFilenameWithoutExtension(img) + ".tga";
    +		if (lookupLocalFile (finalUrl, image.c_str(), false))
    +		{
    +			newImage->setRenderLayer(getRenderLayer()+1);
    +			image = finalUrl;
    +		}
    +		else
    +		{
    +			//
    +			// 2/ if it doesn't work, try to load the image in cache
    +			//
    +			image = localImageName(img);
    +			if (!reloadImg && lookupLocalFile (finalUrl, image.c_str(), false))
    +			{
    +				// don't display image that are not power of 2
    +				try
    +				{
    +					uint32 w, h;
    +					CBitmap::loadSize (image, w, h);
    +					if (w == 0 || h == 0 || ((!NLMISC::isPowerOf2(w) || !NLMISC::isPowerOf2(h)) && !NL3D::CTextureFile::supportNonPowerOfTwoTextures()))
    +						image = "web_del.tga";
    +				}
    +				catch(const NLMISC::Exception &e)
    +				{
    +					nlwarning(e.what());
    +					image = "web_del.tga";
    +				}
    +			}
    +			else
    +			{
    +				//
    +				// 3/ if it doesn't work, display a placeholder and ask to dl the image into the cache
    +				//
    +				if (reloadImg && CFile::fileExists(image))
    +					CFile::deleteFile(image);
    +
    +				image = "web_del.tga";
    +				addImageDownload(img, newImage, style);
    +			}
    +		}
    +		newImage->setTexture (image);
    +		newImage->setModulateGlobalColor(globalColor);
    +
    +		getParagraph()->addChild(newImage);	
    +		paragraphChange ();
    +		
    +		setImageSize(newImage, style);
    +	}
    +
    +	// ***************************************************************************
    +
    +	CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		{
    +			// Not added ?
    +			std::vector > templateParams;
    +			templateParams.push_back (std::pair ("w", toString (cols*getFontSize())));
    +			templateParams.push_back (std::pair ("id", name));
    +			templateParams.push_back (std::pair ("prompt", ""));
    +			templateParams.push_back (std::pair ("multiline", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("fontsize", toString (getFontSize())));
    +			templateParams.push_back (std::pair ("color", getTextColor().toString()));
    +			if (getFontWeight() >= FONT_WEIGHT_BOLD)
    +				templateParams.push_back (std::pair ("fontweight", "bold"));
    +			if (getFontOblique())
    +				templateParams.push_back (std::pair ("fontstyle", "oblique"));
    +			if (multiLine)
    +				templateParams.push_back (std::pair ("multi_min_line", toString(rows)));
    +			templateParams.push_back (std::pair ("want_return", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("enter_recover_focus", "false"));
    +			if (maxlength > 0)
    +				templateParams.push_back (std::pair ("max_num_chars", toString(maxlength)));
    +			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(textArea->getGroup("eb"));
    +				if (eb)
    +					eb->setInputString(decodeHTMLEntities(content));
    +
    +				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 > templateParams;
    +			templateParams.push_back (std::pair ("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(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 > templateParams;
    +		templateParams.push_back(std::pair ("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(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, bool useGlobalColor, const char *actionHandler, const char *actionHandlerParams,
    +									  const char *tooltip, const CStyleParams &style)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// Add the ctrl button
    +		CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
    +
    +		// Load only tga files.. (conversion in dds filename is done in the lookup procedure)
    +		string normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
    +
    +		// if the image doesn't exist on local, we check in the cache
    +	//	if(!CFile::fileExists(normal))
    +		if(!CPath::exists(normal))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(normal);
    +			if(id == -1)
    +			{
    +				normal = localImageName(normalBitmap);
    +				if(!CFile::fileExists(normal))
    +				{
    +					normal = "web_del.tga";
    +					addImageDownload(normalBitmap, ctrlButton, style);
    +				}
    +				else
    +				{
    +					try
    +					{
    +						uint32 w, h;
    +						CBitmap::loadSize(normal, w, h);
    +						if (w == 0 || h == 0)
    +							normal = "web_del.tga";
    +					}
    +					catch(const NLMISC::Exception &e)
    +					{
    +						nlwarning(e.what());
    +						normal = "web_del.tga";
    +					}
    +				}
    +			}
    +		}
    +
    +		string 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(!CFile::fileExists(pushed))
    +		if(!CPath::exists(pushed))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(pushed);
    +			if(id == -1)
    +			{
    +				pushed = localImageName(pushedBitmap);
    +			}
    +		}
    +
    +		string over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga";
    +
    +		ctrlButton->setType (type);
    +		if (!normal.empty())
    +			ctrlButton->setTexture (normal);
    +		if (!pushed.empty())
    +			ctrlButton->setTexturePushed (pushed);
    +		if (!over.empty())
    +			ctrlButton->setTextureOver (over);
    +		ctrlButton->setModulateGlobalColorAll (useGlobalColor);
    +		ctrlButton->setActionOnLeftClick (actionHandler);
    +		ctrlButton->setParamsOnLeftClick (actionHandlerParams);
    +
    +		// Translate the tooltip or display raw text (tooltip from webig)
    +		if (tooltip)
    +		{
    +			if (CI18N::hasTranslation(tooltip))
    +			{
    +				ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
    +				//ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
    +			}
    +			else
    +			{
    +				ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(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();
    +		_TextColor.clear();
    +		_GlobalColor.clear();
    +		_FontSize.clear();
    +		_FontWeight.clear();
    +		_FontOblique.clear();
    +		_FontUnderlined.clear();
    +		_FontStrikeThrough.clear();
    +		_Indent = 0;
    +		_LI = false;
    +		_UL.clear();
    +		_DL.clear();
    +		_A.clear();
    +		_Link.clear();
    +		_LinkTitle.clear();
    +		_Tables.clear();
    +		_Cells.clear();
    +		_TR.clear();
    +		_Forms.clear();
    +		_Groups.clear();
    +		_Anchors.clear();
    +		_AnchorName.clear();
    +		_CellParams.clear();
    +		_Title = false;
    +		_TextArea = false;
    +		_Object = false;
    +		_Localize = false;
    +		_ReadingHeadTag = false;
    +		_IgnoreHeadTag = false;
    +		_IgnoreBaseUrlTag = false;
    +
    +		// TR
    +
    +		paragraphChange ();
    +
    +		// clear the pointer to the current image download since all the button are deleted
    +	#ifdef LOG_DL
    +		nlwarning("Clear pointers to %d curls", Curls.size());
    +	#endif
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			Curls[i].imgs.clear();
    +		}
    +
    +	}
    +
    +	// ***************************************************************************
    +
    +	ucchar CGroupHTML::getLastChar() const
    +	{
    +		if (_CurrentViewLink)
    +		{
    +			const ucstring &str = _CurrentViewLink->getText();
    +			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(child);
    +				_CurrentViewImage = dynamic_cast(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;
    +
    +		// Remove previous paragraph if empty
    +		if (_Paragraph && (_Paragraph->getNumChildren() == 0))
    +		{
    +			_Paragraph->getParent ()->delGroup(_Paragraph);
    +			_Paragraph = NULL;
    +		}
    +
    +		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(group);
    +
    +		CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
    +		const std::vector &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::setTitle (const ucstring &title)
    +	{
    +		CInterfaceElement *parent = getParent();
    +		if (parent)
    +		{
    +			if ((parent = parent->getParent()))
    +			{
    +				CGroupContainer *container = dynamic_cast(parent);
    +				if (container)
    +				{
    +					container->setUCTitle (title);
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
    +	{
    +		result = url;
    +		string tmp;
    +
    +		if (toLower(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:"+toLower(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 formId, const char *submitButtonType, const char *submitButtonName, const char *submitButtonValue, sint32 x, sint32 y)
    +	{
    +		// Form id valid ?
    +		if (formId < _Forms.size())
    +		{
    +			_PostNextTime = true;
    +			_PostFormId = formId;
    +			_PostFormSubmitType = submitButtonType;
    +			_PostFormSubmitButton = submitButtonName;
    +			_PostFormSubmitValue = submitButtonValue;
    +			_PostFormSubmitX = x;
    +			_PostFormSubmitY = y;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				// Change the background color
    +				bitmap->setColor (bgcolor);
    +				bitmap->setModulateGlobalColor(false);
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				bitmap->setParentPosRef(Hotspot_TL);
    +				bitmap->setPosRef(Hotspot_TL);
    +				bitmap->setX(0);
    +				bitmap->setY(0);
    +				bitmap->setRenderLayer(-2);
    +				bitmap->setScale(scale);
    +				bitmap->setTile(tile);
    +				addImageDownload(bgtex, view);
    +			}
    +		}
    +	}
    +
    +
    +	struct CButtonFreezer : public CInterfaceElementVisitor
    +	{
    +		virtual void visitCtrl(CCtrlBase *ctrl)
    +		{
    +			CCtrlBaseButton		*textButt = dynamic_cast(ctrl);
    +			if (textButt)
    +			{
    +				textButt->setFrozen(true);
    +			}
    +		}
    +	};
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::handle ()
    +	{
    +		H_AUTO(RZ_Interface_Html_handle)
    +
    +		const CWidgetManager::SInterfaceTimes × = 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 (_Connecting)
    +		{
    +			// Check timeout if needed
    +			if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
    +			{
    +				browseError(("Connection timeout : "+_URL).c_str());
    +
    +				_Connecting = false;
    +			}
    +		}
    +		else
    +		if (_BrowseNextTime || _PostNextTime)
    +		{
    +			// Set timeout
    +			_Connecting = true;
    +			_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);
    +
    +			_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];
    +
    +		_URL = form.Action;
    +
    +		CUrlParser uri(_URL);
    +		_TrustedDomain = isTrustedDomain(uri.host);
    +		_DocumentDomain = uri.host;
    +
    +		for (i=0; igetGroup ("eb");
    +				if (group)
    +				{
    +					// Should be a CGroupEditBox
    +					CGroupEditBox *editBox = dynamic_cast(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.fromUtf8(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; jgetNumLine(); ++j)
    +					{
    +						CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
    +						if (ig)
    +						{
    +							CCtrlBaseButton *cb = dynamic_cast(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, CI18N::encodeUTF8(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)
    +	{
    +		std::string filename;
    +		if (toLower(uri).find("file:/") == 0)
    +		{
    +			filename = uri.substr(6, uri.size() - 6);
    +		}
    +		else
    +		{
    +			filename = uri;
    +		}
    +
    +	#if LOG_DL
    +		nlwarning("(%s) browse local file '%s'", filename.c_str());
    +	#endif
    +
    +		_TrustedDomain = true;
    +		_DocumentDomain = "localhost";
    +
    +		// Stop previous browse, remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		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(const std::string &url, const std::string &referer, bool doPost, const SFormFields &formfields)
    +	{
    +		// Stop previous request and remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		// Reset the title
    +		if(_TitlePrefix.empty())
    +			setTitle (CI18N::get("uiPleaseWait"));
    +		else
    +			setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
    +
    +	#if LOG_DL
    +		nlwarning("(%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());
    +	#endif
    +
    +		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;
    +		}
    +
    +		// 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());
    +	#ifdef LOG_DL
    +			nlwarning("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
    +	#endif
    +		}
    +
    +		if (doPost)
    +		{
    +			// serialize form data and add it to curl
    +			std::string data;
    +			for(uint i=0; i0)
    +					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 headers;
    +		headers.push_back("Accept-Language: "+options.languageCode);
    +		headers.push_back("Accept-Charset: utf-8");
    +		for(uint i=0; i< headers.size(); ++i)
    +		{
    +			_CurlWWW->HeadersSent = curl_slist_append(_CurlWWW->HeadersSent, headers[i].c_str());
    +		}
    +		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, _CurlWWW->HeadersSent);
    +
    +		// catch headers for redirect
    +		curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
    +
    +		// catch body
    +		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDataCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
    +
    +	#if LOG_DL
    +		// progress callback
    +		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
    +		curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, 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++;
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
    +	#endif
    +
    +		// create  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();
    +	#ifdef LOG_DL
    +				nlwarning("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
    +	#endif
    +			}
    +			catch(...) { }
    +
    +			// create html code with image url inside and do the request again
    +			renderHtmlString(""+_URL+"");
    +		}
    +		else
    +		{
    +			renderHtmlString(content);
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::renderHtmlString(const std::string &html)
    +	{
    +		bool success;
    +
    +		//
    +		_Browsing = true;
    +		_DocumentUrl = _URL;
    +		_NextRefreshTime = 0;
    +		_RefreshUrl.clear();
    +
    +		// clear content
    +		beginBuild();
    +
    +		success = parseHtml(html);
    +
    +		// invalidate coords
    +		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);
    +		}
    +
    +		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 ()
    +	{
    +		CGroupScrollText::draw ();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::endBuild ()
    +	{
    +		invalidateCoords();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::requestTerminated()
    +	{
    +		if (_CurlWWW)
    +		{
    +	#if LOG_DL
    +			nlwarning("(%s) stop curl, url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
    +	#endif
    +			if (MultiCurl)
    +				curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
    +
    +			delete _CurlWWW;
    +
    +			_CurlWWW = NULL;
    +			_Connecting = false;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	string	CGroupHTML::home ()
    +	{
    +		return Home;
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::removeContent ()
    +	{
    +		// Remove old document
    +		if (!_GroupListAdaptor)
    +		{
    +			_GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
    +			_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 color
    +		setBackgroundColor (BgColor);
    +
    +		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;iChildren.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 ¶ms, 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;igetParser()->getProcedureAction(procName, i, procAh, procParams))
    +				{
    +					// recurs proc if needed!
    +					if (actionLaunchUrlRecurs(procAh, procParams, url))
    +						return true;
    +				}
    +			}
    +		}
    +
    +		return false;
    +	}
    +
    +	// ***************************************************************************
    +	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
    +		_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(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
    +		CCtrlBaseButton		*butRedo= dynamic_cast(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(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
    +
    +		bool enabled = !_Browsing && !_Connecting;
    +		if(butRefresh)
    +			butRefresh->setFrozen(!enabled);
    +	}
    +
    +	// ***************************************************************************
    +
    +	NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
    +
    +	CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam ¶m)
    +		: 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::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);
    +
    +		renderHtmlString(html);
    +
    +		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);
    +
    +		ucstring text;
    +		text.fromUtf8(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(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(ucstring(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();
    +		}
    +		string url = getLink();
    +		if (!url.empty())
    +		{
    +			string params = "name=" + getId() + "|url=" + getLink ();
    +			addButton(CCtrlButton::PushButton, ls.toString(1), ls.toString(1), ls.toString(1),
    +								"", ls.toBoolean(2), "browse", params.c_str(), "");
    +		}
    +		else
    +		{
    +			addImage(ls.toString(1), ls.toBoolean(2));
    +		}
    +
    +
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaBeginElement(CLuaState &ls)
    +	{
    +		const char *funcName = "beginElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 2);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +		CLuaIHM::checkArgType(ls, funcName, 2, LUA_TTABLE);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		std::vector present;
    +		std::vector value;
    +		present.resize(30, false);
    +		value.resize(30);
    +
    +		CLuaObject params;
    +		params.pop(ls);
    +		uint max_idx = 0;
    +
    +
    +		ENUM_LUA_TABLE(params, it)
    +		{
    +			if (!it.nextKey().isInteger())
    +			{
    +				nlwarning("%s : bad key encountered with type %s, integer expected.", funcName, it.nextKey().getTypename());
    +				continue;
    +			}
    +			if (!it.nextValue().isString())
    +			{
    +				nlwarning("%s : bad value encountered with type %s for key %s, string expected.", funcName, it.nextValue().getTypename(), it.nextKey().toString().c_str());
    +				continue;
    +			}
    +			uint idx = (uint)it.nextKey().toInteger();
    +
    +			present.insert(present.begin() + (uint)it.nextKey().toInteger(), true);
    +
    +			string str = it.nextValue().toString();
    +			size_t size = str.size() + 1;
    +			char * buffer = new char[ size ];
    +			strncpy(buffer, str.c_str(), size );
    +
    +			value.insert(value.begin() + (uint)it.nextKey().toInteger(), buffer);
    +		}
    +
    +		// ingame lua scripts from browser are using  url scheme
    +		// reason unknown
    +		_LuaHrefHack = true;
    +		beginElement(element_number, present, value);
    +		_LuaHrefHack = false;
    +
    +		return 0;
    +	}
    +
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaEndElement(CLuaState &ls)
    +	{
    +		const char *funcName = "endElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 1);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		endElement(element_number);
    +
    +		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());
    +	}
    +
    +	// ***************************************************************************
    +	inline bool isDigit(ucchar 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 ucchar convertHexDigit(ucchar 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;
    +	}
    +
    +	// ***************************************************************************
    +	ucstring CGroupHTML::decodeHTMLEntities(const ucstring &str)
    +	{
    +		ucstring result;
    +		uint last, pos;
    +
    +		for (uint i=0; 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;
    +					}
    +
    +					ucchar c = 0;
    +
    +					// convert digits to unicode character
    +					while (posfirst == "font-size")
    +			{
    +				if (it->second == "inherit")
    +					style.FontSize = getFontSize();
    +				else
    +				{
    +					float tmp;
    +					sint size = 0;
    +					getPercentage (size, tmp, it->second.c_str());
    +					if (size > 0)
    +						style.FontSize = size;
    +				}
    +			}
    +			else
    +			if (it->first == "font-style")
    +			{
    +				if (it->second == "inherit")
    +					style.FontOblique = getFontOblique();
    +				else
    +				if (it->second == "italic" || it->second == "oblique")
    +					style.FontOblique = true;
    +			}
    +			else
    +			if (it->first == "font-family")
    +			{
    +				if (it->second == "inherit")
    +					style.FontFamily = getFontFamily();
    +				else
    +				if (it->second == "monospace")
    +					style.FontFamily = "monospace";
    +				else
    +					style.FontFamily.clear();
    +			}
    +			else
    +			if (it->first == "font-weight")
    +			{
    +				// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
    +				uint weight = 400;
    +				if (it->second == "inherit")
    +					weight = getFontWeight();
    +				else
    +				if (it->second == "normal")
    +					weight = 400;
    +				else
    +				if (it->second == "bold")
    +					weight = 700;
    +				else
    +				if (it->second == "lighter")
    +				{
    +					const uint lighter[] = {100, 100, 100, 100, 100, 400, 400, 700, 700};
    +					uint index = getFontWeight() / 100 - 1;
    +					clamp(index, 1u, 9u);
    +					weight = lighter[index-1];
    +				}
    +				else
    +				if (it->second == "bolder")
    +				{
    +					const uint bolder[] =  {400, 400, 400, 700, 700, 900, 900, 900, 900};
    +					uint index = getFontWeight() / 100 + 1;
    +					clamp(index, 1u, 9u);
    +					weight = bolder[index-1];
    +				}
    +				else
    +				if (fromString(it->second, weight))
    +				{
    +					weight = (weight / 100);
    +					clamp(weight, 1u, 9u);
    +					weight *= 100;
    +				}
    +				style.FontWeight = weight;
    +			}
    +			else
    +			if (it->first == "color")
    +				if (it->second == "inherit")
    +					style.TextColor = getTextColor();
    +				else
    +					scanHTMLColor(it->second.c_str(), style.TextColor);
    +			else
    +			if (it->first == "text-decoration" || it->first == "text-decoration-line")
    +			{
    +				std::string prop(toLower(it->second));
    +				style.Underlined = (prop.find("underline") != std::string::npos);
    +				style.StrikeThrough = (prop.find("line-through") != std::string::npos);
    +			}
    +			else
    +			if (it->first == "width")
    +				getPercentage(style.Width, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "height")
    +				getPercentage(style.Height, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-width")
    +				getPercentage(style.MaxWidth, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-height")
    +				getPercentage(style.MaxHeight, tmpf, it->second.c_str());
    +		}
    +		if (inherit)
    +		{
    +			style.Underlined = getFontUnderlined() || style.Underlined;
    +			style.StrikeThrough = getFontStrikeThrough() || style.StrikeThrough;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh)
    +	{
    +		if (maxw <= 0) maxw = width;
    +		if (maxh <= 0) maxh = height;
    +
    +		maxw = std::max(minw, maxw);
    +		maxh = std::max(minh, maxh);
    +		
    +		float ratio = (float) width / std::max(1, height);
    +		if (width > maxw)
    +		{
    +			width = maxw;
    +			height = std::max((sint32)(maxw /ratio), minh);
    +		}
    +		if (width < minw)
    +		{
    +			width = minw;
    +			height = std::min((sint32)(minw / ratio), maxh);
    +		}
    +		if (height > maxh)
    +		{
    +			width = std::max((sint32)(maxh * ratio), minw);
    +			height = maxh;
    +		}
    +		if (height < minh)
    +		{
    +			width = std::min((sint32)(minh * ratio), maxw);
    +			height = minh;
    +		}
    +		if (width > maxw && height > maxh)
    +		{
    +			if (maxw/width <= maxh/height)
    +			{
    +				width = maxw;
    +				height = std::max(minh, (sint32)(maxw / ratio));
    +			}
    +			else
    +			{
    +				width = std::max(minw, (sint32)(maxh * ratio));
    +				height = maxh;
    +			}
    +		}
    +		if (width < minw && height < minh)
    +		{
    +			if (minw / width <= minh / height)
    +			{
    +				width = std::min(maxw, (sint32)(minh * ratio));
    +				height = minh;
    +			}
    +			else
    +			{
    +				width = minw;
    +				height = std::min(maxh, (sint32)(minw / ratio));
    +			}
    +		}
    +		if (width < minw && height > maxh)
    +		{
    +			width = minw;
    +			height = maxh;
    +		}
    +		if (width > maxw && height < minh)
    +		{
    +			width = maxw;
    +			height = minh;
    +		}
    +	}
    +	
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlHeaderCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(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;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlDataCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +			me->Content.append(buffer, size * nmemb);
    +
    +		return size * nmemb;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlProgressCallback(void *pCCurlWWWData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +		{
    +			if (dltotal > 0 || dlnow > 0 || ultotal > 0 || ulnow > 0)
    +			{
    +				nlwarning("> dltotal %d, dlnow %d, ultotal %d, ulnow %d, url '%s'", dltotal, dlnow, ultotal, ulnow, me->Url.c_str());
    +			}
    +		}
    +
    +		// return 1 to cancel download
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	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;
    +	}
    +
    +}
    +
    diff --git a/code/nel/src/gui/group_html_REMOTE_3737.cpp b/code/nel/src/gui/group_html_REMOTE_3737.cpp
    new file mode 100644
    index 000000000..c9977589f
    --- /dev/null
    +++ b/code/nel/src/gui/group_html_REMOTE_3737.cpp
    @@ -0,0 +1,6440 @@
    +// Ryzom - MMORPG Framework 
    +// Copyright (C) 2010  Winch Gate Property Limited
    +//
    +// 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 .
    +
    +//#include 
    +
    +#include "stdpch.h"
    +#include "nel/gui/group_html.h"
    +
    +#include 
    +#include "nel/misc/types_nl.h"
    +#include "nel/misc/rgba.h"
    +#include "nel/misc/algo.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/gui/curl_certificates.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 to see the log about image download
    +	//#define LOG_DL 1
    +
    +	CGroupHTML::SWebOptions CGroupHTML::options;
    +
    +	// Return URL with https is host is in HSTS list
    +	static std::string upgradeInsecureUrl(const std::string &url)
    +	{
    +		if (toLower(url.substr(0, 7)) != "http://") {
    +			return url;
    +		}
    +
    +		CUrlParser uri(url);
    +		if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){
    +			return url;
    +		}
    +
    +	#ifdef LOG_DL
    +		nlwarning("HSTS url : '%s', using https", url.c_str());
    +	#endif
    +		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 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 = toLower(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: " 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 (toLower(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 HeadersRecv;
    +	};
    +
    +	// Check if domain is on TrustedDomain
    +	bool CGroupHTML::isTrustedDomain(const string &domain)
    +	{
    +		vector::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(view);
    +		if(btn)
    +		{
    +			if (type == NormalImage)
    +			{
    +				btn->setTexture (file);
    +				btn->setTexturePushed(file);
    +				btn->invalidateCoords();
    +				btn->invalidateContent();
    +				paragraphChange();
    +			}
    +			else
    +			{
    +				btn->setTextureOver(file);
    +			}
    +		}
    +		else
    +		{
    +			CViewBitmap *btm = dynamic_cast(view);
    +			if(btm)
    +			{
    +				btm->setTexture (file);
    +				btm->invalidateCoords();
    +				btm->invalidateContent();
    +				paragraphChange();
    +			}
    +			else
    +			{
    +				CGroupCell *btgc = dynamic_cast(view);
    +				if(btgc)
    +				{
    +					btgc->setTexture (file);
    +					btgc->invalidateCoords();
    +					btgc->invalidateContent();
    +					paragraphChange();
    +				}
    +			}
    +		}
    +	}
    +
    +	// 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(view);
    +		if(btn)
    +		{
    +			btn->fitTexture();
    +			imageWidth = btn->getW(false);
    +			imageHeight = btn->getH(false);
    +		}
    +		else
    +		{
    +			CViewBitmap *btm = dynamic_cast(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)
    +		{
    +			applyCssMinMax(width, height, 0, 0, maxw, maxh);
    +			changed = true;
    +		}
    +
    +		if (changed)
    +		{
    +			CCtrlButton *btn = dynamic_cast(view);
    +			if(btn)
    +			{
    +				btn->setScale(true);
    +				btn->setW(width);
    +				btn->setH(height);
    +			}
    +			else
    +			{
    +				CViewBitmap *image = dynamic_cast(view);
    +				if(image)
    +				{
    +					image->setScale(true);
    +					image->setW(width);
    +					image->setH(height);
    +				}
    +			}
    +		}
    +	}
    +	
    +	// 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;
    +	}
    +
    +	// 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(¤tTime);
    +
    +		CHttpCacheObject cache;
    +		if (CFile::fileExists(download.dest))
    +			cache = CHttpCache::getInstance()->lookup(download.dest);
    +
    +		if (cache.Expires > currentTime)
    +		{
    +	#ifdef LOG_DL
    +			nlwarning("Cache for (%s) is not expired (%s, expires:%d)", download.url.c_str(), download.dest.c_str(), cache.Expires - currentTime);
    +	#endif
    +			return false;
    +		}
    +
    +		string tmpdest = download.dest + ".tmp";
    +
    +		// erase the tmp file if exists
    +		if (CFile::fileExists(tmpdest))
    +			CFile::deleteFile(tmpdest);
    +
    +		FILE *fp = nlfopen (tmpdest, "wb");
    +		if (fp == NULL)
    +		{
    +			nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno));
    +			return false;
    +		}
    +
    +		CURL *curl = curl_easy_init();
    +		if (!curl)
    +		{
    +			fclose(fp);
    +			CFile::deleteFile(tmpdest);
    +
    +			nlwarning("Creating cURL handle failed, unable to download '%s'", download.url.c_str());
    +			return false;
    +		}
    +
    +#if defined(NL_OS_WINDOWS)
    +		// https://
    +		if (toLower(download.url.substr(0, 8)) == "https://")
    +		{
    +			curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction);
    +		}
    +#endif
    +
    +		download.data = new CCurlWWWData(curl, download.url);
    +		download.fp = fp;
    +
    +		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 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, 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);
    +
    +		curl_multi_add_handle(MultiCurl, curl);
    +
    +		return true;
    +	}
    +
    +	// Add a image download request in the multi_curl
    +	void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type)
    +	{
    +		string finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
    +
    +		// use requested url for local name (cache)
    +		string dest = localImageName(url);
    +	#ifdef LOG_DL
    +		nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
    +	#endif
    +
    +		// Display cached image while downloading new
    +		if (type != TImageType::OverImage && CFile::fileExists(dest))
    +		{
    +			setImage(img, dest, type);
    +			setImageSize(img, style);
    +		}
    +
    +		// Search if we are not already downloading this url.
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			if(Curls[i].url == finalUrl)
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("already downloading '%s' img %p", finalUrl.c_str(), img);
    +	#endif
    +				Curls[i].imgs.push_back(CDataImageDownload(img, style, type));
    +				return;
    +			}
    +		}
    +
    +		Curls.push_back(CDataDownload(finalUrl, dest, ImgType, img, "", "", style, type));
    +		if (Curls.size() < options.curlMaxConnections) {
    +			if (!startCurlDownload(Curls.back()))
    +			{
    +				Curls.pop_back();
    +				return;
    +			}
    +
    +			RunningCurls++;
    +	#ifdef LOG_DL
    +			nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size());
    +		}
    +		else
    +		{
    +			nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size());
    +	#endif
    +		}
    +	}
    +
    +	void CGroupHTML::initImageDownload()
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("Init Image Download");
    +	#endif
    +
    +		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(uint i = 0; i < Curls.size(); i++)
    +		{
    +			if(Curls[i].url == url)
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("already downloading '%s'", url.c_str());
    +	#endif
    +				return false;
    +			}
    +		}
    +
    +		string dest = localBnpName(url);
    +	#ifdef LOG_DL
    +		nlwarning("add to download '%s' dest '%s'", url.c_str(), dest.c_str());
    +	#endif
    +
    +		// 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(CDataDownload(url, dest, BnpType, NULL, script, md5sum));
    +			if (Curls.size() < options.curlMaxConnections)
    +			{
    +				if (!startCurlDownload(Curls.back()))
    +				{
    +					Curls.pop_back();
    +					return false;
    +				}
    +				RunningCurls++;
    +	#ifdef LOG_DL
    +				nlwarning("(%s) adding handle %x, %d curls", _Id.c_str(), Curls.back().data->Request, Curls.size());
    +			}
    +			else
    +			{
    +				nlwarning("(%s) download queued, %d curls", _Id.c_str(), Curls.size());
    +	#endif
    +			}
    +		}
    +		else
    +			return true;
    +
    +		return false;
    +	}
    +
    +	void CGroupHTML::initBnpDownload()
    +	{
    +		if (!_TrustedDomain)
    +			return;
    +
    +	#ifdef LOG_DL
    +		nlwarning("Init Bnp Download");
    +	#endif
    +		string pathName = "user";
    +		if ( ! CFile::isExists( pathName ) )
    +			CFile::createDirectory( pathName );
    +	}
    +
    +	// 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))
    +		{
    +	#ifdef LOG_DL
    +			nlwarning("more to do now %d - %d curls", NewRunningCurls, Curls.size());
    +	#endif
    +		}
    +		if(NewRunningCurls < RunningCurls)
    +		{
    +			// some download are done, callback them
    +	#ifdef LOG_DL
    +			nlwarning ("new %d old %d", NewRunningCurls, RunningCurls);
    +	#endif
    +			// check msg
    +			CURLMsg *msg;
    +			int msgs_left;
    +			while ((msg = curl_multi_info_read(MultiCurl, &msgs_left)))
    +			{
    +	#ifdef LOG_DL
    +				nlwarning("> (%s) msgs_left %d", _Id.c_str(), msgs_left);
    +	#endif
    +				if (msg->msg == CURLMSG_DONE)
    +				{
    +					if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle)
    +					{
    +						CURLcode res = msg->data.result;
    +						long code;
    +						curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
    +	#ifdef LOG_DL
    +						nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str());
    +	#endif
    +						// save HSTS header from all requests regardless of HTTP code
    +						if (res == CURLE_OK && _CurlWWW->hasHSTSHeader())
    +						{
    +							CUrlParser uri(_CurlWWW->Url);
    +							CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
    +						}
    +
    +						if (res != CURLE_OK)
    +						{
    +							std::string err;
    +							err = "Connection failed with cURL error: ";
    +							err += curl_easy_strerror(res);
    +							err += "\nURL '" + _CurlWWW->Url + "'";
    +							browseError(err.c_str());
    +						}
    +						else
    +						if ((code >= 301 && code <= 303) || code == 307 || code == 308)
    +						{
    +							if (_RedirectsRemaining < 0)
    +							{
    +								browseError(string("Redirect limit reached : " + _URL).c_str());
    +							}
    +							else
    +							{
    +								receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
    +
    +								// 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())
    +								{
    +	#ifdef LOG_DL
    +									nlwarning("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
    +	#endif
    +									location = getAbsoluteUrl(location);
    +									// throw away this handle and start with new one (easier than reusing)
    +									requestTerminated();
    +
    +									_PostNextTime = false;
    +									_RedirectsRemaining--;
    +
    +									doBrowse(location.c_str());
    +								}
    +								else
    +								{
    +									browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
    +								}
    +							}
    +						}
    +						else
    +						{
    +							receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
    +
    +							_RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
    +
    +							if ( (code < 200 || code >= 300) )
    +							{
    +								browseError(string("Connection failed (curl code " + toString((sint32)res) + ")\nhttp code " + toString((sint32)code) + ")\nURL '" + _CurlWWW->Url + "'").c_str());
    +							}
    +							else
    +							{
    +								char *ch;
    +								std::string contentType;
    +								res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
    +								if (res == CURLE_OK && ch != NULL)
    +								{
    +									contentType = ch;
    +								}
    +
    +								htmlDownloadFinished(_CurlWWW->Content, contentType, code);
    +							}
    +							requestTerminated();
    +						}
    +					}
    +
    +					for (vector::iterator it=Curls.begin(); itdata && it->data->Request == msg->easy_handle)
    +						{
    +							CURLcode res = msg->data.result;
    +							long r;
    +							curl_easy_getinfo(it->data->Request, CURLINFO_RESPONSE_CODE, &r);
    +							fclose(it->fp);
    +
    +							CUrlParser uri(it->url);
    +							if (!uri.host.empty())
    +								receiveCookies(it->data->Request, uri.host, isTrustedDomain(uri.host));
    +	#ifdef LOG_DL
    +							nlwarning("(%s) transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), it->data->Request, res, r, it->url.size(), it->url.c_str());
    +	#endif
    +							curl_multi_remove_handle(MultiCurl, it->data->Request);
    +
    +							// save HSTS header from all requests regardless of HTTP code
    +							if (res == CURLE_OK && it->data->hasHSTSHeader())
    +							{
    +								CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, it->data->getHSTSHeader());
    +							}
    +
    +							string tmpfile = it->dest + ".tmp";
    +							if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString())))
    +							{
    +								if (it->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT && ((r >= 301 && r <= 303) || r == 307 || r == 308))
    +								{
    +									std::string location(it->data->getLocationHeader());
    +									if (!location.empty())
    +									{
    +										CUrlParser uri(location);
    +										if (!uri.isAbsolute())
    +										{
    +											uri.inherit(it->url);
    +											location = uri.toString();
    +										}
    +
    +										it->url = location;
    +										it->fp = NULL;
    +
    +										// release CCurlWWWData
    +										delete it->data;
    +										it->data = NULL;
    +
    +										it->redirects++;
    +	#ifdef LOG_DL
    +										nlwarning("Redirect '%s'", location.c_str());
    +	#endif
    +										// keep the request in queue
    +										continue;
    +									}
    +									else
    +										nlwarning("Redirected to empty url '%s'", it->url.c_str());
    +								}
    +								else
    +								{
    +									if (it->redirects >= DEFAULT_RYZOM_REDIRECT_LIMIT)
    +										nlwarning("Redirect limit reached for '%s'", it->url.c_str());
    +
    +									NLMISC::CFile::deleteFile(tmpfile.c_str());
    +
    +									// 304 Not Modified
    +									if (res == CURLE_OK && r == 304)
    +									{
    +										CHttpCacheObject obj;
    +										obj.Expires = it->data->getExpires();
    +										obj.Etag = it->data->getEtag();
    +										obj.LastModified = it->data->getLastModified();
    +
    +										CHttpCache::getInstance()->store(it->dest, obj);
    +									}
    +								}
    +							}
    +							else
    +							{
    +								CHttpCacheObject obj;
    +								obj.Expires = it->data->getExpires();
    +								obj.Etag = it->data->getEtag();
    +								obj.LastModified = it->data->getLastModified();
    +
    +								CHttpCache::getInstance()->store(it->dest, obj);
    +
    +								string finalUrl;
    +								if (it->type == ImgType)
    +								{
    +									// there is race condition if two browser instances are downloading same file
    +									// second instance deletes first tmpfile and creates new file for itself.
    +									if (CFile::getFileSize(tmpfile) > 0)
    +									{
    +										try
    +										{
    +											// verify that image is not corrupted
    +											uint32 w, h;
    +											CBitmap::loadSize(tmpfile, w, h);
    +											if (w != 0 && h != 0)
    +											{
    +												if (CFile::fileExists(it->dest))
    +													CFile::deleteFile(it->dest);
    +
    +												CFile::moveFile(it->dest, tmpfile);
    +												for(uint i = 0; i < it->imgs.size(); i++)
    +												{
    +													setImage(it->imgs[i].Image, it->dest, it->imgs[i].Type);
    +													setImageSize(it->imgs[i].Image, it->imgs[i].Style);
    +												}
    +											}
    +										}
    +										catch(const NLMISC::Exception &e)
    +										{
    +											// exception message has .tmp file name, so keep it for further analysis
    +											nlwarning("Invalid image (%s): %s", it->url.c_str(), e.what());
    +										}
    +									}
    +								}
    +								else
    +								{
    +									CFile::moveFile(it->dest, tmpfile);
    +									//if (lookupLocalFile (finalUrl, file.c_str(), false))
    +									{
    +										CLuaManager::getInstance().executeLuaScript( it->luaScript, true );
    +									}
    +								}
    +							}
    +
    +							// release CCurlWWWData
    +							delete it->data;
    +
    +							Curls.erase(it);
    +							break;
    +						}
    +					}
    +				}
    +			}
    +		}
    +
    +		RunningCurls = NewRunningCurls;
    +
    +		if (RunningCurls < options.curlMaxConnections)
    +		{
    +			for (vector::iterator it=Curls.begin(); itdata == NULL) {
    +	#ifdef LOG_DL
    +					nlwarning("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str());
    +	#endif
    +					if (!startCurlDownload(*it))
    +					{
    +						Curls.erase(it);
    +						break;
    +					}
    +
    +					RunningCurls++;
    +					if (RunningCurls >= options.curlMaxConnections)
    +						break;
    +				}
    +			}
    +		}
    +
    +	#ifdef LOG_DL
    +		if (RunningCurls > 0 || !Curls.empty())
    +			nlwarning("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size());
    +	#endif
    +	}
    +
    +
    +	void CGroupHTML::releaseDownloads()
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("Release Downloads");
    +	#endif
    +		if(MultiCurl)
    +			curl_multi_cleanup(MultiCurl);
    +	}
    +
    +	class CGroupListAdaptor : public CInterfaceGroup
    +	{
    +	public:
    +		CGroupListAdaptor(const TCtorParam ¶m)
    +			: 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 void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); }
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::beginBuild ()
    +	{
    +		if (_Browsing)
    +		{
    +			_Connecting = false;
    +
    +			removeContent ();
    +		}
    +		else
    +			nlwarning("_Browsing = FALSE");
    +	}
    +
    +
    +	TStyle CGroupHTML::parseStyle (const string &str_styles)
    +	{
    +		TStyle	styles;
    +		vector elements;
    +		NLMISC::splitString(str_styles, ";", elements);
    +
    +		for(uint i = 0; i < elements.size(); ++i)
    +		{
    +			vector 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])] = trim(fullstyle);
    +			}
    +		}
    +
    +		return styles;
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addText (const char * buf, int len)
    +	{
    +		if (_Browsing)
    +		{
    +			if (_IgnoreText)
    +				return;
    +
    +			// Build a UTF8 string
    +			string inputString(buf, buf+len);
    +
    +			if (_ParsingLua && _TrustedDomain)
    +			{
    +				// we are parsing a lua script
    +				_LuaScript += inputString;
    +				// no more to do
    +				return;
    +			}
    +
    +			// Build a unicode string
    +			ucstring inputUCString;
    +			inputUCString.fromUtf8(inputString);
    +
    +			// Build the final unicode string
    +			ucstring tmp;
    +			tmp.reserve(len);
    +			uint ucLen = (uint)inputUCString.size();
    +			for (uint i=0; i= 'a' && tolower(c) <= 'f');
    +	}
    +
    +	static uint8 convertHexa(char c)
    +	{
    +		return (uint8) (tolower(c) - (isdigit(c) ? '0' : ('a' - 10)));
    +	}
    +
    +	// scan a color component, and return pointer to next position
    +	static const char *scanColorComponent(const char *src, uint8 &intensity)
    +	{
    +		if (!src) return NULL;
    +		if (!isHexa(*src)) return NULL;
    +		uint8 value = convertHexa(*src++) << 4;
    +		if (!isHexa(*src)) return NULL;
    +		value += convertHexa(*src++);
    +		intensity = value;
    +		return src;
    +	}
    +
    +	static float hueToRgb(float m1, float m2, float h)
    +	{
    +		if (h < 0) h += 1.0f;
    +		if (h > 1) h -= 1.0f;
    +		if (h*6 < 1.0f) return m1 + (m2 - m1)*h*6;
    +		if (h*2 < 1.0f) return m2;
    +		if (h*3 < 2.0f) return m1 + (m2 - m1) * (2.0f/3.0f - h)*6;
    +		return m1;
    +	}
    +
    +	static void hslToRgb(float h, float s, float l, CRGBA &result)
    +	{
    +		float m1, m2;
    +		if (l <= 0.5f)
    +			m2 = l * (s + 1.0f);
    +		else
    +			m2 = l + s - l * s;
    +		m1 = l*2 - m2;
    +
    +		result.R = 255 * hueToRgb(m1, m2, h + 1.0f/3.0f);
    +		result.G = 255 * hueToRgb(m1, m2, h);
    +		result.B = 255 * hueToRgb(m1, m2, h - 1.0f/3.0f);
    +		result.A = 255;
    +	}
    +
    +	class CNameToCol
    +	{
    +	public:
    +		const char *Name;
    +		CRGBA Color;
    +		CNameToCol(const char *name, CRGBA color) : Name(name), Color(color) {}
    +	};
    +
    +	static CNameToCol htmlColorNameToRGBA[] =
    +	{
    +		CNameToCol("AliceBlue", CRGBA(0xF0, 0xF8, 0xFF)),
    +		CNameToCol("AntiqueWhite", CRGBA(0xFA, 0xEB, 0xD7)),
    +		CNameToCol("Aqua", CRGBA(0x00, 0xFF, 0xFF)),
    +		CNameToCol("Aquamarine", CRGBA(0x7F, 0xFF, 0xD4)),
    +		CNameToCol("Azure", CRGBA(0xF0, 0xFF, 0xFF)),
    +		CNameToCol("Beige", CRGBA(0xF5, 0xF5, 0xDC)),
    +		CNameToCol("Bisque", CRGBA(0xFF, 0xE4, 0xC4)),
    +		CNameToCol("Black", CRGBA(0x00, 0x00, 0x00)),
    +		CNameToCol("BlanchedAlmond", CRGBA(0xFF, 0xEB, 0xCD)),
    +		CNameToCol("Blue", CRGBA(0x00, 0x00, 0xFF)),
    +		CNameToCol("BlueViolet", CRGBA(0x8A, 0x2B, 0xE2)),
    +		CNameToCol("Brown", CRGBA(0xA5, 0x2A, 0x2A)),
    +		CNameToCol("BurlyWood", CRGBA(0xDE, 0xB8, 0x87)),
    +		CNameToCol("CadetBlue", CRGBA(0x5F, 0x9E, 0xA0)),
    +		CNameToCol("Chartreuse", CRGBA(0x7F, 0xFF, 0x00)),
    +		CNameToCol("Chocolate", CRGBA(0xD2, 0x69, 0x1E)),
    +		CNameToCol("Coral", CRGBA(0xFF, 0x7F, 0x50)),
    +		CNameToCol("CornflowerBlue", CRGBA(0x64, 0x95, 0xED)),
    +		CNameToCol("Cornsilk", CRGBA(0xFF, 0xF8, 0xDC)),
    +		CNameToCol("Crimson", CRGBA(0xDC, 0x14, 0x3C)),
    +		CNameToCol("Cyan", CRGBA(0x00, 0xFF, 0xFF)),
    +		CNameToCol("DarkBlue", CRGBA(0x00, 0x00, 0x8B)),
    +		CNameToCol("DarkCyan", CRGBA(0x00, 0x8B, 0x8B)),
    +		CNameToCol("DarkGoldenRod", CRGBA(0xB8, 0x86, 0x0B)),
    +		CNameToCol("DarkGray", CRGBA(0xA9, 0xA9, 0xA9)),
    +		CNameToCol("DarkGreen", CRGBA(0x00, 0x64, 0x00)),
    +		CNameToCol("DarkKhaki", CRGBA(0xBD, 0xB7, 0x6B)),
    +		CNameToCol("DarkMagenta", CRGBA(0x8B, 0x00, 0x8B)),
    +		CNameToCol("DarkOliveGreen", CRGBA(0x55, 0x6B, 0x2F)),
    +		CNameToCol("Darkorange", CRGBA(0xFF, 0x8C, 0x00)),
    +		CNameToCol("DarkOrchid", CRGBA(0x99, 0x32, 0xCC)),
    +		CNameToCol("DarkRed", CRGBA(0x8B, 0x00, 0x00)),
    +		CNameToCol("DarkSalmon", CRGBA(0xE9, 0x96, 0x7A)),
    +		CNameToCol("DarkSeaGreen", CRGBA(0x8F, 0xBC, 0x8F)),
    +		CNameToCol("DarkSlateBlue", CRGBA(0x48, 0x3D, 0x8B)),
    +		CNameToCol("DarkSlateGray", CRGBA(0x2F, 0x4F, 0x4F)),
    +		CNameToCol("DarkTurquoise", CRGBA(0x00, 0xCE, 0xD1)),
    +		CNameToCol("DarkViolet", CRGBA(0x94, 0x00, 0xD3)),
    +		CNameToCol("DeepPink", CRGBA(0xFF, 0x14, 0x93)),
    +		CNameToCol("DeepSkyBlue", CRGBA(0x00, 0xBF, 0xFF)),
    +		CNameToCol("DimGray", CRGBA(0x69, 0x69, 0x69)),
    +		CNameToCol("DodgerBlue", CRGBA(0x1E, 0x90, 0xFF)),
    +		CNameToCol("Feldspar", CRGBA(0xD1, 0x92, 0x75)),
    +		CNameToCol("FireBrick", CRGBA(0xB2, 0x22, 0x22)),
    +		CNameToCol("FloralWhite", CRGBA(0xFF, 0xFA, 0xF0)),
    +		CNameToCol("ForestGreen", CRGBA(0x22, 0x8B, 0x22)),
    +		CNameToCol("Fuchsia", CRGBA(0xFF, 0x00, 0xFF)),
    +		CNameToCol("Gainsboro", CRGBA(0xDC, 0xDC, 0xDC)),
    +		CNameToCol("GhostWhite", CRGBA(0xF8, 0xF8, 0xFF)),
    +		CNameToCol("Gold", CRGBA(0xFF, 0xD7, 0x00)),
    +		CNameToCol("GoldenRod", CRGBA(0xDA, 0xA5, 0x20)),
    +		CNameToCol("Gray", CRGBA(0x80, 0x80, 0x80)),
    +		CNameToCol("Green", CRGBA(0x00, 0x80, 0x00)),
    +		CNameToCol("GreenYellow", CRGBA(0xAD, 0xFF, 0x2F)),
    +		CNameToCol("HoneyDew", CRGBA(0xF0, 0xFF, 0xF0)),
    +		CNameToCol("HotPink", CRGBA(0xFF, 0x69, 0xB4)),
    +		CNameToCol("IndianRed ", CRGBA(0xCD, 0x5C, 0x5C)),
    +		CNameToCol("Indigo  ", CRGBA(0x4B, 0x00, 0x82)),
    +		CNameToCol("Ivory", CRGBA(0xFF, 0xFF, 0xF0)),
    +		CNameToCol("Khaki", CRGBA(0xF0, 0xE6, 0x8C)),
    +		CNameToCol("Lavender", CRGBA(0xE6, 0xE6, 0xFA)),
    +		CNameToCol("LavenderBlush", CRGBA(0xFF, 0xF0, 0xF5)),
    +		CNameToCol("LawnGreen", CRGBA(0x7C, 0xFC, 0x00)),
    +		CNameToCol("LemonChiffon", CRGBA(0xFF, 0xFA, 0xCD)),
    +		CNameToCol("LightBlue", CRGBA(0xAD, 0xD8, 0xE6)),
    +		CNameToCol("LightCoral", CRGBA(0xF0, 0x80, 0x80)),
    +		CNameToCol("LightCyan", CRGBA(0xE0, 0xFF, 0xFF)),
    +		CNameToCol("LightGoldenRodYellow", CRGBA(0xFA, 0xFA, 0xD2)),
    +		CNameToCol("LightGrey", CRGBA(0xD3, 0xD3, 0xD3)),
    +		CNameToCol("LightGreen", CRGBA(0x90, 0xEE, 0x90)),
    +		CNameToCol("LightPink", CRGBA(0xFF, 0xB6, 0xC1)),
    +		CNameToCol("LightSalmon", CRGBA(0xFF, 0xA0, 0x7A)),
    +		CNameToCol("LightSeaGreen", CRGBA(0x20, 0xB2, 0xAA)),
    +		CNameToCol("LightSkyBlue", CRGBA(0x87, 0xCE, 0xFA)),
    +		CNameToCol("LightSlateBlue", CRGBA(0x84, 0x70, 0xFF)),
    +		CNameToCol("LightSlateGray", CRGBA(0x77, 0x88, 0x99)),
    +		CNameToCol("LightSteelBlue", CRGBA(0xB0, 0xC4, 0xDE)),
    +		CNameToCol("LightYellow", CRGBA(0xFF, 0xFF, 0xE0)),
    +		CNameToCol("Lime", CRGBA(0x00, 0xFF, 0x00)),
    +		CNameToCol("LimeGreen", CRGBA(0x32, 0xCD, 0x32)),
    +		CNameToCol("Linen", CRGBA(0xFA, 0xF0, 0xE6)),
    +		CNameToCol("Magenta", CRGBA(0xFF, 0x00, 0xFF)),
    +		CNameToCol("Maroon", CRGBA(0x80, 0x00, 0x00)),
    +		CNameToCol("MediumAquaMarine", CRGBA(0x66, 0xCD, 0xAA)),
    +		CNameToCol("MediumBlue", CRGBA(0x00, 0x00, 0xCD)),
    +		CNameToCol("MediumOrchid", CRGBA(0xBA, 0x55, 0xD3)),
    +		CNameToCol("MediumPurple", CRGBA(0x93, 0x70, 0xD8)),
    +		CNameToCol("MediumSeaGreen", CRGBA(0x3C, 0xB3, 0x71)),
    +		CNameToCol("MediumSlateBlue", CRGBA(0x7B, 0x68, 0xEE)),
    +		CNameToCol("MediumSpringGreen", CRGBA(0x00, 0xFA, 0x9A)),
    +		CNameToCol("MediumTurquoise", CRGBA(0x48, 0xD1, 0xCC)),
    +		CNameToCol("MediumVioletRed", CRGBA(0xC7, 0x15, 0x85)),
    +		CNameToCol("MidnightBlue", CRGBA(0x19, 0x19, 0x70)),
    +		CNameToCol("MintCream", CRGBA(0xF5, 0xFF, 0xFA)),
    +		CNameToCol("MistyRose", CRGBA(0xFF, 0xE4, 0xE1)),
    +		CNameToCol("Moccasin", CRGBA(0xFF, 0xE4, 0xB5)),
    +		CNameToCol("NavajoWhite", CRGBA(0xFF, 0xDE, 0xAD)),
    +		CNameToCol("Navy", CRGBA(0x00, 0x00, 0x80)),
    +		CNameToCol("OldLace", CRGBA(0xFD, 0xF5, 0xE6)),
    +		CNameToCol("Olive", CRGBA(0x80, 0x80, 0x00)),
    +		CNameToCol("OliveDrab", CRGBA(0x6B, 0x8E, 0x23)),
    +		CNameToCol("Orange", CRGBA(0xFF, 0xA5, 0x00)),
    +		CNameToCol("OrangeRed", CRGBA(0xFF, 0x45, 0x00)),
    +		CNameToCol("Orchid", CRGBA(0xDA, 0x70, 0xD6)),
    +		CNameToCol("PaleGoldenRod", CRGBA(0xEE, 0xE8, 0xAA)),
    +		CNameToCol("PaleGreen", CRGBA(0x98, 0xFB, 0x98)),
    +		CNameToCol("PaleTurquoise", CRGBA(0xAF, 0xEE, 0xEE)),
    +		CNameToCol("PaleVioletRed", CRGBA(0xD8, 0x70, 0x93)),
    +		CNameToCol("PapayaWhip", CRGBA(0xFF, 0xEF, 0xD5)),
    +		CNameToCol("PeachPuff", CRGBA(0xFF, 0xDA, 0xB9)),
    +		CNameToCol("Peru", CRGBA(0xCD, 0x85, 0x3F)),
    +		CNameToCol("Pink", CRGBA(0xFF, 0xC0, 0xCB)),
    +		CNameToCol("Plum", CRGBA(0xDD, 0xA0, 0xDD)),
    +		CNameToCol("PowderBlue", CRGBA(0xB0, 0xE0, 0xE6)),
    +		CNameToCol("Purple", CRGBA(0x80, 0x00, 0x80)),
    +		CNameToCol("Red", CRGBA(0xFF, 0x00, 0x00)),
    +		CNameToCol("RosyBrown", CRGBA(0xBC, 0x8F, 0x8F)),
    +		CNameToCol("RoyalBlue", CRGBA(0x41, 0x69, 0xE1)),
    +		CNameToCol("SaddleBrown", CRGBA(0x8B, 0x45, 0x13)),
    +		CNameToCol("Salmon", CRGBA(0xFA, 0x80, 0x72)),
    +		CNameToCol("SandyBrown", CRGBA(0xF4, 0xA4, 0x60)),
    +		CNameToCol("SeaGreen", CRGBA(0x2E, 0x8B, 0x57)),
    +		CNameToCol("SeaShell", CRGBA(0xFF, 0xF5, 0xEE)),
    +		CNameToCol("Sienna", CRGBA(0xA0, 0x52, 0x2D)),
    +		CNameToCol("Silver", CRGBA(0xC0, 0xC0, 0xC0)),
    +		CNameToCol("SkyBlue", CRGBA(0x87, 0xCE, 0xEB)),
    +		CNameToCol("SlateBlue", CRGBA(0x6A, 0x5A, 0xCD)),
    +		CNameToCol("SlateGray", CRGBA(0x70, 0x80, 0x90)),
    +		CNameToCol("Snow", CRGBA(0xFF, 0xFA, 0xFA)),
    +		CNameToCol("SpringGreen", CRGBA(0x00, 0xFF, 0x7F)),
    +		CNameToCol("SteelBlue", CRGBA(0x46, 0x82, 0xB4)),
    +		CNameToCol("Tan", CRGBA(0xD2, 0xB4, 0x8C)),
    +		CNameToCol("Teal", CRGBA(0x00, 0x80, 0x80)),
    +		CNameToCol("Thistle", CRGBA(0xD8, 0xBF, 0xD8)),
    +		CNameToCol("Tomato", CRGBA(0xFF, 0x63, 0x47)),
    +		CNameToCol("Turquoise", CRGBA(0x40, 0xE0, 0xD0)),
    +		CNameToCol("Violet", CRGBA(0xEE, 0x82, 0xEE)),
    +		CNameToCol("VioletRed", CRGBA(0xD0, 0x20, 0x90)),
    +		CNameToCol("Wheat", CRGBA(0xF5, 0xDE, 0xB3)),
    +		CNameToCol("White", CRGBA(0xFF, 0xFF, 0xFF)),
    +		CNameToCol("WhiteSmoke", CRGBA(0xF5, 0xF5, 0xF5)),
    +		CNameToCol("Yellow", CRGBA(0xFF, 0xFF, 0x00)),
    +		CNameToCol("YellowGreen", CRGBA(0x9A, 0xCD, 0x32))
    +	};
    +
    +	// scan a color from a HTML form (#rrggbb format)
    +	bool scanHTMLColor(const char *src, CRGBA &dest)
    +	{
    +		if (!src || *src == '\0') return false;
    +		if (*src == '#')
    +		{
    +			++src;
    +			if (strlen(src) == 3 || strlen(src) == 4)
    +			{
    +				bool hasAlpha = (strlen(src) == 4);
    +				// check RGB for valid hex
    +				if (isHexa(src[0]) && isHexa(src[1]) && isHexa(src[2]))
    +				{
    +					// check optional A for valid hex
    +					if (hasAlpha && !isHexa(src[3])) return false;
    +
    +					dest.R = convertHexa(src[0]);
    +					dest.G = convertHexa(src[1]);
    +					dest.B = convertHexa(src[2]);
    +
    +					dest.R = dest.R << 4 | dest.R;
    +					dest.G = dest.G << 4 | dest.G;
    +					dest.B = dest.B << 4 | dest.B;
    +
    +					if (hasAlpha)
    +					{
    +						dest.A = convertHexa(src[3]);
    +						dest.A = dest.A << 4 | dest.A;
    +					}
    +					else
    +						dest.A = 255;
    +
    +					return true;
    +				}
    +
    +				return false;
    +			}
    +
    +			CRGBA result;
    +			src = scanColorComponent(src, result.R); if (!src) return false;
    +			src = scanColorComponent(src, result.G); if (!src) return false;
    +			src = scanColorComponent(src, result.B); if (!src) return false;
    +			src = scanColorComponent(src, result.A);
    +			if (!src)
    +			{
    +				// Alpha is optional
    +				result.A = 255;
    +			}
    +			dest = result;
    +			return true;
    +		}
    +
    +		if (strnicmp(src, "rgb(", 4) == 0 || strnicmp(src, "rgba(", 5) == 0)
    +		{
    +			src += 4;
    +			if (*src == '(') src++;
    +
    +			vector parts;
    +			NLMISC::splitString(src, ",", parts);
    +			if (parts.size() >= 3)
    +			{
    +				CRGBA result;
    +				sint tmpv;
    +				float tmpf;
    +
    +				// R
    +				if (getPercentage(tmpv, tmpf, parts[0].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.R = tmpv;
    +
    +				// G
    +				if (getPercentage(tmpv, tmpf, parts[1].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.G = tmpv;
    +
    +				// B
    +				if (getPercentage(tmpv, tmpf, parts[2].c_str())) tmpv = 255 * tmpf;
    +				clamp(tmpv, 0, 255);
    +				result.B = tmpv;
    +
    +				// A
    +				if (parts.size() == 4)
    +				{
    +					if (!fromString(parts[3], tmpf)) return false;
    +					if (parts[3].find_first_of("%") != std::string::npos)
    +						tmpf /= 100;
    +
    +					tmpv = 255 * tmpf;
    +					clamp(tmpv, 0, 255);
    +					result.A = tmpv;
    +				}
    +				else
    +					result.A = 255;
    +
    +				dest = result;
    +				return true;
    +			}
    +
    +			return false;
    +		}
    +
    +		if (strnicmp(src, "hsl(", 4) == 0 || strnicmp(src, "hsla(", 5) == 0)
    +		{
    +			src += 4;
    +			if (*src == '(') src++;
    +
    +			vector parts;
    +			NLMISC::splitString(src, ",", parts);
    +			if (parts.size() >= 3)
    +			{
    +				sint tmpv;
    +				float h, s, l;
    +				// hue
    +				if (!fromString(parts[0], tmpv)) return false;
    +				tmpv = ((tmpv % 360) + 360) % 360;
    +				h = (float) tmpv / 360.0f;
    +
    +				// saturation
    +				if (!getPercentage(tmpv, s, parts[1].c_str())) return false;
    +				clamp(s, 0.0f, 1.0f);
    +
    +				// lightness
    +				if (!getPercentage(tmpv, l, parts[2].c_str())) return false;
    +				clamp(l, 0.0f, 1.0f);
    +
    +				CRGBA result;
    +				hslToRgb(h, s, l, result);
    +
    +				// A
    +				if (parts.size() == 4)
    +				{
    +					float tmpf;
    +					if (!fromString(parts[3], tmpf)) return false;
    +					if (parts[3].find_first_of("%") != std::string::npos)
    +						tmpf /= 100;
    +					clamp(tmpf, 0.0f, 1.0f);
    +					result.A = 255 * tmpf;
    +				}
    +
    +				dest = result;
    +				return true;
    +			}
    +
    +			return false;
    +		}
    +
    +		{
    +			// slow but should suffice for now
    +			for(uint k = 0; k < sizeofarray(htmlColorNameToRGBA); ++k)
    +			{
    +				if (nlstricmp(src, htmlColorNameToRGBA[k].Name) == 0)
    +				{
    +					dest = htmlColorNameToRGBA[k].Color;
    +					return true;
    +				}
    +			}
    +			return false;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::beginElement (uint element_number, const std::vector &present, const std::vector &value)
    +	{
    +		if (_Browsing)
    +		{
    +			// Paragraph ?
    +			switch(element_number)
    +			{
    +			case HTML_HEAD:
    +				_ReadingHeadTag = !_IgnoreHeadTag;
    +				_IgnoreHeadTag = true;
    +				break;
    +			case HTML_BASE:
    +				if (_ReadingHeadTag && !_IgnoreBaseUrlTag)
    +				{
    +					if (present[HTML_BASE_HREF] && value[HTML_BASE_HREF])
    +					{
    +						CUrlParser uri(value[HTML_BASE_HREF]);
    +						if (uri.isAbsolute())
    +						{
    +							_URL = uri.toString();
    +							_IgnoreBaseUrlTag = true;
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_META:
    +				if (_ReadingHeadTag)
    +				{
    +					bool httpEquiv = present[HTML_META_HTTP_EQUIV] && value[HTML_META_HTTP_EQUIV];
    +					bool httpContent = present[HTML_META_CONTENT] && value[HTML_META_CONTENT];
    +					if (httpEquiv && httpContent)
    +					{
    +						// only first http-equiv="refresh" should be handled
    +						if (_RefreshUrl.empty() && toLower(value[HTML_META_HTTP_EQUIV]) == "refresh")
    +						{
    +							const CWidgetManager::SInterfaceTimes × = CWidgetManager::getInstance()->getInterfaceTimes();
    +							double timeSec = times.thisFrameMs / 1000.0f;
    +							string content(value[HTML_META_CONTENT]);
    +
    +							string::size_type pos = content.find_first_of(";");
    +							if (pos == string::npos)
    +							{
    +								fromString(content, _NextRefreshTime);
    +								_RefreshUrl = _URL;
    +							}
    +							else
    +							{
    +								fromString(content.substr(0, pos), _NextRefreshTime);
    +
    +								pos = toLower(content).find("url=");
    +								if (pos != string::npos)
    +									_RefreshUrl = getAbsoluteUrl(content.substr(pos + 4));
    +							}
    +
    +							_NextRefreshTime += timeSec;
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_A:
    +			{
    +				registerAnchorName(MY_HTML_A);
    +
    +				CStyleParams style;
    +				style.FontFamily = getFontFamily();
    +				style.FontSize = getFontSize();
    +				style.TextColor = LinkColor;
    +				style.Underlined = true;
    +				style.StrikeThrough = getFontStrikeThrough();
    +				style.GlobalColor = LinkColorGlobalColor;
    +
    +				if (present[HTML_A_STYLE] && value[HTML_A_STYLE])
    +					getStyleParams(value[HTML_A_STYLE], style);
    +
    +				_FontFamily.push_back(style.FontFamily);
    +				_FontSize.push_back(style.FontSize);
    +				_TextColor.push_back(style.TextColor);
    +				_FontUnderlined.push_back(style.Underlined);
    +				_FontStrikeThrough.push_back(style.StrikeThrough);
    +				_GlobalColor.push_back(style.GlobalColor);
    +				_A.push_back(true);
    +				_Link.push_back ("");
    +				_LinkTitle.push_back("");
    +				_LinkClass.push_back("");
    +
    +				// #fragment works with both ID and NAME so register both
    +				if (present[MY_HTML_A_NAME] && value[MY_HTML_A_NAME])
    +					_AnchorName.push_back(value[MY_HTML_A_NAME]);
    +				if (present[MY_HTML_A_TITLE] && value[MY_HTML_A_TITLE])
    +					_LinkTitle.back() = value[MY_HTML_A_TITLE];
    +				if (present[MY_HTML_A_CLASS] && value[MY_HTML_A_CLASS])
    +					_LinkClass.back() = value[MY_HTML_A_CLASS];
    +				if (present[MY_HTML_A_HREF] && value[MY_HTML_A_HREF])
    +				{
    +					string suri = value[MY_HTML_A_HREF];
    +					if(suri.find("ah:") == 0)
    +					{
    +						if (_TrustedDomain)
    +							_Link.back() = suri;
    +					}
    +					else if (_TrustedDomain && suri[0] == '#' && _LuaHrefHack)
    +					{
    +						// Direct url (hack for lua beginElement)
    +						_Link.back() = suri.substr(1);
    +					}
    +					else
    +					{
    +						// convert href from "?key=val" into "http://domain.com/?key=val"
    +						_Link.back() = getAbsoluteUrl(suri);
    +					}
    +				}
    +			}
    +				break;
    +			case HTML_DIV:
    +			{
    +				_BlockLevelElement.push_back(true);
    +				registerAnchorName(MY_HTML_DIV);
    +
    +				if (present[MY_HTML_DIV_NAME] && value[MY_HTML_DIV_NAME])
    +					_DivName = value[MY_HTML_DIV_NAME];
    +
    +				string instClass;
    +				if (present[MY_HTML_DIV_CLASS] && value[MY_HTML_DIV_CLASS])
    +					instClass = value[MY_HTML_DIV_CLASS];
    +
    +				// use generic template system
    +				if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
    +				{
    +					string id;
    +					if (present[MY_HTML_DIV_ID] && value[MY_HTML_DIV_ID])
    +						id = value[MY_HTML_DIV_ID];
    +
    +					string style;
    +					if (present[MY_HTML_DIV_STYLE] && value[MY_HTML_DIV_STYLE])
    +						style = value[MY_HTML_DIV_STYLE];
    +
    +					typedef pair TTmplParam;
    +					vector 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+":"+id, 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);
    +
    +									_BlockLevelElement.back() = false;
    +							}
    +							else
    +							{
    +								getParagraph()->addChild(inst);
    +								paragraphChange();
    +							}
    +							_Divs.push_back(inst);
    +						}
    +					}
    +				}
    +
    +				if (isBlockLevelElement())
    +				{
    +					newParagraph(0);
    +				}
    +			}
    +				break;
    +			case HTML_FONT:
    +			{
    +				bool found = false;
    +				if (present[HTML_FONT_COLOR] && value[HTML_FONT_COLOR])
    +				{
    +					CRGBA color;
    +					if (scanHTMLColor(value[HTML_FONT_COLOR], color))
    +					{
    +						_TextColor.push_back(color);
    +						found = true;
    +					}
    +				}
    +				if (!found)
    +				{
    +					_TextColor.push_back(_TextColor.empty() ? CRGBA::White : _TextColor.back());
    +				}
    +
    +				if (present[HTML_FONT_SIZE] && value[HTML_FONT_SIZE])
    +				{
    +					uint fontsize;
    +					fromString(value[HTML_FONT_SIZE], fontsize);
    +					_FontSize.push_back(fontsize);
    +				}
    +				else
    +				{
    +					_FontSize.push_back(_FontSize.empty() ? TextFontSize : _FontSize.back());
    +				}
    +			}
    +				break;
    +			case HTML_BR:
    +			{
    +				endParagraph();
    +
    +				// insert zero-width-space (0x200B) to prevent removal of empty lines
    +				ucstring tmp;
    +				tmp.fromUtf8("\xe2\x80\x8b");
    +				addString(tmp);
    +			}
    +				break;
    +			case HTML_BODY:
    +				{
    +					if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR])
    +					{
    +						CRGBA bgColor;
    +						if (scanHTMLColor(value[HTML_BODY_BGCOLOR], bgColor))
    +							setBackgroundColor (bgColor);
    +					}
    +					
    +					string style;
    +					if (present[HTML_BODY_STYLE] && value[HTML_BODY_STYLE])
    +						style = value[HTML_BODY_STYLE];
    +					
    +					
    +					if (!style.empty())
    +					{
    +						TStyle styles = parseStyle(style);
    +						TStyle::iterator	it;
    +
    +						it = styles.find("background-repeat");
    +						bool repeat = (it != styles.end() && it->second == "1");
    +						
    +						// Webig only
    +						it = styles.find("background-scale");
    +						bool scale = (it != styles.end() && it->second == "1");
    +
    +						it = styles.find("background-image");
    +						if (it != styles.end())
    +						{
    +							string image = it->second;
    +							string::size_type texExt = toLower(image).find("url(");
    +							// Url image
    +							if (texExt != string::npos)
    +								// Remove url()
    +								image = image.substr(4, image.size()-5);
    +							setBackground (image, scale, repeat);
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_FORM:
    +				{
    +					// Build the form
    +					CGroupHTML::CForm form;
    +
    +					// Get the action name
    +					if (present[HTML_FORM_ACTION] && value[HTML_FORM_ACTION])
    +					{
    +						form.Action = getAbsoluteUrl(string(value[HTML_FORM_ACTION]));
    +					}
    +					else
    +					{
    +						form.Action = _URL;
    +					}
    +					_Forms.push_back(form);
    +				}
    +				break;
    +			case HTML_H1:
    +				registerAnchorName(MY_HTML_H1);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H1FontSize);
    +				_TextColor.push_back(H1Color);
    +				_GlobalColor.push_back(H1ColorGlobalColor);
    +				break;
    +			case HTML_H2:
    +				registerAnchorName(MY_HTML_H2);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H2FontSize);
    +				_TextColor.push_back(H2Color);
    +				_GlobalColor.push_back(H2ColorGlobalColor);
    +				break;
    +			case HTML_H3:
    +				registerAnchorName(MY_HTML_H3);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H3FontSize);
    +				_TextColor.push_back(H3Color);
    +				_GlobalColor.push_back(H3ColorGlobalColor);
    +				break;
    +			case HTML_H4:
    +				registerAnchorName(MY_HTML_H4);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H4FontSize);
    +				_TextColor.push_back(H4Color);
    +				_GlobalColor.push_back(H4ColorGlobalColor);
    +				break;
    +			case HTML_H5:
    +				registerAnchorName(MY_HTML_H5);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H5FontSize);
    +				_TextColor.push_back(H5Color);
    +				_GlobalColor.push_back(H5ColorGlobalColor);
    +				break;
    +			case HTML_H6:
    +				registerAnchorName(MY_HTML_H6);
    +				newParagraph(PBeginSpace);
    +				_FontSize.push_back(H6FontSize);
    +				_TextColor.push_back(H6Color);
    +				_GlobalColor.push_back(H6ColorGlobalColor);
    +				break;
    +			case HTML_IMG:
    +				{
    +					// Get the string name
    +					if (present[MY_HTML_IMG_SRC] && value[MY_HTML_IMG_SRC])
    +					{
    +						CStyleParams style;
    +						float tmpf;
    +						
    +						if (present[MY_HTML_IMG_WIDTH] && value[MY_HTML_IMG_WIDTH])
    +							getPercentage(style.Width, tmpf, value[MY_HTML_IMG_WIDTH]);
    +						if (present[MY_HTML_IMG_HEIGHT] && value[MY_HTML_IMG_HEIGHT])
    +							getPercentage(style.Height, tmpf, value[MY_HTML_IMG_HEIGHT]);
    +
    +						// Get the global color name
    +						if (present[MY_HTML_IMG_GLOBAL_COLOR])
    +							style.GlobalColor = true;
    +
    +						// width, height from inline css
    +						if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE])
    +							getStyleParams(value[MY_HTML_IMG_STYLE], style);
    +
    +						// Tooltip
    +						const char *tooltip = NULL;
    +						// keep "alt" attribute for backward compatibility
    +						if (present[MY_HTML_IMG_ALT] && value[MY_HTML_IMG_ALT])
    +							tooltip = value[MY_HTML_IMG_ALT];
    +						// tooltip
    +						if (present[MY_HTML_IMG_TITLE] && value[MY_HTML_IMG_TITLE])
    +							tooltip = value[MY_HTML_IMG_TITLE];
    +
    +						// Mouse over image
    +						string overSrc;
    +						if (present[MY_HTML_IMG_DATA_OVER_SRC] && value[MY_HTML_IMG_DATA_OVER_SRC])
    +						{
    +							overSrc = value[MY_HTML_IMG_DATA_OVER_SRC];
    +						}
    +
    +
    +						if (getA() && getParent () && getParent ()->getParent())
    +						{
    +							string params = "name=" + getId() + "|url=" + getLink ();
    +							addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC],
    +								overSrc, "browse", params.c_str(), tooltip, style);
    +						}
    +						else
    +						if (tooltip || !overSrc.empty())
    +						{
    +							addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC],
    +								overSrc, "", "", tooltip, style);
    +						}
    +						else
    +						{
    +							// Get the option to reload (class==reload)
    +							bool reloadImg = false;
    +
    +							string styleString;
    +							if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE])
    +								styleString = value[MY_HTML_IMG_STYLE];
    +
    +							if (!styleString.empty())
    +							{
    +								TStyle styles = parseStyle(styleString);
    +								TStyle::iterator	it;
    +
    +								it = styles.find("reload");
    +								if (it != styles.end() && (*it).second == "1")
    +									reloadImg = true;
    +							}
    +
    +							addImage (value[MY_HTML_IMG_SRC], reloadImg, style);
    +						}
    +					}
    +				}
    +				break;
    +			case HTML_INPUT:
    +				// Got one form ?
    +				if (!(_Forms.empty()))
    +				{
    +					// read general property
    +					string templateName;
    +					string minWidth;
    +
    +					// Widget template name
    +					if (present[MY_HTML_INPUT_Z_BTN_TMPL] && value[MY_HTML_INPUT_Z_BTN_TMPL])
    +						templateName = value[MY_HTML_INPUT_Z_BTN_TMPL];
    +					// Input name is the new
    +					if (present[MY_HTML_INPUT_Z_INPUT_TMPL] && value[MY_HTML_INPUT_Z_INPUT_TMPL])
    +						templateName = value[MY_HTML_INPUT_Z_INPUT_TMPL];
    +					// Widget minimal width
    +					if (present[MY_HTML_INPUT_Z_INPUT_WIDTH] && value[MY_HTML_INPUT_Z_INPUT_WIDTH])
    +						minWidth = value[MY_HTML_INPUT_Z_INPUT_WIDTH];
    +
    +					// Get the type
    +					if (present[MY_HTML_INPUT_TYPE] && value[MY_HTML_INPUT_TYPE])
    +					{
    +						// by default not inherited, font family defaults to system font
    +						CStyleParams style;
    +						style.TextColor = TextColor;
    +						style.FontSize = TextFontSize;
    +						style.FontWeight = FONT_WEIGHT_NORMAL;
    +						style.FontOblique = false;
    +
    +						// Global color flag
    +						if (present[MY_HTML_INPUT_GLOBAL_COLOR])
    +							style.GlobalColor = true;
    +
    +						// Tooltip
    +						const char *tooltip = NULL;
    +						if (present[MY_HTML_INPUT_ALT] && value[MY_HTML_INPUT_ALT])
    +							tooltip = value[MY_HTML_INPUT_ALT];
    +
    +						if (present[MY_HTML_INPUT_STYLE] && value[MY_HTML_INPUT_STYLE])
    +							getStyleParams(value[MY_HTML_INPUT_STYLE], style);
    +
    +						_TextColor.push_back(style.TextColor);
    +						_FontFamily.push_back(style.FontFamily);
    +						_FontSize.push_back(style.FontSize);
    +						_FontWeight.push_back(style.FontWeight);
    +						_FontOblique.push_back(style.FontOblique);
    +
    +						string type = toLower(value[MY_HTML_INPUT_TYPE]);
    +						if (type == "image")
    +						{
    +							// The submit button
    +							string name;
    +							string normal;
    +							string pushed;
    +							string over;
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +
    +							// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +							string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=image";
    +
    +							// Add the ctrl button
    +							addButton (CCtrlButton::PushButton, name, normal, pushed.empty()?normal:pushed, over,
    +								"html_submit_form", param.c_str(), tooltip, style);
    +						}
    +						if (type == "button" || type == "submit")
    +						{
    +							// The submit button
    +							string name;
    +							string text;
    +							string normal;
    +							string pushed;
    +							string over;
    +
    +							string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup );
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								text = value[MY_HTML_INPUT_VALUE];
    +
    +							// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +							string param = "name=" + getId() + "|form=" + toString (_Forms.size()-1) + "|submit_button=" + name + "|submit_button_type=submit";
    +							if (!text.empty())
    +							{
    +								// escape AH param separator
    +								string tmp = text;
    +								while(NLMISC::strFindReplace(tmp, "|", "|"))
    +									;
    +								param = param + "|submit_button_value=" + tmp;
    +							}
    +
    +							// Add the ctrl button
    +							if (!_Paragraph)
    +							{
    +								newParagraph (0);
    +								paragraphChange ();
    +							}
    +
    +							typedef pair TTmplParam;
    +							vector 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("text", text));
    +							tmplParams.push_back(TTmplParam("active", "true"));
    +							if (!minWidth.empty())
    +								tmplParams.push_back(TTmplParam("wmin", minWidth));
    +							CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
    +							if (buttonGroup)
    +							{
    +
    +								// Add the ctrl button
    +								CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button"));
    +								if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b"));
    +								if (ctrlButton)
    +								{
    +									ctrlButton->setModulateGlobalColorAll (style.GlobalColor);
    +
    +									// Translate the tooltip
    +									if (tooltip)
    +									{
    +										if (CI18N::hasTranslation(tooltip))
    +										{
    +											ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
    +										}
    +										else
    +										{
    +											ctrlButton->setDefaultContextHelp(ucstring(tooltip));
    +										}
    +									}
    +
    +									ctrlButton->setText(ucstring::makeFromUtf8(text));
    +								}
    +								getParagraph()->addChild (buttonGroup);
    +								paragraphChange ();
    +							}
    +						}
    +						else if (type == "text")
    +						{
    +							// Get the string name
    +							string name;
    +							ucstring ucValue;
    +							uint size = 120;
    +							uint maxlength = 1024;
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SIZE] && value[MY_HTML_INPUT_SIZE])
    +								fromString(value[MY_HTML_INPUT_SIZE], size);
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +							if (present[MY_HTML_INPUT_MAXLENGTH] && value[MY_HTML_INPUT_MAXLENGTH])
    +								fromString(value[MY_HTML_INPUT_MAXLENGTH], maxlength);
    +
    +							string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
    +							// Add the editbox
    +							CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size/12, 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")
    +						{
    +							CCtrlButton::EType btnType;
    +							string name;
    +							string normal;
    +							string pushed;
    +							string over;
    +							ucstring ucValue = ucstring("on");
    +							bool checked = false;
    +
    +							if (type == "radio")
    +							{
    +								btnType = CCtrlButton::RadioButton;
    +								normal = DefaultRadioButtonBitmapNormal;
    +								pushed = DefaultRadioButtonBitmapPushed;
    +								over = DefaultRadioButtonBitmapOver;
    +							}
    +							else
    +							{
    +								btnType = CCtrlButton::ToggleButton;
    +								normal = DefaultCheckBoxBitmapNormal;
    +								pushed = DefaultCheckBoxBitmapPushed;
    +								over = DefaultCheckBoxBitmapOver;
    +							}
    +
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +								name = value[MY_HTML_INPUT_NAME];
    +							if (present[MY_HTML_INPUT_SRC] && value[MY_HTML_INPUT_SRC])
    +								normal = value[MY_HTML_INPUT_SRC];
    +							if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +								ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +							checked = (present[MY_HTML_INPUT_CHECKED] && value[MY_HTML_INPUT_CHECKED]);
    +
    +							// Add the ctrl button
    +							CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, style);
    +							if (checkbox)
    +							{
    +								if (btnType == CCtrlButton::RadioButton)
    +								{
    +									// group together buttons with same name
    +									CForm &form = _Forms.back();
    +									bool notfound = true;
    +									for (uint i=0; igetType() == 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);
    +							}
    +						}
    +						else if (type == "hidden")
    +						{
    +							if (present[MY_HTML_INPUT_NAME] && value[MY_HTML_INPUT_NAME])
    +							{
    +								// Get the name
    +								string name = value[MY_HTML_INPUT_NAME];
    +
    +								// Get the value
    +								ucstring ucValue;
    +								if (present[MY_HTML_INPUT_VALUE] && value[MY_HTML_INPUT_VALUE])
    +									ucValue.fromUtf8(value[MY_HTML_INPUT_VALUE]);
    +
    +								// Add an entry
    +								CGroupHTML::CForm::CEntry entry;
    +								entry.Name = name;
    +								entry.Value = decodeHTMLEntities(ucValue);
    +								_Forms.back().Entries.push_back (entry);
    +							}
    +						}
    +
    +						popIfNotEmpty(_FontFamily);
    +						popIfNotEmpty(_FontSize);
    +						popIfNotEmpty(_TextColor);
    +						popIfNotEmpty(_FontWeight);
    +						popIfNotEmpty(_FontOblique);
    +					}
    +				}
    +				break;
    +			case HTML_SELECT:
    +				if (!(_Forms.empty()))
    +				{
    +					CStyleParams style;
    +
    +					// A select box
    +					string name;
    +					bool multiple = false;
    +					sint32 size = 0;
    +
    +					if (present[HTML_SELECT_NAME] && value[HTML_SELECT_NAME])
    +						name = value[HTML_SELECT_NAME];
    +					if (present[HTML_SELECT_SIZE] && value[HTML_SELECT_SIZE])
    +						fromString(value[HTML_SELECT_SIZE], size);
    +					if (present[HTML_SELECT_MULTIPLE] && value[HTML_SELECT_MULTIPLE])
    +						multiple = true;
    +					if (present[HTML_SELECT_STYLE] && value[HTML_SELECT_STYLE])
    +						getStyleParams(value[HTML_SELECT_STYLE], style);
    +
    +					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.Width > -1)
    +								sb->setMinW(style.Width);
    +
    +							if (style.Height > -1)
    +								sb->setMinH(style.Height);
    +
    +							sb->setMaxVisibleLine(size);
    +						}
    +
    +						entry.SelectBox = sb;
    +					}
    +					else
    +					{
    +						CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
    +						entry.ComboBox = cb;
    +					}
    +					_Forms.back().Entries.push_back (entry);
    +				}
    +			break;
    +			case HTML_OPTION:
    +				// Got one form ?
    +				if (!(_Forms.empty()))
    +				{
    +					if (!_Forms.back().Entries.empty())
    +					{
    +						// clear the option string
    +						_SelectOptionStr.clear();
    +
    +						std::string optionValue;
    +						if (present[HTML_OPTION_VALUE] && value[HTML_OPTION_VALUE])
    +							optionValue = value[HTML_OPTION_VALUE];
    +						_Forms.back().Entries.back().SelectValues.push_back(optionValue);
    +
    +						if (present[HTML_OPTION_SELECTED])
    +							_Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
    +						if (present[HTML_OPTION_DISABLED])
    +							_Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
    +					}
    +				}
    +				_SelectOption = true;
    +			break;
    +			case HTML_LI:
    +				if (!_UL.empty())
    +				{
    +					// 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 
  • attribute + if (present[HTML_LI_VALUE] && value[HTML_LI_VALUE]) + fromString(value[HTML_LI_VALUE], _UL.back().Value); + + ucstring str; + str.fromUtf8(_UL.back().getListMarkerText()); + addString (str); + + sint32 indent = LIIndent; + // list-style-type: outside + if (_CurrentViewLink) + { + getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW()); + } + + flushString (); + + _UL.back().Value++; + } + break; + case HTML_P: + newParagraph(PBeginSpace); + break; + case HTML_PRE: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = "monospace"; + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); + + if (present[HTML_PRE_STYLE] && value[HTML_PRE_STYLE]) + getStyleParams(value[HTML_PRE_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); + + _PRE.push_back(true); + } + break; + case HTML_TABLE: + { + registerAnchorName(MY_HTML_TABLE); + + // Get cells parameters + getCellsParameters (MY_HTML_TABLE, false); + + CGroupTable *table = new CGroupTable(TCtorParam()); + table->BgColor = _CellParams.back().BgColor; + + if (present[MY_HTML_TABLE_WIDTH] && value[MY_HTML_TABLE_WIDTH]) + getPercentage (table->ForceWidthMin, table->TableRatio, value[MY_HTML_TABLE_WIDTH]); + if (present[MY_HTML_TABLE_BORDER] && value[MY_HTML_TABLE_BORDER]) + fromString(value[MY_HTML_TABLE_BORDER], table->Border); + if (present[MY_HTML_TABLE_BORDERCOLOR] && value[MY_HTML_TABLE_BORDERCOLOR]) + scanHTMLColor(value[MY_HTML_TABLE_BORDERCOLOR], table->BorderColor); + if (present[MY_HTML_TABLE_CELLSPACING] && value[MY_HTML_TABLE_CELLSPACING]) + fromString(value[MY_HTML_TABLE_CELLSPACING], table->CellSpacing); + if (present[MY_HTML_TABLE_CELLPADDING] && value[MY_HTML_TABLE_CELLPADDING]) + fromString(value[MY_HTML_TABLE_CELLPADDING], table->CellPadding); + + table->setMarginLeft(getIndent()); + addHtmlGroup (table, 0); + + _Tables.push_back(table); + + // Add a cell pointer + _Cells.push_back(NULL); + _TR.push_back(false); + _Indent.push_back(0); + } + break; + case HTML_TH: + // TH is similar to TD, just different font style + case HTML_TD: + { + // Get cells parameters + getCellsParameters (MY_HTML_TD, true); + + if (element_number == HTML_TH) + { + _FontWeight.push_back(FONT_WEIGHT_BOLD); + // center if not specified otherwise. TD/TH present/value arrays have same indices + if (!(present[MY_HTML_TD_ALIGN] && value[MY_HTML_TD_ALIGN])) + _CellParams.back().Align = CGroupCell::Center; + } + + CGroupTable *table = getTable(); + if (table) + { + if (!_Cells.empty()) + { + _Cells.back() = new CGroupCell(CViewBase::TCtorParam()); + string style; + if (present[MY_HTML_TD_STYLE] && value[MY_HTML_TD_STYLE]) + style = value[MY_HTML_TD_STYLE]; + + // Set the cell parameters + if (!style.empty()) + { + TStyle styles = parseStyle(style); + TStyle::iterator it; + + it = styles.find("background-repeat"); + _Cells.back()->setTextureTile(it != styles.end()); + + // Webig only + it = styles.find("background-scale"); + _Cells.back()->setTextureScale(it != styles.end()); + + it = styles.find("background-image"); + if (it != styles.end()) + { + string image = (*it).second; + string::size_type texExt = toLower(image).find("url("); + // Url image + if (texExt != string::npos) + { + // Remove url() + image = image.substr(4, image.size()-5); + addImageDownload(image, _Cells.back()); + // Image in BNP + } + else + { + _Cells.back()->setTexture(image); + } + } + } + + if (present[MY_HTML_TD_COLSPAN] && value[MY_HTML_TD_COLSPAN]) + fromString(value[MY_HTML_TD_COLSPAN], _Cells.back()->ColSpan); + if (present[MY_HTML_TD_ROWSPAN] && value[MY_HTML_TD_ROWSPAN]) + fromString(value[MY_HTML_TD_ROWSPAN], _Cells.back()->RowSpan); + + _Cells.back()->BgColor = _CellParams.back().BgColor; + _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); + + float temp; + if (present[MY_HTML_TD_WIDTH] && value[MY_HTML_TD_WIDTH]) + getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, value[MY_HTML_TD_WIDTH]); + if (present[MY_HTML_TD_HEIGHT] && value[MY_HTML_TD_HEIGHT]) + getPercentage (_Cells.back()->Height, temp, value[MY_HTML_TD_HEIGHT]); + + _Cells.back()->NewLine = getTR(); + 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; + } + } + } + break; + case HTML_TEXTAREA: + _PRE.push_back(true); + + // Got one form ? + if (!(_Forms.empty())) + { + // not inherited by default, font family defaults to system font + CStyleParams style; + style.TextColor = TextColor; + style.FontWeight = FONT_WEIGHT_NORMAL; + style.FontOblique = false; + style.FontSize = TextFontSize; + + if (present[MY_HTML_TEXTAREA_STYLE] && value[MY_HTML_TEXTAREA_STYLE]) + getStyleParams(value[MY_HTML_TEXTAREA_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + + // read general property + string templateName; + + // Widget template name + if (present[MY_HTML_TEXTAREA_Z_INPUT_TMPL] && value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]) + templateName = value[MY_HTML_TEXTAREA_Z_INPUT_TMPL]; + + // Get the string name + _TextAreaName.clear(); + _TextAreaRow = 1; + _TextAreaCols = 10; + _TextAreaContent.clear(); + _TextAreaMaxLength = 1024; + if (present[MY_HTML_TEXTAREA_NAME] && value[MY_HTML_TEXTAREA_NAME]) + _TextAreaName = value[MY_HTML_TEXTAREA_NAME]; + if (present[MY_HTML_TEXTAREA_ROWS] && value[MY_HTML_TEXTAREA_ROWS]) + fromString(value[MY_HTML_TEXTAREA_ROWS], _TextAreaRow); + if (present[MY_HTML_TEXTAREA_COLS] && value[MY_HTML_TEXTAREA_COLS]) + fromString(value[MY_HTML_TEXTAREA_COLS], _TextAreaCols); + if (present[MY_HTML_TEXTAREA_MAXLENGTH] && value[MY_HTML_TEXTAREA_MAXLENGTH]) + fromString(value[MY_HTML_TEXTAREA_MAXLENGTH], _TextAreaMaxLength); + + _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup; + _TextArea = true; + } + break; + case HTML_TITLE: + { + if(!_TitlePrefix.empty()) + _TitleString = _TitlePrefix + " - "; + else + _TitleString.clear(); + _Title = true; + } + break; + case HTML_I: + { + _Localize = true; + } + break; + case HTML_TR: + { + // Get cells parameters + getCellsParameters (MY_HTML_TR, true); + + // Set TR flag + if (!_TR.empty()) + _TR.back() = true; + } + break; + case HTML_UL: + 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); + endParagraph(); + break; + case HTML_OBJECT: + _ObjectType.clear(); + _ObjectData.clear(); + _ObjectMD5Sum.clear(); + _ObjectAction.clear(); + if (present[HTML_OBJECT_TYPE] && value[HTML_OBJECT_TYPE]) + _ObjectType = value[HTML_OBJECT_TYPE]; + if (present[HTML_OBJECT_DATA] && value[HTML_OBJECT_DATA]) + _ObjectData = value[HTML_OBJECT_DATA]; + if (present[HTML_OBJECT_ID] && value[HTML_OBJECT_ID]) + _ObjectMD5Sum = value[HTML_OBJECT_ID]; + if (present[HTML_OBJECT_STANDBY] && value[HTML_OBJECT_STANDBY]) + _ObjectAction = value[HTML_OBJECT_STANDBY]; + _Object = true; + + break; + case HTML_SPAN: + { + CStyleParams style; + style.TextColor = getTextColor(); + style.FontFamily = getFontFamily(); + style.FontSize = getFontSize(); + style.FontWeight = getFontWeight(); + style.FontOblique = getFontOblique(); + style.Underlined = getFontUnderlined(); + style.StrikeThrough = getFontStrikeThrough(); + style.GlobalColor = getGlobalColor(); + + if (present[MY_HTML_SPAN_STYLE] && value[MY_HTML_SPAN_STYLE]) + getStyleParams(value[MY_HTML_SPAN_STYLE], style); + + _TextColor.push_back(style.TextColor); + _FontFamily.push_back(style.FontFamily); + _FontSize.push_back(style.FontSize); + _FontWeight.push_back(style.FontWeight); + _FontOblique.push_back(style.FontOblique); + _FontUnderlined.push_back(style.Underlined); + _FontStrikeThrough.push_back(style.StrikeThrough); + _GlobalColor.push_back(style.GlobalColor); + } + break; + case HTML_DEL: + _FontStrikeThrough.push_back(true); + break; + case HTML_U: + _FontUnderlined.push_back(true); + break; + case HTML_EM: + _FontOblique.push_back(true); + break; + case HTML_STRONG: + _FontWeight.push_back(FONT_WEIGHT_BOLD); + break; + case HTML_SMALL: + _FontSize.push_back(getFontSizeSmaller()); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = true; + break; + case HTML_DL: + _DL.push_back(HTMLDListElement()); + _LI = _DL.size() > 1 || !_UL.empty(); + endParagraph(); + break; + case HTML_DT: + if (!_DL.empty()) + { + // close DT if still open + if (_DL.back().DD) + { + _DL.back().DD = false; + popIfNotEmpty(_Indent); + } + + // see if this is the first
    , closing tag not required + if (!_DL.back().DT) + { + _DL.back().DT = true; + _FontWeight.push_back(FONT_WEIGHT_BOLD); + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // if there was no closing tag for
    , then remove
    style + if (_DL.back().DT) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + + if (!_DL.back().DD) + { + _DL.back().DD = true; + _Indent.push_back(getIndent() + ULIndent); + } + + if (!_LI) + { + _LI = true; + newParagraph(ULBeginSpace); + } + else + { + newParagraph(LIBeginSpace); + } + } + break; + case HTML_OL: + { + sint32 start = 1; + std::string type("1"); + + if (present[HTML_OL_START] && value[HTML_OL_START]) + fromString(value[HTML_OL_START], start); + if (present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) + type = value[HTML_OL_TYPE]; + + _UL.push_back(HTMLOListElement(start, type)); + // if LI is already present + _LI = _UL.size() > 1 || _DL.size() > 1; + _Indent.push_back(getIndent() + ULIndent); + endParagraph(); + } + break; + case HTML_HR: + { + newParagraph(0); + + CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0); + if (sep) + { + CStyleParams style; + style.TextColor = CRGBA(120, 120, 120, 255); + style.Height = 0; + style.Width = 0; + + if (present[HTML_HR_STYLE] && value[HTML_HR_STYLE]) + getStyleParams(value[HTML_HR_STYLE], style); + + CViewBitmap *bitmap = dynamic_cast(sep->getView("hr")); + if (bitmap) + { + bitmap->setColor(style.TextColor); + if (style.Width > 0) + { + clamp(style.Width, 1, 32000); + bitmap->setW(style.Width); + bitmap->setSizeRef(CInterfaceElement::none); + } + if (style.Height > 0) + { + clamp(style.Height, 1, 1000); + bitmap->setH(style.Height); + } + } + + getParagraph()->addChild(sep); + endParagraph(); + } + } + break; + } + } + } + + // *************************************************************************** + + void CGroupHTML::endElement (uint element_number) + { + if (_Browsing) + { + // Paragraph ? + switch(element_number) + { + case HTML_HEAD: + _ReadingHeadTag = false; + break; + case HTML_FONT: + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontSize); + break; + case HTML_A: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_GlobalColor); + popIfNotEmpty (_A); + popIfNotEmpty (_Link); + popIfNotEmpty (_LinkTitle); + popIfNotEmpty (_LinkClass); + break; + case HTML_H1: + case HTML_H2: + case HTML_H3: + case HTML_H4: + case HTML_H5: + case HTML_H6: + popIfNotEmpty (_FontSize); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_GlobalColor); + endParagraph(); + break; + case HTML_P: + endParagraph(); + break; + case HTML_PRE: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_PRE); + break; + case HTML_DIV: + if (isBlockLevelElement()) + { + endParagraph(); + } + _DivName.clear(); + popIfNotEmpty (_Divs); + popIfNotEmpty (_BlockLevelElement); + break; + + case HTML_TABLE: + popIfNotEmpty (_CellParams); + popIfNotEmpty (_TR); + popIfNotEmpty (_Cells); + popIfNotEmpty (_Tables); + popIfNotEmpty (_Indent); + endParagraph(); + // Add a cell + break; + case HTML_TH: + popIfNotEmpty (_FontWeight); + // no break; + case HTML_TD: + popIfNotEmpty (_CellParams); + if (!_Cells.empty()) + _Cells.back() = NULL; + break; + case HTML_TR: + popIfNotEmpty (_CellParams); + break; + case HTML_TEXTAREA: + { + _TextArea = false; + if (!(_Forms.empty())) + { + CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, _TextAreaContent, _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); + } + + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + } + + popIfNotEmpty (_PRE); + } + break; + case HTML_TITLE: + { + _Title = false; + + // Get the parent container + setTitle (_TitleString); + } + break; + case HTML_SELECT: + { + _SelectOption = false; + if (!(_Forms.empty())) + { + if (!_Forms.back().Entries.empty()) + { + CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox; + if (cb) + { + cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection); + cb->setW(cb->evalContentWidth() + 16); + } + } + } + } + break; + case HTML_OPTION: + if (!(_Forms.empty()) && !(_Forms.back().Entries.empty())) + { + // 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(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; + } + } + } + } + } + } + break; + case HTML_I: + { + _Localize = false; + } + break; + case HTML_OL: + case HTML_UL: + if (!_UL.empty()) + { + endParagraph(); + popIfNotEmpty(_UL); + popIfNotEmpty(_Indent); + } + break; + case HTML_DL: + if (!_DL.empty()) + { + endParagraph(); + + // unclosed DT + if (_DL.back().DT) + { + popIfNotEmpty (_FontWeight); + } + + // unclosed DD + if (_DL.back().DD) + { + popIfNotEmpty(_Indent); + } + + popIfNotEmpty (_DL); + } + break; + case HTML_DT: + if (!_DL.empty()) + { + _DL.back().DT = false; + popIfNotEmpty (_FontWeight); + } + break; + case HTML_DD: + if (!_DL.empty()) + { + // 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); + } + } + break; + case HTML_SPAN: + popIfNotEmpty (_FontFamily); + popIfNotEmpty (_FontSize); + popIfNotEmpty (_FontWeight); + popIfNotEmpty (_FontOblique); + popIfNotEmpty (_TextColor); + popIfNotEmpty (_FontUnderlined); + popIfNotEmpty (_FontStrikeThrough); + popIfNotEmpty (_GlobalColor); + break; + case HTML_DEL: + popIfNotEmpty (_FontStrikeThrough); + break; + case HTML_U: + popIfNotEmpty (_FontUnderlined); + break; + case HTML_EM: + popIfNotEmpty (_FontOblique); + break; + case HTML_STRONG: + popIfNotEmpty (_FontWeight); + break; + case HTML_SMALL: + popIfNotEmpty (_FontSize); + break; + case HTML_STYLE: + case HTML_SCRIPT: + _IgnoreText = false; + break; + case HTML_OBJECT: + if (_TrustedDomain) + { + if (_ObjectType=="application/ryzom-data") + { + if (!_ObjectData.empty()) + { + if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum)) + { + CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true); + } + _ObjectScript.clear(); + } + } + _Object = false; + } + break; + } + } + } + + // *************************************************************************** + void CGroupHTML::beginUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + // we receive an embeded lua script + _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain + _LuaScript.clear(); + } + } + + // *************************************************************************** + void CGroupHTML::endUnparsedElement(const char *buffer, int length) + { + string str(buffer, buffer+length); + if (stricmp(str.c_str(), "lua") == 0) + { + if (_ParsingLua && _TrustedDomain) + { + _ParsingLua = false; + // execute the embeded lua script + _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript; + CLuaManager::getInstance().executeLuaScript(_LuaScript, true); + } + } + } + + + // *************************************************************************** + NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html"); + + + // *************************************************************************** + uint32 CGroupHTML::_GroupHtmlUIDPool= 0; + CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID; + + + // *************************************************************************** + CGroupHTML::CGroupHTML(const TCtorParam ¶m) + : CGroupScrollText(param), + _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT), + _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT) + { + // 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; + _BrowseNextTime = false; + _PostNextTime = false; + _Browsing = false; + _Connecting = false; + _CurrentViewLink = NULL; + _CurrentViewImage = NULL; + _Indent.clear(); + _LI = false; + _SelectOption = false; + _GroupListAdaptor = NULL; + _UrlFragment.clear(); + _RefreshUrl.clear(); + _NextRefreshTime = 0.0; + _LastRefreshTime = 0.0; + + // Register + CWidgetManager::getInstance()->registerClockMsgTarget(this); + + // HTML parameters + BgColor = CRGBA::Black; + ErrorColor = CRGBA(255, 0, 0); + LinkColor = CRGBA(0, 0, 255); + TextColor = CRGBA(255, 255, 255); + H1Color = CRGBA(255, 255, 255); + H2Color = CRGBA(255, 255, 255); + H3Color = CRGBA(255, 255, 255); + H4Color = CRGBA(255, 255, 255); + H5Color = CRGBA(255, 255, 255); + H6Color = CRGBA(255, 255, 255); + ErrorColorGlobalColor = false; + LinkColorGlobalColor = false; + TextColorGlobalColor = false; + H1ColorGlobalColor = false; + H2ColorGlobalColor = false; + H3ColorGlobalColor = false; + H4ColorGlobalColor = false; + H5ColorGlobalColor = false; + H6ColorGlobalColor = false; + TextFontSize = 9; + H1FontSize = 18; + H2FontSize = 15; + H3FontSize = 12; + H4FontSize = 9; + H5FontSize = 9; + H6FontSize = 9; + LIBeginSpace = 4; + ULBeginSpace = 12; + PBeginSpace = 12; + TDBeginSpace = 0; + LIIndent = -10; + 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(); + initLibWWW(); + } + + // *************************************************************************** + + 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); + + // stop browsing + stopBrowse (); // NB : we don't call updateRefreshButton here, because : + // 1) it is useless, + // 2) it crashed before when it called getElementFromId (that didn't work when a master group was being removed...). Btw it should work now + // this is why the call to 'updateRefreshButton' has been removed from stopBrowse + + clearContext(); + if (_CurlWWW) + delete _CurlWWW; + } + + std::string CGroupHTML::getProperty( const std::string &name ) const + { + if( name == "url" ) + { + return _URL; + } + else + if( name == "title_prefix" ) + { + return _TitlePrefix.toString(); + } + else + if( name == "background_color" ) + { + return toString( BgColor ); + } + else + if( name == "error_color" ) + { + return toString( ErrorColor ); + } + else + if( name == "link_color" ) + { + return toString( LinkColor ); + } + else + if( name == "h1_color" ) + { + return toString( H1Color ); + } + else + if( name == "h2_color" ) + { + return toString( H2Color ); + } + else + if( name == "h3_color" ) + { + return toString( H3Color ); + } + else + if( name == "h4_color" ) + { + return toString( H4Color ); + } + else + if( name == "h5_color" ) + { + return toString( H5Color ); + } + else + if( name == "h6_color" ) + { + return toString( H6Color ); + } + 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 == "h1_color_global_color" ) + { + return toString( H1ColorGlobalColor ); + } + else + if( name == "h2_color_global_color" ) + { + return toString( H2ColorGlobalColor ); + } + else + if( name == "h3_color_global_color" ) + { + return toString( H3ColorGlobalColor ); + } + else + if( name == "h4_color_global_color" ) + { + return toString( H4ColorGlobalColor ); + } + else + if( name == "h5_color_global_color" ) + { + return toString( H5ColorGlobalColor ); + } + else + if( name == "h6_color_global_color" ) + { + return toString( H6ColorGlobalColor ); + } + else + if( name == "text_font_size" ) + { + return toString( TextFontSize ); + } + else + if( name == "h1_font_size" ) + { + return toString( H1FontSize ); + } + else + if( name == "h2_font_size" ) + { + return toString( H2FontSize ); + } + else + if( name == "h3_font_size" ) + { + return toString( H3FontSize ); + } + else + if( name == "h4_font_size" ) + { + return toString( H4FontSize ); + } + else + if( name == "h5_font_size" ) + { + return toString( H5FontSize ); + } + else + if( name == "h6_font_size" ) + { + return toString( H6FontSize ); + } + 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 == "li_indent" ) + { + return toString( LIIndent ); + } + 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 + 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 == "background_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + BgColor = c; + 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 == "h1_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H1Color = c; + return; + } + else + if( name == "h2_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H2Color = c; + return; + } + else + if( name == "h3_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H3Color = c; + return; + } + else + if( name == "h4_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H4Color = c; + return; + } + else + if( name == "h5_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H5Color = c; + return; + } + else + if( name == "h6_color" ) + { + CRGBA c; + if( fromString( value, c ) ) + H6Color = 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 == "h1_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H1ColorGlobalColor = b; + return; + } + else + if( name == "h2_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H2ColorGlobalColor = b; + return; + } + else + if( name == "h3_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H3ColorGlobalColor = b; + return; + } + else + if( name == "h4_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H4ColorGlobalColor = b; + return; + } + else + if( name == "h5_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H5ColorGlobalColor = b; + return; + } + else + if( name == "h6_color_global_color" ) + { + bool b; + if( fromString( value, b ) ) + H6ColorGlobalColor = b; + return; + } + else + if( name == "text_font_size" ) + { + uint i; + if( fromString( value, i ) ) + TextFontSize = i; + return; + } + else + if( name == "h1_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H1FontSize = i; + return; + } + else + if( name == "h2_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H2FontSize = i; + return; + } + else + if( name == "h3_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H3FontSize = i; + return; + } + else + if( name == "h4_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H4FontSize = i; + return; + } + else + if( name == "h5_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H5FontSize = i; + return; + } + else + if( name == "h6_font_size" ) + { + uint i; + if( fromString( value, i ) ) + H6FontSize = i; + 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 == "li_indent" ) + { + uint i; + if( fromString( value, i ) ) + LIIndent = 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 + 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.toString().c_str() ); + xmlSetProp( node, BAD_CAST "background_color", BAD_CAST toString( BgColor ).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 "background_color", BAD_CAST toString( BgColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_color", BAD_CAST toString( H1Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color", BAD_CAST toString( H2Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color", BAD_CAST toString( H3Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color", BAD_CAST toString( H4Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color", BAD_CAST toString( H5Color ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color", BAD_CAST toString( H6Color ).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 "h1_color_global_color", + BAD_CAST toString( H1ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_color_global_color", + BAD_CAST toString( H2ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_color_global_color", + BAD_CAST toString( H3ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_color_global_color", + BAD_CAST toString( H4ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_color_global_color", + BAD_CAST toString( H5ColorGlobalColor ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_color_global_color", + BAD_CAST toString( H6ColorGlobalColor ).c_str() ); + + xmlSetProp( node, BAD_CAST "text_font_size", BAD_CAST toString( TextFontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h1_font_size", BAD_CAST toString( H1FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h2_font_size", BAD_CAST toString( H2FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h3_font_size", BAD_CAST toString( H3FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h4_font_size", BAD_CAST toString( H4FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h5_font_size", BAD_CAST toString( H5FontSize ).c_str() ); + xmlSetProp( node, BAD_CAST "h6_font_size", BAD_CAST toString( H6FontSize ).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 "li_indent", BAD_CAST toString( LIIndent ).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() ); + + 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*)"background_color"); + if (ptr) + BgColor = convertColor(ptr); + 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*)"text_color"); + if (ptr) + TextColor = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h1_color"); + if (ptr) + H1Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color"); + if (ptr) + H2Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color"); + if (ptr) + H3Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color"); + if (ptr) + H4Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color"); + if (ptr) + H5Color = convertColor(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color"); + if (ptr) + H6Color = 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*)"h1_color_global_color"); + if (ptr) + H1ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h2_color_global_color"); + if (ptr) + H2ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h3_color_global_color"); + if (ptr) + H3ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h4_color_global_color"); + if (ptr) + H4ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h5_color_global_color"); + if (ptr) + H5ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"h6_color_global_color"); + if (ptr) + H6ColorGlobalColor = convertBool(ptr); + ptr = xmlGetProp (cur, (xmlChar*)"text_font_size"); + if (ptr) + fromString((const char*)ptr, TextFontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h1_font_size"); + if (ptr) + fromString((const char*)ptr, H1FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h2_font_size"); + if (ptr) + fromString((const char*)ptr, H2FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h3_font_size"); + if (ptr) + fromString((const char*)ptr, H3FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h4_font_size"); + if (ptr) + fromString((const char*)ptr, H4FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h5_font_size"); + if (ptr) + fromString((const char*)ptr, H5FontSize); + ptr = xmlGetProp (cur, (xmlChar*)"h6_font_size"); + if (ptr) + fromString((const char*)ptr, H6FontSize); + 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*)"li_indent"); + if (ptr) + fromString((const char*)ptr, LIIndent); + 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); + + 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() + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + _Paragraph = NULL; + + paragraphChange (); + } + + // *************************************************************************** + + void CGroupHTML::newParagraph(uint beginSpace) + { + // Remove previous paragraph if empty + if (_Paragraph && (_Paragraph->getNumChildren() == 0)) + { + _Paragraph->getParent ()->delGroup(_Paragraph); + _Paragraph = NULL; + } + + // Add a new paragraph + CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam()); + newParagraph->setResizeFromChildH(true); + + newParagraph->setMarginLeft(getIndent()); + + // 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) + { + // Stop previous browse + if (_Browsing) + { + // Clear all the context + clearContext(); + + _Browsing = false; + updateRefreshButton(); + + #ifdef LOG_DL + nlwarning("(%s) *** ALREADY BROWSING, break first", _Id.c_str()); + #endif + } + + #ifdef LOG_DL + nlwarning("(%s) Browsing URL : '%s'", _Id.c_str(), url); + #endif + + + 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(); + _Connecting = false; + _BrowseNextTime = true; + + // 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(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) + { + // 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); + + stopBrowse (); + updateRefreshButton(); + } + + // *************************************************************************** + + bool CGroupHTML::isBrowsing() + { + return _Browsing; + } + + + void CGroupHTML::stopBrowse () + { + #ifdef LOG_DL + nlwarning("*** STOP BROWSE (%s)", _Id.c_str()); + #endif + + // Clear all the context + clearContext(); + + _Browsing = false; + + requestTerminated(); + } + + // *************************************************************************** + + 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(); + } + } + + // *************************************************************************** + + bool CGroupHTML::translateChar(ucchar &output, ucchar input, ucchar lastCharParam) const + { + // Keep this char ? + bool keep = true; + + switch (input) + { + // Return / tab only in
     mode
    +		case '\t':
    +		case '\n':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
    +				if(!getPRE())
    +					input = ' ';
    +			}
    +			break;
    +		case ' ':
    +			{
    +				// Get the last char
    +				ucchar lastChar = lastCharParam;
    +				if (lastChar == 0)
    +					lastChar = getLastChar();
    +				keep = ((lastChar != (ucchar)' ') &&
    +						(lastChar != (ucchar)'\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();
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addString(const ucstring &str)
    +	{
    +		ucstring tmpStr = str;
    +
    +		if (_Localize)
    +		{
    +			string	_str = tmpStr.toString();
    +			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 (_Title)
    +		{
    +			_TitleString += tmpStr;
    +		}
    +		else if (_TextArea)
    +		{
    +			_TextAreaContent += tmpStr;
    +		}
    +		else if (_Object)
    +		{
    +			_ObjectScript += tmpStr.toString();
    +		}
    +		else if (_SelectOption)
    +		{
    +			if (!(_Forms.empty()))
    +			{
    +				if (!_Forms.back().Entries.empty())
    +				{
    +					_SelectOptionStr += tmpStr;
    +				}
    +			}
    +		}
    +		else
    +		{
    +			// In a paragraph ?
    +			if (!_Paragraph)
    +			{
    +				newParagraph (0);
    +				paragraphChange ();
    +			}
    +
    +			// Text added ?
    +			bool added = false;
    +			bool embolden = getFontWeight() >= FONT_WEIGHT_BOLD;
    +
    +			// Number of child in this paragraph
    +			if (_CurrentViewLink)
    +			{
    +				bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == (ucchar) '\n';
    +				// Compatible with current parameters ?
    +				if (!skipLine &&
    +					(getTextColor() == _CurrentViewLink->getColor()) &&
    +					(getFontFamily() == _CurrentViewLink->getFontName()) &&
    +					(getFontSize() == (uint)_CurrentViewLink->getFontSize()) &&
    +					(getFontUnderlined() == _CurrentViewLink->getUnderlined()) &&
    +					(getFontStrikeThrough() == _CurrentViewLink->getStrikeThrough()) &&
    +					(embolden == _CurrentViewLink->getEmbolden()) &&
    +					(getFontOblique() == _CurrentViewLink->getOblique()) &&
    +					(getLink() == _CurrentViewLink->Link) &&
    +					(getGlobalColor() == _CurrentViewLink->getModulateGlobalColor()))
    +				{
    +					// Concat the text
    +					_CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
    +					_CurrentViewLink->invalidateContent();
    +					added = true;
    +				}
    +			}
    +
    +			// Not added ?
    +			if (!added)
    +			{
    +				if (getA() && string(getLinkClass()) == "ryzom-ui-button")
    +				{
    +					string buttonTemplate = DefaultButtonGroup;
    +					// Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
    +					string param = "name=" + this->_Id + "|url=" + getLink();
    +
    +					typedef pair TTmplParam;
    +					vector 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(buttonTemplate, _Paragraph->getId(), tmplParams);
    +					if (buttonGroup)
    +					{
    +
    +						// Add the ctrl button
    +						CCtrlTextButton *ctrlButton = dynamic_cast(buttonGroup->getCtrl("button"));
    +						if (!ctrlButton) ctrlButton = dynamic_cast(buttonGroup->getCtrl("b"));
    +						if (ctrlButton)
    +						{
    +							ctrlButton->setModulateGlobalColorAll (false);
    +
    +							// Translate the tooltip
    +							ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(getLinkTitle()));
    +							ctrlButton->setText(tmpStr);
    +						}
    +						getParagraph()->addChild (buttonGroup);
    +						paragraphChange ();
    +					}
    +		
    +				}
    +				else
    +				{
    +					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(tmpStr);
    +					newLink->setColor(getTextColor());
    +					newLink->setFontName(getFontFamily());
    +					newLink->setFontSize(getFontSize());
    +					newLink->setEmbolden(embolden);
    +					newLink->setOblique(getFontOblique());
    +					newLink->setUnderlined(getFontUnderlined());
    +					newLink->setStrikeThrough(getFontStrikeThrough());
    +					newLink->setMultiLineSpace((uint)((float)getFontSize()*LineSpaceFontFactor));
    +					newLink->setMultiLine(true);
    +					newLink->setModulateGlobalColor(getGlobalColor());
    +					// newLink->setLineAtBottom (true);
    +
    +					registerAnchor(newLink);
    +
    +					if (getA() && !newLink->Link.empty())
    +					{
    +						getParagraph()->addChildLink(newLink);
    +					}
    +					else
    +					{
    +						getParagraph()->addChild(newLink);
    +					}
    +					paragraphChange ();
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addImage(const char *img, bool reloadImg, const CStyleParams &style)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		string finalUrl;
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		// Not added ?
    +		CViewBitmap *newImage = new CViewBitmap (TCtorParam());
    +
    +		//
    +		// 1/ try to load the image with the old system (local files in bnp)
    +		//
    +		string image = CFile::getPath(img) + CFile::getFilenameWithoutExtension(img) + ".tga";
    +		if (lookupLocalFile (finalUrl, image.c_str(), false))
    +		{
    +			newImage->setRenderLayer(getRenderLayer()+1);
    +			image = finalUrl;
    +		}
    +		else
    +		{
    +			//
    +			// 2/ if it doesn't work, try to load the image in cache
    +			//
    +			image = localImageName(img);
    +
    +			if (reloadImg && CFile::fileExists(image))
    +				CFile::deleteFile(image);
    +
    +			if (lookupLocalFile (finalUrl, image.c_str(), false))
    +			{
    +				// don't display image that are not power of 2
    +				try
    +				{
    +					uint32 w, h;
    +					CBitmap::loadSize (image, w, h);
    +					if (w == 0 || h == 0 || ((!NLMISC::isPowerOf2(w) || !NLMISC::isPowerOf2(h)) && !NL3D::CTextureFile::supportNonPowerOfTwoTextures()))
    +						image = "web_del.tga";
    +				}
    +				catch(const NLMISC::Exception &e)
    +				{
    +					nlwarning(e.what());
    +					image = "web_del.tga";
    +				}
    +			}
    +			else
    +			{
    +				// no image in cache
    +				image = "web_del.tga";
    +			}
    +
    +			addImageDownload(img, newImage, style);
    +		}
    +		newImage->setTexture (image);
    +		newImage->setModulateGlobalColor(style.GlobalColor);
    +
    +		getParagraph()->addChild(newImage);
    +		paragraphChange ();
    +
    +		setImageSize(newImage, style);
    +	}
    +
    +	// ***************************************************************************
    +
    +	CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// No more text in this text view
    +		_CurrentViewLink = NULL;
    +
    +		{
    +			// Not added ?
    +			std::vector > templateParams;
    +			templateParams.push_back (std::pair ("w", toString (cols*getFontSize())));
    +			templateParams.push_back (std::pair ("id", name));
    +			templateParams.push_back (std::pair ("prompt", ""));
    +			templateParams.push_back (std::pair ("multiline", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("fontsize", toString (getFontSize())));
    +			templateParams.push_back (std::pair ("color", getTextColor().toString()));
    +			if (getFontWeight() >= FONT_WEIGHT_BOLD)
    +				templateParams.push_back (std::pair ("fontweight", "bold"));
    +			if (getFontOblique())
    +				templateParams.push_back (std::pair ("fontstyle", "oblique"));
    +			if (multiLine)
    +				templateParams.push_back (std::pair ("multi_min_line", toString(rows)));
    +			templateParams.push_back (std::pair ("want_return", multiLine?"true":"false"));
    +			templateParams.push_back (std::pair ("enter_recover_focus", "false"));
    +			if (maxlength > 0)
    +				templateParams.push_back (std::pair ("max_num_chars", toString(maxlength)));
    +			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(textArea->getGroup("eb"));
    +				if (eb)
    +					eb->setInputString(decodeHTMLEntities(content));
    +
    +				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 > templateParams;
    +			templateParams.push_back (std::pair ("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(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 > templateParams;
    +		templateParams.push_back(std::pair ("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(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 char *tooltip, const CStyleParams &style)
    +	{
    +		// In a paragraph ?
    +		if (!_Paragraph)
    +		{
    +			newParagraph (0);
    +			paragraphChange ();
    +		}
    +
    +		// Add the ctrl button
    +		CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
    +
    +		// Load only tga files.. (conversion in dds filename is done in the lookup procedure)
    +		string normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
    +
    +		// if the image doesn't exist on local, we check in the cache
    +	//	if(!CFile::fileExists(normal))
    +		if(!CPath::exists(normal))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(normal);
    +			if(id == -1)
    +			{
    +				normal = localImageName(normalBitmap);
    +				if(!CFile::fileExists(normal))
    +				{
    +					normal = "web_del.tga";
    +					addImageDownload(normalBitmap, ctrlButton, style);
    +				}
    +				else
    +				{
    +					try
    +					{
    +						uint32 w, h;
    +						CBitmap::loadSize(normal, w, h);
    +						if (w == 0 || h == 0)
    +							normal = "web_del.tga";
    +					}
    +					catch(const NLMISC::Exception &e)
    +					{
    +						nlwarning(e.what());
    +						normal = "web_del.tga";
    +					}
    +				}
    +			}
    +		}
    +
    +		string 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(!CFile::fileExists(pushed))
    +		if(!CPath::exists(pushed))
    +		{
    +			// search in the compressed texture
    +			CViewRenderer &rVR = *CViewRenderer::getInstance();
    +			sint32 id = rVR.getTextureIdFromName(pushed);
    +			if(id == -1)
    +			{
    +				pushed = localImageName(pushedBitmap);
    +			}
    +		}
    +
    +		string 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);
    +				if (!CFile::fileExists(over))
    +				{
    +					addImageDownload(overBitmap, ctrlButton, style, TImageType::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)
    +		{
    +			if (CI18N::hasTranslation(tooltip))
    +			{
    +				ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
    +				//ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
    +			}
    +			else
    +			{
    +				ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(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();
    +		_TextColor.clear();
    +		_GlobalColor.clear();
    +		_FontSize.clear();
    +		_FontWeight.clear();
    +		_FontOblique.clear();
    +		_FontUnderlined.clear();
    +		_FontStrikeThrough.clear();
    +		_Indent.clear();
    +		_LI = false;
    +		_UL.clear();
    +		_DL.clear();
    +		_A.clear();
    +		_Link.clear();
    +		_LinkTitle.clear();
    +		_Tables.clear();
    +		_Cells.clear();
    +		_TR.clear();
    +		_Forms.clear();
    +		_Groups.clear();
    +		_Anchors.clear();
    +		_AnchorName.clear();
    +		_CellParams.clear();
    +		_Title = false;
    +		_TextArea = false;
    +		_Object = false;
    +		_Localize = false;
    +		_ReadingHeadTag = false;
    +		_IgnoreHeadTag = false;
    +		_IgnoreBaseUrlTag = false;
    +
    +		// TR
    +
    +		paragraphChange ();
    +
    +		// clear the pointer to the current image download since all the button are deleted
    +	#ifdef LOG_DL
    +		nlwarning("Clear pointers to %d curls", Curls.size());
    +	#endif
    +		for(uint i = 0; i < Curls.size(); i++)
    +		{
    +			Curls[i].imgs.clear();
    +		}
    +
    +		// remove download that are still queued
    +		for (vector::iterator it=Curls.begin(); itdata == NULL) {
    +	#ifdef LOG_DL
    +		nlwarning("Remove waiting curl download (%s)", it->url.c_str());
    +	#endif
    +				it = Curls.erase(it);
    +			} else {
    +				++it;
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	ucchar CGroupHTML::getLastChar() const
    +	{
    +		if (_CurrentViewLink)
    +		{
    +			const ucstring &str = _CurrentViewLink->getText();
    +			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(child);
    +				_CurrentViewImage = dynamic_cast(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;
    +
    +		// Remove previous paragraph if empty
    +		if (_Paragraph && (_Paragraph->getNumChildren() == 0))
    +		{
    +			_Paragraph->getParent ()->delGroup(_Paragraph);
    +			_Paragraph = NULL;
    +		}
    +
    +		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(group);
    +
    +		CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
    +		const std::vector &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::setTitle (const ucstring &title)
    +	{
    +		CInterfaceElement *parent = getParent();
    +		if (parent)
    +		{
    +			if ((parent = parent->getParent()))
    +			{
    +				CGroupContainer *container = dynamic_cast(parent);
    +				if (container)
    +				{
    +					container->setUCTitle (title);
    +				}
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
    +	{
    +		result = url;
    +		string tmp;
    +
    +		if (toLower(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:"+toLower(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 formId, const char *submitButtonType, const char *submitButtonName, const char *submitButtonValue, sint32 x, sint32 y)
    +	{
    +		// Form id valid ?
    +		if (formId < _Forms.size())
    +		{
    +			_PostNextTime = true;
    +			_PostFormId = formId;
    +			_PostFormSubmitType = submitButtonType;
    +			_PostFormSubmitButton = submitButtonName;
    +			_PostFormSubmitValue = submitButtonValue;
    +			_PostFormSubmitX = x;
    +			_PostFormSubmitY = y;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				// Change the background color
    +				bitmap->setColor (bgcolor);
    +				bitmap->setModulateGlobalColor(false);
    +			}
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
    +	{
    +		// Should have a child named bg
    +		CViewBase *view = getView (DefaultBackgroundBitmapView);
    +		if (view)
    +		{
    +			CViewBitmap *bitmap = dynamic_cast (view);
    +			if (bitmap)
    +			{
    +				bitmap->setParentPosRef(Hotspot_TL);
    +				bitmap->setPosRef(Hotspot_TL);
    +				bitmap->setX(0);
    +				bitmap->setY(0);
    +				bitmap->setRenderLayer(-2);
    +				bitmap->setScale(scale);
    +				bitmap->setTile(tile);
    +				addImageDownload(bgtex, view);
    +			}
    +		}
    +	}
    +
    +
    +	struct CButtonFreezer : public CInterfaceElementVisitor
    +	{
    +		virtual void visitCtrl(CCtrlBase *ctrl)
    +		{
    +			CCtrlBaseButton		*textButt = dynamic_cast(ctrl);
    +			if (textButt)
    +			{
    +				textButt->setFrozen(true);
    +			}
    +		}
    +	};
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::handle ()
    +	{
    +		H_AUTO(RZ_Interface_Html_handle)
    +
    +		const CWidgetManager::SInterfaceTimes × = 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 (_Connecting)
    +		{
    +			// Check timeout if needed
    +			if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
    +			{
    +				browseError(("Connection timeout : "+_URL).c_str());
    +
    +				_Connecting = false;
    +			}
    +		}
    +		else
    +		if (_BrowseNextTime || _PostNextTime)
    +		{
    +			// Set timeout
    +			_Connecting = true;
    +			_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);
    +
    +			_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];
    +
    +		_URL = form.Action;
    +
    +		CUrlParser uri(_URL);
    +		_TrustedDomain = isTrustedDomain(uri.host);
    +		_DocumentDomain = uri.host;
    +
    +		for (i=0; igetGroup ("eb");
    +				if (group)
    +				{
    +					// Should be a CGroupEditBox
    +					CGroupEditBox *editBox = dynamic_cast(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.fromUtf8(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; jgetNumLine(); ++j)
    +					{
    +						CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
    +						if (ig)
    +						{
    +							CCtrlBaseButton *cb = dynamic_cast(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, CI18N::encodeUTF8(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)
    +	{
    +		std::string filename;
    +		if (toLower(uri).find("file:/") == 0)
    +		{
    +			filename = uri.substr(6, uri.size() - 6);
    +		}
    +		else
    +		{
    +			filename = uri;
    +		}
    +
    +	#if LOG_DL
    +		nlwarning("browse local file '%s'", filename.c_str());
    +	#endif
    +
    +		_TrustedDomain = true;
    +		_DocumentDomain = "localhost";
    +
    +		// Stop previous browse, remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		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 previous request and remove content
    +		stopBrowse ();
    +
    +		_Browsing = true;
    +		updateRefreshButton();
    +
    +		// Reset the title
    +		if(_TitlePrefix.empty())
    +			setTitle (CI18N::get("uiPleaseWait"));
    +		else
    +			setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
    +
    +		url = upgradeInsecureUrl(url);
    +
    +	#if LOG_DL
    +		nlwarning("(%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());
    +	#endif
    +
    +		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;
    +		}
    +
    +#if defined(NL_OS_WINDOWS)
    +		// https://
    +		if (toLower(url.substr(0, 8)) == "https://")
    +		{
    +			curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction);
    +		}
    +#endif
    +
    +		// 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());
    +	#ifdef LOG_DL
    +			nlwarning("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
    +	#endif
    +		}
    +
    +		if (doPost)
    +		{
    +			// serialize form data and add it to curl
    +			std::string data;
    +			for(uint i=0; i0)
    +					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 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, curlHeaderCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
    +
    +		// catch body
    +		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDataCallback);
    +		curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
    +
    +	#if LOG_DL
    +		// progress callback
    +		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
    +		curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, 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++;
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
    +	{
    +	#ifdef LOG_DL
    +		nlwarning("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
    +	#endif
    +
    +		// create  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();
    +	#ifdef LOG_DL
    +				nlwarning("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
    +	#endif
    +			}
    +			catch(...) { }
    +
    +			// create html code with image url inside and do the request again
    +			renderHtmlString(""+_URL+"");
    +		}
    +		else
    +		{
    +			renderHtmlString(content);
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	bool CGroupHTML::renderHtmlString(const std::string &html)
    +	{
    +		bool success;
    +
    +		//
    +		_Browsing = true;
    +		_DocumentUrl = _URL;
    +		_NextRefreshTime = 0;
    +		_RefreshUrl.clear();
    +
    +		// clear content
    +		beginBuild();
    +
    +		success = parseHtml(html);
    +
    +		// invalidate coords
    +		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);
    +		}
    +
    +		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 ()
    +	{
    +		CGroupScrollText::draw ();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::endBuild ()
    +	{
    +		invalidateCoords();
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
    +	{
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::requestTerminated()
    +	{
    +		if (_CurlWWW)
    +		{
    +	#if LOG_DL
    +			nlwarning("(%s) stop curl, url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
    +	#endif
    +			if (MultiCurl)
    +				curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
    +
    +			delete _CurlWWW;
    +
    +			_CurlWWW = NULL;
    +			_Connecting = false;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +
    +	string	CGroupHTML::home ()
    +	{
    +		return Home;
    +	}
    +
    +	// ***************************************************************************
    +
    +	void CGroupHTML::removeContent ()
    +	{
    +		// Remove old document
    +		if (!_GroupListAdaptor)
    +		{
    +			_GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
    +			_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 color
    +		setBackgroundColor (BgColor);
    +
    +		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;iChildren.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 ¶ms, 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;igetParser()->getProcedureAction(procName, i, procAh, procParams))
    +				{
    +					// recurs proc if needed!
    +					if (actionLaunchUrlRecurs(procAh, procParams, url))
    +						return true;
    +				}
    +			}
    +		}
    +
    +		return false;
    +	}
    +
    +	// ***************************************************************************
    +	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
    +		_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(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
    +		CCtrlBaseButton		*butRedo= dynamic_cast(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(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
    +
    +		bool enabled = !_Browsing && !_Connecting;
    +		if(butRefresh)
    +			butRefresh->setFrozen(!enabled);
    +	}
    +
    +	// ***************************************************************************
    +
    +	NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
    +
    +	CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam ¶m)
    +		: 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::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);
    +
    +		renderHtmlString(html);
    +
    +		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);
    +
    +		ucstring text;
    +		text.fromUtf8(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(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(ucstring(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), ls.toString(1),
    +								"", "browse", params.c_str(), "", style);
    +		}
    +		else
    +		{
    +			addImage(ls.toString(1), false, style);
    +		}
    +
    +
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaBeginElement(CLuaState &ls)
    +	{
    +		const char *funcName = "beginElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 2);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +		CLuaIHM::checkArgType(ls, funcName, 2, LUA_TTABLE);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		std::vector present;
    +		std::vector value;
    +		present.resize(30, false);
    +		value.resize(30);
    +
    +		CLuaObject params;
    +		params.pop(ls);
    +		uint max_idx = 0;
    +
    +
    +		ENUM_LUA_TABLE(params, it)
    +		{
    +			if (!it.nextKey().isInteger())
    +			{
    +				nlwarning("%s : bad key encountered with type %s, integer expected.", funcName, it.nextKey().getTypename());
    +				continue;
    +			}
    +			if (!it.nextValue().isString())
    +			{
    +				nlwarning("%s : bad value encountered with type %s for key %s, string expected.", funcName, it.nextValue().getTypename(), it.nextKey().toString().c_str());
    +				continue;
    +			}
    +			uint idx = (uint)it.nextKey().toInteger();
    +
    +			present.insert(present.begin() + (uint)it.nextKey().toInteger(), true);
    +
    +			string str = it.nextValue().toString();
    +			size_t size = str.size() + 1;
    +			char * buffer = new char[ size ];
    +			strncpy(buffer, str.c_str(), size );
    +
    +			value.insert(value.begin() + (uint)it.nextKey().toInteger(), buffer);
    +		}
    +
    +		// ingame lua scripts from browser are using  url scheme
    +		// reason unknown
    +		_LuaHrefHack = true;
    +		beginElement(element_number, present, value);
    +		_LuaHrefHack = false;
    +
    +		return 0;
    +	}
    +
    +
    +	// ***************************************************************************
    +	int CGroupHTML::luaEndElement(CLuaState &ls)
    +	{
    +		const char *funcName = "endElement";
    +		CLuaIHM::checkArgCount(ls, funcName, 1);
    +		CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
    +
    +		uint element_number = (uint)ls.toInteger(1);
    +		endElement(element_number);
    +
    +		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());
    +	}
    +
    +	// ***************************************************************************
    +	inline bool isDigit(ucchar 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 ucchar convertHexDigit(ucchar 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;
    +	}
    +
    +	// ***************************************************************************
    +	ucstring CGroupHTML::decodeHTMLEntities(const ucstring &str)
    +	{
    +		ucstring result;
    +		uint last, pos;
    +
    +		for (uint i=0; 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;
    +					}
    +
    +					ucchar c = 0;
    +
    +					// convert digits to unicode character
    +					while (posfirst == "font-size")
    +			{
    +				if (it->second == "inherit")
    +					style.FontSize = getFontSize();
    +				else
    +				{
    +					float tmp;
    +					sint size = 0;
    +					getPercentage (size, tmp, it->second.c_str());
    +					if (size > 0)
    +						style.FontSize = size;
    +				}
    +			}
    +			else
    +			if (it->first == "font-style")
    +			{
    +				if (it->second == "inherit")
    +					style.FontOblique = getFontOblique();
    +				else
    +				if (it->second == "italic" || it->second == "oblique")
    +					style.FontOblique = true;
    +			}
    +			else
    +			if (it->first == "font-family")
    +			{
    +				if (it->second == "inherit")
    +					style.FontFamily = getFontFamily();
    +				else
    +				if (it->second == "monospace")
    +					style.FontFamily = "monospace";
    +				else
    +					style.FontFamily.clear();
    +			}
    +			else
    +			if (it->first == "font-weight")
    +			{
    +				// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
    +				uint weight = 400;
    +				if (it->second == "inherit")
    +					weight = getFontWeight();
    +				else
    +				if (it->second == "normal")
    +					weight = 400;
    +				else
    +				if (it->second == "bold")
    +					weight = 700;
    +				else
    +				if (it->second == "lighter")
    +				{
    +					const uint lighter[] = {100, 100, 100, 100, 100, 400, 400, 700, 700};
    +					uint index = getFontWeight() / 100 - 1;
    +					clamp(index, 1u, 9u);
    +					weight = lighter[index-1];
    +				}
    +				else
    +				if (it->second == "bolder")
    +				{
    +					const uint bolder[] =  {400, 400, 400, 700, 700, 900, 900, 900, 900};
    +					uint index = getFontWeight() / 100 + 1;
    +					clamp(index, 1u, 9u);
    +					weight = bolder[index-1];
    +				}
    +				else
    +				if (fromString(it->second, weight))
    +				{
    +					weight = (weight / 100);
    +					clamp(weight, 1u, 9u);
    +					weight *= 100;
    +				}
    +				style.FontWeight = weight;
    +			}
    +			else
    +			if (it->first == "color")
    +				if (it->second == "inherit")
    +					style.TextColor = getTextColor();
    +				else
    +					scanHTMLColor(it->second.c_str(), style.TextColor);
    +			else
    +			if (it->first == "text-decoration" || it->first == "text-decoration-line")
    +			{
    +				std::string prop(toLower(it->second));
    +				style.Underlined = (prop.find("underline") != std::string::npos);
    +				style.StrikeThrough = (prop.find("line-through") != std::string::npos);
    +			}
    +			else
    +			if (it->first == "width")
    +				getPercentage(style.Width, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "height")
    +				getPercentage(style.Height, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-width")
    +				getPercentage(style.MaxWidth, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "max-height")
    +				getPercentage(style.MaxHeight, tmpf, it->second.c_str());
    +			else
    +			if (it->first == "-ryzom-modulate-color")
    +			{
    +				bool b;
    +				if (it->second == "inherit")
    +					style.GlobalColor = getGlobalColor();
    +				else
    +				if (fromString(it->second, b))
    +					style.GlobalColor = b;
    +			}
    +		}
    +		if (inherit)
    +		{
    +			style.Underlined = getFontUnderlined() || style.Underlined;
    +			style.StrikeThrough = getFontStrikeThrough() || style.StrikeThrough;
    +		}
    +	}
    +
    +	// ***************************************************************************
    +	void CGroupHTML::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh)
    +	{
    +		if (maxw <= 0) maxw = width;
    +		if (maxh <= 0) maxh = height;
    +
    +		maxw = std::max(minw, maxw);
    +		maxh = std::max(minh, maxh);
    +		
    +		float ratio = (float) width / std::max(1, height);
    +		if (width > maxw)
    +		{
    +			width = maxw;
    +			height = std::max((sint32)(maxw /ratio), minh);
    +		}
    +		if (width < minw)
    +		{
    +			width = minw;
    +			height = std::min((sint32)(minw / ratio), maxh);
    +		}
    +		if (height > maxh)
    +		{
    +			width = std::max((sint32)(maxh * ratio), minw);
    +			height = maxh;
    +		}
    +		if (height < minh)
    +		{
    +			width = std::min((sint32)(minh * ratio), maxw);
    +			height = minh;
    +		}
    +		if (width > maxw && height > maxh)
    +		{
    +			if (maxw/width <= maxh/height)
    +			{
    +				width = maxw;
    +				height = std::max(minh, (sint32)(maxw / ratio));
    +			}
    +			else
    +			{
    +				width = std::max(minw, (sint32)(maxh * ratio));
    +				height = maxh;
    +			}
    +		}
    +		if (width < minw && height < minh)
    +		{
    +			if (minw / width <= minh / height)
    +			{
    +				width = std::min(maxw, (sint32)(minh * ratio));
    +				height = minh;
    +			}
    +			else
    +			{
    +				width = minw;
    +				height = std::min(maxh, (sint32)(minw / ratio));
    +			}
    +		}
    +		if (width < minw && height > maxh)
    +		{
    +			width = minw;
    +			height = maxh;
    +		}
    +		if (width > maxw && height < minh)
    +		{
    +			width = maxw;
    +			height = minh;
    +		}
    +	}
    +	
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlHeaderCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(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;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlDataCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +			me->Content.append(buffer, size * nmemb);
    +
    +		return size * nmemb;
    +	}
    +
    +	// ***************************************************************************
    +	size_t CGroupHTML::curlProgressCallback(void *pCCurlWWWData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
    +	{
    +		CCurlWWWData * me = static_cast(pCCurlWWWData);
    +		if (me)
    +		{
    +			if (dltotal > 0 || dlnow > 0 || ultotal > 0 || ulnow > 0)
    +			{
    +				nlwarning("> dltotal %d, dlnow %d, ultotal %d, ulnow %d, url '%s'", dltotal, dlnow, ultotal, ulnow, me->Url.c_str());
    +			}
    +		}
    +
    +		// return 1 to cancel download
    +		return 0;
    +	}
    +
    +	// ***************************************************************************
    +	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;
    +	}
    +
    +}
    +
    diff --git a/code/nel/src/gui/group_table.cpp b/code/nel/src/gui/group_table.cpp
    index f3f009dfc..ef3eca57a 100644
    --- a/code/nel/src/gui/group_table.cpp
    +++ b/code/nel/src/gui/group_table.cpp
    @@ -1209,6 +1209,13 @@ namespace NLGUI
     	// ----------------------------------------------------------------------------
     	sint32	CGroupTable::getMaxUsedW() const
     	{
    +		// Return table width if its requested by user.
    +		// Need to do this because width of long line of text in here is calculated
    +		// differently than final cell width in updateCoords()
    +		// This will break tables with too narrow width set by user.
    +		if (ForceWidthMin > 0)
    +			return ForceWidthMin;
    +
     		uint i;
     		uint column = 0;
     		vector columns;
    diff --git a/code/nel/src/gui/group_tree.cpp b/code/nel/src/gui/group_tree.cpp
    index c65ceba17..e1fa8292a 100644
    --- a/code/nel/src/gui/group_tree.cpp
    +++ b/code/nel/src/gui/group_tree.cpp
    @@ -271,6 +271,14 @@ namespace NLGUI
     		pNode->setFather(this);
     	}
     
    +	// ----------------------------------------------------------------------------
    +	void CGroupTree::SNode::openAll()
    +	{
    +		Opened = true;
    +		for (uint i = 0; i < Children.size(); ++i)
    +			Children[i]->openAll();
    +	}
    +
     	// ----------------------------------------------------------------------------
     	void CGroupTree::SNode::closeAll()
     	{
    @@ -1082,7 +1090,9 @@ namespace NLGUI
     				}
     			}
     
    -			if (eventDesc.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mouseleftdown)
    +			bool toggleOne = (eventDesc.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mouseleftdown);
    +			bool toggleAll = (eventDesc.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mouserightdown);
    +			if (toggleOne || toggleAll)
     			{
     				// line selection
     				if (bText)
    @@ -1118,6 +1128,13 @@ namespace NLGUI
     						{
     							// open/close the node
     							changedNode->Opened = !changedNode->Opened;
    +							if (toggleAll)
    +							{
    +								if (changedNode->Opened)
    +									changedNode->openAll();
    +								else
    +									changedNode->closeAll();
    +							}
     						}
     						// else must close all necessary nodes.
     						else
    diff --git a/code/nel/src/gui/http_cache.cpp b/code/nel/src/gui/http_cache.cpp
    new file mode 100644
    index 000000000..a032247fc
    --- /dev/null
    +++ b/code/nel/src/gui/http_cache.cpp
    @@ -0,0 +1,212 @@
    +// Ryzom - MMORPG Framework 
    +// Copyright (C) 2010  Winch Gate Property Limited
    +//
    +// 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 .
    +
    +#include "stdpch.h"
    +#include "nel/gui/http_cache.h"
    +
    +using namespace std;
    +using namespace NLMISC;
    +
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
    +#if defined(GCC_VERSION) && !defined(CLANG_VERSION) && defined(NL_ISO_CPP0X_AVAILABLE) && (GCC_VERSION <= 40804)
    +// hack to fix std::map::erase wrong return type (void instead of iterator in C++11) in GCC 4.8.4
    +#undef NL_ISO_CPP0X_AVAILABLE
    +#endif
    +
    +namespace NLGUI
    +{
    +	CHttpCache* CHttpCache::instance = NULL;
    +
    +	CHttpCache* CHttpCache::getInstance()
    +	{
    +		if (!instance)
    +		{
    +			instance = new CHttpCache();
    +		}
    +
    +		return instance;
    +	}
    +
    +	void CHttpCache::release()
    +	{
    +		delete instance;
    +		instance = NULL;
    +	}
    +
    +	CHttpCache::CHttpCache()
    +		: _Initialized(false)
    +		, _MaxObjects(100)
    +	{ };
    +
    +	CHttpCache::~CHttpCache()
    +	{
    +		flushCache();
    +	}
    +
    +	void CHttpCache::setCacheIndex(const std::string& fname)
    +	{
    +		_IndexFilename = fname;
    +		_Initialized = false;
    +	}
    +
    +	CHttpCacheObject CHttpCache::lookup(const std::string& fname)
    +	{
    +		if (!_Initialized)
    +			init();
    +
    +		if (_List.count(fname) > 0)
    +			return _List[fname];
    +
    +		return CHttpCacheObject();
    +	}
    +
    +	void CHttpCache::store(const std::string& fname, const CHttpCacheObject& data)
    +	{
    +		if (!_Initialized)
    +			init();
    +
    +		_List[fname] = data;
    +	}
    +
    +	void CHttpCache::init()
    +	{
    +		if (_Initialized)
    +			return;
    +
    +		_Initialized = true;
    +
    +		if (_IndexFilename.empty() || !CFile::fileExists(_IndexFilename))
    +			return;
    +
    +		CIFile in;
    +		if (!in.open(_IndexFilename)) {
    +			nlwarning("Unable to open %s for reading", _IndexFilename.c_str());
    +			return;
    +		}
    +
    +		serial(in);
    +	}
    +
    +	void CHttpCacheObject::serial(NLMISC::IStream& f)
    +	{
    +		f.serialVersion(1);
    +		f.serial(Expires);
    +		f.serial(LastModified);
    +		f.serial(Etag);
    +	}
    +
    +	void CHttpCache::serial(NLMISC::IStream& f)
    +	{
    +		// saved state is ignored when version checks fail
    +		try {
    +			f.serialVersion(1);
    +
    +			// CacheIdx
    +			f.serialCheck(NELID("hcaC"));
    +			f.serialCheck(NELID("xdIe"));
    +
    +			if (f.isReading())
    +			{
    +				uint32 numFiles;
    +				f.serial(numFiles);
    +
    +				_List.clear();
    +				for (uint k = 0; k < numFiles; ++k)
    +				{
    +					std::string fname;
    +					f.serial(fname);
    +
    +					CHttpCacheObject obj;
    +					obj.serial(f);
    +
    +					_List[fname] = obj;
    +				}
    +			}
    +			else
    +			{
    +				uint32 numFiles = _List.size();
    +				f.serial(numFiles);
    +
    +				for (THttpCacheMap::iterator it = _List.begin(); it != _List.end(); ++it)
    +				{
    +					std::string fname(it->first);
    +					f.serial(fname);
    +
    +					(*it).second.serial(f);
    +				}
    +			}
    +		} catch (...) {
    +			_List.clear();
    +			nlwarning("Invalid cache index format (%s)", _IndexFilename.c_str());
    +			return;
    +		}
    +	}
    +
    +	void CHttpCache::pruneCache()
    +	{
    +		if (_List.size() < _MaxObjects)
    +			return;
    +
    +		size_t mustDrop = _List.size() - _MaxObjects;
    +
    +		time_t currentTime;
    +		time(¤tTime);
    +
    +		// if we over object limit, then start removing expired objects
    +		// this does not guarantee that max limit is reached
    +		for (THttpCacheMap::iterator it = _List.begin(); it != _List.end();)
    +		{
    +			if (it->second.Expires <= currentTime)
    +			{
    +#ifdef NL_ISO_CPP0X_AVAILABLE
    +				it = _List.erase(it);
    +#else
    +				THttpCacheMap::iterator itToErase = it++;
    +				_List.erase(itToErase);
    +#endif
    +
    +				--mustDrop;
    +				if (mustDrop == 0)
    +					break;
    +			}
    +			else
    +			{
    +				++it;
    +			}
    +		}
    +	}
    +
    +	void CHttpCache::flushCache()
    +	{
    +		if (_IndexFilename.empty())
    +			return;
    +
    +		pruneCache();
    +
    +		COFile out;
    +		if (!out.open(_IndexFilename))
    +		{
    +			nlwarning("Unable to open %s for writing", _IndexFilename.c_str());
    +			return;
    +		}
    +
    +		serial(out);
    +		out.close();
    +	}
    +}
    diff --git a/code/nel/src/gui/http_hsts.cpp b/code/nel/src/gui/http_hsts.cpp
    new file mode 100644
    index 000000000..980bdabda
    --- /dev/null
    +++ b/code/nel/src/gui/http_hsts.cpp
    @@ -0,0 +1,245 @@
    +// Ryzom - MMORPG Framework 
    +// Copyright (C) 2010  Winch Gate Property Limited
    +//
    +// 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 .
    +
    +#include "stdpch.h"
    +#include "nel/gui/http_hsts.h"
    +
    +using namespace std;
    +using namespace NLMISC;
    +
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
    +namespace NLGUI {
    +	CStrictTransportSecurity* CStrictTransportSecurity::instance = NULL;
    +	CStrictTransportSecurity* CStrictTransportSecurity::getInstance()
    +	{
    +		if (!instance)
    +		{
    +			instance= new CStrictTransportSecurity();
    +		}
    +		return instance;
    +	}
    +
    +	void CStrictTransportSecurity::release()
    +	{
    +		delete instance;
    +		instance = NULL;
    +	}
    +
    +	CStrictTransportSecurity::~CStrictTransportSecurity()
    +	{
    +		save();
    +	}
    +
    +	// ************************************************************************
    +	bool CStrictTransportSecurity::isSecureHost(const std::string &domain) const
    +	{
    +		SHSTSObject hsts;
    +		if (get(domain, hsts))
    +		{
    +			time_t currentTime;
    +			time(¤tTime);
    +
    +			return (hsts.Expires < currentTime);
    +		}
    +
    +		return false;
    +	}
    +
    +	// ************************************************************************
    +	void CStrictTransportSecurity::erase(const std::string &domain)
    +	{
    +		if (_Domains.count(domain) > 0)
    +		{
    +			_Domains.erase(domain);
    +		}
    +	}
    +
    +	void CStrictTransportSecurity::set(const std::string &domain, uint64 expires, bool includeSubDomains)
    +	{
    +		if (expires == 0)
    +		{
    +			erase(domain);
    +			return;
    +		}
    +
    +		_Domains[domain].Expires = expires;
    +		_Domains[domain].IncludeSubDomains = includeSubDomains;
    +	}
    +
    +	bool CStrictTransportSecurity::get(const std::string &domain, SHSTSObject &hsts) const
    +	{
    +		if (domain.empty() || _Domains.empty())
    +			return false;
    +
    +		if (_Domains.count(domain) > 0)
    +		{
    +			hsts = _Domains.at(domain);
    +			return true;
    +		}
    +
    +		size_t firstOf = domain.find_first_of(".");
    +		size_t lastOf = domain.find_last_of(".");
    +		while(firstOf != lastOf)
    +		{
    +			std::string tmp;
    +			tmp = domain.substr(firstOf+1);
    +			if (_Domains.count(tmp))
    +			{
    +				if (_Domains.at(tmp).IncludeSubDomains)
    +				{
    +					hsts = _Domains.at(tmp);
    +					return true;
    +				}
    +
    +				return false;
    +			}
    +
    +			firstOf = domain.find_first_of(".",  firstOf + 1);
    +		}
    +
    +		return false;
    +	}
    +
    +	void CStrictTransportSecurity::init(const std::string &fname)
    +	{
    +		_Domains.clear();
    +		_Filename = fname;
    +
    +		if (_Filename.empty() || !CFile::fileExists(_Filename))
    +		{
    +			return;
    +		}
    +
    +		CIFile in;
    +		if (!in.open(_Filename))
    +		{
    +			nlwarning("Unable to open %s for reading", _Filename.c_str());
    +			return;
    +		}
    +
    +		serial(in);
    +	}
    +
    +	void CStrictTransportSecurity::save()
    +	{
    +		if (_Filename.empty())
    +			return;
    +
    +		if (_Domains.empty())
    +		{
    +			CFile::deleteFile(_Filename);
    +			return;
    +		}
    +
    +		COFile out;
    +		if (!out.open(_Filename))
    +		{
    +			nlwarning("Unable to open %s for writing", _Filename.c_str());
    +			return;
    +		}
    +
    +		serial(out);
    +		out.close();
    +	}
    +
    +	void CStrictTransportSecurity::serial(NLMISC::IStream& f)
    +	{
    +		try
    +		{
    +			f.serialVersion(1);
    +			// HSTS
    +			f.serialCheck(NELID("STSH"));
    +
    +			if (f.isReading())
    +			{
    +				uint32 nbItems;
    +				f.serial(nbItems);
    +				for(uint32 k = 0; k < nbItems; ++k)
    +				{
    +					std::string domain;
    +					f.serial(domain);
    +					f.serial(_Domains[domain].Expires);
    +					f.serial(_Domains[domain].IncludeSubDomains);
    +				}
    +			}
    +			else
    +			{
    +				uint32 nbItems = _Domains.size();
    +				f.serial(nbItems);
    +				for (THSTSObject::iterator it = _Domains.begin(); it != _Domains.end(); ++it)
    +				{
    +					std::string domain(it->first);
    +					f.serial(domain);
    +					f.serial(_Domains[domain].Expires);
    +					f.serial(_Domains[domain].IncludeSubDomains);
    +				}
    +			}
    +		}
    +		catch (...)
    +		{
    +			_Domains.clear();
    +			nlwarning("Invalid HTST file format (%s)", _Filename.c_str());
    +		}
    +	}
    +
    +	// ***************************************************************************
    +	void CStrictTransportSecurity::setFromHeader(const std::string &domain, const std::string &header)
    +	{
    +		// max-age=; includeSubdomains; preload;
    +		std::vector elements;
    +		NLMISC::splitString(toLower(header), ";", elements);
    +		if (elements.empty()) return;
    +
    +		time_t currentTime;
    +		time(¤tTime);
    +
    +		uint64 expire = 0;
    +		bool includeSubDomains = false;
    +
    +		for(uint i=0; i< elements.size(); ++i)
    +		{
    +			std::string str(trim(elements[i]));
    +			if (str.substr(0, 8) == "max-age=")
    +			{
    +				uint64 ttl;
    +				if (fromString(str.substr(8), ttl))
    +				{
    +					if (ttl > 0)
    +					{
    +						expire = currentTime + ttl;
    +					}
    +				}
    +			}
    +			else if (str == "includesubdomains")
    +			{
    +				includeSubDomains = true;
    +			}
    +		}
    +
    +		if (expire == 0)
    +		{
    +			erase(domain);
    +		}
    +		else
    +		{
    +			set(domain, expire, includeSubDomains);
    +		}
    +	}
    +
    +}
    diff --git a/code/nel/src/gui/interface_element.cpp b/code/nel/src/gui/interface_element.cpp
    index e33d1dbd8..2e13d80e6 100644
    --- a/code/nel/src/gui/interface_element.cpp
    +++ b/code/nel/src/gui/interface_element.cpp
    @@ -509,7 +509,7 @@ namespace NLGUI
     	// ------------------------------------------------------------------------------------------------
     	void CInterfaceElement::updateCoords()
     	{
    -		_XReal = _X;
    +		_XReal = _X + _MarginLeft;
     		_YReal = _Y;
     		_WReal = getW();
     		_HReal = getH();
    @@ -526,7 +526,7 @@ namespace NLGUI
     		if (el == NULL)
     			return;
     
    -		_XReal += el->_XReal;
    +		_XReal += el->_XReal - el->_MarginLeft;
     		_YReal += el->_YReal;
     
     		THotSpot hsParent = _ParentPosRef;
    diff --git a/code/nel/src/gui/interface_group.cpp b/code/nel/src/gui/interface_group.cpp
    index fc75ae0db..dce9fc266 100644
    --- a/code/nel/src/gui/interface_group.cpp
    +++ b/code/nel/src/gui/interface_group.cpp
    @@ -1457,7 +1457,7 @@ namespace NLGUI
     		}
     
     		CViewBase::updateCoords();
    -		_XReal += _OffsetX;
    +		_XReal += _OffsetX + _MarginLeft;
     		_YReal += _OffsetY;
     
     		//update all children elements
    @@ -1468,7 +1468,7 @@ namespace NLGUI
     			pIE->updateCoords();
     		}
     
    -		_XReal -= _OffsetX;
    +		_XReal -= _OffsetX - _MarginLeft;
     		_YReal -= _OffsetY;
     	}
     
    @@ -1958,9 +1958,9 @@ namespace NLGUI
     			newSciH = newSciH - ((newSciY+newSciH)-(oldSciY+oldSciH));
     		}
     
    -		newSciXDest = newSciX;
    +		newSciXDest = newSciX - _MarginLeft;
     		newSciYDest = newSciY;
    -		newSciWDest = newSciW;
    +		newSciWDest = newSciW + _MarginLeft;
     		newSciHDest = newSciH;
     
     	}
    diff --git a/code/nel/src/gui/libwww.cpp b/code/nel/src/gui/libwww.cpp
    index 919d50562..c8b41a38f 100644
    --- a/code/nel/src/gui/libwww.cpp
    +++ b/code/nel/src/gui/libwww.cpp
    @@ -150,6 +150,8 @@ namespace NLGUI
     			HTML_ATTR(IMG,USEMAP),
     			HTML_ATTR(IMG,VSPACE),
     			HTML_ATTR(IMG,WIDTH),
    +			// not sorted to keep enum values
    +			HTML_ATTR(IMG,DATA-OVER-SRC),
     			{ 0 }
     	};
     
    diff --git a/code/nel/src/gui/stdpch.h b/code/nel/src/gui/stdpch.h
    index e0be5837e..a4ba0ecac 100644
    --- a/code/nel/src/gui/stdpch.h
    +++ b/code/nel/src/gui/stdpch.h
    @@ -26,6 +26,7 @@
     
     #include 
     #include 
    +#include 
     
     #include "nel/misc/types_nl.h"
     #include "nel/misc/algo.h"
    diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp
    index e145db91c..2faae3a0a 100644
    --- a/code/nel/src/gui/view_text.cpp
    +++ b/code/nel/src/gui/view_text.cpp
    @@ -2494,7 +2494,7 @@ namespace NLGUI
     	}
     
     	// ***************************************************************************
    -	void CViewText::setFirstLineX(uint firstLineX)
    +	void CViewText::setFirstLineX(sint firstLineX)
     	{
     		_FirstLineX = firstLineX;
     	}
    diff --git a/code/nel/src/misc/CMakeLists.txt b/code/nel/src/misc/CMakeLists.txt
    index 98b471e44..ec0dcd357 100644
    --- a/code/nel/src/misc/CMakeLists.txt
    +++ b/code/nel/src/misc/CMakeLists.txt
    @@ -202,7 +202,9 @@ ENDIF()
     
     IF(UNIX)
       TARGET_LINK_LIBRARIES(nelmisc -lc -ldl)
    -  IF(NOT APPLE)
    +  IF(APPLE)
    +    TARGET_LINK_LIBRARIES(nelmisc ${CARBON_FRAMEWORK})
    +  ELSE()
         TARGET_LINK_LIBRARIES(nelmisc -lrt)
       ENDIF()
     ENDIF()
    diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp
    index 31f289381..7eebdafbf 100644
    --- a/code/nel/src/misc/common.cpp
    +++ b/code/nel/src/misc/common.cpp
    @@ -82,6 +82,31 @@ extern "C" long _ftol2( double dblSource ) { return _ftol( dblSource ); }
     
     #if defined(NL_HAS_SSE2) && !defined(NL_CPU_X86_64)
     
    +#ifdef NL_NO_EXCEPTION_SPECS
    +void *operator new(size_t size)
    +{
    +	void *p = aligned_malloc(size, NL_DEFAULT_MEMORY_ALIGNMENT);
    +	if (p == NULL) throw std::bad_alloc();
    +	return p;
    +}
    +
    +void *operator new[](size_t size)
    +{
    +	void *p = aligned_malloc(size, NL_DEFAULT_MEMORY_ALIGNMENT);
    +	if (p == NULL) throw std::bad_alloc();
    +	return p;
    +}
    +
    +void operator delete(void *p) noexcept
    +{
    +	aligned_free(p);
    +}
    +
    +void operator delete[](void *p) noexcept
    +{
    +	aligned_free(p);
    +}
    +#else
     void *operator new(size_t size) throw(std::bad_alloc)
     {
     	void *p = aligned_malloc(size, NL_DEFAULT_MEMORY_ALIGNMENT);
    @@ -105,6 +130,7 @@ void operator delete[](void *p) throw()
     {
     	aligned_free(p);
     }
    +#endif
     
     #endif /* NL_HAS_SSE2 */
     
    diff --git a/code/nel/src/misc/i_xml.cpp b/code/nel/src/misc/i_xml.cpp
    index e3d6f19a6..ae5c8615e 100644
    --- a/code/nel/src/misc/i_xml.cpp
    +++ b/code/nel/src/misc/i_xml.cpp
    @@ -17,7 +17,6 @@
     #include "stdmisc.h"
     
     #include "nel/misc/i_xml.h"
    -#include "nel/misc/sstring.h"
     
     #ifndef NL_DONT_USE_EXTERNAL_CODE
     
    @@ -39,6 +38,10 @@ namespace NLMISC
     
     const char SEPARATOR = ' ';
     
    +std::string CIXml::_ErrorString;
    +
    +bool CIXml::_LibXmlIntialized = false;
    +
     // ***************************************************************************
     
     #define readnumber(dest,digits) \
    @@ -124,7 +127,7 @@ void xmlGenericErrorFuncRead (void *ctx, const char *msg, ...)
     	// Get the error string
     	string str;
     	NLMISC_CONVERT_VARGS (str, msg, NLMISC::MaxCStringSize);
    -	((CIXml*)ctx)->_ErrorString += str;
    +	CIXml::_ErrorString += str;
     }
     
     // ***************************************************************************
    @@ -134,7 +137,7 @@ bool CIXml::init (IStream &stream)
     	// Release
     	release ();
     
    -	xmlInitParser();
    +	initLibXml();
     
     	// Default : XML mode
     	_BinaryStream = NULL;
    @@ -190,12 +193,7 @@ bool CIXml::init (IStream &stream)
     			}
     		}
     
    -		// Set error handler
     		_ErrorString.clear();
    -		xmlSetGenericErrorFunc	(this, xmlGenericErrorFuncRead);
    -
    -		// Ask to get debug info
    -		xmlLineNumbersDefault(1);
     
     		// The parser context
             _Parser = xmlCreatePushParserCtxt(NULL, NULL, buffer, 4, NULL);
    @@ -1068,6 +1066,7 @@ bool CIXml::getPropertyString (std::string &result, xmlNodePtr node, const std::
     		// Found
     		return true;
     	}
    +
     	return false;
     }
     
    @@ -1075,18 +1074,21 @@ bool CIXml::getPropertyString (std::string &result, xmlNodePtr node, const std::
     
     int CIXml::getIntProperty(xmlNodePtr node, const std::string &property, int defaultValue)
     {
    -	CSString s;
    -	bool b;
    +	std::string s;
     
    -	b=getPropertyString(s,node,property);
    -	if (b==false)
    +	bool b = getPropertyString(s, node, property);
    +
    +	if (!b)
     		return defaultValue;
     
    -	s=s.strip();
    -	sint val=s.atoi();
    -	if (val==0 && s!="0")
    +	// remove leading and trailing spaces
    +	s = trim(s);
    +
    +	sint val;
    +	
    +	if (!fromString(s, val) || (val == 0 && s != "0"))
     	{
    -		nlwarning("bad integer value: %s",s.c_str());
    +		nlwarning("Bad integer value: %s",s.c_str());
     		return defaultValue;
     	}
     
    @@ -1097,14 +1099,25 @@ int CIXml::getIntProperty(xmlNodePtr node, const std::string &property, int defa
     
     double CIXml::getFloatProperty(xmlNodePtr node, const std::string &property, float defaultValue)
     {
    -	CSString s;
    -	bool b;
    +	std::string s;
     
    -	b=getPropertyString(s,node,property);
    -	if (b==false)
    +	bool b = getPropertyString(s, node, property);
    +
    +	if (!b)
     		return defaultValue;
     
    -	return s.strip().atof();
    +	// remove leading and trailing spaces
    +	s = trim(s);
    +
    +	float val;
    +
    +	if (!fromString(s, val))
    +	{
    +		nlwarning("Bad float value: %s", s.c_str());
    +		return defaultValue;
    +	}
    +
    +	return val;
     }
     
     // ***************************************************************************
    @@ -1112,10 +1125,10 @@ double CIXml::getFloatProperty(xmlNodePtr node, const std::string &property, flo
     std::string CIXml::getStringProperty(xmlNodePtr node, const std::string &property, const std::string& defaultValue)
     {
     	std::string s;
    -	bool b;
     
    -	b=getPropertyString(s,node,property);
    -	if (b==false)
    +	bool b = getPropertyString(s, node, property);
    +
    +	if (!b)
     		return defaultValue;
     
     	return s;
    @@ -1141,9 +1154,43 @@ bool CIXml::getContentString (std::string &result, xmlNodePtr node)
     
     // ***************************************************************************
     
    +void CIXml::initLibXml()
    +{
    +	if (_LibXmlIntialized) return;
    +
    +	_ErrorString.clear();
    +	
    +	// Set error handler
    +	xmlSetGenericErrorFunc	(NULL, xmlGenericErrorFuncRead);
    +
    +	LIBXML_TEST_VERSION
    +	
    +	// an error occured during initialization
    +	if (!_ErrorString.empty())
    +	{
    +		throw EXmlParsingError (_ErrorString);
    +	}
    +
    +	// Ask to get debug info
    +	xmlLineNumbersDefault(1);
    +
    +	_LibXmlIntialized = true;
    +}
    +
    +// ***************************************************************************
    +
     void CIXml::releaseLibXml()
     {
    +	if (!_LibXmlIntialized) return;
    +
     	xmlCleanupParser();
    +
    +	_LibXmlIntialized = false;
    +}
    +
    +std::string CIXml::getErrorString()
    +{
    +	return _ErrorString;
     }
     
     } // NLMISC
    diff --git a/code/nel/src/misc/o_xml.cpp b/code/nel/src/misc/o_xml.cpp
    index 2228b24dc..5458ebf4c 100644
    --- a/code/nel/src/misc/o_xml.cpp
    +++ b/code/nel/src/misc/o_xml.cpp
    @@ -17,6 +17,7 @@
     #include "stdmisc.h"
     
     #include "nel/misc/o_xml.h"
    +#include "nel/misc/i_xml.h"
     
     #ifndef NL_DONT_USE_EXTERNAL_CODE
     
    @@ -142,27 +143,15 @@ COXml::COXml () : IStream (false /* Output mode */)
     
     // ***************************************************************************
     
    -void xmlGenericErrorFuncWrite (void *ctx, const char *msg, ...)
    -{
    -	// Get the error string
    -	string str;
    -	NLMISC_CONVERT_VARGS (str, msg, NLMISC::MaxCStringSize);
    -	((COXml*)ctx)->_ErrorString += str;
    -}
    -
    -// ***************************************************************************
    -
     bool COXml::init (IStream *stream, const std::string &version)
     {
     	resetPtrTable();
     
    +	CIXml::initLibXml();
    +
     	// Output stream ?
     	if (!stream->isReading())
     	{
    -		// Set error handler
    -		_ErrorString.clear();
    -		xmlSetGenericErrorFunc	(this, xmlGenericErrorFuncWrite);
    -
     		// Set XML mode
     		setXMLMode (true);
     
    @@ -673,9 +662,10 @@ bool COXml::isStringValidForProperties (const std::string &str)
     
     // ***************************************************************************
     
    -const char *COXml::getErrorString () const
    +std::string COXml::getErrorString()
     {
    -	return _ErrorString.c_str ();
    +	// error string is managed by CIXml
    +	return CIXml::getErrorString();
     }
     
     } // NLMISC
    diff --git a/code/nel/src/misc/system_info.cpp b/code/nel/src/misc/system_info.cpp
    index 680329dcf..545de0cc6 100644
    --- a/code/nel/src/misc/system_info.cpp
    +++ b/code/nel/src/misc/system_info.cpp
    @@ -1390,7 +1390,7 @@ uint64 CSystemInfo::availableHDSpace (const string &filename)
     	struct statfs stfs;
     	if (::statfs(path.c_str(), &stfs) != 0) return 0;
     
    -	return (uint64)(stfs.f_bavail * stfs.f_bsize);
    +	return (uint64)stfs.f_bavail * (uint64)stfs.f_bsize;
     #else
     	ULARGE_INTEGER freeSpace = {0};
     	BOOL bRes = ::GetDiskFreeSpaceExW(utf8ToWide(path), &freeSpace, NULL, NULL);
    diff --git a/code/nel/src/sound/driver/dsound/buffer_dsound.cpp b/code/nel/src/sound/driver/dsound/buffer_dsound.cpp
    index 53fa8dcec..72423bba7 100644
    --- a/code/nel/src/sound/driver/dsound/buffer_dsound.cpp
    +++ b/code/nel/src/sound/driver/dsound/buffer_dsound.cpp
    @@ -23,6 +23,10 @@
     #include 
     #include 
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace NLMISC;
     using namespace std;
     
    diff --git a/code/nel/src/sound/driver/dsound/listener_dsound.cpp b/code/nel/src/sound/driver/dsound/listener_dsound.cpp
    index 3bed4f821..d8c81b58b 100644
    --- a/code/nel/src/sound/driver/dsound/listener_dsound.cpp
    +++ b/code/nel/src/sound/driver/dsound/listener_dsound.cpp
    @@ -18,6 +18,9 @@
     #include "listener_dsound.h"
     #include "sound_driver_dsound.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
     
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/dsound/sound_driver_dsound.cpp b/code/nel/src/sound/driver/dsound/sound_driver_dsound.cpp
    index 986993c49..d95b80ca0 100644
    --- a/code/nel/src/sound/driver/dsound/sound_driver_dsound.cpp
    +++ b/code/nel/src/sound/driver/dsound/sound_driver_dsound.cpp
    @@ -31,6 +31,9 @@
     #include "sound_driver_dsound.h"
     #include "listener_dsound.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
     
     using namespace std;
     using namespace NLMISC;
    diff --git a/code/nel/src/sound/driver/dsound/source_dsound.cpp b/code/nel/src/sound/driver/dsound/source_dsound.cpp
    index c3bff3289..b80d03590 100644
    --- a/code/nel/src/sound/driver/dsound/source_dsound.cpp
    +++ b/code/nel/src/sound/driver/dsound/source_dsound.cpp
    @@ -21,6 +21,9 @@
     #include "buffer_dsound.h"
     #include "listener_dsound.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
     
     using namespace NLMISC;
     using namespace std;
    diff --git a/code/nel/src/sound/driver/dsound/stddsound.h b/code/nel/src/sound/driver/dsound/stddsound.h
    index f550e8150..cb953e630 100644
    --- a/code/nel/src/sound/driver/dsound/stddsound.h
    +++ b/code/nel/src/sound/driver/dsound/stddsound.h
    @@ -14,6 +14,16 @@
     // You should have received a copy of the GNU Affero General Public License
     // along with this program.  If not, see .
     
    +#ifndef STDDSOUND_H
    +#define STDDSOUND_H
    +
    +#if defined(_MSC_VER) && defined(_DEBUG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
    +#endif
    +
     #include "nel/misc/types_nl.h"
     
     #define EAX_AVAILABLE 0
    @@ -44,4 +54,5 @@
     #include "nel/sound/driver/source.h"
     #include "nel/sound/driver/listener.h"
     
    +#endif
     /* end of file */
    diff --git a/code/nel/src/sound/driver/fmod/buffer_fmod.cpp b/code/nel/src/sound/driver/fmod/buffer_fmod.cpp
    index 69081b013..edba0bec2 100644
    --- a/code/nel/src/sound/driver/fmod/buffer_fmod.cpp
    +++ b/code/nel/src/sound/driver/fmod/buffer_fmod.cpp
    @@ -25,6 +25,10 @@
     #	include 
     #endif
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace NLMISC;
     using namespace std;
     
    diff --git a/code/nel/src/sound/driver/fmod/listener_fmod.cpp b/code/nel/src/sound/driver/fmod/listener_fmod.cpp
    index ad308c937..776a23d70 100644
    --- a/code/nel/src/sound/driver/fmod/listener_fmod.cpp
    +++ b/code/nel/src/sound/driver/fmod/listener_fmod.cpp
    @@ -18,6 +18,9 @@
     #include "listener_fmod.h"
     #include "sound_driver_fmod.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
     
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp b/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp
    index 3640869b6..9abfd1006 100644
    --- a/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp
    +++ b/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp
    @@ -18,6 +18,10 @@
     #include "music_channel_fmod.h"
     #include "sound_driver_fmod.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/fmod/sound_driver_fmod.cpp b/code/nel/src/sound/driver/fmod/sound_driver_fmod.cpp
    index 07455b963..d98aa600f 100644
    --- a/code/nel/src/sound/driver/fmod/sound_driver_fmod.cpp
    +++ b/code/nel/src/sound/driver/fmod/sound_driver_fmod.cpp
    @@ -26,6 +26,10 @@
     #include 
     #endif
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/fmod/source_fmod.cpp b/code/nel/src/sound/driver/fmod/source_fmod.cpp
    index 6026ce2f5..1224d647d 100644
    --- a/code/nel/src/sound/driver/fmod/source_fmod.cpp
    +++ b/code/nel/src/sound/driver/fmod/source_fmod.cpp
    @@ -21,6 +21,9 @@
     #include "buffer_fmod.h"
     #include "listener_fmod.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
     
     using namespace NLMISC;
     using namespace std;
    diff --git a/code/nel/src/sound/driver/fmod/stdfmod.h b/code/nel/src/sound/driver/fmod/stdfmod.h
    index afb1af3f1..4274adfe3 100644
    --- a/code/nel/src/sound/driver/fmod/stdfmod.h
    +++ b/code/nel/src/sound/driver/fmod/stdfmod.h
    @@ -14,6 +14,16 @@
     // You should have received a copy of the GNU Affero General Public License
     // along with this program.  If not, see .
     
    +#ifndef STDFMOD_H
    +#define STDFMOD_H
    +
    +#if defined(_MSC_VER) && defined(_DEBUG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
    +#endif
    +
     #include 
     
     #ifdef NL_OS_WINDOWS
    @@ -42,4 +52,6 @@
     #include "nel/sound/driver/source.h"
     #include "nel/sound/driver/listener.h"
     
    +#endif
    +
     /* end of file */
    diff --git a/code/nel/src/sound/driver/openal/buffer_al.cpp b/code/nel/src/sound/driver/openal/buffer_al.cpp
    index 352128c3e..ae0054096 100644
    --- a/code/nel/src/sound/driver/openal/buffer_al.cpp
    +++ b/code/nel/src/sound/driver/openal/buffer_al.cpp
    @@ -18,6 +18,10 @@
     #include "sound_driver_al.h"
     #include "buffer_al.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace NLMISC;
     
     namespace NLSOUND
    diff --git a/code/nel/src/sound/driver/openal/effect_al.cpp b/code/nel/src/sound/driver/openal/effect_al.cpp
    index 0eec97756..9a09ba7ac 100644
    --- a/code/nel/src/sound/driver/openal/effect_al.cpp
    +++ b/code/nel/src/sound/driver/openal/effect_al.cpp
    @@ -19,6 +19,10 @@
     #include "effect_al.h"
     #include "sound_driver_al.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     // using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/openal/listener_al.cpp b/code/nel/src/sound/driver/openal/listener_al.cpp
    index a79e551b6..f39f0f18f 100644
    --- a/code/nel/src/sound/driver/openal/listener_al.cpp
    +++ b/code/nel/src/sound/driver/openal/listener_al.cpp
    @@ -18,6 +18,10 @@
     #include "listener_al.h"
     #include "sound_driver_al.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace NLMISC;
     
     namespace NLSOUND {
    diff --git a/code/nel/src/sound/driver/openal/sound_driver_al.cpp b/code/nel/src/sound/driver/openal/sound_driver_al.cpp
    index ab9574483..d237f533a 100644
    --- a/code/nel/src/sound/driver/openal/sound_driver_al.cpp
    +++ b/code/nel/src/sound/driver/openal/sound_driver_al.cpp
    @@ -22,6 +22,10 @@
     #include "ext_al.h"
     #include "effect_al.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/openal/source_al.cpp b/code/nel/src/sound/driver/openal/source_al.cpp
    index c13db52af..825d9f1e9 100644
    --- a/code/nel/src/sound/driver/openal/source_al.cpp
    +++ b/code/nel/src/sound/driver/openal/source_al.cpp
    @@ -22,6 +22,10 @@
     #include "source_al.h"
     #include "ext_al.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     // #define NLSOUND_DEBUG_GAIN
     
     using namespace std;
    diff --git a/code/nel/src/sound/driver/xaudio2/adpcm_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/adpcm_xaudio2.cpp
    index 2db913268..e70fec076 100644
    --- a/code/nel/src/sound/driver/xaudio2/adpcm_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/adpcm_xaudio2.cpp
    @@ -20,6 +20,10 @@
     #include "buffer_xaudio2.h"
     #include "adpcm_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     // using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/xaudio2/buffer_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/buffer_xaudio2.cpp
    index 106d565f9..acdb95ab4 100644
    --- a/code/nel/src/sound/driver/xaudio2/buffer_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/buffer_xaudio2.cpp
    @@ -20,6 +20,10 @@
     #include "buffer_xaudio2.h"
     #include "sound_driver_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/xaudio2/effect_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/effect_xaudio2.cpp
    index 5c1f58722..ac3814226 100644
    --- a/code/nel/src/sound/driver/xaudio2/effect_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/effect_xaudio2.cpp
    @@ -21,6 +21,10 @@
     #include "listener_xaudio2.h"
     #include "effect_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/xaudio2/listener_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/listener_xaudio2.cpp
    index 10b7e7e48..c6b364046 100644
    --- a/code/nel/src/sound/driver/xaudio2/listener_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/listener_xaudio2.cpp
    @@ -20,6 +20,10 @@
     #include "sound_driver_xaudio2.h"
     #include "listener_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp
    index 529954006..992f6c66d 100644
    --- a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp
    @@ -22,6 +22,10 @@
     #include "effect_xaudio2.h"
     #include "sound_driver_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp
    index 021935e50..26566612c 100644
    --- a/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp
    +++ b/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp
    @@ -26,6 +26,10 @@
     #include "effect_xaudio2.h"
     #include "source_xaudio2.h"
     
    +#ifdef DEBUG_NEW
    +#define new DEBUG_NEW
    +#endif
    +
     using namespace std;
     using namespace NLMISC;
     
    diff --git a/code/nel/tools/nel_unit_test/CMakeLists.txt b/code/nel/tools/nel_unit_test/CMakeLists.txt
    index 7e26a2498..6abc06a49 100644
    --- a/code/nel/tools/nel_unit_test/CMakeLists.txt
    +++ b/code/nel/tools/nel_unit_test/CMakeLists.txt
    @@ -8,6 +8,6 @@ TARGET_LINK_LIBRARIES(nel_unit_test ${CPPTEST_LIBRARIES} nelmisc nelnet nelligo)
     NL_DEFAULT_PROPS(nel_unit_test "Unit Tests")
     NL_ADD_RUNTIME_FLAGS(nel_unit_test)
     
    -ADD_DEFINITIONS(-DNEL_UNIT_BASE="${PROJECT_SOURCE_DIR}/tools/nel_unit_test/")
    +ADD_DEFINITIONS(-DNEL_UNIT_BASE="${PROJECT_SOURCE_DIR}/nel/tools/nel_unit_test/")
     
     INSTALL(TARGETS nel_unit_test RUNTIME DESTINATION ${NL_BIN_PREFIX})
    diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg
    index 1bc9e8916..d63d5361d 100644
    --- a/code/ryzom/client/client_default.cfg
    +++ b/code/ryzom/client/client_default.cfg
    @@ -1,9 +1,9 @@
     //////////////////////////
    +//////////////////////////
     /// CLIENT CONFIG FILE ///
     //////////////////////////
     //////////////////////////
     
    -//////////////////////////
     
     // If you set this variable to 1, your client.cfg will be overwritten when you quit the client.
     // You will loose all the comments and identation in this file.
    @@ -23,16 +23,16 @@ PositionY  = 0;
     Frequency  = 0;
     Depth      = 32;
     Sleep      = -1;
    -ProcessPriority        = 0;        // -2 = idle, -1 = below normal, 0 = normal, 1 = above normal, 2 = high, 3 = real time
    -Contrast   = 0.0;                // -1.0 ~ 1.0
    -Luminosity = 0.0;                // -1.0 ~ 1.0
    -Gamma      = 0.0;                // -1.0 ~ 1.0
    -Contrast_min        = -1.0;
    -Luminosity_min        = -1.0;
    -Gamma_min        = -1.0;
    -Contrast_max        = 1.0;
    -Luminosity_max        = 1.0;
    -Gamma_max        = 1.0;
    +ProcessPriority	= 0;	// -2 = idle, -1 = below normal, 0 = normal, 1 = above normal, 2 = high, 3 = real time
    +Contrast   = 0.0;		// -1.0 ~ 1.0
    +Luminosity = 0.0;		// -1.0 ~ 1.0
    +Gamma      = 0.0;		// -1.0 ~ 1.0
    +Contrast_min	= -1.0;
    +Luminosity_min	= -1.0;
    +Gamma_min	= -1.0;
    +Contrast_max	= 1.0;
    +Luminosity_max	= 1.0;
    +Gamma_max	= 1.0;
     
     
     /////////////
    @@ -67,23 +67,23 @@ LanguageCode = "fr";                // english
     XMLInputFile = "input_config_v3.xml";
     
     XMLLoginInterfaceFiles = {
    -        "login_config.xml",
    -        "login_widgets.xml",
    -        "login_main.xml",
    -        "login_keys.xml",
    +	"login_config.xml",
    +	"login_widgets.xml",
    +	"login_main.xml",
    +	"login_keys.xml",
     };
     
     XMLOutGameInterfaceFiles = {
    -        "out_v2_config.xml",
    -        "out_v2_widgets.xml",
    -        "out_v2_connect.xml",
    -        "out_v2_intro.xml",
    -        "out_v2_select.xml",
    -        "out_v2_appear.xml",
    -        "out_v2_location.xml",
    -        "out_v2_crash.xml",
    -        "out_v2_hierarchy.xml",
    -        "out_v2_keys.xml",
    +	"out_v2_config.xml",
    +	"out_v2_widgets.xml",
    +	"out_v2_connect.xml",
    +	"out_v2_intro.xml",
    +	"out_v2_select.xml",
    +	"out_v2_appear.xml",
    +	"out_v2_location.xml",
    +	"out_v2_crash.xml",
    +	"out_v2_hierarchy.xml",
    +	"out_v2_keys.xml",
     };
     
      TexturesInterface    = {
    @@ -104,29 +104,29 @@ VerboseLog = 1;
     ///////////
     // MOUSE //
     ///////////
    -HardwareCursor                = 1;
    +HardwareCursor		= 1;
     
    -CursorSpeed                        = 1.0;                        // In pixels per mickey
    -CursorSpeed_min                = 0.5;
    -CursorSpeed_max                = 2.0;
    +CursorSpeed			= 1.0;			// In pixels per mickey
    +CursorSpeed_min		= 0.5;
    +CursorSpeed_max		= 2.0;
     
    -CursorAcceleration                = 40;                // Threshold in mickey
    -CursorAcceleration_min        = 20;
    -CursorAcceleration_max        = 80;
    +CursorAcceleration		= 40;		// Threshold in mickey
    +CursorAcceleration_min	= 20;
    +CursorAcceleration_max	= 80;
     
    -FreeLookSpeed                = 0.004;                // In radian per mickey
    -FreeLookSpeed_min        = 0.0001;
    -FreeLookSpeed_max        = 0.01;
    +FreeLookSpeed		= 0.004;		// In radian per mickey
    +FreeLookSpeed_min	= 0.0001;
    +FreeLookSpeed_max	= 0.01;
     
    -FreeLookAcceleration                = 40;        // Threshold in mickey
    -FreeLookAcceleration_min        = 20;
    -FreeLookAcceleration_max        = 80;
    +FreeLookAcceleration		= 40;	// Threshold in mickey
    +FreeLookAcceleration_min	= 20;
    +FreeLookAcceleration_max	= 80;
     
    -FreeLookInverted                        = 0;
    +FreeLookInverted			= 0;
     FreeLookTablet			= 0;
    -AutomaticCamera                      = 0;
    -DblClickMode                        = 1;
    -AutoEquipTool                        = 1;
    +AutomaticCamera      		= 0;
    +DblClickMode			= 1;
    +AutoEquipTool			= 1;
     
     ///////////////////
     // RENDER CONFIG //
    @@ -158,172 +158,173 @@ LandscapeThreshold_ps1  = 500.0;
     LandscapeThreshold_ps2  = 1000.0;
     LandscapeThreshold_ps3  = 2000.0;
     
    -Vision                = 500.000000;
    -Vision_min        = 200.000000;
    -Vision_max        = 800.000000;
    -Vision_step        = 100.000000;
    -Vision_ps0        = 200.0;
    -Vision_ps1        = 400.0;
    -Vision_ps2        = 500.0;
    -Vision_ps3        = 800.0;
    +Vision		= 500.000000;
    +Vision_min	= 200.000000;
    +Vision_max	= 800.000000;
    +Vision_step	= 100.000000;
    +Vision_ps0	= 200.0;
    +Vision_ps1	= 400.0;
    +Vision_ps2	= 500.0;
    +Vision_ps3	= 800.0;
     
    -MicroVeget      = 1;        // Enable/Disable MicroVeget.
    -MicroVeget_ps0        = 0;
    -MicroVeget_ps1        = 1;
    -MicroVeget_ps2        = 1;
    -MicroVeget_ps3        = 1;
    +MicroVeget      = 1;	// Enable/Disable MicroVeget.
    +MicroVeget_ps0	= 0;
    +MicroVeget_ps1	= 1;
    +MicroVeget_ps2	= 1;
    +MicroVeget_ps3	= 1;
     
    -MicroVegetDensity                = 80.0;
    -MicroVegetDensity_min        = 10.0;
    -MicroVegetDensity_max        = 100.0;
    -MicroVegetDensity_step        = 10.0;
    -MicroVegetDensity_ps0        = 10.0;        // not used since disabled!
    -MicroVegetDensity_ps1        = 30.0;
    -MicroVegetDensity_ps2        = 80.0;
    -MicroVegetDensity_ps3        = 100.0;
    +MicroVegetDensity		= 80.0;
    +MicroVegetDensity_min	= 10.0;
    +MicroVegetDensity_max	= 100.0;
    +MicroVegetDensity_step	= 10.0;
    +MicroVegetDensity_ps0	= 10.0;	// not used since disabled!
    +MicroVegetDensity_ps1	= 30.0;
    +MicroVegetDensity_ps2	= 80.0;
    +MicroVegetDensity_ps3	= 100.0;
     
     
     // *** FX
    -FxNbMaxPoly                = 20000;
    -FxNbMaxPoly_min        = 2000;
    -FxNbMaxPoly_max        = 50000;
    +FxNbMaxPoly		= 20000;
    +FxNbMaxPoly_min	= 2000;
    +FxNbMaxPoly_max	= 50000;
     FxNbMaxPoly_step= 2000;
    -FxNbMaxPoly_ps0        = 2000;
    -FxNbMaxPoly_ps1        = 10000;
    -FxNbMaxPoly_ps2        = 20000;
    -FxNbMaxPoly_ps3        = 50000;
    +FxNbMaxPoly_ps0	= 2000;
    +FxNbMaxPoly_ps1	= 10000;
    +FxNbMaxPoly_ps2	= 20000;
    +FxNbMaxPoly_ps3	= 50000;
     
     Cloud = 1;
    -Cloud_ps0        = 0        ;
    -Cloud_ps1        = 1        ;
    -Cloud_ps2        = 1        ;
    -Cloud_ps3        = 1        ;
    +Cloud_ps0	= 0	;
    +Cloud_ps1	= 1	;
    +Cloud_ps2	= 1	;
    +Cloud_ps3	= 1	;
     
    -CloudQuality                = 160.0;
    -CloudQuality_min        = 80.0;
    -CloudQuality_max        = 320.0;
    -CloudQuality_step        = 20.0;
    -CloudQuality_ps0        = 80.0;        // not used since disabled!
    -CloudQuality_ps1        = 80.0;
    -CloudQuality_ps2        = 160.0;
    -CloudQuality_ps3        = 320.0;
    +CloudQuality		= 160.0;
    +CloudQuality_min	= 80.0;
    +CloudQuality_max	= 320.0;
    +CloudQuality_step	= 20.0;
    +CloudQuality_ps0	= 80.0;	// not used since disabled!
    +CloudQuality_ps1	= 80.0;
    +CloudQuality_ps2	= 160.0;
    +CloudQuality_ps3	= 320.0;
     
    -CloudUpdate                = 1;
    -CloudUpdate_min        = 1;
    -CloudUpdate_max        = 8;
    +CloudUpdate		= 1;
    +CloudUpdate_min	= 1;
    +CloudUpdate_max	= 8;
     CloudUpdate_step= 1;
    -CloudUpdate_ps0        = 1;        // not used since disabled!
    -CloudUpdate_ps1        = 1;
    -CloudUpdate_ps2        = 1;
    -CloudUpdate_ps3        = 3;
    +CloudUpdate_ps0	= 1;	// not used since disabled!
    +CloudUpdate_ps1	= 1;
    +CloudUpdate_ps2	= 1;
    +CloudUpdate_ps3	= 3;
     
     Shadows     = 1;
    -Shadows_ps0        = 0;
    -Shadows_ps1        = 1;
    -Shadows_ps2        = 1;
    -Shadows_ps3        = 1;
    +Shadows_ps0	= 0;
    +Shadows_ps1	= 1;
    +Shadows_ps2	= 1;
    +Shadows_ps3	= 1;
     
    -FXAA                = 1;
    -FXAA_ps0        = 0;
    -FXAA_ps1        = 1;
    -FXAA_ps2        = 1;
    -FXAA_ps3        = 1;
    +FXAA		= 1;
    +FXAA_ps0	= 0;
    +FXAA_ps1	= 1;
    +FXAA_ps2	= 1;
    +FXAA_ps3	= 1;
     
    -AnisotropicFilter        = 0;
    +AnisotropicFilter	= 0;
     
     Bloom = 1;
    -Bloom_ps0        = 0;
    -Bloom_ps1        = 1;
    -Bloom_ps2        = 1;
    -Bloom_ps3        = 1;
    +Bloom_ps0	= 0;
    +Bloom_ps1	= 1;
    +Bloom_ps2	= 1;
    +Bloom_ps3	= 1;
     
     SquareBloom = 1;
    -SquareBloom_ps0        = 0;
    -SquareBloom_ps1        = 1;
    -SquareBloom_ps2        = 1;
    -SquareBloom_ps3        = 1;
    +SquareBloom_ps0	= 0;
    +SquareBloom_ps1	= 1;
    +SquareBloom_ps2	= 1;
    +SquareBloom_ps3	= 1;
     
     DensityBloom = 255.0;
    -DensityBloom_min        = 0.0;
    -DensityBloom_max        = 255.0;
    -DensityBloom_step        = 1.0;
    -DensityBloom_ps0        = 255.0;
    -DensityBloom_ps1        = 255.0;
    -DensityBloom_ps2        = 255.0;
    -DensityBloom_ps3        = 255.0;
    +DensityBloom_min	= 0.0;
    +DensityBloom_max	= 255.0;
    +DensityBloom_step	= 1.0;
    +DensityBloom_ps0	= 255.0;
    +DensityBloom_ps1	= 255.0;
    +DensityBloom_ps2	= 255.0;
    +DensityBloom_ps3	= 255.0;
     
     
     // *** CHARACTERS
    -SkinNbMaxPoly                = 100000;
    -SkinNbMaxPoly_min        = 5000;
    -SkinNbMaxPoly_max        = 250000;
    -SkinNbMaxPoly_step        = 5000;
    -SkinNbMaxPoly_ps0        = 10000;
    -SkinNbMaxPoly_ps1        = 70000;
    -SkinNbMaxPoly_ps2        = 100000;
    -SkinNbMaxPoly_ps3        = 200000;
    +SkinNbMaxPoly		= 100000;
    +SkinNbMaxPoly_min	= 5000;
    +SkinNbMaxPoly_max	= 250000;
    +SkinNbMaxPoly_step	= 5000;
    +SkinNbMaxPoly_ps0	= 10000;
    +SkinNbMaxPoly_ps1	= 70000;
    +SkinNbMaxPoly_ps2	= 100000;
    +SkinNbMaxPoly_ps3	= 200000;
     
    -NbMaxSkeletonNotCLod                = 125;
    -NbMaxSkeletonNotCLod_min        = 5;
    -NbMaxSkeletonNotCLod_max        = 255;
    -NbMaxSkeletonNotCLod_step        = 5;
    -NbMaxSkeletonNotCLod_ps0        = 10;
    -NbMaxSkeletonNotCLod_ps1        = 50;
    -NbMaxSkeletonNotCLod_ps2        = 125;
    -NbMaxSkeletonNotCLod_ps3        = 255;
    +NbMaxSkeletonNotCLod		= 125;
    +NbMaxSkeletonNotCLod_min	= 5;
    +NbMaxSkeletonNotCLod_max	= 255;
    +NbMaxSkeletonNotCLod_step	= 5;
    +NbMaxSkeletonNotCLod_ps0	= 10;
    +NbMaxSkeletonNotCLod_ps1	= 50;
    +NbMaxSkeletonNotCLod_ps2	= 125;
    +NbMaxSkeletonNotCLod_ps3	= 255;
     
    -CharacterFarClip                = 200.0;
    -CharacterFarClip_min        = 50.0;
    -CharacterFarClip_max        = 500.0;
    -CharacterFarClip_step        = 10.0;
    -CharacterFarClip_ps0        = 50.0;
    -CharacterFarClip_ps1        = 100.0;
    -CharacterFarClip_ps2        = 200.0;
    -CharacterFarClip_ps3        = 500.0;
    +CharacterFarClip		= 200.0;
    +CharacterFarClip_min	= 50.0;
    +CharacterFarClip_max	= 500.0;
    +CharacterFarClip_step	= 10.0;
    +CharacterFarClip_ps0	= 50.0;
    +CharacterFarClip_ps1	= 100.0;
    +CharacterFarClip_ps2	= 200.0;
    +CharacterFarClip_ps3	= 500.0;
     
    -EnableRacialAnimation        = 1;
    +EnableRacialAnimation	= 1;
     
     // *** MISC
     // This is the actual aspect ratio of your screen (no relation with the resolution!!). Set 1.7777 if you got a 16/9 screen for instance
    -ScreenAspectRatio        = 0.0;
    -ForceDXTC           = 1;        // Enable/Disable DXTC.
    -DivideTextureSizeBy2= 0;        // Divide texture size
    -DisableVtxProgram   = 0;        // Disable Hardware Vertex Program.
    -DisableVtxAGP       = 0;        // Disable Hardware Vertex AGP.
    -DisableTextureShdr  = 0;        // Disable Hardware Texture Shader.
    +ScreenAspectRatio	= 0.0;
    +ForceDXTC           = 1;	// Enable/Disable DXTC.
    +DivideTextureSizeBy2= 0;	// Divide texture size
    +DisableVtxProgram   = 0;	// Disable Hardware Vertex Program.
    +DisableVtxAGP       = 0;	// Disable Hardware Vertex AGP.
    +DisableTextureShdr  = 0;	// Disable Hardware Texture Shader.
     HDEntityTexture     = 1;
     HDTextureInstalled  = 1;
    -WaitVBL             = 0;        // 0 or 1 to wait Vertical Sync.
    +WaitVBL             = 0;	// 0 or 1 to wait Vertical Sync.
     
     //////////////////
     // GAME OPTIONS //
     //////////////////
    -SelectWithRClick        = 1;
    -DisplayWeapons                = 1;
    -RotKeySpeedMax                = 2.0;
    -RotKeySpeedMax_min        = 1.0;
    -RotKeySpeedMax_max        = 4.0;
    -RotKeySpeedMin                = 1.0;
    -RotKeySpeedMin_min        = 0.5;
    -RotKeySpeedMin_max        = 2.0;
    -RotAccel                        = 3.0;
    -FollowOnAtk                        = 0;
    -AtkOnSelect                        = 0;
    -ZCPacsPrim                        = "gen_bt_col_ext.pacs_prim";
    +SelectWithRClick	= 1;
    +DisplayWeapons		= 1;
    +RotKeySpeedMax		= 2.0;
    +RotKeySpeedMax_min	= 1.0;
    +RotKeySpeedMax_max	= 4.0;
    +RotKeySpeedMin		= 1.0;
    +RotKeySpeedMin_min	= 0.5;
    +RotKeySpeedMin_max	= 2.0;
    +RotAccel			= 3.0;
    +FollowOnAtk			= 0;
    +AtkOnSelect			= 0;
    +ZCPacsPrim			= "gen_bt_col_ext.pacs_prim";
     
     /////////////////
     // PREFERENCES //
     /////////////////
    -FPV                                        = 0;        // FPV(First Person View) : default is false (Third Person View).
    -CameraHeight                = 2.2;        // Camera Height (in meter) from the ground (for the Third Person View).
    -CameraDistance                = 3.0;        // Camera Distance(in meter) from the user (for the Third Person View).
    -CameraDistStep                = 1.0;
    -CameraDistMin                = 1.0;
    +FPV					= 0;	// FPV(First Person View) : default is false (Third Person View).
    +CameraHeight		= 2.2;	// Camera Height (in meter) from the ground (for the Third Person View).
    +CameraDistance		= 3.0;	// Camera Distance(in meter) from the user (for the Third Person View).
    +CameraDistStep		= 1.0;
    +CameraDistMin		= 1.0;
    +CameraDistMax		= 25.0;
     CameraDistMax                = 250.0;
    -CameraAccel                        = 5.0;
    -CameraSpeedMin                = 2.0;
    -CameraSpeedMax                = 100.0;
    -CameraResetSpeed        = 10.0;                // Speed in radian/s
    +CameraAccel			= 5.0;
    +CameraSpeedMin		= 2.0;
    +CameraSpeedMax		= 100.0;
    +CameraResetSpeed	= 10.0;		// Speed in radian/s
     
     //////////////////
     // SOUND CONFIG //
    @@ -350,8 +351,7 @@ SoundGameMusicVolume_max = 1.0;
     SoundGameMusicVolume_step = 0.001;
     
     // MISC
    -PreDataPath = { "user", "data", "patch", "examples", };
    -DataPath = { "data" };
    +PreDataPath = { "user", "patch", "data", "examples" };
     NeedComputeVS = 0;
     
     NegFiltersDebug = {"Update DB", "Reading:", "Read Value :", "impulseCallBack", "CLIMPD:", "LNET" };
    @@ -359,15 +359,15 @@ NegFiltersInfo = { "CLIMPD:", "CPath::lookup" , "LNET" };
     NegFiltersWarning = { "'basics.Equipment Slot'.", "_usercolor.tga", "PACS" };
     
     // Big screen shot
    -ScreenShotWidth                 = 0;
    -ScreenShotHeight         = 0;
    +ScreenShotWidth		 = 0;
    +ScreenShotHeight	 = 0;
     ScreenShotFullDetail = 1; // 1 to switch full detail mode for characters (both standard & big screenshots)
     
     // Read : "ID", "R G B A MODE [FX]"
     SystemInfoColors =
     {
     // OLD STUFF Here for compatibility
    -"RG", "0   0   0   255 normal",        // Black to see when there is an error
    +"RG", "0   0   0   255 normal",	// Black to see when there is an error
     "BC", "0   0   0   255 normal", // Black to see when there is an error
     "JA", "0   0   0   255 normal", // Black to see when there is an error
     "BL", "0   0   0   255 normal", // Black to see when there is an error
    @@ -375,38 +375,38 @@ SystemInfoColors =
     "VI", "0   0   0   255 normal", // Black to see when there is an error
     
     // NEW System Info Categories
    -"SYS",        "255 255 255 255 normal",        // Default system messages
    -"BC",        "255 255 255 255 centeraround",        // Broadcast messages
    -"TAGBC",        "255 255 255 255 centeraround", // Taged broadcast messages : color should remain white as some word are tagged
    -"XP",        "255 255 64  255 over",                // XP Gain
    -"SP",        "255 255 64  255 over",                // SP Gain
    -"TTL",        "255 255 64  255 over",                // Title
    -"TSK",        "255 255 255 255 over",                // Task
    -"ZON",        "255 255 255 255 center",        // Zone
    -"DG",        "255 0   0   255 normal",        // Damage to me
    -"DMG",        "255 0   0   255 normal",        // Damage to me
    -"DGP",        "200 0   0   255 normal",        // Damage to me from player
    -"DGM",        "255 128 64  255 normal",        // Damage from me
    -"MIS",        "150 150 150 255 normal",        // The opponent misses
    -"MISM",        "255 255 255 255 normal",        // I miss
    -"ITM",        "0   200 0   255 over",                // Item
    -"ITMO",        "170 170 255 255 overonly",        // Item other in group
    -"ITMF",        "220 0   220 255 over",                // Item failed
    -"SPL",        "50  50  250 255 normal",        // Spell to me
    -"SPLM",        "50  150 250 255 normal",        // Spell from me
    -"EMT",        "255 150 150 255 normal",        // Emote
    -"MTD",        "255 255 0   255 over",                // Message Of The Day
    -"FORLD","64  255 64  255 overonly",        // Forage Locate Deposit
    -"CHK",        "255 120 60  255 center",        // Tous ce qui ne remplit pas une condition
    -"CHKCB","255 255  0  255 center",        // Tous ce qui ne remplit pas une condition en combat (trop loin, cible invalide, pas assez de mana, etc.)
    -"PVPTM","255 120 60  255 overonly",        // PVP timer
    -"THM",        "255 255 64  255 over misc_levelup.ps",        // Thema finished
    -"AMB",        "255 255 64  255 center",        // Ambiance
    -"ISE",        "192 208 255 255 normal",        // Item special effect
    -"ISE2",        "192 208 255 255 center",        // Item special effect with center text (for effects without flying text)
    -"OSM",        "128 160 255 255 center",        // Outpost state message
    -"AROUND","255 255 0 255 around",        // Only in around channel
    -"R2_INVITE","0 255 0 255 around",        // Ring invitation
    +"SYS",	"255 255 255 255 normal",	// Default system messages
    +"BC",	"255 255 255 255 centeraround",	// Broadcast messages
    +"TAGBC",	"255 255 255 255 centeraround", // Taged broadcast messages : color should remain white as some word are tagged
    +"XP",	"255 255 64  255 over",		// XP Gain
    +"SP",	"255 255 64  255 over",		// SP Gain
    +"TTL",	"255 255 64  255 over",		// Title
    +"TSK",	"255 255 255 255 over",		// Task
    +"ZON",	"255 255 255 255 center",	// Zone
    +"DG",	"255 0   0   255 normal",	// Damage to me
    +"DMG",	"255 0   0   255 normal",	// Damage to me
    +"DGP",	"200 0   0   255 normal",	// Damage to me from player
    +"DGM",	"255 128 64  255 normal",	// Damage from me
    +"MIS",	"150 150 150 255 normal",	// The opponent misses
    +"MISM",	"255 255 255 255 normal",	// I miss
    +"ITM",	"0   200 0   255 over",		// Item
    +"ITMO",	"170 170 255 255 overonly",	// Item other in group
    +"ITMF",	"220 0   220 255 over",		// Item failed
    +"SPL",	"50  50  250 255 normal",	// Spell to me
    +"SPLM",	"50  150 250 255 normal",	// Spell from me
    +"EMT",	"255 150 150 255 normal",	// Emote
    +"MTD",	"255 255 0   255 over",		// Message Of The Day
    +"FORLD","64  255 64  255 overonly",	// Forage Locate Deposit
    +"CHK",	"255 120 60  255 center",	// Tous ce qui ne remplit pas une condition
    +"CHKCB","255 255  0  255 center",	// Tous ce qui ne remplit pas une condition en combat (trop loin, cible invalide, pas assez de mana, etc.)
    +"PVPTM","255 120 60  255 overonly",	// PVP timer
    +"THM",	"255 255 64  255 over misc_levelup.ps",	// Thema finished
    +"AMB",	"255 255 64  255 center",	// Ambiance
    +"ISE",	"192 208 255 255 normal",	// Item special effect
    +"ISE2",	"192 208 255 255 center",	// Item special effect with center text (for effects without flying text)
    +"OSM",	"128 160 255 255 center",	// Outpost state message
    +"AROUND","255 255 0 255 around",	// Only in around channel
    +"R2_INVITE","0 255 0 255 around",	// Ring invitation
     };
     
     PrintfCommands       = {
    @@ -436,73 +436,74 @@ LoadingStringCount = 54;
     
     // Some R2 parameters ...
     
    -R2Mode                      = 1;
    +R2Mode               = 1;
     R2EDEnabled          = 1;
    -R2EDExtendedDebug         = 0;
    -R2EDLightPalette         = 0;
    +R2EDExtendedDebug	 = 0;
    +R2EDLightPalette	 = 0;
     R2ClientGw           = "r2linux01";
    -LoadLuaDebugger = 0;
    -CheckR2ScenarioMD5 = 1;
    -LevelDesignEnabled     = 0;
    +LoadLuaDebugger      = 0;
    +CheckR2ScenarioMD5   = 1;
    +LevelDesignEnabled   = 0;
     
    -DmCameraDistMax        = 25;
    -DmRun                  = 20;
    -DmWalk                 = 6;
    +DmCameraDistMax      = 25;
    +DmRun                = 20;
    +DmWalk               = 6;
     
     R2EDReloadFiles      = {
    -        "r2ed.xml",
    -        "r2_basic_bricks.lua",
    -        "r2_components.lua",
    -        "r2_core.lua",
    -        "r2_features_default.lua",
    -        "r2_features_fauna.lua",
    -        "r2_features_npc_groups.lua",
    -        "r2_palette.lua",
    -        "r2_scenario.lua",
    -        "r2_ui.lua"
    +	"r2ed.xml",
    +	"r2_basic_bricks.lua",
    +	"r2_components.lua",
    +	"r2_core.lua",
    +	"r2_features_default.lua",
    +	"r2_features_fauna.lua",
    +	"r2_features_npc_groups.lua",
    +	"r2_palette.lua",
    +	"r2_scenario.lua",
    +	"r2_ui.lua"
     };
     
     XMLInterfaceFiles    = {
    -        "config.xml",
    -        "widgets.xml",
    +	"config.xml",
    +	"widgets.xml",
     	"webig_widgets.xml",
     	"appzone.xml",
    -        "player.xml",
    -        "inventory.xml",
    -        "interaction.xml",
    +	"player.xml",
    +	"inventory.xml",
    +	"interaction.xml",
     	"phrase.xml",
    -        "harvest.xml",
    -        "macros.xml",
    -        "info_player.xml",
    -        "outpost.xml",
    -        "guild.xml",
    -        "taskbar.xml",
    +	"harvest.xml",
    +	"macros.xml",
    +	"info_player.xml",
    +	"outpost.xml",
    +	"guild.xml",
    +	"taskbar.xml",
     	"game_config.xml",
    -        "game_context_menu.xml",
    -        "player_trade.xml",
    -        "bot_chat_v4.xml",
    -        "compass.xml",
    -        "map.xml",
    +	"game_context_menu.xml",
    +	"player_trade.xml",
    +	"bot_chat_v4.xml",
    +	"compass.xml",
    +	"map.xml",
     	"hierarchy.xml",
    -        "reset.xml",
    -        "actions.xml",
    -        "help.xml",
    -        "encyclopedia.xml",
    -        "commands.xml",
    -        "commands2.xml",
    -        "ring_access_point_filter.xml",
    -        "ring_window.xml",
    +	"reset.xml",
    +	"actions.xml",
    +	"help.xml",
    +	"encyclopedia.xml",
    +	"commands.xml",
    +	"commands2.xml",
    +	"ring_access_point_filter.xml",
    +	"ring_window.xml",
     	"bg_downloader.xml",
    +	"ryzhome_toolbar.xml"
     };
     
     XMLR2EDInterfaceFiles =
     {
    -        "r2ed.xml",
    -        "r2_triggers.xml",
    -        "r2_logic_entities.xml",
    -        "r2ed_acts.xml",
    -        "r2ed_scenario.xml",
    -        "r2ed_connect.xml"
    +	"r2ed.xml",
    +	"r2_triggers.xml",
    +	"r2_logic_entities.xml",
    +	"r2ed_acts.xml",
    +	"r2ed_scenario.xml",
    +	"r2ed_connect.xml"
     };
     
     FogDistAndDepthLookupBias = 20; // bias for lookup of fog distance and depth
    @@ -514,45 +515,45 @@ FogDistAndDepthLookupBias = 20; // bias for lookup of fog distance and depth
     // * individual .tga files for hardware cursor bitmap not looked for, and not supported yet
     HardwareCursors =
     {
    -        "curs_can_pan.tga",
    -        "curs_can_pan_dup.tga",
    -        "curs_create.tga",
    -        "curs_create_multi.tga",
    -        "curs_create_vertex_invalid.tga",
    -        "curs_default.tga",
    -        "curs_dup.tga",
    -        "curs_L.tga",
    -        "curs_M.tga",
    -        "curs_pan.tga",
    -        "curs_pan_dup.tga",
    -        "curs_pick.tga",
    -        "curs_pick_dup.tga",
    -        "curs_R.tga",
    -        "curs_resize_BL_TR.tga",
    -        "curs_resize_BR_TL.tga",
    -        "curs_resize_LR.tga",
    -        "curs_resize_TB.tga",
    -        "curs_rotate.tga",
    -        "curs_scale.tga",
    -        "curs_stop.tga",
    -        "text_cursor.tga",
    -        "r2_hand_can_pan.tga",
    -        "r2_hand_pan.tga",
    -        "r2ed_tool_can_pick.tga",
    -        "r2ed_tool_can_rotate.tga",
    -        "r2ed_tool_pick.tga",
    -        "r2ed_tool_rotate.tga",
    -        "r2ed_tool_rotating.tga"
    +	"curs_can_pan.tga",
    +	"curs_can_pan_dup.tga",
    +	"curs_create.tga",
    +	"curs_create_multi.tga",
    +	"curs_create_vertex_invalid.tga",
    +	"curs_default.tga",
    +	"curs_dup.tga",
    +	"curs_L.tga",
    +	"curs_M.tga",
    +	"curs_pan.tga",
    +	"curs_pan_dup.tga",
    +	"curs_pick.tga",
    +	"curs_pick_dup.tga",
    +	"curs_R.tga",
    +	"curs_resize_BL_TR.tga",
    +	"curs_resize_BR_TL.tga",
    +	"curs_resize_LR.tga",
    +	"curs_resize_TB.tga",
    +	"curs_rotate.tga",
    +	"curs_scale.tga",
    +	"curs_stop.tga",
    +	"text_cursor.tga",
    +	"r2_hand_can_pan.tga",
    +	"r2_hand_pan.tga",
    +	"r2ed_tool_can_pick.tga",
    +	"r2ed_tool_can_rotate.tga",
    +	"r2ed_tool_pick.tga",
    +	"r2ed_tool_rotate.tga",
    +	"r2ed_tool_rotating.tga"
     };
     
    -Loading_BG                        = "new_loading_bg.tga";                        // Default name for the loading background file.
    -Launch_BG                        = "new_launcher_bg.tga";                        // Default name for the launch background file.
    -TeleportKami_BG                = "new_teleport_kami_bg.tga";
    -TeleportKaravan_BG        = "new_teleport_caravan_bg.tga";
    -Elevator_BG                        = "new_elevator_bg.tga";                // Default name for the loading background file.
    -ResurectKami_BG                = "new_resurect_kami_bg.tga";
    -ResurectKaravan_BG        = "new_resurect_caravane_bg.tga";
    -End_BG                                = "end_bg.tga";                                // Default name for the last background file.
    +Loading_BG			= "new_loading_bg.tga";			// Default name for the loading background file.
    +Launch_BG			= "new_launcher_bg.tga";			// Default name for the launch background file.
    +TeleportKami_BG		= "new_teleport_kami_bg.tga";
    +TeleportKaravan_BG	= "new_teleport_caravan_bg.tga";
    +Elevator_BG			= "new_elevator_bg.tga";		// Default name for the loading background file.
    +ResurectKami_BG		= "new_resurect_kami_bg.tga";
    +ResurectKaravan_BG	= "new_resurect_caravane_bg.tga";
    +End_BG				= "end_bg.tga";				// Default name for the last background file.
     
     ScenarioSavePath = "./my_scenarios/";
     
    @@ -564,9 +565,9 @@ ScenarioSavePath = "./my_scenarios/";
     BuiltInKeySets =
     {
             "",           // default khanat keyboard layout
    -        "bi.zqsd", // european keyboard fps displacement style (NB : don't change this layout name, ryzom will automatically select it if keyboard is french or belgian)
    -        "bi.wasd",  // english keyboard fps displacement style (NB : don't change this layout name, ryzom will automatically select it if keyboard is not french nor belgian)
    -        "bi.wow_alike" // 'world of warcraft' like keyboard style. (NB : not available for ring)
    +	"bi.zqsd", // european keyboard fps displacement style (NB : don't change this layout name, ryzom will automatically select it if keyboard is french or belgian)
    +	"bi.wasd",  // english keyboard fps displacement style (NB : don't change this layout name, ryzom will automatically select it if keyboard is not french nor belgian)
    +	"bi.wow_alike" // 'world of warcraft' like keyboard style. (NB : not available for ring)
     };
     
     // "Newbie Training", "Story Telling", "Mistery", "Hack & Slash", "Guild Training", "Other"
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/actions.xml b/code/ryzom/client/data/gamedev/interfaces_v3/actions.xml
    index 8696de32a..d628a6996 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/actions.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/actions.xml
    @@ -220,6 +220,21 @@
     		
     	
     	
    +	
    +		
    +	
    +	
    +		
    +		
    +			
    +			
    +			
    +			
    +			
    +			
    +			
    +		
    +	
     
     
      
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/bg_downloader.xml b/code/ryzom/client/data/gamedev/interfaces_v3/bg_downloader.xml
    index b1081d7af..914b4f2ff 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/bg_downloader.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/bg_downloader.xml
    @@ -2,48 +2,73 @@
     
     
     
    +
     
     
     
     	
    +	
     
     
     
     	
     
     
    +
     
    -
    -
    -	
    +	
    +	
     	
    -
    -	
    +	
    +			
     	
    +					
    +		
    +		
     
    -	
    -		
    -		
    -		
    -		
    -		
    -			
    +		
    +			
    +
    +				
    +						
     			
     			
    -			
    +						
     		
     	
     
     
     
     
    -
    +
    \ No newline at end of file
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/config.xml b/code/ryzom/client/data/gamedev/interfaces_v3/config.xml
    index 8544de2fc..d1039b185 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/config.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/config.xml
    @@ -3712,6 +3712,7 @@ This MUST follow the Enum MISSION_DESC::TIconId
       
       
    -      
         
         
       
       
    +    
    +    
    +    
    +      
    +      
    +      
    +      
    +      
    +    
    +    
    +      
    +             
    +        
    +        
    +        
    +        
    +        
    +      
    +    
    +  
    +  
     
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/help.xml b/code/ryzom/client/data/gamedev/interfaces_v3/help.xml
    index 00e1267de..298e0c9f5 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/help.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/help.xml
    @@ -906,7 +906,6 @@
                group_params_r="menu=ui:interface:base_menu_with_color"
                on_active="lua"
                on_active_params="help:openCSBrowserHeader('cs_browser')">
    -       
                
         
     
     
    -    
    -	
    -	
    -	
    +	
    +	
    +	
     	
    -	
    +	
     	
    -	
    -    
    +	
    +	
    +	
     
     
     
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/info_player.lua b/code/ryzom/client/data/gamedev/interfaces_v3/info_player.lua
    index 8768f3fee..64858c69c 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/info_player.lua
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/info_player.lua
    @@ -1431,6 +1431,8 @@ function game:onInGameDbInitialized()
     	end
     
     	game:setInfoPlayerCharacterRace()
    +
    +	runAH(nil, "sort_tribefame", "")
     end
     
     function game:onWebIgReady()
    @@ -1606,9 +1608,9 @@ end
     
     function game:addRpJob(jobtype, id, value, rpjobs)
     	local base_path = "ui:interface:info_player_skills:content:rpjobs:rpjob_"..jobtype.."_"..id..":rpjob_"..jobtype.."_infos_"..id
    -	
    +
     	local group = getUI("ui:interface:info_player_skills:content:rpjobs:rpjob_"..jobtype.."_"..id)
    -	
    +
     	if (value == nil) then
     		group.active = false
     	else
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
    index 9f672e961..bdf838f88 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
    @@ -217,7 +217,7 @@ end
     -- Is its level known (not too high ...)
     -- Boss/Mini-bosses/Names colored ring
     function game:updateTargetConsiderUI()
    -	--debugInfo("Updating consider widget")
    +	-- debugInfo("Updating consider widget")
     
     	local targetWindow = getUI("ui:interface:target")
     	--
    @@ -228,12 +228,14 @@ function game:updateTargetConsiderUI()
     	local wgToolTip     = targetWindow:find("target_tooltip")
     	local wgPvPTag      = targetWindow:find("pvp_tags")
     	local wgHeader      = targetWindow:find("header_opened")
    +	local wgLock        = targetWindow:find("lock")
     
     	wgTargetSlotForce.active = true
     	wgImpossible.active = true
     
     	-- no selection ?
     	if twGetTargetLevel() == -1 then
    +		wgLock.active = false
     		wgTargetSlotForce.active = false
     		wgTargetLevel.active = false
     		wgImpossible.active = false
    @@ -254,20 +256,36 @@ function game:updateTargetConsiderUI()
     	wgPvPTag.active = false
     	wgHeader.h = 34;
     
    +
    +-- /luaScript getUI("ui:interface:target:header_opened:lock").active=true
    +
     	-- if the selection is a player, then both the local & targeted player must be in PVP mode for the level to be displayed
     	if (twIsTargetPlayer()) then
     		-- don't display anything ...
    +		wgLock.active = false
     		wgTargetSlotForce.active = false
     		wgTargetLevel.active = false
     		wgImpossible.active = false
     		wgSlotRing.active  = false
     		wgToolTip.tooltip = ""
     		if twIsTargetInPVPMode() then
    -			debugInfo("target in pvp")
     			wgPvPTag.active = true
     			wgHeader.h = 56;
     		end
     		return
    +	else
    +		wgLock.active = false
    +		local level = getDbProp(getDefine("target_player_level"))
    +
    +		if level == 2 then -- Locked by team of player
    +			wgLock.active = true
    +			wgLock.color = "50 250 250 255"
    +		else
    +			if level == 1 then -- Locked by another team
    +				wgLock.active = true
    +				wgLock.color = "250 50 50 255"
    +			end
    +		end
     	end
     
     	-- depending on the number of people in the group, set the max diff for visibility between player level
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
    index 160c924af..639ac12b9 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
    @@ -277,6 +277,19 @@
                 global_color="false"
                 render_layer="1"
                 active="true" />
    +      
           
             
    +        
    +        
             
             
             
    +        
    +        
             
             
             
    +        
    +        
             
             
    @@ -168,6 +180,10 @@
                   type="I10" />
             
    +        
    +        
             
             
             
    +        
    +        
             
             
             
    +        
    +        
             
             
    @@ -309,6 +333,10 @@
                   type="I10" />
             
    +        
    +        
             
             
    @@ -352,6 +380,10 @@
                 type="I10" />
           
    +      
    +      
           
           
    diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/login_main.xml b/code/ryzom/client/data/gamedev/interfaces_v3/login_main.xml
    index 9b9eff8d8..293690214 100644
    --- a/code/ryzom/client/data/gamedev/interfaces_v3/login_main.xml
    +++ b/code/ryzom/client/data/gamedev/interfaces_v3/login_main.xml
    @@ -1,913 +1,912 @@
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -    
    -
    -  
    -
    -    
    -    
    -    
    -
    -    
    -    
    -    
    -    
    -
    -			
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -
    -	
    -    
    -
    -	
    -    
    -
    -	
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -
    -
    -    
    -																
    -    
    -    
    -    
    -
    -    
    -    
    -    
    -
    -    
    -    
    -
    -
    -      
    -    
    -
    -	
    -
    -     
    -    
    -
    -    
    -
    -
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -	
    -	
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -	
    -		
    -		
    -		
    -	
    -  
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -  
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -
    -    
    -    
    -    
    -
    -    
    -    
    -
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -      
    -      
    -    
    -      
    -      
    -      
    -    
    -
    -    
    -    
    -    
    -
    -    
    -    
    -    
    -
    -    
    -    
    -
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    	
    -    	
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -      
    -      
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -
    -
    -    
    -		
    -		
    -	    
    -	
    -    
    -    
    -    
    -    
    -    
    -	
    -
    -    
    -    
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -    
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -  
    -  
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -    
    -	
    -    
    -    
    -    
    -    
    -
    -    
    -	
    -    
    -    
    -    
    -
    -	
    -    
    -	
    -		
    -	
    -    
    -    
    -
    -
    -    
    -    
    -	
    -
    -  
    -
    -
    -
    -
    -
    -
    -
    -
    -