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