Fix css style order when using external css files
[ryzomcore.git] / nel / src / gui / group_html.cpp
blob3d37e42bc8ddfaf48cca71c0001dbdc8c87a7b6d
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
6 // Copyright (C) 2019-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 //#include <crtdbg.h>
23 #include "stdpch.h"
24 #include "nel/gui/group_html.h"
26 #include <string>
27 #include "nel/misc/types_nl.h"
28 #include "nel/misc/rgba.h"
29 #include "nel/misc/algo.h"
30 #include "nel/misc/utf_string_view.h"
31 #include "nel/gui/libwww.h"
32 #include "nel/gui/group_html.h"
33 #include "nel/gui/group_list.h"
34 #include "nel/gui/group_menu.h"
35 #include "nel/gui/group_container.h"
36 #include "nel/gui/view_link.h"
37 #include "nel/gui/ctrl_scroll.h"
38 #include "nel/gui/ctrl_button.h"
39 #include "nel/gui/ctrl_text_button.h"
40 #include "nel/gui/action_handler.h"
41 #include "nel/gui/group_paragraph.h"
42 #include "nel/gui/group_editbox.h"
43 #include "nel/gui/widget_manager.h"
44 #include "nel/gui/lua_manager.h"
45 #include "nel/gui/view_bitmap.h"
46 #include "nel/gui/dbgroup_combo_box.h"
47 #include "nel/gui/lua_ihm.h"
48 #include "nel/misc/i18n.h"
49 #include "nel/misc/md5.h"
50 #include "nel/3d/texture_file.h"
51 #include "nel/misc/big_file.h"
52 #include "nel/gui/url_parser.h"
53 #include "nel/gui/http_cache.h"
54 #include "nel/gui/http_hsts.h"
55 #include "nel/web/curl_certificates.h"
56 #include "nel/gui/html_parser.h"
57 #include "nel/gui/html_element.h"
58 #include "nel/gui/css_style.h"
59 #include "nel/gui/css_parser.h"
60 #include "nel/gui/css_border_renderer.h"
62 #include <curl/curl.h>
64 using namespace std;
65 using namespace NLMISC;
67 #ifdef DEBUG_NEW
68 #define new DEBUG_NEW
69 #endif
71 // Default maximum time the request is allowed to take
72 #define DEFAULT_RYZOM_CONNECTION_TIMEOUT (300.0)
73 // Allow up to 10 redirects, then give up
74 #define DEFAULT_RYZOM_REDIRECT_LIMIT (10)
76 #define FONT_WEIGHT_NORMAL 400
77 #define FONT_WEIGHT_BOLD 700
79 namespace NLGUI
82 // Uncomment nlwarning() to see the log about curl downloads
83 #define LOG_DL(fmt, ...) //nlwarning(fmt, ## __VA_ARGS__)
84 // Uncomment to log curl progess
85 //#define LOG_CURL_PROGRESS 1
87 CGroupHTML::SWebOptions CGroupHTML::options;
89 // Return URL with https is host is in HSTS list
90 static std::string upgradeInsecureUrl(const std::string &url)
92 if (toLowerAscii(url.substr(0, 7)) != "http://") {
93 return url;
96 CUrlParser uri(url);
97 if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){
98 return url;
101 LOG_DL("HSTS url : '%s', using https", url.c_str());
102 uri.scheme = "https";
104 return uri.toString();
107 // Active cURL www transfer
108 class CCurlWWWData
110 public:
111 CCurlWWWData(CURL *curl, const std::string &url)
112 : Request(curl), Url(url), Content(""), HeadersSent(NULL)
115 ~CCurlWWWData()
117 if (Request)
118 curl_easy_cleanup(Request);
120 if (HeadersSent)
121 curl_slist_free_all(HeadersSent);
124 void sendHeaders(const std::vector<std::string> headers)
126 for(uint i = 0; i < headers.size(); ++i)
128 HeadersSent = curl_slist_append(HeadersSent, headers[i].c_str());
130 curl_easy_setopt(Request, CURLOPT_HTTPHEADER, HeadersSent);
133 void setRecvHeader(const std::string &header)
135 size_t pos = header.find(": ");
136 if (pos == std::string::npos)
137 return;
139 std::string key = toLowerAscii(header.substr(0, pos));
140 if (pos != std::string::npos)
142 HeadersRecv[key] = header.substr(pos + 2);
143 //nlinfo(">> received header '%s' = '%s'", key.c_str(), HeadersRecv[key].c_str());
147 // return last received "Location: <url>" header or empty string if no header set
148 const std::string getLocationHeader()
150 if (HeadersRecv.count("location") > 0)
151 return HeadersRecv["location"];
153 return "";
156 const uint32 getExpires()
158 time_t ret = 0;
159 if (HeadersRecv.count("expires") > 0)
160 ret = curl_getdate(HeadersRecv["expires"].c_str(), NULL);
162 return ret > -1 ? ret : 0;
165 const std::string getLastModified()
167 if (HeadersRecv.count("last-modified") > 0)
169 return HeadersRecv["last-modified"];
172 return "";
175 const std::string getEtag()
177 if (HeadersRecv.count("etag") > 0)
179 return HeadersRecv["etag"];
182 return "";
185 bool hasHSTSHeader()
187 // ignore header if not secure connection
188 if (toLowerAscii(Url.substr(0, 8)) != "https://")
190 return false;
193 return HeadersRecv.count("strict-transport-security") > 0;
196 const std::string getHSTSHeader()
198 if (hasHSTSHeader())
200 return HeadersRecv["strict-transport-security"];
203 return "";
206 public:
207 CURL *Request;
209 std::string Url;
210 std::string Content;
212 private:
213 // headers sent with curl request, must be released after transfer
214 curl_slist * HeadersSent;
216 // headers received from curl transfer
217 std::map<std::string, std::string> HeadersRecv;
220 // cURL transfer callbacks
221 // ***************************************************************************
222 static size_t curlHeaderCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
224 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
225 if (me)
227 std::string header;
228 header.append(buffer, size * nmemb);
229 me->setRecvHeader(header.substr(0, header.find_first_of("\n\r")));
232 return size * nmemb;
235 // ***************************************************************************
236 static size_t curlDataCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
238 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
239 if (me)
240 me->Content.append(buffer, size * nmemb);
242 return size * nmemb;
245 // ***************************************************************************
246 static size_t curlProgressCallback(void *pCCurlWWWData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
248 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
249 if (me)
251 if (dltotal > 0 || dlnow > 0 || ultotal > 0 || ulnow > 0)
253 #ifdef LOG_CURL_PROGRESS
254 nlwarning("> dltotal %ld, dlnow %ld, ultotal %ld, ulnow %ld, url '%s'", dltotal, dlnow, ultotal, ulnow, me->Url.c_str());
255 #endif
259 // return 1 to cancel download
260 return 0;
263 CGroupHTML::CDataDownload::~CDataDownload()
265 delete data;
266 data = NULL;
269 // Check if domain is on TrustedDomain
270 bool CGroupHTML::isTrustedDomain(const string &domain)
272 vector<string>::iterator it;
273 it = find ( options.trustedDomains.begin(), options.trustedDomains.end(), domain);
274 return it != options.trustedDomains.end();
277 // Update view after download has finished
278 void CGroupHTML::setImage(CViewBase * view, const string &file, const TImageType type)
280 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
281 if(btn)
283 if (type == NormalImage)
285 btn->setTexture (file);
286 btn->setTexturePushed(file);
287 btn->invalidateCoords();
288 btn->invalidateContent();
289 paragraphChange();
291 else
293 btn->setTextureOver(file);
296 return;
299 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
300 if(btm)
302 btm->setTexture (file);
303 btm->invalidateCoords();
304 btm->invalidateContent();
305 paragraphChange();
307 return;
310 CGroupCell *btgc = dynamic_cast<CGroupCell*>(view);
311 if(btgc)
313 btgc->setTexture (file);
314 btgc->invalidateCoords();
315 btgc->invalidateContent();
316 paragraphChange();
318 return;
321 CGroupTable *table = dynamic_cast<CGroupTable*>(view);
322 if (table)
324 table->setTexture(file);
326 return;
330 // Force image width, height
331 void CGroupHTML::setImageSize(CViewBase *view, const CStyleParams &style)
333 sint32 width = style.Width;
334 sint32 height = style.Height;
335 sint32 maxw = style.MaxWidth;
336 sint32 maxh = style.MaxHeight;
338 sint32 imageWidth, imageHeight;
339 bool changed = true;
341 // get image texture size
342 // if image is being downloaded, then correct size is set after thats done
343 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
344 if(btn)
346 btn->fitTexture();
347 imageWidth = btn->getW(false);
348 imageHeight = btn->getH(false);
350 else
352 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
353 if(btm)
355 btm->fitTexture();
356 imageWidth = btm->getW(false);
357 imageHeight = btm->getH(false);
359 else
361 // not supported
362 return;
366 // if width/height is not requested, then use image size
367 // else recalculate missing value, keep image ratio
368 if (width == -1 && height == -1)
370 width = imageWidth;
371 height = imageHeight;
373 changed = false;
375 else
376 if (width == -1 || height == -1) {
377 float ratio = (float) imageWidth / std::max(1, imageHeight);
378 if (width == -1)
379 width = height * ratio;
380 else
381 height = width / ratio;
384 // apply max-width, max-height rules if asked
385 if (maxw > -1 || maxh > -1)
387 _Style.applyCssMinMax(width, height, 0, 0, maxw, maxh);
388 changed = true;
391 if (changed)
393 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
394 if(btn)
396 btn->setScale(true);
397 btn->setW(width);
398 btn->setH(height);
400 else
402 CViewBitmap *image = dynamic_cast<CViewBitmap*>(view);
403 if(image)
405 image->setScale(true);
406 image->setW(width);
407 image->setH(height);
413 void CGroupHTML::setTextButtonStyle(CCtrlTextButton *ctrlButton, const CStyleParams &style)
415 // this will also set size for <a class="ryzom-ui-button"> treating it like "display: inline-block;"
416 if (style.Width > 0) ctrlButton->setWMin(style.Width);
417 if (style.Height > 0) ctrlButton->setHMin(style.Height);
419 CViewText *pVT = ctrlButton->getViewText();
420 if (pVT)
422 setTextStyle(pVT, style);
425 if (style.hasStyle("background-color"))
427 ctrlButton->setColor(style.BackgroundColor);
428 if (style.hasStyle("-ryzom-background-color-over"))
430 ctrlButton->setColorOver(style.BackgroundColorOver);
432 else
434 ctrlButton->setColorOver(style.BackgroundColor);
436 ctrlButton->setTexture("", "blank.tga", "", false);
437 ctrlButton->setTextureOver("", "blank.tga", "");
438 ctrlButton->setProperty("force_text_over", "true");
440 else if (style.hasStyle("-ryzom-background-color-over"))
442 ctrlButton->setColorOver(style.BackgroundColorOver);
443 ctrlButton->setProperty("force_text_over", "true");
444 ctrlButton->setTextureOver("blank.tga", "blank.tga", "blank.tga");
448 void CGroupHTML::setTextStyle(CViewText *pVT, const CStyleParams &style)
450 if (pVT)
452 pVT->setColor(style.TextColor);
453 pVT->setFontName(style.FontFamily);
454 pVT->setFontSize(style.FontSize, false);
455 pVT->setEmbolden(style.FontWeight >= FONT_WEIGHT_BOLD);
456 pVT->setOblique(style.FontOblique);
457 pVT->setUnderlined(style.Underlined);
458 pVT->setStrikeThrough(style.StrikeThrough);
459 if (style.TextShadow.Enabled)
461 pVT->setShadow(true);
462 pVT->setShadowColor(style.TextShadow.Color);
463 pVT->setShadowOutline(style.TextShadow.Outline);
464 pVT->setShadowOffset(style.TextShadow.X, style.TextShadow.Y);
469 // Get an url and return the local filename with the path where the url image should be
470 string CGroupHTML::localImageName(const string &url)
472 string dest = "cache/";
473 dest += getMD5((uint8 *)url.c_str(), (uint32)url.size()).toString();
474 dest += ".cache";
475 return dest;
478 void CGroupHTML::pumpCurlQueue()
480 if (RunningCurls < options.curlMaxConnections)
482 std::list<CDataDownload>::iterator it=Curls.begin();
483 uint c = 0;
484 while(it != Curls.end() && RunningCurls < options.curlMaxConnections)
486 if (it->data == NULL)
488 LOG_DL("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str());
489 if (!startCurlDownload(*it))
491 LOG_DL("(%s) failed to start '%s)'", _Id.c_str(), it->url.c_str());
492 finishCurlDownload(*it);
493 it = Curls.erase(it);
494 continue;
498 ++it;
502 if (RunningCurls > 0 || !Curls.empty())
503 LOG_DL("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size());
506 // Add url to MultiCurl queue and return cURL handle
507 bool CGroupHTML::startCurlDownload(CDataDownload &download)
509 if (!MultiCurl)
511 nlwarning("Invalid MultiCurl handle, unable to download '%s'", download.url.c_str());
512 return false;
515 time_t currentTime;
516 time(&currentTime);
518 CHttpCacheObject cache;
519 if (CFile::fileExists(download.dest))
520 cache = CHttpCache::getInstance()->lookup(download.dest);
522 if (cache.Expires > currentTime)
524 LOG_DL("Cache for (%s) is not expired (%s, expires:%d)", download.url.c_str(), download.dest.c_str(), cache.Expires - currentTime);
525 return false;
528 string tmpdest = download.dest + ".tmp";
530 // erase the tmp file if exists
531 if (CFile::fileExists(tmpdest))
533 CFile::deleteFile(tmpdest);
536 FILE *fp = nlfopen (tmpdest, "wb");
537 if (fp == NULL)
539 nlwarning("Can't open file '%s' for writing: code=%d '%s'", tmpdest.c_str (), errno, strerror(errno));
540 return false;
543 CURL *curl = curl_easy_init();
544 if (!curl)
546 fclose(fp);
547 CFile::deleteFile(tmpdest);
549 nlwarning("Creating cURL handle failed, unable to download '%s'", download.url.c_str());
550 return false;
552 LOG_DL("curl easy handle %p created for '%s'", curl, download.url.c_str());
554 // https://
555 if (toLowerAscii(download.url.substr(0, 8)) == "https://")
557 // if supported, use custom SSL context function to load certificates
558 NLWEB::CCurlCertificates::useCertificates(curl);
561 download.data = new CCurlWWWData(curl, download.url);
562 download.fp = fp;
564 // initial connection timeout, curl default is 300sec
565 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, download.ConnectionTimeout);
567 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true);
568 curl_easy_setopt(curl, CURLOPT_URL, download.url.c_str());
570 // limit curl to HTTP and HTTPS protocols only
571 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
572 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
574 std::vector<std::string> headers;
575 if (!cache.Etag.empty())
576 headers.push_back("If-None-Match: " + cache.Etag);
578 if (!cache.LastModified.empty())
579 headers.push_back("If-Modified-Since: " + cache.LastModified);
581 if (headers.size() > 0)
582 download.data->sendHeaders(headers);
584 // catch headers
585 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
586 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, download.data);
588 std::string userAgent = options.appName + "/" + options.appVersion;
589 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
591 CUrlParser uri(download.url);
592 if (!uri.host.empty())
593 sendCookies(curl, uri.host, isTrustedDomain(uri.host));
595 curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
596 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
598 CURLMcode ret = curl_multi_add_handle(MultiCurl, curl);
599 if (ret != CURLM_OK)
601 nlwarning("cURL multi handle %p error %d on '%s'", curl, ret, download.url.c_str());
602 return false;
605 RunningCurls++;
606 return true;
609 void CGroupHTML::finishCurlDownload(const CDataDownload &download)
611 std::string tmpfile = download.dest + ".tmp";
613 if (download.type == ImgType)
615 // there is race condition if two browser instances are downloading same file
616 // second instance deletes first tmpfile and creates new file for itself.
617 if (CFile::getFileSize(tmpfile) > 0)
621 // verify that image is not corrupted
622 uint32 w, h;
623 CBitmap::loadSize(tmpfile, w, h);
624 if (w != 0 && h != 0)
626 // if not tmpfile, then img is already in cache
627 if (CFile::fileExists(tmpfile))
629 if (CFile::fileExists(download.dest))
631 CFile::deleteFile(download.dest);
634 // to reload image on page, the easiest seems to be changing texture
635 // to temp file temporarily. that forces driver to reload texture from disk
636 // ITexture::touch() seem not to do this.
637 // cache was updated, first set texture as temp file
638 for(uint i = 0; i < download.imgs.size(); i++)
640 setImage(download.imgs[i].Image, tmpfile, download.imgs[i].Type);
641 setImageSize(download.imgs[i].Image, download.imgs[i].Style);
644 CFile::moveFile(download.dest, tmpfile);
647 for(uint i = 0; i < download.imgs.size(); i++)
649 setImage(download.imgs[i].Image, download.dest, download.imgs[i].Type);
650 setImageSize(download.imgs[i].Image, download.imgs[i].Style);
655 catch(const NLMISC::Exception &e)
657 // exception message has .tmp file name, so keep it for further analysis
658 nlwarning("Invalid image (%s): %s", download.url.c_str(), e.what());
662 return;
665 if (download.type == StylesheetType)
667 if (CFile::fileExists(tmpfile))
669 if (CFile::fileExists(download.dest))
671 CFile::deleteFile(download.dest);
673 CFile::moveFile(download.dest, tmpfile);
675 cssDownloadFinished(download.url, download.dest);
677 return;
680 if (download.type == BnpType)
682 bool verified = false;
683 // no tmpfile if file was already in cache
684 if (CFile::fileExists(tmpfile))
686 verified = download.md5sum.empty() || (download.md5sum != getMD5(tmpfile).toString());
687 if (verified)
689 if (CFile::fileExists(download.dest))
691 CFile::deleteFile(download.dest);
693 CFile::moveFile(download.dest, tmpfile);
695 else
697 CFile::deleteFile(tmpfile);
700 else if (CFile::fileExists(download.dest))
702 verified = download.md5sum.empty() || (download.md5sum != getMD5(download.dest).toString());
705 std::string script = "\nlocal __CURRENT_WINDOW__ = \""+this->_Id+"\"";
706 script += toString("\nlocal __DOWNLOAD_STATUS__ = %s\n", verified ? "true" : "false");
707 script += download.luaScript;
708 CLuaManager::getInstance().executeLuaScript(script, true );
710 return;
713 nlwarning("Unknown CURL download type (%d) finished '%s'", download.type, download.url.c_str());
716 // Add a image download request in the multi_curl
717 void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type, const std::string &placeholder)
719 std::string finalUrl;
720 img->setModulateGlobalColor(style.GlobalColor);
722 // data:image/png;base64,AA...==
723 if (startsWith(url, "data:image/"))
725 setImage(img, decodeURIComponent(url), type);
726 setImageSize(img, style);
727 return;
730 // load the image from local files/bnp
731 std::string image = CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga";
732 if (lookupLocalFile(finalUrl, image.c_str(), false))
734 setImage(img, image, type);
735 setImageSize(img, style);
736 return;
739 finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
741 // use requested url for local name (cache)
742 string dest = localImageName(url);
743 LOG_DL("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
745 // Display cached image while downloading new
746 if (type != OverImage)
748 std::string temp = dest;
749 if (!CFile::fileExists(temp))
751 temp = placeholder;
753 setImage(img, temp, type);
754 setImageSize(img, style);
757 // Search if we are not already downloading this url.
758 for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
760 if(it->url == finalUrl)
762 LOG_DL("already downloading '%s' img %p", finalUrl.c_str(), img);
763 it->imgs.push_back(CDataImageDownload(img, style, type));
764 return;
768 Curls.push_back(CDataDownload(finalUrl, dest, ImgType, img, "", "", style, type));
769 pumpCurlQueue();
772 void CGroupHTML::removeImageDownload(CViewBase *img)
774 for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
776 // check all active downloads because image does not keep url around
777 std::vector<CDataImageDownload>::iterator imgIter = it->imgs.begin();
778 while(imgIter != it->imgs.end())
780 if (imgIter->Image == img)
782 it->imgs.erase(imgIter);
783 break;
785 ++imgIter;
790 void CGroupHTML::initImageDownload()
792 LOG_DL("Init Image Download");
794 string pathName = "cache";
795 if ( ! CFile::isExists( pathName ) )
796 CFile::createDirectory( pathName );
800 // Get an url and return the local filename with the path where the bnp should be
801 string CGroupHTML::localBnpName(const string &url)
803 size_t lastIndex = url.find_last_of("/");
804 string dest = "user/"+url.substr(lastIndex+1);
805 return dest;
808 // Add a bnp download request in the multi_curl, return true if already downloaded
809 bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum)
811 url = upgradeInsecureUrl(getAbsoluteUrl(url));
813 // Search if we are not already downloading this url.
814 for(std::list<CDataDownload>::const_iterator it = Curls.begin(); it != Curls.end(); ++it)
816 if(it->url == url)
818 LOG_DL("already downloading '%s'", url.c_str());
819 return false;
823 string dest = localBnpName(url);
824 LOG_DL("add to download '%s' dest '%s'", url.c_str(), dest.c_str());
826 // create/delete the local file
827 if (NLMISC::CFile::fileExists(dest))
829 if (action == "override" || action == "delete")
831 CFile::setRWAccess(dest);
832 NLMISC::CFile::deleteFile(dest);
834 else
836 return true;
839 if (action != "delete")
841 Curls.push_back(CDataDownload(url, dest, BnpType, NULL, script, md5sum));
842 pumpCurlQueue();
844 else
845 return true;
847 return false;
850 void CGroupHTML::initBnpDownload()
852 if (!_TrustedDomain)
853 return;
855 LOG_DL("Init Bnp Download");
856 string pathName = "user";
857 if ( ! CFile::isExists( pathName ) )
858 CFile::createDirectory( pathName );
861 void CGroupHTML::addStylesheetDownload(const std::vector<CHtmlParser::StyleLink> links)
863 for(uint i = 0; i < links.size(); ++i)
865 _StylesheetQueue.push_back(links[i]);
866 std::string url = getAbsoluteUrl(links[i].Url);
867 _StylesheetQueue.back().Url = url;
869 // push to the front of the queue
870 Curls.push_front(CDataDownload(url, localImageName(url), StylesheetType, NULL, "", ""));
872 pumpCurlQueue();
875 // Call this evenly to check if an element is downloaded and then manage it
876 void CGroupHTML::checkDownloads()
878 //nlassert(_CrtCheckMemory());
880 if(Curls.empty() && _CurlWWW == NULL)
882 return;
885 int NewRunningCurls = 0;
886 while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(MultiCurl, &NewRunningCurls))
888 LOG_DL("more to do now %d - %d curls", NewRunningCurls, Curls.size());
891 LOG_DL("NewRunningCurls:%d, RunningCurls:%d", NewRunningCurls, RunningCurls);
893 // check which downloads are done
894 CURLMsg *msg;
895 int msgs_left;
896 while ((msg = curl_multi_info_read(MultiCurl, &msgs_left)))
898 LOG_DL("> (%s) msgs_left %d", _Id.c_str(), msgs_left);
899 if (msg->msg == CURLMSG_DONE)
901 if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle)
903 std::string error;
904 bool success = msg->data.result == CURLE_OK;
905 if (!success)
907 error = curl_easy_strerror(msg->data.result);
909 LOG_DL("html download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
910 htmlDownloadFinished(success, error);
912 else
914 for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
916 if(it->data && it->data->Request == msg->easy_handle)
918 std::string error;
919 bool success = msg->data.result == CURLE_OK;
920 if (!success)
922 error = curl_easy_strerror(msg->data.result);
924 LOG_DL("data download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
925 dataDownloadFinished(success, error, *it);
927 Curls.erase(it);
928 break;
935 RunningCurls = NewRunningCurls;
936 pumpCurlQueue();
940 void CGroupHTML::releaseDownloads()
942 LOG_DL("Release Downloads");
944 if (_CurlWWW)
946 LOG_DL("(%s) stop html url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
947 if (MultiCurl)
948 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
950 delete _CurlWWW;
951 _CurlWWW = NULL;
954 // remove all queued and already started downloads
955 for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
957 if (it->data)
959 LOG_DL("(%s) stop data url '%s'", _Id.c_str(), it->url.c_str());
960 if (MultiCurl)
962 curl_multi_remove_handle(MultiCurl, it->data->Request);
965 // close and remove temp file
966 if (it->fp)
968 fclose(it->fp);
970 if (CFile::fileExists(it->dest + ".tmp"))
972 CFile::deleteFile(it->dest + ".tmp");
977 Curls.clear();
979 // also clear css queue as it depends on Curls
980 _StylesheetQueue.clear();
983 class CGroupListAdaptor : public CInterfaceGroup
985 public:
986 CGroupListAdaptor(const TCtorParam &param)
987 : CInterfaceGroup(param)
990 private:
991 void updateCoords()
993 if (_Parent)
995 // Get the W max from the parent
996 _W = std::min(_Parent->getMaxWReal(), _Parent->getWReal());
997 _WReal = _W;
999 CInterfaceGroup::updateCoords();
1003 // ***************************************************************************
1005 template<class A> void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); }
1007 // ***************************************************************************
1008 TStyle CGroupHTML::parseStyle (const string &str_styles)
1010 TStyle styles;
1011 vector<string> elements;
1012 NLMISC::splitString(str_styles, ";", elements);
1014 for(uint i = 0; i < elements.size(); ++i)
1016 vector<string> style;
1017 NLMISC::splitString(elements[i], ":", style);
1018 if (style.size() >= 2)
1020 string fullstyle = style[1];
1021 for (uint j=2; j < style.size(); j++)
1022 fullstyle += ":"+style[j];
1023 styles[trim(style[0])] = trimSeparators(fullstyle);
1027 return styles;
1030 // ***************************************************************************
1032 void CGroupHTML::addText (const char *buf, int len)
1034 if (_Browsing)
1036 if (_IgnoreText)
1037 return;
1039 // Build a UTF8 string
1040 if (_ParsingLua && _TrustedDomain)
1042 // we are parsing a lua script
1043 _LuaScript += string(buf, buf + len);
1044 // no more to do
1045 return;
1048 // Build a unicode string
1049 CUtfStringView inputStringView(buf, len);
1051 // Build the final unicode string
1052 string tmp;
1053 tmp.reserve(len);
1054 u32char lastChar = 0;
1055 u32char inputStringView0 = *inputStringView.begin();
1056 for (CUtfStringView::iterator it(inputStringView.begin()), end(inputStringView.end()); it != end; ++it)
1058 u32char output;
1059 bool keep;
1060 // special treatment for 'nbsp' (which is returned as a discreet space)
1061 if (len == 1 && inputStringView0 == 32)
1063 // this is a nbsp entity
1064 output = *it;
1065 keep = true;
1067 else
1069 // not nbsp, use normal white space removal routine
1070 keep = translateChar (output, *it, lastChar);
1073 if (keep)
1075 CUtfStringView::append(tmp, output);
1076 lastChar = output;
1080 if (!tmp.empty())
1081 addString(tmp);
1085 // ***************************************************************************
1087 #define registerAnchorName(prefix) \
1089 if (present[prefix##_ID] && value[prefix##_ID]) \
1090 _AnchorName.push_back(value[prefix##_ID]); \
1093 // ***************************************************************************
1094 void CGroupHTML::beginElement (CHtmlElement &elm)
1096 _Style.pushStyle();
1097 _CurrentHTMLElement = &elm;
1098 _CurrentHTMLNextSibling = elm.nextSibling;
1100 // set element style from css and style attribute
1101 _Style.getStyleFor(elm);
1102 if (!elm.Style.empty())
1104 _Style.applyStyle(elm.Style);
1107 if (elm.hasNonEmptyAttribute("name"))
1109 _AnchorName.push_back(elm.getAttribute("name"));
1111 if (elm.hasNonEmptyAttribute("id"))
1113 _AnchorName.push_back(elm.getAttribute("id"));
1116 if (_Style.Current.DisplayBlock)
1118 endParagraph();
1121 switch(elm.ID)
1123 case HTML_A: htmlA(elm); break;
1124 case HTML_BASE: htmlBASE(elm); break;
1125 case HTML_BODY: htmlBODY(elm); break;
1126 case HTML_BR: htmlBR(elm); break;
1127 case HTML_BUTTON: htmlBUTTON(elm); break;
1128 case HTML_DD: htmlDD(elm); break;
1129 case HTML_DEL: renderPseudoElement(":before", elm); break;
1130 case HTML_DIV: htmlDIV(elm); break;
1131 case HTML_DL: htmlDL(elm); break;
1132 case HTML_DT: htmlDT(elm); break;
1133 case HTML_EM: renderPseudoElement(":before", elm); break;
1134 case HTML_FONT: htmlFONT(elm); break;
1135 case HTML_FORM: htmlFORM(elm); break;
1136 case HTML_H1://no-break
1137 case HTML_H2://no-break
1138 case HTML_H3://no-break
1139 case HTML_H4://no-break
1140 case HTML_H5://no-break
1141 case HTML_H6: htmlH(elm); break;
1142 case HTML_HEAD: htmlHEAD(elm); break;
1143 case HTML_HR: htmlHR(elm); break;
1144 case HTML_HTML: htmlHTML(elm); break;
1145 case HTML_I: htmlI(elm); break;
1146 case HTML_IMG: htmlIMG(elm); break;
1147 case HTML_INPUT: htmlINPUT(elm); break;
1148 case HTML_LI: htmlLI(elm); break;
1149 case HTML_LUA: htmlLUA(elm); break;
1150 case HTML_META: htmlMETA(elm); break;
1151 case HTML_METER: htmlMETER(elm); break;
1152 case HTML_OBJECT: htmlOBJECT(elm); break;
1153 case HTML_OL: htmlOL(elm); break;
1154 case HTML_OPTION: htmlOPTION(elm); break;
1155 case HTML_P: htmlP(elm); break;
1156 case HTML_PRE: htmlPRE(elm); break;
1157 case HTML_PROGRESS: htmlPROGRESS(elm); break;
1158 case HTML_SCRIPT: htmlSCRIPT(elm); break;
1159 case HTML_SELECT: htmlSELECT(elm); break;
1160 case HTML_SMALL: renderPseudoElement(":before", elm); break;
1161 case HTML_SPAN: renderPseudoElement(":before", elm); break;
1162 case HTML_STRONG: renderPseudoElement(":before", elm); break;
1163 case HTML_STYLE: htmlSTYLE(elm); break;
1164 case HTML_TABLE: htmlTABLE(elm); break;
1165 case HTML_TBODY: renderPseudoElement(":before", elm); break;
1166 case HTML_TD: htmlTD(elm); break;
1167 case HTML_TEXTAREA: htmlTEXTAREA(elm); break;
1168 case HTML_TFOOT: renderPseudoElement(":before", elm); break;
1169 case HTML_TH: htmlTH(elm); break;
1170 case HTML_TITLE: htmlTITLE(elm); break;
1171 case HTML_TR: htmlTR(elm); break;
1172 case HTML_U: renderPseudoElement(":before", elm); break;
1173 case HTML_UL: htmlUL(elm); break;
1174 default:
1175 renderPseudoElement(":before", elm);
1176 break;
1180 // ***************************************************************************
1181 void CGroupHTML::endElement(CHtmlElement &elm)
1183 _CurrentHTMLElement = &elm;
1185 switch(elm.ID)
1187 case HTML_A: htmlAend(elm); break;
1188 case HTML_BASE: break;
1189 case HTML_BODY: renderPseudoElement(":after", elm); break;
1190 case HTML_BR: break;
1191 case HTML_BUTTON: htmlBUTTONend(elm); break;
1192 case HTML_DD: htmlDDend(elm); break;
1193 case HTML_DEL: renderPseudoElement(":after", elm); break;
1194 case HTML_DIV: htmlDIVend(elm); break;
1195 case HTML_DL: htmlDLend(elm); break;
1196 case HTML_DT: htmlDTend(elm); break;
1197 case HTML_EM: renderPseudoElement(":after", elm);break;
1198 case HTML_FONT: break;
1199 case HTML_FORM: htmlFORMend(elm); break;
1200 case HTML_H1://no-break
1201 case HTML_H2://no-break
1202 case HTML_H3://no-break
1203 case HTML_H4://no-break
1204 case HTML_H5://no-break
1205 case HTML_H6: htmlHend(elm); break;
1206 case HTML_HEAD: htmlHEADend(elm); break;
1207 case HTML_HR: break;
1208 case HTML_HTML: break;
1209 case HTML_I: htmlIend(elm); break;
1210 case HTML_IMG: break;
1211 case HTML_INPUT: break;
1212 case HTML_LI: htmlLIend(elm); break;
1213 case HTML_LUA: htmlLUAend(elm); break;
1214 case HTML_META: break;
1215 case HTML_METER: break;
1216 case HTML_OBJECT: htmlOBJECTend(elm); break;
1217 case HTML_OL: htmlOLend(elm); break;
1218 case HTML_OPTION: htmlOPTIONend(elm); break;
1219 case HTML_P: htmlPend(elm); break;
1220 case HTML_PRE: htmlPREend(elm); break;
1221 case HTML_SCRIPT: htmlSCRIPTend(elm); break;
1222 case HTML_SELECT: htmlSELECTend(elm); break;
1223 case HTML_SMALL: renderPseudoElement(":after", elm);break;
1224 case HTML_SPAN: renderPseudoElement(":after", elm);break;
1225 case HTML_STRONG: renderPseudoElement(":after", elm);break;
1226 case HTML_STYLE: htmlSTYLEend(elm); break;
1227 case HTML_TABLE: htmlTABLEend(elm); break;
1228 case HTML_TD: htmlTDend(elm); break;
1229 case HTML_TBODY: renderPseudoElement(":after", elm); break;
1230 case HTML_TEXTAREA: htmlTEXTAREAend(elm); break;
1231 case HTML_TFOOT: renderPseudoElement(":after", elm); break;
1232 case HTML_TH: htmlTHend(elm); break;
1233 case HTML_TITLE: htmlTITLEend(elm); break;
1234 case HTML_TR: htmlTRend(elm); break;
1235 case HTML_U: renderPseudoElement(":after", elm); break;
1236 case HTML_UL: htmlULend(elm); break;
1237 default:
1238 renderPseudoElement(":after", elm);
1239 break;
1242 if (_Style.Current.DisplayBlock)
1244 endParagraph();
1247 _Style.popStyle();
1250 // ***************************************************************************
1251 void CGroupHTML::renderPseudoElement(const std::string &pseudo, const CHtmlElement &elm)
1253 if (pseudo != ":before" && pseudo != ":after")
1254 return;
1256 if (!elm.hasPseudo(pseudo))
1257 return;
1259 _Style.pushStyle();
1260 _Style.applyStyle(elm.getPseudo(pseudo));
1262 // TODO: 'content' should already be tokenized in css parser as it has all the functions for that
1263 std::string content = trim(_Style.getStyle("content"));
1264 if (toLowerAscii(content) == "none" || toLowerAscii(content) == "normal")
1266 _Style.popStyle();
1267 return;
1270 std::string::size_type pos = 0;
1271 while(pos < content.size())
1273 std::string::size_type start;
1274 std::string token;
1276 // not supported
1277 // counter, open-quote, close-quote, no-open-quote, no-close-quote
1278 if (content[pos] == '"' || content[pos] == '\'')
1280 char quote = content[pos];
1281 pos++;
1282 start = pos;
1283 while(pos < content.size() && content[pos] != quote)
1285 if (content[pos] == '\\') pos++;
1286 pos++;
1288 token = content.substr(start, pos - start);
1289 addString(token);
1291 // skip closing quote
1292 pos++;
1294 else if (content[pos] == 'u' && pos < content.size() - 6 && toLowerAscii(content.substr(pos, 4)) == "url(")
1296 // url(/path-to/image.jpg) / "Alt!"
1297 // url("/path to/image.jpg") / "Alt!"
1298 std::string tooltip;
1300 start = pos + 4;
1301 // fails if url contains ')'
1302 pos = content.find(")", start);
1303 token = trim(content.substr(start, pos - start));
1304 // skip ')'
1305 pos++;
1307 // scan for tooltip
1308 start = pos;
1309 while(pos < content.size() && content[pos] == ' ' && content[pos] != '/')
1311 pos++;
1313 if (pos < content.size() && content[pos] == '/')
1315 // skip '/'
1316 pos++;
1318 // skip whitespace
1319 while(pos < content.size() && content[pos] == ' ')
1321 pos++;
1323 if (pos < content.size() && (content[pos] == '\'' || content[pos] == '"'))
1325 char openQuote = content[pos];
1326 pos++;
1327 start = pos;
1328 while(pos < content.size() && content[pos] != openQuote)
1330 if (content[pos] == '\\') pos++;
1331 pos++;
1333 tooltip = content.substr(start, pos - start);
1335 // skip closing quote
1336 pos++;
1338 else
1340 // tooltip should be quoted
1341 pos = start;
1342 tooltip.clear();
1345 else
1347 // no tooltip
1348 pos = start;
1351 if (tooltip.empty())
1353 addImage(getId() + pseudo, token, false, _Style.Current);
1355 else
1357 tooltip = trimQuotes(tooltip);
1358 addButton(CCtrlButton::PushButton, getId() + pseudo, token, token, "", "", "", tooltip.c_str(), _Style.Current);
1361 else if (content[pos] == 'a' && pos < content.size() - 7)
1363 // attr(title)
1364 start = pos + 5;
1365 pos = content.find(")", start);
1366 token = content.substr(start, pos - start);
1367 // skip ')'
1368 pos++;
1370 if (elm.hasAttribute(token))
1372 addString(elm.getAttribute(token));
1375 else
1377 pos++;
1381 _Style.popStyle();
1384 // ***************************************************************************
1385 void CGroupHTML::renderDOM(CHtmlElement &elm)
1387 if (elm.Type == CHtmlElement::TEXT_NODE)
1389 addText(elm.Value.c_str(), elm.Value.size());
1391 else
1393 beginElement(elm);
1395 std::list<CHtmlElement>::iterator it = elm.Children.begin();
1396 if (!_IgnoreChildElements)
1398 while(it != elm.Children.end())
1400 renderDOM(*it);
1402 ++it;
1405 _IgnoreChildElements = false;
1407 endElement(elm);
1411 // ***************************************************************************
1412 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html");
1415 // ***************************************************************************
1416 uint32 CGroupHTML::_GroupHtmlUIDPool= 0;
1417 CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID;
1420 // ***************************************************************************
1421 CGroupHTML::CGroupHTML(const TCtorParam &param)
1422 : CGroupScrollText(param),
1423 _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT),
1424 _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT),
1425 _CurrentHTMLElement(NULL)
1427 // add it to map of group html created
1428 _GroupHtmlUID= ++_GroupHtmlUIDPool; // valid assigned Id begin to 1!
1429 _GroupHtmlByUID[_GroupHtmlUID]= this;
1431 // init
1432 _TrustedDomain = false;
1433 _ParsingLua = false;
1434 _LuaHrefHack = false;
1435 _IgnoreText = false;
1436 _IgnoreChildElements = false;
1437 _BrowseNextTime = false;
1438 _PostNextTime = false;
1439 _Browsing = false;
1440 _CurrentViewLink = NULL;
1441 _CurrentViewImage = NULL;
1442 _Indent.clear();
1443 _LI = false;
1444 _SelectOption = false;
1445 _GroupListAdaptor = NULL;
1446 _UrlFragment.clear();
1447 _RefreshUrl.clear();
1448 _NextRefreshTime = 0.0;
1449 _LastRefreshTime = 0.0;
1450 _RenderNextTime = false;
1451 _WaitingForStylesheet = false;
1452 _AutoIdSeq = 0;
1453 _FormOpen = false;
1455 // Register
1456 CWidgetManager::getInstance()->registerClockMsgTarget(this);
1458 // HTML parameters
1459 BgColor = CRGBA::Black;
1460 ErrorColor = CRGBA(255, 0, 0);
1461 LinkColor = CRGBA(0, 0, 255);
1462 TextColor = CRGBA(255, 255, 255);
1463 H1Color = CRGBA(255, 255, 255);
1464 H2Color = CRGBA(255, 255, 255);
1465 H3Color = CRGBA(255, 255, 255);
1466 H4Color = CRGBA(255, 255, 255);
1467 H5Color = CRGBA(255, 255, 255);
1468 H6Color = CRGBA(255, 255, 255);
1469 ErrorColorGlobalColor = false;
1470 LinkColorGlobalColor = false;
1471 TextColorGlobalColor = false;
1472 H1ColorGlobalColor = false;
1473 H2ColorGlobalColor = false;
1474 H3ColorGlobalColor = false;
1475 H4ColorGlobalColor = false;
1476 H5ColorGlobalColor = false;
1477 H6ColorGlobalColor = false;
1478 TextFontSize = 9;
1479 H1FontSize = 18;
1480 H2FontSize = 15;
1481 H3FontSize = 12;
1482 H4FontSize = 9;
1483 H5FontSize = 9;
1484 H6FontSize = 9;
1485 LIBeginSpace = 4;
1486 ULBeginSpace = 12;
1487 PBeginSpace = 12;
1488 TDBeginSpace = 0;
1489 LIIndent = -10;
1490 ULIndent = 30;
1491 LineSpaceFontFactor = 0.5f;
1492 DefaultButtonGroup = "html_text_button";
1493 DefaultFormTextGroup = "edit_box_widget";
1494 DefaultFormTextAreaGroup = "edit_box_widget_multiline";
1495 DefaultFormSelectGroup = "html_form_select_widget";
1496 DefaultFormSelectBoxMenuGroup = "html_form_select_box_menu_widget";
1497 DefaultCheckBoxBitmapNormal = "checkbox_normal.tga";
1498 DefaultCheckBoxBitmapPushed = "checkbox_pushed.tga";
1499 DefaultCheckBoxBitmapOver = "checkbox_over.tga";
1500 DefaultRadioButtonBitmapNormal = "w_radiobutton.png";
1501 DefaultRadioButtonBitmapPushed = "w_radiobutton_pushed.png";
1502 DefaultBackgroundBitmapView = "bg";
1503 clearContext();
1505 MultiCurl = curl_multi_init();
1506 #ifdef CURLMOPT_MAX_HOST_CONNECTIONS
1507 if (MultiCurl)
1509 // added in libcurl 7.30.0
1510 curl_multi_setopt(MultiCurl, CURLMOPT_MAX_HOST_CONNECTIONS, options.curlMaxConnections);
1511 curl_multi_setopt(MultiCurl, CURLMOPT_PIPELINING, 1);
1513 #endif
1514 RunningCurls = 0;
1515 _CurlWWW = NULL;
1517 initImageDownload();
1518 initBnpDownload();
1520 // setup default browser style
1521 setProperty("browser_css_file", "browser.css");
1524 // ***************************************************************************
1526 CGroupHTML::~CGroupHTML()
1528 //releaseImageDownload();
1530 // TestYoyo
1531 //nlinfo("** CGroupHTML Destroy: %x, %s, uid%d", this, _Id.c_str(), _GroupHtmlUID);
1533 /* Erase from map of Group HTML (thus requestTerminated() callback won't be called)
1534 Do it first, just because don't want requestTerminated() to be called while I'm destroying
1535 (useless and may be dangerous)
1537 _GroupHtmlByUID.erase(_GroupHtmlUID);
1539 clearContext();
1540 releaseDownloads();
1542 if (_CurlWWW)
1543 delete _CurlWWW;
1545 if(MultiCurl)
1546 curl_multi_cleanup(MultiCurl);
1549 std::string CGroupHTML::getProperty( const std::string &name ) const
1551 if( name == "url" )
1553 return _URL;
1555 else
1556 if( name == "title_prefix" )
1558 return _TitlePrefix;
1560 else
1561 if( name == "background_color" )
1563 return toString( BgColor );
1565 else
1566 if( name == "error_color" )
1568 return toString( ErrorColor );
1570 else
1571 if( name == "link_color" )
1573 return toString( LinkColor );
1575 else
1576 if( name == "h1_color" )
1578 return toString( H1Color );
1580 else
1581 if( name == "h2_color" )
1583 return toString( H2Color );
1585 else
1586 if( name == "h3_color" )
1588 return toString( H3Color );
1590 else
1591 if( name == "h4_color" )
1593 return toString( H4Color );
1595 else
1596 if( name == "h5_color" )
1598 return toString( H5Color );
1600 else
1601 if( name == "h6_color" )
1603 return toString( H6Color );
1605 else
1606 if( name == "error_color_global_color" )
1608 return toString( ErrorColorGlobalColor );
1610 else
1611 if( name == "link_color_global_color" )
1613 return toString( LinkColorGlobalColor );
1615 else
1616 if( name == "text_color_global_color" )
1618 return toString( TextColorGlobalColor );
1620 else
1621 if( name == "h1_color_global_color" )
1623 return toString( H1ColorGlobalColor );
1625 else
1626 if( name == "h2_color_global_color" )
1628 return toString( H2ColorGlobalColor );
1630 else
1631 if( name == "h3_color_global_color" )
1633 return toString( H3ColorGlobalColor );
1635 else
1636 if( name == "h4_color_global_color" )
1638 return toString( H4ColorGlobalColor );
1640 else
1641 if( name == "h5_color_global_color" )
1643 return toString( H5ColorGlobalColor );
1645 else
1646 if( name == "h6_color_global_color" )
1648 return toString( H6ColorGlobalColor );
1650 else
1651 if( name == "text_font_size" )
1653 return toString( TextFontSize );
1655 else
1656 if( name == "h1_font_size" )
1658 return toString( H1FontSize );
1660 else
1661 if( name == "h2_font_size" )
1663 return toString( H2FontSize );
1665 else
1666 if( name == "h3_font_size" )
1668 return toString( H3FontSize );
1670 else
1671 if( name == "h4_font_size" )
1673 return toString( H4FontSize );
1675 else
1676 if( name == "h5_font_size" )
1678 return toString( H5FontSize );
1680 else
1681 if( name == "h6_font_size" )
1683 return toString( H6FontSize );
1685 else
1686 if( name == "td_begin_space" )
1688 return toString( TDBeginSpace );
1690 else
1691 if( name == "paragraph_begin_space" )
1693 return toString( PBeginSpace );
1695 else
1696 if( name == "li_begin_space" )
1698 return toString( LIBeginSpace );
1700 else
1701 if( name == "ul_begin_space" )
1703 return toString( ULBeginSpace );
1705 else
1706 if( name == "li_indent" )
1708 return toString( LIIndent );
1710 else
1711 if( name == "ul_indent" )
1713 return toString( ULIndent );
1715 else
1716 if( name == "multi_line_space_factor" )
1718 return toString( LineSpaceFontFactor );
1720 else
1721 if( name == "form_text_area_group" )
1723 return DefaultFormTextGroup;
1725 else
1726 if( name == "form_select_group" )
1728 return DefaultFormSelectGroup;
1730 else
1731 if( name == "checkbox_bitmap_normal" )
1733 return DefaultCheckBoxBitmapNormal;
1735 else
1736 if( name == "checkbox_bitmap_pushed" )
1738 return DefaultCheckBoxBitmapPushed;
1740 else
1741 if( name == "checkbox_bitmap_over" )
1743 return DefaultCheckBoxBitmapOver;
1745 else
1746 if( name == "radiobutton_bitmap_normal" )
1748 return DefaultRadioButtonBitmapNormal;
1750 else
1751 if( name == "radiobutton_bitmap_pushed" )
1753 return DefaultRadioButtonBitmapPushed;
1755 else
1756 if( name == "radiobutton_bitmap_over" )
1758 return DefaultRadioButtonBitmapOver;
1760 else
1761 if( name == "background_bitmap_view" )
1763 return DefaultBackgroundBitmapView;
1765 else
1766 if( name == "home" )
1768 return Home;
1770 else
1771 if( name == "browse_next_time" )
1773 return toString( _BrowseNextTime );
1775 else
1776 if( name == "browse_tree" )
1778 return _BrowseTree;
1780 else
1781 if( name == "browse_undo" )
1783 return _BrowseUndoButton;
1785 else
1786 if( name == "browse_redo" )
1788 return _BrowseRedoButton;
1790 else
1791 if( name == "browse_refresh" )
1793 return _BrowseRefreshButton;
1795 else
1796 if( name == "timeout" )
1798 return toString( _TimeoutValue );
1800 else
1801 if( name == "browser_css_file" )
1803 return _BrowserCssFile;
1805 else
1806 return CGroupScrollText::getProperty( name );
1809 void CGroupHTML::setProperty( const std::string &name, const std::string &value )
1811 if( name == "url" )
1813 _URL = value;
1814 return;
1816 else
1817 if( name == "title_prefix" )
1819 _TitlePrefix = value;
1820 return;
1822 else
1823 if( name == "background_color" )
1825 CRGBA c;
1826 if( fromString( value, c ) )
1827 BgColor = c;
1828 return;
1830 else
1831 if( name == "error_color" )
1833 CRGBA c;
1834 if( fromString( value, c ) )
1835 ErrorColor = c;
1836 return;
1838 else
1839 if( name == "link_color" )
1841 CRGBA c;
1842 if( fromString( value, c ) )
1843 LinkColor = c;
1844 return;
1846 else
1847 if( name == "h1_color" )
1849 CRGBA c;
1850 if( fromString( value, c ) )
1851 H1Color = c;
1852 return;
1854 else
1855 if( name == "h2_color" )
1857 CRGBA c;
1858 if( fromString( value, c ) )
1859 H2Color = c;
1860 return;
1862 else
1863 if( name == "h3_color" )
1865 CRGBA c;
1866 if( fromString( value, c ) )
1867 H3Color = c;
1868 return;
1870 else
1871 if( name == "h4_color" )
1873 CRGBA c;
1874 if( fromString( value, c ) )
1875 H4Color = c;
1876 return;
1878 else
1879 if( name == "h5_color" )
1881 CRGBA c;
1882 if( fromString( value, c ) )
1883 H5Color = c;
1884 return;
1886 else
1887 if( name == "h6_color" )
1889 CRGBA c;
1890 if( fromString( value, c ) )
1891 H6Color = c;
1892 return;
1894 else
1895 if( name == "error_color_global_color" )
1897 bool b;
1898 if( fromString( value, b ) )
1899 ErrorColorGlobalColor = b;
1900 return;
1902 else
1903 if( name == "link_color_global_color" )
1905 bool b;
1906 if( fromString( value, b ) )
1907 LinkColorGlobalColor = b;
1908 return;
1910 else
1911 if( name == "text_color_global_color" )
1913 bool b;
1914 if( fromString( value, b ) )
1915 TextColorGlobalColor = b;
1916 return;
1918 else
1919 if( name == "h1_color_global_color" )
1921 bool b;
1922 if( fromString( value, b ) )
1923 H1ColorGlobalColor = b;
1924 return;
1926 else
1927 if( name == "h2_color_global_color" )
1929 bool b;
1930 if( fromString( value, b ) )
1931 H2ColorGlobalColor = b;
1932 return;
1934 else
1935 if( name == "h3_color_global_color" )
1937 bool b;
1938 if( fromString( value, b ) )
1939 H3ColorGlobalColor = b;
1940 return;
1942 else
1943 if( name == "h4_color_global_color" )
1945 bool b;
1946 if( fromString( value, b ) )
1947 H4ColorGlobalColor = b;
1948 return;
1950 else
1951 if( name == "h5_color_global_color" )
1953 bool b;
1954 if( fromString( value, b ) )
1955 H5ColorGlobalColor = b;
1956 return;
1958 else
1959 if( name == "h6_color_global_color" )
1961 bool b;
1962 if( fromString( value, b ) )
1963 H6ColorGlobalColor = b;
1964 return;
1966 else
1967 if( name == "text_font_size" )
1969 uint i;
1970 if( fromString( value, i ) )
1971 TextFontSize = i;
1972 return;
1974 else
1975 if( name == "h1_font_size" )
1977 uint i;
1978 if( fromString( value, i ) )
1979 H1FontSize = i;
1980 return;
1982 else
1983 if( name == "h2_font_size" )
1985 uint i;
1986 if( fromString( value, i ) )
1987 H2FontSize = i;
1988 return;
1990 else
1991 if( name == "h3_font_size" )
1993 uint i;
1994 if( fromString( value, i ) )
1995 H3FontSize = i;
1996 return;
1998 else
1999 if( name == "h4_font_size" )
2001 uint i;
2002 if( fromString( value, i ) )
2003 H4FontSize = i;
2004 return;
2006 else
2007 if( name == "h5_font_size" )
2009 uint i;
2010 if( fromString( value, i ) )
2011 H5FontSize = i;
2012 return;
2014 else
2015 if( name == "h6_font_size" )
2017 uint i;
2018 if( fromString( value, i ) )
2019 H6FontSize = i;
2020 return;
2022 else
2023 if( name == "td_begin_space" )
2025 uint i;
2026 if( fromString( value, i ) )
2027 TDBeginSpace = i;
2028 return;
2030 else
2031 if( name == "paragraph_begin_space" )
2033 uint i;
2034 if( fromString( value, i ) )
2035 PBeginSpace = i;
2036 return;
2038 else
2039 if( name == "li_begin_space" )
2041 uint i;
2042 if( fromString( value, i ) )
2043 LIBeginSpace = i;
2044 return;
2046 else
2047 if( name == "ul_begin_space" )
2049 uint i;
2050 if( fromString( value, i ) )
2051 ULBeginSpace = i;
2052 return;
2054 else
2055 if( name == "li_indent" )
2057 uint i;
2058 if( fromString( value, i ) )
2059 LIIndent = i;
2060 return;
2062 else
2063 if( name == "ul_indent" )
2065 uint i;
2066 if( fromString( value, i ) )
2067 ULIndent = i;
2068 return;
2070 else
2071 if( name == "multi_line_space_factor" )
2073 float f;
2074 if( fromString( value, f ) )
2075 LineSpaceFontFactor = f;
2076 return;
2078 else
2079 if( name == "form_text_area_group" )
2081 DefaultFormTextGroup = value;
2082 return;
2084 else
2085 if( name == "form_select_group" )
2087 DefaultFormSelectGroup = value;
2088 return;
2090 else
2091 if( name == "checkbox_bitmap_normal" )
2093 DefaultCheckBoxBitmapNormal = value;
2094 return;
2096 else
2097 if( name == "checkbox_bitmap_pushed" )
2099 DefaultCheckBoxBitmapPushed = value;
2100 return;
2102 else
2103 if( name == "checkbox_bitmap_over" )
2105 DefaultCheckBoxBitmapOver = value;
2106 return;
2108 else
2109 if( name == "radiobutton_bitmap_normal" )
2111 DefaultRadioButtonBitmapNormal = value;
2112 return;
2114 else
2115 if( name == "radiobutton_bitmap_pushed" )
2117 DefaultRadioButtonBitmapPushed = value;
2118 return;
2120 else
2121 if( name == "radiobutton_bitmap_over" )
2123 DefaultRadioButtonBitmapOver = value;
2124 return;
2126 else
2127 if( name == "background_bitmap_view" )
2129 DefaultBackgroundBitmapView = value;
2130 return;
2132 else
2133 if( name == "home" )
2135 Home = value;
2136 return;
2138 else
2139 if( name == "browse_next_time" )
2141 bool b;
2142 if( fromString( value, b ) )
2143 _BrowseNextTime = b;
2144 return;
2146 else
2147 if( name == "browse_tree" )
2149 _BrowseTree = value;
2150 return;
2152 else
2153 if( name == "browse_undo" )
2155 _BrowseUndoButton = value;
2156 return;
2158 else
2159 if( name == "browse_redo" )
2161 _BrowseRedoButton = value;
2162 return;
2164 else
2165 if( name == "browse_refresh" )
2167 _BrowseRefreshButton = value;
2168 return;
2170 else
2171 if( name == "timeout" )
2173 double d;
2174 if( fromString( value, d ) )
2175 _TimeoutValue = d;
2176 return;
2178 else
2179 if( name == "browser_css_file")
2181 _BrowserStyle.reset();
2182 _BrowserCssFile = value;
2183 if (!_BrowserCssFile.empty())
2185 std::string filename = CPath::lookup(_BrowserCssFile, false, true, true);
2186 if (!filename.empty())
2188 CIFile in;
2189 if (in.open(filename))
2191 std::string css;
2192 if (in.readAll(css))
2193 _BrowserStyle.parseStylesheet(css);
2194 else
2195 nlwarning("Failed to read browser css from '%s'", filename.c_str());
2197 else
2199 nlwarning("Failed to open browser css file '%s'", filename.c_str());
2202 else
2204 nlwarning("Browser css file '%s' not found", _BrowserCssFile.c_str());
2208 else
2209 CGroupScrollText::setProperty( name, value );
2212 xmlNodePtr CGroupHTML::serialize( xmlNodePtr parentNode, const char *type ) const
2214 xmlNodePtr node = CGroupScrollText::serialize( parentNode, type );
2215 if( node == NULL )
2216 return NULL;
2218 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html" );
2219 xmlSetProp( node, BAD_CAST "url", BAD_CAST _URL.c_str() );
2220 xmlSetProp( node, BAD_CAST "title_prefix", BAD_CAST _TitlePrefix.c_str() );
2221 xmlSetProp( node, BAD_CAST "background_color", BAD_CAST toString( BgColor ).c_str() );
2222 xmlSetProp( node, BAD_CAST "error_color", BAD_CAST toString( ErrorColor ).c_str() );
2223 xmlSetProp( node, BAD_CAST "link_color", BAD_CAST toString( LinkColor ).c_str() );
2224 xmlSetProp( node, BAD_CAST "background_color", BAD_CAST toString( BgColor ).c_str() );
2225 xmlSetProp( node, BAD_CAST "h1_color", BAD_CAST toString( H1Color ).c_str() );
2226 xmlSetProp( node, BAD_CAST "h2_color", BAD_CAST toString( H2Color ).c_str() );
2227 xmlSetProp( node, BAD_CAST "h3_color", BAD_CAST toString( H3Color ).c_str() );
2228 xmlSetProp( node, BAD_CAST "h4_color", BAD_CAST toString( H4Color ).c_str() );
2229 xmlSetProp( node, BAD_CAST "h5_color", BAD_CAST toString( H5Color ).c_str() );
2230 xmlSetProp( node, BAD_CAST "h6_color", BAD_CAST toString( H6Color ).c_str() );
2232 xmlSetProp( node, BAD_CAST "error_color_global_color",
2233 BAD_CAST toString( ErrorColorGlobalColor ).c_str() );
2234 xmlSetProp( node, BAD_CAST "link_color_global_color",
2235 BAD_CAST toString( LinkColorGlobalColor ).c_str() );
2236 xmlSetProp( node, BAD_CAST "text_color_global_color",
2237 BAD_CAST toString( TextColorGlobalColor ).c_str() );
2238 xmlSetProp( node, BAD_CAST "h1_color_global_color",
2239 BAD_CAST toString( H1ColorGlobalColor ).c_str() );
2240 xmlSetProp( node, BAD_CAST "h2_color_global_color",
2241 BAD_CAST toString( H2ColorGlobalColor ).c_str() );
2242 xmlSetProp( node, BAD_CAST "h3_color_global_color",
2243 BAD_CAST toString( H3ColorGlobalColor ).c_str() );
2244 xmlSetProp( node, BAD_CAST "h4_color_global_color",
2245 BAD_CAST toString( H4ColorGlobalColor ).c_str() );
2246 xmlSetProp( node, BAD_CAST "h5_color_global_color",
2247 BAD_CAST toString( H5ColorGlobalColor ).c_str() );
2248 xmlSetProp( node, BAD_CAST "h6_color_global_color",
2249 BAD_CAST toString( H6ColorGlobalColor ).c_str() );
2251 xmlSetProp( node, BAD_CAST "text_font_size", BAD_CAST toString( TextFontSize ).c_str() );
2252 xmlSetProp( node, BAD_CAST "h1_font_size", BAD_CAST toString( H1FontSize ).c_str() );
2253 xmlSetProp( node, BAD_CAST "h2_font_size", BAD_CAST toString( H2FontSize ).c_str() );
2254 xmlSetProp( node, BAD_CAST "h3_font_size", BAD_CAST toString( H3FontSize ).c_str() );
2255 xmlSetProp( node, BAD_CAST "h4_font_size", BAD_CAST toString( H4FontSize ).c_str() );
2256 xmlSetProp( node, BAD_CAST "h5_font_size", BAD_CAST toString( H5FontSize ).c_str() );
2257 xmlSetProp( node, BAD_CAST "h6_font_size", BAD_CAST toString( H6FontSize ).c_str() );
2258 xmlSetProp( node, BAD_CAST "td_begin_space", BAD_CAST toString( TDBeginSpace ).c_str() );
2259 xmlSetProp( node, BAD_CAST "paragraph_begin_space", BAD_CAST toString( PBeginSpace ).c_str() );
2260 xmlSetProp( node, BAD_CAST "li_begin_space", BAD_CAST toString( LIBeginSpace ).c_str() );
2261 xmlSetProp( node, BAD_CAST "ul_begin_space", BAD_CAST toString( ULBeginSpace ).c_str() );
2262 xmlSetProp( node, BAD_CAST "li_indent", BAD_CAST toString( LIIndent ).c_str() );
2263 xmlSetProp( node, BAD_CAST "ul_indent", BAD_CAST toString( ULIndent ).c_str() );
2264 xmlSetProp( node, BAD_CAST "multi_line_space_factor", BAD_CAST toString( LineSpaceFontFactor ).c_str() );
2265 xmlSetProp( node, BAD_CAST "form_text_area_group", BAD_CAST DefaultFormTextGroup.c_str() );
2266 xmlSetProp( node, BAD_CAST "form_select_group", BAD_CAST DefaultFormSelectGroup.c_str() );
2267 xmlSetProp( node, BAD_CAST "checkbox_bitmap_normal", BAD_CAST DefaultCheckBoxBitmapNormal.c_str() );
2268 xmlSetProp( node, BAD_CAST "checkbox_bitmap_pushed", BAD_CAST DefaultCheckBoxBitmapPushed.c_str() );
2269 xmlSetProp( node, BAD_CAST "checkbox_bitmap_over", BAD_CAST DefaultCheckBoxBitmapOver.c_str() );
2270 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_normal", BAD_CAST DefaultRadioButtonBitmapNormal.c_str() );
2271 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_pushed", BAD_CAST DefaultRadioButtonBitmapPushed.c_str() );
2272 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_over", BAD_CAST DefaultRadioButtonBitmapOver.c_str() );
2273 xmlSetProp( node, BAD_CAST "background_bitmap_view", BAD_CAST DefaultBackgroundBitmapView.c_str() );
2274 xmlSetProp( node, BAD_CAST "home", BAD_CAST Home.c_str() );
2275 xmlSetProp( node, BAD_CAST "browse_next_time", BAD_CAST toString( _BrowseNextTime ).c_str() );
2276 xmlSetProp( node, BAD_CAST "browse_tree", BAD_CAST _BrowseTree.c_str() );
2277 xmlSetProp( node, BAD_CAST "browse_undo", BAD_CAST _BrowseUndoButton.c_str() );
2278 xmlSetProp( node, BAD_CAST "browse_redo", BAD_CAST _BrowseRedoButton.c_str() );
2279 xmlSetProp( node, BAD_CAST "browse_refresh", BAD_CAST _BrowseRefreshButton.c_str() );
2280 xmlSetProp( node, BAD_CAST "timeout", BAD_CAST toString( _TimeoutValue ).c_str() );
2281 xmlSetProp( node, BAD_CAST "browser_css_file", BAD_CAST _BrowserCssFile.c_str() );
2283 return node;
2286 // ***************************************************************************
2288 bool CGroupHTML::parse(xmlNodePtr cur,CInterfaceGroup *parentGroup)
2290 nlassert( CWidgetManager::getInstance()->isClockMsgTarget(this));
2293 if(!CGroupScrollText::parse(cur, parentGroup))
2294 return false;
2296 // TestYoyo
2297 //nlinfo("** CGroupHTML parsed Ok: %x, %s, %s, uid%d", this, _Id.c_str(), typeid(this).name(), _GroupHtmlUID);
2299 CXMLAutoPtr ptr;
2301 // Get the url
2302 ptr = xmlGetProp (cur, (xmlChar*)"url");
2303 if (ptr)
2304 _URL = (const char*)ptr;
2306 // Bkup default for undo/redo
2307 _AskedUrl= _URL;
2309 ptr = xmlGetProp (cur, (xmlChar*)"title_prefix");
2310 if (ptr)
2311 _TitlePrefix = CI18N::get((const char*)ptr);
2313 // Parameters
2314 ptr = xmlGetProp (cur, (xmlChar*)"background_color");
2315 if (ptr)
2316 BgColor = convertColor(ptr);
2317 ptr = xmlGetProp (cur, (xmlChar*)"error_color");
2318 if (ptr)
2319 ErrorColor = convertColor(ptr);
2320 ptr = xmlGetProp (cur, (xmlChar*)"link_color");
2321 if (ptr)
2322 LinkColor = convertColor(ptr);
2323 ptr = xmlGetProp (cur, (xmlChar*)"text_color");
2324 if (ptr)
2325 TextColor = convertColor(ptr);
2326 ptr = xmlGetProp (cur, (xmlChar*)"h1_color");
2327 if (ptr)
2328 H1Color = convertColor(ptr);
2329 ptr = xmlGetProp (cur, (xmlChar*)"h2_color");
2330 if (ptr)
2331 H2Color = convertColor(ptr);
2332 ptr = xmlGetProp (cur, (xmlChar*)"h3_color");
2333 if (ptr)
2334 H3Color = convertColor(ptr);
2335 ptr = xmlGetProp (cur, (xmlChar*)"h4_color");
2336 if (ptr)
2337 H4Color = convertColor(ptr);
2338 ptr = xmlGetProp (cur, (xmlChar*)"h5_color");
2339 if (ptr)
2340 H5Color = convertColor(ptr);
2341 ptr = xmlGetProp (cur, (xmlChar*)"h6_color");
2342 if (ptr)
2343 H6Color = convertColor(ptr);
2344 ptr = xmlGetProp (cur, (xmlChar*)"error_color_global_color");
2345 if (ptr)
2346 ErrorColorGlobalColor = convertBool(ptr);
2347 ptr = xmlGetProp (cur, (xmlChar*)"link_color_global_color");
2348 if (ptr)
2349 LinkColorGlobalColor = convertBool(ptr);
2350 ptr = xmlGetProp (cur, (xmlChar*)"text_color_global_color");
2351 if (ptr)
2352 TextColorGlobalColor = convertBool(ptr);
2353 ptr = xmlGetProp (cur, (xmlChar*)"h1_color_global_color");
2354 if (ptr)
2355 H1ColorGlobalColor = convertBool(ptr);
2356 ptr = xmlGetProp (cur, (xmlChar*)"h2_color_global_color");
2357 if (ptr)
2358 H2ColorGlobalColor = convertBool(ptr);
2359 ptr = xmlGetProp (cur, (xmlChar*)"h3_color_global_color");
2360 if (ptr)
2361 H3ColorGlobalColor = convertBool(ptr);
2362 ptr = xmlGetProp (cur, (xmlChar*)"h4_color_global_color");
2363 if (ptr)
2364 H4ColorGlobalColor = convertBool(ptr);
2365 ptr = xmlGetProp (cur, (xmlChar*)"h5_color_global_color");
2366 if (ptr)
2367 H5ColorGlobalColor = convertBool(ptr);
2368 ptr = xmlGetProp (cur, (xmlChar*)"h6_color_global_color");
2369 if (ptr)
2370 H6ColorGlobalColor = convertBool(ptr);
2371 ptr = xmlGetProp (cur, (xmlChar*)"text_font_size");
2372 if (ptr)
2373 fromString((const char*)ptr, TextFontSize);
2374 ptr = xmlGetProp (cur, (xmlChar*)"h1_font_size");
2375 if (ptr)
2376 fromString((const char*)ptr, H1FontSize);
2377 ptr = xmlGetProp (cur, (xmlChar*)"h2_font_size");
2378 if (ptr)
2379 fromString((const char*)ptr, H2FontSize);
2380 ptr = xmlGetProp (cur, (xmlChar*)"h3_font_size");
2381 if (ptr)
2382 fromString((const char*)ptr, H3FontSize);
2383 ptr = xmlGetProp (cur, (xmlChar*)"h4_font_size");
2384 if (ptr)
2385 fromString((const char*)ptr, H4FontSize);
2386 ptr = xmlGetProp (cur, (xmlChar*)"h5_font_size");
2387 if (ptr)
2388 fromString((const char*)ptr, H5FontSize);
2389 ptr = xmlGetProp (cur, (xmlChar*)"h6_font_size");
2390 if (ptr)
2391 fromString((const char*)ptr, H6FontSize);
2392 ptr = xmlGetProp (cur, (xmlChar*)"td_begin_space");
2393 if (ptr)
2394 fromString((const char*)ptr, TDBeginSpace);
2395 ptr = xmlGetProp (cur, (xmlChar*)"paragraph_begin_space");
2396 if (ptr)
2397 fromString((const char*)ptr, PBeginSpace);
2398 ptr = xmlGetProp (cur, (xmlChar*)"li_begin_space");
2399 if (ptr)
2400 fromString((const char*)ptr, LIBeginSpace);
2401 ptr = xmlGetProp (cur, (xmlChar*)"ul_begin_space");
2402 if (ptr)
2403 fromString((const char*)ptr, ULBeginSpace);
2404 ptr = xmlGetProp (cur, (xmlChar*)"li_indent");
2405 if (ptr)
2406 fromString((const char*)ptr, LIIndent);
2407 ptr = xmlGetProp (cur, (xmlChar*)"ul_indent");
2408 if (ptr)
2409 fromString((const char*)ptr, ULIndent);
2410 ptr = xmlGetProp (cur, (xmlChar*)"multi_line_space_factor");
2411 if (ptr)
2412 fromString((const char*)ptr, LineSpaceFontFactor);
2413 ptr = xmlGetProp (cur, (xmlChar*)"form_text_group");
2414 if (ptr)
2415 DefaultFormTextGroup = (const char*)(ptr);
2416 ptr = xmlGetProp (cur, (xmlChar*)"form_text_area_group");
2417 if (ptr)
2418 DefaultFormTextAreaGroup = (const char*)(ptr);
2419 ptr = xmlGetProp (cur, (xmlChar*)"form_select_group");
2420 if (ptr)
2421 DefaultFormSelectGroup = (const char*)(ptr);
2422 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_normal");
2423 if (ptr)
2424 DefaultCheckBoxBitmapNormal = (const char*)(ptr);
2425 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_pushed");
2426 if (ptr)
2427 DefaultCheckBoxBitmapPushed = (const char*)(ptr);
2428 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_over");
2429 if (ptr)
2430 DefaultCheckBoxBitmapOver = (const char*)(ptr);
2431 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_normal");
2432 if (ptr)
2433 DefaultRadioButtonBitmapNormal = (const char*)(ptr);
2434 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_pushed");
2435 if (ptr)
2436 DefaultRadioButtonBitmapPushed = (const char*)(ptr);
2437 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_over");
2438 if (ptr)
2439 DefaultRadioButtonBitmapOver = (const char*)(ptr);
2440 ptr = xmlGetProp (cur, (xmlChar*)"background_bitmap_view");
2441 if (ptr)
2442 DefaultBackgroundBitmapView = (const char*)(ptr);
2443 ptr = xmlGetProp (cur, (xmlChar*)"home");
2444 if (ptr)
2445 Home = (const char*)(ptr);
2446 ptr = xmlGetProp (cur, (xmlChar*)"browse_next_time");
2447 if (ptr)
2448 _BrowseNextTime = convertBool(ptr);
2449 ptr = xmlGetProp (cur, (xmlChar*)"browse_tree");
2450 if(ptr)
2451 _BrowseTree = (const char*)ptr;
2452 ptr = xmlGetProp (cur, (xmlChar*)"browse_undo");
2453 if(ptr)
2454 _BrowseUndoButton= (const char*)ptr;
2455 ptr = xmlGetProp (cur, (xmlChar*)"browse_redo");
2456 if(ptr)
2457 _BrowseRedoButton = (const char*)ptr;
2458 ptr = xmlGetProp (cur, (xmlChar*)"browse_refresh");
2459 if(ptr)
2460 _BrowseRefreshButton = (const char*)ptr;
2461 ptr = xmlGetProp (cur, (xmlChar*)"timeout");
2462 if(ptr)
2463 fromString((const char*)ptr, _TimeoutValue);
2465 ptr = xmlGetProp (cur, (xmlChar*)"browser_css_file");
2466 if (ptr)
2468 setProperty("browser_css_file", (const char *)ptr);
2471 return true;
2474 // ***************************************************************************
2476 bool CGroupHTML::handleEvent (const NLGUI::CEventDescriptor& eventDesc)
2478 bool traited = false;
2480 if (eventDesc.getType() == NLGUI::CEventDescriptor::mouse)
2482 const NLGUI::CEventDescriptorMouse &mouseEvent = (const NLGUI::CEventDescriptorMouse &)eventDesc;
2483 if (mouseEvent.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mousewheel)
2485 // Check if mouse wheel event was on any of multiline select box widgets
2486 // Must do this before CGroupScrollText
2487 for (uint i=0; i<_Forms.size() && !traited; i++)
2489 for (uint j=0; j<_Forms[i].Entries.size() && !traited; j++)
2491 if (_Forms[i].Entries[j].SelectBox)
2493 if (_Forms[i].Entries[j].SelectBox->handleEvent(eventDesc))
2495 traited = true;
2496 break;
2504 if (!traited)
2505 traited = CGroupScrollText::handleEvent (eventDesc);
2507 if (eventDesc.getType() == NLGUI::CEventDescriptor::system)
2509 const NLGUI::CEventDescriptorSystem &systemEvent = (const NLGUI::CEventDescriptorSystem &) eventDesc;
2510 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::clocktick)
2512 // Handle now
2513 handle ();
2515 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::activecalledonparent)
2517 if (!((NLGUI::CEventDescriptorActiveCalledOnParent &) systemEvent).getActive())
2519 // stop refresh when window gets hidden
2520 _NextRefreshTime = 0;
2524 return traited;
2527 // ***************************************************************************
2529 void CGroupHTML::endParagraph()
2531 _Paragraph = NULL;
2533 paragraphChange ();
2536 // ***************************************************************************
2538 void CGroupHTML::newParagraph(uint beginSpace)
2540 // Add a new paragraph
2541 CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam());
2542 newParagraph->setId(getCurrentGroup()->getId() + ":PARAGRAPH" + toString(getNextAutoIdSeq()));
2543 newParagraph->setResizeFromChildH(true);
2545 newParagraph->setMarginLeft(getIndent());
2547 // Add to the group
2548 addHtmlGroup (newParagraph, beginSpace);
2549 _Paragraph = newParagraph;
2551 paragraphChange ();
2554 // ***************************************************************************
2556 void CGroupHTML::browse(const char *url)
2558 // modify undo/redo
2559 pushUrlUndoRedo(url);
2561 // do the browse, with no undo/redo
2562 doBrowse(url);
2565 // ***************************************************************************
2566 void CGroupHTML::refresh()
2568 if (!_URL.empty())
2569 doBrowse(_URL.c_str(), true);
2572 // ***************************************************************************
2573 void CGroupHTML::doBrowse(const char *url, bool force)
2575 LOG_DL("(%s) Browsing URL : '%s'", _Id.c_str(), url);
2577 CUrlParser uri(url);
2578 if (!uri.hash.empty())
2580 // Anchor to scroll after page has loaded
2581 _UrlFragment = uri.hash;
2583 uri.inherit(_DocumentUrl);
2584 uri.hash.clear();
2586 // compare urls and see if we only navigating to new anchor
2587 if (!force && _DocumentUrl == uri.toString())
2589 // scroll happens in updateCoords()
2590 invalidateCoords();
2591 return;
2594 else
2595 _UrlFragment.clear();
2597 // go
2598 _URL = uri.toString();
2599 _BrowseNextTime = true;
2600 _WaitingForStylesheet = false;
2602 // if a BrowseTree is bound to us, try to select the node that opens this URL (auto-locate)
2603 if(!_BrowseTree.empty())
2605 CGroupTree *groupTree=dynamic_cast<CGroupTree*>(CWidgetManager::getInstance()->getElementFromId(_BrowseTree));
2606 if(groupTree)
2608 string nodeId= selectTreeNodeRecurs(groupTree->getRootNode(), url);
2609 // select the node
2610 if(!nodeId.empty())
2612 groupTree->selectNodeById(nodeId);
2618 // ***************************************************************************
2620 void CGroupHTML::browseError (const char *msg)
2622 releaseDownloads();
2624 // Get the list group from CGroupScrollText
2625 removeContent();
2626 newParagraph(0);
2627 CViewText *viewText = new CViewText ("", (string("Error : ")+msg).c_str());
2628 viewText->setColor (ErrorColor);
2629 viewText->setModulateGlobalColor(ErrorColorGlobalColor);
2630 viewText->setMultiLine (true);
2631 getParagraph()->addChild (viewText);
2632 if(!_TitlePrefix.empty())
2633 setTitle (_TitlePrefix);
2635 updateRefreshButton();
2636 invalidateCoords();
2639 // ***************************************************************************
2641 bool CGroupHTML::isBrowsing()
2643 // do not show spinning cursor for image downloads (!Curls.empty())
2644 return _BrowseNextTime || _PostNextTime || _RenderNextTime ||
2645 _Browsing || _WaitingForStylesheet ||
2646 _CurlWWW;
2649 // ***************************************************************************
2651 void CGroupHTML::updateCoords()
2653 CGroupScrollText::updateCoords();
2655 // all elements are in their correct place, tell scrollbar to scroll to anchor
2656 if (!_Browsing && !_UrlFragment.empty())
2658 doBrowseAnchor(_UrlFragment);
2659 _UrlFragment.clear();
2663 // ***************************************************************************
2665 bool CGroupHTML::translateChar(u32char &output, u32char input, u32char lastCharParam) const
2667 // Keep this char ?
2668 bool keep = true;
2670 // char is between table elements
2671 // TODO: only whitespace is handled, text is added to either TD, or after TABLE (should be before)
2672 bool tableWhitespace = getTable() && (_Cells.empty() || _Cells.back() == NULL);
2674 switch (input)
2676 // Return / tab only in <PRE> mode
2677 case '\t':
2678 case '\n':
2680 if (tableWhitespace)
2682 keep = false;
2684 else
2686 // Get the last char
2687 u32char lastChar = lastCharParam;
2688 if (lastChar == 0)
2689 lastChar = getLastChar();
2690 keep = ((lastChar != (u32char)' ') &&
2691 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2692 if(!getPRE())
2693 input = (u32char)' ';
2696 break;
2697 case ' ':
2699 if (tableWhitespace)
2701 keep = false;
2703 else
2705 // Get the last char
2706 u32char lastChar = lastCharParam;
2707 if (lastChar == 0)
2708 lastChar = getLastChar();
2709 keep = ((lastChar != (u32char)' ') &&
2710 (lastChar != (u32char)'\n') &&
2711 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2714 break;
2715 case 0xd:
2716 keep = false;
2717 break;
2720 if (keep)
2722 output = input;
2725 return keep;
2728 // ***************************************************************************
2730 void CGroupHTML::registerAnchor(CInterfaceElement* elm)
2732 if (!_AnchorName.empty())
2734 for(uint32 i=0; i < _AnchorName.size(); ++i)
2736 // filter out duplicates and register only first
2737 if (!_AnchorName[i].empty() && _Anchors.count(_AnchorName[i]) == 0)
2739 _Anchors[_AnchorName[i]] = elm;
2743 _AnchorName.clear();
2747 // ***************************************************************************
2749 void CGroupHTML::addString(const std::string &str)
2751 string tmpStr = str;
2753 if (_Localize)
2755 string _str = tmpStr;
2756 string::size_type p = _str.find('#');
2757 if (p == string::npos)
2759 tmpStr = CI18N::get(_str);
2761 else
2763 string cmd = _str.substr(0, p);
2764 string arg = _str.substr(p+1);
2766 if (cmd == "date")
2768 uint year, month, day;
2769 sscanf(arg.c_str(), "%d/%d/%d", &year, &month, &day);
2770 tmpStr = CI18N::get( "uiMFIDate");
2772 year += (year > 70 ? 1900 : 2000);
2774 strFindReplace(tmpStr, "%year", toString("%d", year) );
2775 strFindReplace(tmpStr, "%month", CI18N::get(toString("uiMonth%02d", month)) );
2776 strFindReplace(tmpStr, "%day", toString("%d", day) );
2778 else
2780 tmpStr = arg;
2785 // In title ?
2786 if (_Title)
2788 _TitleString += tmpStr;
2790 else if (_TextArea)
2792 _TextAreaContent += tmpStr;
2794 else if (_Object)
2796 _ObjectScript += tmpStr;
2798 else if (_SelectOption)
2800 if (!(_Forms.empty()))
2802 if (!_Forms.back().Entries.empty())
2804 _SelectOptionStr += tmpStr;
2808 else
2810 // In a paragraph ?
2811 if (!_Paragraph)
2813 newParagraph (0);
2814 paragraphChange ();
2817 CStyleParams &style = _Style.Current;
2819 // Text added ?
2820 bool added = false;
2821 bool embolden = style.FontWeight >= FONT_WEIGHT_BOLD;
2823 // Number of child in this paragraph
2824 if (_CurrentViewLink)
2826 bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == '\n';
2827 bool sameShadow = style.TextShadow.Enabled && _CurrentViewLink->getShadow();
2828 if (sameShadow && style.TextShadow.Enabled)
2830 sint sx, sy;
2831 _CurrentViewLink->getShadowOffset(sx, sy);
2832 sameShadow = (style.TextShadow.Color == _CurrentViewLink->getShadowColor());
2833 sameShadow = sameShadow && (style.TextShadow.Outline == _CurrentViewLink->getShadowOutline());
2834 sameShadow = sameShadow && (style.TextShadow.X == sx) && (style.TextShadow.Y == sy);
2836 // Compatible with current parameters ?
2837 if (!skipLine && sameShadow &&
2838 (style.TextColor == _CurrentViewLink->getColor()) &&
2839 (style.FontFamily == _CurrentViewLink->getFontName()) &&
2840 (style.FontSize == (uint)_CurrentViewLink->getFontSize()) &&
2841 (style.Underlined == _CurrentViewLink->getUnderlined()) &&
2842 (style.StrikeThrough == _CurrentViewLink->getStrikeThrough()) &&
2843 (embolden == _CurrentViewLink->getEmbolden()) &&
2844 (style.FontOblique == _CurrentViewLink->getOblique()) &&
2845 (getLink() == _CurrentViewLink->Link) &&
2846 (style.GlobalColor == _CurrentViewLink->getModulateGlobalColor()))
2848 // Concat the text
2849 _CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
2850 _CurrentViewLink->invalidateContent();
2851 added = true;
2855 // Not added ?
2856 if (!added)
2858 if (getA() && string(getLinkClass()) == "ryzom-ui-button")
2860 string buttonTemplate = DefaultButtonGroup;
2861 // Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
2862 string param = "name=" + this->_Id + "|url=" + getLink();
2863 string name;
2864 if (!_AnchorName.empty())
2865 name = _AnchorName.back();
2866 typedef pair<string, string> TTmplParam;
2867 vector<TTmplParam> tmplParams;
2868 tmplParams.push_back(TTmplParam("id", ""));
2869 tmplParams.push_back(TTmplParam("onclick", "browse"));
2870 tmplParams.push_back(TTmplParam("onclick_param", param));
2871 tmplParams.push_back(TTmplParam("active", "true"));
2872 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, getId()+":"+name, tmplParams);
2873 if (buttonGroup)
2875 buttonGroup->setId(getId()+":"+name);
2876 // Add the ctrl button
2877 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
2878 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
2879 if (ctrlButton)
2881 ctrlButton->setModulateGlobalColorAll (false);
2883 // Translate the tooltip
2884 ctrlButton->setDefaultContextHelp(getLinkTitle());
2885 ctrlButton->setText(tmpStr);
2886 // empty url / button disabled
2887 bool disabled = string(getLink()).empty();
2888 ctrlButton->setFrozen(disabled);
2890 setTextButtonStyle(ctrlButton, style);
2892 getParagraph()->addChild (buttonGroup);
2893 paragraphChange ();
2897 else
2899 CViewLink *newLink = new CViewLink(CViewBase::TCtorParam());
2900 if (getA())
2902 newLink->Link = getLink();
2903 newLink->LinkTitle = getLinkTitle();
2904 if (!newLink->Link.empty())
2906 newLink->setHTMLView (this);
2908 newLink->setActionOnLeftClick("browse");
2909 newLink->setParamsOnLeftClick("name=" + getId() + "|url=" + newLink->Link);
2912 newLink->setText(tmpStr);
2913 newLink->setMultiLineSpace((uint)((float)(style.FontSize)*LineSpaceFontFactor));
2914 newLink->setMultiLine(true);
2915 newLink->setModulateGlobalColor(style.GlobalColor);
2916 setTextStyle(newLink, style);
2917 // newLink->setLineAtBottom (true);
2919 registerAnchor(newLink);
2921 if (getA() && !newLink->Link.empty())
2923 getParagraph()->addChildLink(newLink);
2925 else
2927 getParagraph()->addChild(newLink);
2929 paragraphChange ();
2935 // ***************************************************************************
2937 void CGroupHTML::addImage(const std::string &id, const std::string &img, bool reloadImg, const CStyleParams &style)
2939 // In a paragraph ?
2940 if (!_Paragraph)
2942 newParagraph (0);
2943 paragraphChange ();
2946 // No more text in this text view
2947 _CurrentViewLink = NULL;
2949 // Not added ?
2950 CViewBitmap *newImage = new CViewBitmap (TCtorParam());
2951 newImage->setId(id);
2953 addImageDownload(img, newImage, style, NormalImage);
2954 newImage->setRenderLayer(getRenderLayer()+1);
2956 getParagraph()->addChild(newImage);
2957 paragraphChange ();
2959 setImageSize(newImage, style);
2962 // ***************************************************************************
2964 CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const std::string &content, uint maxlength)
2966 // In a paragraph ?
2967 if (!_Paragraph)
2969 newParagraph (0);
2970 paragraphChange ();
2973 // No more text in this text view
2974 _CurrentViewLink = NULL;
2976 CStyleParams &style = _Style.Current;
2978 // override cols/rows values from style
2979 if (style.Width > 0) cols = style.Width / style.FontSize;
2980 if (style.Height > 0) rows = style.Height / style.FontSize;
2982 // Not added ?
2983 std::vector<std::pair<std::string,std::string> > templateParams;
2984 templateParams.push_back (std::pair<std::string,std::string> ("w", toString (cols*style.FontSize)));
2985 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
2986 templateParams.push_back (std::pair<std::string,std::string> ("prompt", ""));
2987 templateParams.push_back (std::pair<std::string,std::string> ("multiline", multiLine?"true":"false"));
2988 templateParams.push_back (std::pair<std::string,std::string> ("fontsize", toString (style.FontSize)));
2989 templateParams.push_back (std::pair<std::string,std::string> ("color", style.TextColor.toString()));
2990 if (style.FontWeight >= FONT_WEIGHT_BOLD)
2991 templateParams.push_back (std::pair<std::string,std::string> ("fontweight", "bold"));
2992 if (style.FontOblique)
2993 templateParams.push_back (std::pair<std::string,std::string> ("fontstyle", "oblique"));
2994 if (multiLine)
2995 templateParams.push_back (std::pair<std::string,std::string> ("multi_min_line", toString(rows)));
2996 templateParams.push_back (std::pair<std::string,std::string> ("want_return", multiLine?"true":"false"));
2997 templateParams.push_back (std::pair<std::string,std::string> ("onenter", ""));
2998 templateParams.push_back (std::pair<std::string,std::string> ("enter_recover_focus", "false"));
2999 if (maxlength > 0)
3000 templateParams.push_back (std::pair<std::string,std::string> ("max_num_chars", toString(maxlength)));
3001 templateParams.push_back (std::pair<std::string,std::string> ("shadow", toString(style.TextShadow.Enabled)));
3002 if (style.TextShadow.Enabled)
3004 templateParams.push_back (std::pair<std::string,std::string> ("shadow_x", toString(style.TextShadow.X)));
3005 templateParams.push_back (std::pair<std::string,std::string> ("shadow_y", toString(style.TextShadow.Y)));
3006 templateParams.push_back (std::pair<std::string,std::string> ("shadow_color", style.TextShadow.Color.toString()));
3007 templateParams.push_back (std::pair<std::string,std::string> ("shadow_outline", toString(style.TextShadow.Outline)));
3010 CInterfaceGroup *textArea = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
3011 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
3013 // Group created ?
3014 if (textArea)
3016 // Set the content
3017 CGroupEditBox *eb = dynamic_cast<CGroupEditBox*>(textArea->getGroup("eb"));
3018 if (eb)
3020 eb->setInputString(decodeHTMLEntities(content));
3021 if (style.hasStyle("background-color"))
3023 CViewBitmap *bg = dynamic_cast<CViewBitmap*>(eb->getView("bg"));
3024 if (bg)
3026 bg->setTexture("blank.tga");
3027 bg->setColor(style.BackgroundColor);
3032 textArea->invalidateCoords();
3033 getParagraph()->addChild (textArea);
3034 paragraphChange ();
3036 return textArea;
3040 // Not group created
3041 return NULL;
3044 // ***************************************************************************
3045 CDBGroupComboBox *CGroupHTML::addComboBox(const std::string &templateName, const char *name)
3047 // In a paragraph ?
3048 if (!_Paragraph)
3050 newParagraph (0);
3051 paragraphChange ();
3056 // Not added ?
3057 std::vector<std::pair<std::string,std::string> > templateParams;
3058 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
3059 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
3060 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
3062 // Group created ?
3063 if (group)
3065 // Set the content
3066 CDBGroupComboBox *cb = dynamic_cast<CDBGroupComboBox *>(group);
3067 if (!cb)
3069 nlwarning("'%s' template has bad type, combo box expected", templateName.c_str());
3070 delete cb;
3071 return NULL;
3073 else
3075 getParagraph()->addChild (cb);
3076 paragraphChange ();
3077 return cb;
3082 // Not group created
3083 return NULL;
3086 // ***************************************************************************
3087 CGroupMenu *CGroupHTML::addSelectBox(const std::string &templateName, const char *name)
3089 // In a paragraph ?
3090 if (!_Paragraph)
3092 newParagraph (0);
3093 paragraphChange ();
3096 // Not added ?
3097 std::vector<std::pair<std::string,std::string> > templateParams;
3098 templateParams.push_back(std::pair<std::string,std::string> ("id", name));
3099 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName.c_str(),
3100 getParagraph()->getId(), &(templateParams[0]), (uint)templateParams.size());
3102 // Group created ?
3103 if (group)
3105 // Set the content
3106 CGroupMenu *sb = dynamic_cast<CGroupMenu *>(group);
3107 if (!sb)
3109 nlwarning("'%s' template has bad type, CGroupMenu expected", templateName.c_str());
3110 delete sb;
3111 return NULL;
3113 else
3115 getParagraph()->addChild (sb);
3116 paragraphChange ();
3117 return sb;
3121 // No group created
3122 return NULL;
3125 // ***************************************************************************
3127 CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &name, const std::string &normalBitmap, const std::string &pushedBitmap,
3128 const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams,
3129 const std::string &tooltip, const CStyleParams &style)
3131 // In a paragraph ?
3132 if (!_Paragraph)
3134 newParagraph (0);
3135 paragraphChange ();
3138 // Add the ctrl button
3139 CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
3140 if (!name.empty())
3142 ctrlButton->setId(name);
3145 std::string normal;
3146 if (startsWith(normalBitmap, "data:image/"))
3148 normal = decodeURIComponent(normalBitmap);
3150 else
3152 // Load only tga files.. (conversion in dds filename is done in the lookup procedure)
3153 normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
3155 // if the image doesn't exist on local, we check in the cache
3156 if(!CPath::exists(normal))
3158 // search in the compressed texture
3159 CViewRenderer &rVR = *CViewRenderer::getInstance();
3160 sint32 id = rVR.getTextureIdFromName(normal);
3161 if(id == -1)
3163 normal = localImageName(normalBitmap);
3164 addImageDownload(normalBitmap, ctrlButton, style);
3169 std::string pushed;
3170 if (startsWith(pushedBitmap, "data:image/"))
3172 pushed = decodeURIComponent(pushedBitmap);
3174 else
3176 pushed = pushedBitmap.empty()?"":CFile::getPath(pushedBitmap) + CFile::getFilenameWithoutExtension(pushedBitmap) + ".tga";
3177 // if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it
3178 if(!CPath::exists(pushed))
3180 // search in the compressed texture
3181 CViewRenderer &rVR = *CViewRenderer::getInstance();
3182 sint32 id = rVR.getTextureIdFromName(pushed);
3183 if(id == -1)
3185 pushed = localImageName(pushedBitmap);
3190 std::string over;
3191 if (startsWith(overBitmap, "data:image/"))
3193 over = decodeURIComponent(overBitmap);
3195 else
3197 over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga";
3198 // schedule mouseover bitmap for download if its different from normal
3199 if (!over.empty() && !CPath::exists(over))
3201 if (overBitmap != normalBitmap)
3203 over = localImageName(overBitmap);
3204 addImageDownload(overBitmap, ctrlButton, style, OverImage);
3209 ctrlButton->setType (type);
3210 if (!normal.empty())
3211 ctrlButton->setTexture (normal);
3212 if (!pushed.empty())
3213 ctrlButton->setTexturePushed (pushed);
3214 if (!over.empty())
3215 ctrlButton->setTextureOver (over);
3216 ctrlButton->setModulateGlobalColorAll (style.GlobalColor);
3217 ctrlButton->setActionOnLeftClick (actionHandler);
3218 ctrlButton->setParamsOnLeftClick (actionHandlerParams);
3220 // Translate the tooltip or display raw text (tooltip from webig)
3221 if (!tooltip.empty())
3223 if (CI18N::hasTranslation(tooltip))
3225 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
3226 //ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
3228 else
3230 ctrlButton->setDefaultContextHelp(tooltip);
3231 //ctrlButton->setOnContextHelp(string(tooltip));
3234 ctrlButton->setInstantContextHelp(true);
3235 ctrlButton->setToolTipParent(TTMouse);
3236 ctrlButton->setToolTipParentPosRef(Hotspot_TTAuto);
3237 ctrlButton->setToolTipPosRef(Hotspot_TTAuto);
3240 getParagraph()->addChild (ctrlButton);
3241 paragraphChange ();
3243 setImageSize(ctrlButton, style);
3245 return ctrlButton;
3248 // ***************************************************************************
3250 void CGroupHTML::flushString()
3252 _CurrentViewLink = NULL;
3255 // ***************************************************************************
3257 void CGroupHTML::clearContext()
3259 _Paragraph = NULL;
3260 _PRE.clear();
3261 _Indent.clear();
3262 _LI = false;
3263 _UL.clear();
3264 _DL.clear();
3265 _A.clear();
3266 _Link.clear();
3267 _LinkTitle.clear();
3268 _Tables.clear();
3269 _Cells.clear();
3270 _TR.clear();
3271 _Forms.clear();
3272 _FormOpen = false;
3273 _FormSubmit.clear();
3274 _Groups.clear();
3275 _Divs.clear();
3276 _Anchors.clear();
3277 _AnchorName.clear();
3278 _CellParams.clear();
3279 _Title = false;
3280 _TextArea = false;
3281 _Object = false;
3282 _Localize = false;
3283 _ReadingHeadTag = false;
3284 _IgnoreHeadTag = false;
3285 _IgnoreBaseUrlTag = false;
3286 _AutoIdSeq = 0;
3288 paragraphChange ();
3290 // clear the pointer to the current image download since all the button are deleted
3291 LOG_DL("Clear pointers to %d curls", Curls.size());
3293 // remove image refs from downloads
3294 for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
3296 it->imgs.clear();
3300 // ***************************************************************************
3302 u32char CGroupHTML::getLastChar() const
3304 if (_CurrentViewLink)
3306 ::u32string str = CUtfStringView(_CurrentViewLink->getText()).toUtf32(); // FIXME: Optimize reverse UTF iteration
3307 if (!str.empty())
3308 return str[str.length()-1];
3310 return 0;
3313 // ***************************************************************************
3315 void CGroupHTML::paragraphChange ()
3317 _CurrentViewLink = NULL;
3318 _CurrentViewImage = NULL;
3319 CGroupParagraph *paragraph = getParagraph();
3320 if (paragraph)
3322 // Number of child in this paragraph
3323 uint numChild = paragraph->getNumChildren();
3324 if (numChild)
3326 // Get the last child
3327 CViewBase *child = paragraph->getChild(numChild-1);
3329 // Is this a string view ?
3330 _CurrentViewLink = dynamic_cast<CViewLink*>(child);
3331 _CurrentViewImage = dynamic_cast<CViewBitmap*>(child);
3336 // ***************************************************************************
3338 CInterfaceGroup *CGroupHTML::getCurrentGroup()
3340 if (!_Cells.empty() && _Cells.back())
3341 return _Cells.back()->Group;
3342 else
3343 return _GroupListAdaptor;
3346 // ***************************************************************************
3348 void CGroupHTML::addHtmlGroup (CInterfaceGroup *group, uint beginSpace)
3350 if (!group)
3351 return;
3353 registerAnchor(group);
3355 if (!_DivName.empty())
3357 group->setName(_DivName);
3358 _Groups.push_back(group);
3361 group->setSizeRef(CInterfaceElement::width);
3363 // Compute begin space between paragraph and tables
3364 // * If first in group, no begin space
3366 // Pointer on the current paragraph (can be a table too)
3367 CGroupParagraph *p = dynamic_cast<CGroupParagraph*>(group);
3369 CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
3370 const std::vector<CInterfaceGroup*> &groups = parentGroup->getGroups ();
3371 group->setParent(parentGroup);
3372 group->setParentSize(parentGroup);
3373 if (groups.empty())
3375 group->setParentPos(parentGroup);
3376 group->setPosRef(Hotspot_TL);
3377 group->setParentPosRef(Hotspot_TL);
3378 beginSpace = 0;
3380 else
3382 // Last is a paragraph ?
3383 group->setParentPos(groups.back());
3384 group->setPosRef(Hotspot_TL);
3385 group->setParentPosRef(Hotspot_BL);
3388 // Set the begin space
3389 if (p)
3390 p->setTopSpace(beginSpace);
3391 else
3392 group->setY(-(sint32)beginSpace);
3393 parentGroup->addGroup (group);
3396 // ***************************************************************************
3398 void CGroupHTML::setContainerTitle (const std::string &title)
3400 CInterfaceElement *parent = getParent();
3401 if (parent)
3403 if ((parent = parent->getParent()))
3405 CGroupContainer *container = dynamic_cast<CGroupContainer*>(parent);
3406 if (container)
3408 container->setTitle(title);
3414 void CGroupHTML::setTitle(const std::string &title)
3416 _TitleString.clear();
3417 if(!_TitlePrefix.empty())
3419 _TitleString = _TitlePrefix + " - ";
3421 _TitleString += title;
3423 setContainerTitle(_TitleString);
3426 std::string CGroupHTML::getTitle() const {
3427 return _TitleString;
3430 // ***************************************************************************
3432 bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
3434 result = url;
3435 string tmp;
3437 if (toLowerAscii(result).find("file:") == 0 && result.size() > 5)
3439 result = result.substr(5, result.size()-5);
3441 else if (result.find("://") != string::npos || result.find("//") == 0)
3443 // http://, https://, etc or protocol-less url "//domain.com/image.png"
3444 return false;
3447 tmp = CPath::lookup (CFile::getFilename(result), false, false, false);
3448 if (tmp.empty())
3450 // try to find in local directory
3451 tmp = CPath::lookup (result, false, false, true);
3454 if (!tmp.empty())
3456 // Normalize the path
3457 if (isUrl)
3458 //result = "file:"+toLowerAscii(CPath::standardizePath (CPath::getFullPath (CFile::getPath(result)))+CFile::getFilename(result));*/
3459 result = "file:/"+tmp;
3460 else
3461 result = tmp;
3462 return true;
3464 else
3466 // Is it a texture in the big texture ?
3467 if (CViewRenderer::getInstance()->getTextureIdFromName (result) >= 0)
3469 return true;
3471 else
3473 // This is not a file in the CPath, let libwww open this URL
3474 result = url;
3475 return false;
3480 // ***************************************************************************
3482 void CGroupHTML::submitForm(uint button, sint32 x, sint32 y)
3484 if (button >= _FormSubmit.size())
3485 return;
3487 for(uint formId = 0; formId < _Forms.size(); formId++)
3489 // case sensitive search (user id is lowecase, auto id is uppercase)
3490 if (_Forms[formId].id == _FormSubmit[button].form)
3492 _PostNextTime = true;
3493 _PostFormId = formId;
3494 _PostFormAction = _FormSubmit[button].formAction;
3495 _PostFormSubmitType = _FormSubmit[button].type;
3496 _PostFormSubmitButton = _FormSubmit[button].name;
3497 _PostFormSubmitValue = _FormSubmit[button].value;
3498 _PostFormSubmitX = x;
3499 _PostFormSubmitY = y;
3501 return;
3505 nlwarning("Unable to find form '%s' to submit (button '%s')", _FormSubmit[button].form.c_str(), _FormSubmit[button].name.c_str());
3508 // ***************************************************************************
3510 void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
3512 // Should have a child named bg
3513 CViewBase *view = getView (DefaultBackgroundBitmapView);
3514 if (view)
3516 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*> (view);
3517 if (bitmap)
3519 // TODO: background color should have separate bitmap from background texture
3520 // Change the background color
3521 bitmap->setColor (bgcolor);
3522 bitmap->setModulateGlobalColor(false);
3527 // ***************************************************************************
3529 void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
3531 // Should have a child named bg
3532 CViewBase *view = getView (DefaultBackgroundBitmapView);
3533 if (view)
3535 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*> (view);
3536 if (bitmap)
3538 bitmap->setParentPosRef(Hotspot_TL);
3539 bitmap->setPosRef(Hotspot_TL);
3540 bitmap->setX(0);
3541 bitmap->setY(0);
3542 // FIXME: renders behind container background
3543 bitmap->setRenderLayer(-2);
3544 bitmap->setScale(scale);
3545 bitmap->setTile(tile);
3547 // clear size ref for non-scaled image or it does not show up
3548 if (scale || tile)
3549 bitmap->setSizeRef("wh");
3550 else
3551 bitmap->setSizeRef("");
3553 addImageDownload(bgtex, view, CStyleParams(), NormalImage, "");
3559 struct CButtonFreezer : public CInterfaceElementVisitor
3561 virtual void visitCtrl(CCtrlBase *ctrl)
3563 CCtrlBaseButton *textButt = dynamic_cast<CCtrlTextButton *>(ctrl);
3564 if (textButt)
3566 textButt->setFrozen(true);
3571 // ***************************************************************************
3573 void CGroupHTML::handle ()
3575 H_AUTO(RZ_Interface_Html_handle)
3577 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
3579 // handle curl downloads
3580 checkDownloads();
3582 // handle refresh timer
3583 if (_NextRefreshTime > 0 && _NextRefreshTime <= (times.thisFrameMs / 1000.0f) )
3585 // there might be valid uses for 0sec refresh, but two in a row is probably a mistake
3586 if (_NextRefreshTime - _LastRefreshTime >= 1.0)
3588 _LastRefreshTime = _NextRefreshTime;
3589 doBrowse(_RefreshUrl.c_str());
3591 else
3592 nlwarning("Ignore second 0sec http-equiv refresh in a row (url '%s')", _URL.c_str());
3594 _NextRefreshTime = 0;
3597 if (_CurlWWW)
3599 // still transfering html page
3600 if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
3602 browseError(("Connection timeout : "+_URL).c_str());
3605 else
3606 if (_RenderNextTime)
3608 _RenderNextTime = false;
3609 renderHtmlString(_DocumentHtml);
3611 else
3612 if (_WaitingForStylesheet)
3614 renderDocument();
3616 else
3617 if (_BrowseNextTime || _PostNextTime)
3619 // Set timeout
3620 _ConnectingTimeout = ( times.thisFrameMs / 1000.0f ) + _TimeoutValue;
3622 // freeze form buttons
3623 CButtonFreezer freezer;
3624 this->visit(&freezer);
3626 // Home ?
3627 if (_URL == "home")
3628 _URL = home();
3630 string finalUrl;
3631 bool isLocal = lookupLocalFile (finalUrl, _URL.c_str(), true);
3633 _URL = finalUrl;
3635 CUrlParser uri (_URL);
3636 _TrustedDomain = isTrustedDomain(uri.host);
3637 _DocumentDomain = uri.host;
3639 // file is probably from bnp (ingame help)
3640 if (isLocal)
3642 doBrowseLocalFile(finalUrl);
3644 else
3646 SFormFields formfields;
3647 if (_PostNextTime)
3649 buildHTTPPostParams(formfields);
3650 // _URL is set from form.Action
3651 finalUrl = _URL;
3653 else
3655 // Add custom get params from child classes
3656 addHTTPGetParams (finalUrl, _TrustedDomain);
3659 doBrowseRemoteUrl(finalUrl, "", _PostNextTime, formfields);
3662 _BrowseNextTime = false;
3663 _PostNextTime = false;
3667 // ***************************************************************************
3668 void CGroupHTML::buildHTTPPostParams (SFormFields &formfields)
3670 // Add text area text
3671 uint i;
3673 if (_PostFormId >= _Forms.size())
3675 nlwarning("(%s) invalid form index %d, _Forms %d", _Id.c_str(), _PostFormId, _Forms.size());
3676 return;
3678 // Ref the form
3679 CForm &form = _Forms[_PostFormId];
3681 // button can override form action url (and methor, but we only do POST)
3682 _URL = _PostFormAction.empty() ? form.Action : _PostFormAction;
3684 CUrlParser uri(_URL);
3685 _TrustedDomain = isTrustedDomain(uri.host);
3686 _DocumentDomain = uri.host;
3688 for (i=0; i<form.Entries.size(); i++)
3690 // Text area ?
3691 bool addEntry = false;
3692 string entryData;
3693 if (form.Entries[i].TextArea)
3695 // Get the edit box view
3696 CInterfaceGroup *group = form.Entries[i].TextArea->getGroup ("eb");
3697 if (group)
3699 // Should be a CGroupEditBox
3700 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
3701 if (editBox)
3703 entryData = editBox->getViewText()->getText();
3704 addEntry = true;
3708 else if (form.Entries[i].Checkbox)
3710 // todo handle unicode POST here
3711 if (form.Entries[i].Checkbox->getPushed ())
3713 entryData = form.Entries[i].Value;
3714 addEntry = true;
3717 else if (form.Entries[i].ComboBox)
3719 CDBGroupComboBox *cb = form.Entries[i].ComboBox;
3720 entryData = form.Entries[i].SelectValues[cb->getSelection()];
3721 addEntry = true;
3723 else if (form.Entries[i].SelectBox)
3725 CGroupMenu *sb = form.Entries[i].SelectBox;
3726 CGroupSubMenu *rootMenu = sb->getRootMenu();
3727 if (rootMenu)
3729 for(uint j=0; j<rootMenu->getNumLine(); ++j)
3731 CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
3732 if (ig)
3734 CCtrlBaseButton *cb = dynamic_cast<CCtrlBaseButton *>(ig->getCtrl("b"));
3735 if (cb && cb->getPushed())
3736 formfields.add(form.Entries[i].Name, form.Entries[i].SelectValues[j]);
3741 // This is a hidden value
3742 else
3744 entryData = form.Entries[i].Value;
3745 addEntry = true;
3748 // Add this entry
3749 if (addEntry)
3751 formfields.add(form.Entries[i].Name, entryData);
3755 if (_PostFormSubmitType == "image")
3757 // Add the button coordinates
3758 if (_PostFormSubmitButton.find_first_of("[") == string::npos)
3760 formfields.add(_PostFormSubmitButton + "_x", NLMISC::toString(_PostFormSubmitX));
3761 formfields.add(_PostFormSubmitButton + "_y", NLMISC::toString(_PostFormSubmitY));
3763 else
3765 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitX));
3766 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitY));
3769 else
3770 formfields.add(_PostFormSubmitButton, _PostFormSubmitValue);
3772 // Add custom params from child classes
3773 addHTTPPostParams(formfields, _TrustedDomain);
3776 // ***************************************************************************
3777 void CGroupHTML::doBrowseLocalFile(const std::string &uri)
3779 releaseDownloads();
3780 updateRefreshButton();
3782 std::string filename;
3783 if (toLowerAscii(uri).find("file:/") == 0)
3785 filename = uri.substr(6, uri.size() - 6);
3787 else
3789 filename = uri;
3792 LOG_DL("browse local file '%s'", filename.c_str());
3794 _TrustedDomain = true;
3795 _DocumentDomain = "localhost";
3797 CIFile in;
3798 if (in.open(filename))
3800 std::string html;
3801 while(!in.eof())
3803 char buf[1024];
3804 in.getline(buf, 1024);
3805 html += std::string(buf) + "\n";
3807 in.close();
3809 if (!renderHtmlString(html))
3811 browseError((string("Failed to parse html from file : ")+filename).c_str());
3814 else
3816 browseError((string("The page address is malformed : ")+filename).c_str());
3820 // ***************************************************************************
3821 void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
3823 // stop all downloads from previous page
3824 releaseDownloads();
3825 updateRefreshButton();
3827 // Reset the title
3828 if(_TitlePrefix.empty())
3829 setTitle (CI18N::get("uiPleaseWait"));
3830 else
3831 setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
3833 url = upgradeInsecureUrl(url);
3835 LOG_DL("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d",
3836 _Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size());
3838 if (!MultiCurl)
3840 browseError(string("Invalid MultCurl handle, loading url failed : "+url).c_str());
3841 return;
3844 CURL *curl = curl_easy_init();
3845 if (!curl)
3847 nlwarning("(%s) failed to create curl handle", _Id.c_str());
3848 browseError(string("Failed to create cURL handle : " + url).c_str());
3849 return;
3852 // https://
3853 if (toLowerAscii(url.substr(0, 8)) == "https://")
3855 // if supported, use custom SSL context function to load certificates
3856 NLWEB::CCurlCertificates::useCertificates(curl);
3859 // do not follow redirects, we have own handler
3860 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
3861 // after redirect
3862 curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
3864 // tell curl to use compression if possible (gzip, deflate)
3865 // leaving this empty allows all encodings that curl supports
3866 //curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
3868 // limit curl to HTTP and HTTPS protocols only
3869 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3870 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3872 // Destination
3873 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
3875 // User-Agent:
3876 std::string userAgent = options.appName + "/" + options.appVersion;
3877 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
3879 // Cookies
3880 sendCookies(curl, _DocumentDomain, _TrustedDomain);
3882 // Referer
3883 if (!referer.empty())
3885 curl_easy_setopt(curl, CURLOPT_REFERER, referer.c_str());
3886 LOG_DL("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
3889 if (doPost)
3891 // serialize form data and add it to curl
3892 std::string data;
3893 for(uint i=0; i<formfields.Values.size(); ++i)
3895 char * escapedName = curl_easy_escape(curl, formfields.Values[i].name.c_str(), formfields.Values[i].name.size());
3896 char * escapedValue = curl_easy_escape(curl, formfields.Values[i].value.c_str(), formfields.Values[i].value.size());
3898 if (i>0)
3899 data += "&";
3901 data += std::string(escapedName) + "=" + escapedValue;
3903 curl_free(escapedName);
3904 curl_free(escapedValue);
3906 curl_easy_setopt(curl, CURLOPT_POST, 1);
3907 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
3908 curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data.c_str());
3910 else
3912 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
3915 // transfer handle
3916 _CurlWWW = new CCurlWWWData(curl, url);
3918 // set the language code used by the client
3919 std::vector<std::string> headers;
3920 headers.push_back("Accept-Language: "+options.languageCode);
3921 headers.push_back("Accept-Charset: utf-8");
3922 _CurlWWW->sendHeaders(headers);
3924 // catch headers for redirect
3925 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
3926 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
3928 // catch body
3929 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NLGUI::curlDataCallback);
3930 curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
3932 #ifdef LOG_CURL_PROGRESS
3933 // progress callback
3934 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
3935 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, NLGUI::curlProgressCallback);
3936 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, _CurlWWW);
3937 #else
3938 // progress off
3939 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
3940 #endif
3943 curl_multi_add_handle(MultiCurl, curl);
3945 // start the transfer
3946 int NewRunningCurls = 0;
3947 curl_multi_perform(MultiCurl, &NewRunningCurls);
3948 RunningCurls++;
3950 _RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
3953 // ***************************************************************************
3954 void CGroupHTML::htmlDownloadFinished(bool success, const std::string &error)
3956 if (!success)
3958 std::string err;
3959 err = "Connection failed with cURL error: ";
3960 err += error;
3961 err += "\nURL '" + _CurlWWW->Url + "'";
3962 browseError(err.c_str());
3963 return;
3966 // received content from remote
3967 std::string content = trim(_CurlWWW->Content);
3969 // save HSTS header from all requests regardless of HTTP code
3970 if (_CurlWWW->hasHSTSHeader())
3972 CUrlParser uri(_CurlWWW->Url);
3973 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
3976 receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
3978 long code;
3979 curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
3980 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());
3982 if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3984 if (_RedirectsRemaining < 0)
3986 browseError(string("Redirect limit reached : " + _URL).c_str());
3987 return;
3990 // redirect, get the location and try browse again
3991 // we cant use curl redirection because 'addHTTPGetParams()' must be called on new destination
3992 std::string location(_CurlWWW->getLocationHeader());
3993 if (location.empty())
3995 browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
3996 return;
3999 LOG_DL("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
4000 location = getAbsoluteUrl(location);
4002 _PostNextTime = false;
4003 _RedirectsRemaining--;
4005 doBrowse(location.c_str());
4007 else if ( (code < 200 || code >= 300) )
4009 // catches 304 not modified, but html is not in cache anyway
4010 // if server did not send any error back
4011 if (content.empty())
4013 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>");
4017 char *ch;
4018 std::string contentType;
4019 CURLcode res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
4020 if (res == CURLE_OK && ch != NULL)
4022 contentType = ch;
4025 htmlDownloadFinished(content, contentType, code);
4027 // clear curl handler
4028 if (MultiCurl)
4030 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
4033 delete _CurlWWW;
4034 _CurlWWW = NULL;
4036 // refresh button uses _CurlWWW. refresh button may stay disabled if
4037 // there is no css files to download and page is rendered before _CurlWWW is freed
4038 updateRefreshButton();
4041 void CGroupHTML::dataDownloadFinished(bool success, const std::string &error, CDataDownload &data)
4043 fclose(data.fp);
4045 CUrlParser uri(data.url);
4046 if (!uri.host.empty())
4048 receiveCookies(data.data->Request, uri.host, isTrustedDomain(uri.host));
4051 long code = -1;
4052 curl_easy_getinfo(data.data->Request, CURLINFO_RESPONSE_CODE, &code);
4054 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());
4055 curl_multi_remove_handle(MultiCurl, data.data->Request);
4057 // save HSTS header from all requests regardless of HTTP code
4058 if (success)
4060 if (data.data->hasHSTSHeader())
4062 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, data.data->getHSTSHeader());
4065 // 2XX success, 304 Not Modified
4066 if ((code >= 200 && code <= 204) || code == 304)
4068 CHttpCacheObject obj;
4069 obj.Expires = data.data->getExpires();
4070 obj.Etag = data.data->getEtag();
4071 obj.LastModified = data.data->getLastModified();
4073 CHttpCache::getInstance()->store(data.dest, obj);
4074 std::string tmpfile = data.dest + ".tmp";
4075 if (code == 304 && CFile::fileExists(tmpfile))
4077 CFile::deleteFile(tmpfile);
4080 else if ((code >= 301 && code <= 303) || code == 307 || code == 308)
4082 if (data.redirects < DEFAULT_RYZOM_REDIRECT_LIMIT)
4084 std::string location(data.data->getLocationHeader());
4085 if (!location.empty())
4087 CUrlParser uri(location);
4088 if (!uri.isAbsolute())
4090 uri.inherit(data.url);
4091 location = uri.toString();
4094 // push same request in the front of the queue
4095 // cache filename is based of original url
4096 Curls.push_front(data);
4097 // clear old request state
4098 Curls.front().data = NULL;
4099 Curls.front().fp = NULL;
4100 Curls.front().url = location;
4101 Curls.front().redirects++;
4103 LOG_DL("Redirect '%s'", location.c_str());
4104 // no finished callback called, so cleanup old temp
4105 std::string tmpfile = data.dest + ".tmp";
4106 if (CFile::fileExists(tmpfile))
4108 CFile::deleteFile(tmpfile);
4110 return;
4113 nlwarning("Redirected to empty url '%s'", data.url.c_str());
4115 else
4117 nlwarning("Redirect limit reached for '%s'", data.url.c_str());
4120 else
4122 nlwarning("HTTP request failed with code [%d] for '%s'\n",code, data.url.c_str());
4123 // 404, 500, etc
4124 if (CFile::fileExists(data.dest))
4126 CFile::deleteFile(data.dest);
4130 else
4132 nlwarning("DATA download failed '%s', error '%s'", data.url.c_str(), error.c_str());
4135 finishCurlDownload(data);
4138 void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
4140 LOG_DL("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
4142 // create <html> markup for image downloads
4143 if (type.find("image/") == 0 && !content.empty())
4147 std::string dest = localImageName(_URL);
4148 COFile out;
4149 out.open(dest);
4150 out.serialBuffer((uint8 *)(content.c_str()), content.size());
4151 out.close();
4152 LOG_DL("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
4154 catch(...) { }
4156 // create html code with image url inside and do the request again
4157 renderHtmlString("<html><head><title>"+_URL+"</title></head><body><img src=\"" + _URL + "\"></body></html>");
4159 else if (_TrustedDomain && type.find("text/lua") == 0)
4161 setTitle(_TitleString);
4163 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+content;
4164 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
4165 _LuaScript.clear();
4167 // disable refresh button
4168 clearRefresh();
4169 // disable redo into this url
4170 _AskedUrl.clear();
4172 else
4174 // Sanitize downloaded HTML UTF-8 encoding, and render
4175 renderHtmlString(CUtfStringView(content).toUtf8(true));
4179 // ***************************************************************************
4180 void CGroupHTML::cssDownloadFinished(const std::string &url, const std::string &local)
4182 for(std::vector<CHtmlParser::StyleLink>::iterator it = _StylesheetQueue.begin();
4183 it != _StylesheetQueue.end(); ++it)
4185 if (it->Url == url)
4187 // read downloaded file into HtmlStyles
4188 if (CFile::fileExists(local) && it->Index < _HtmlStyles.size())
4190 CIFile in;
4191 if (in.open(local))
4193 if (!in.readAll(_HtmlStyles[it->Index]))
4195 nlwarning("Failed to read downloaded css file(%s), url(%s)", local.c_str(), url.c_str());
4200 _StylesheetQueue.erase(it);
4201 break;
4206 void CGroupHTML::renderDocument()
4208 if (!Curls.empty() && !_StylesheetQueue.empty())
4210 // waiting for stylesheets to finish downloading
4211 return;
4213 _WaitingForStylesheet = false;
4215 //TGameTime renderStart = CTime::getLocalTime();
4217 // clear previous state and page
4218 beginBuild();
4219 removeContent();
4221 // process all <style> and <link rel=stylesheet> elements
4222 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4224 if (!_HtmlStyles[i].empty())
4226 _Style.parseStylesheet(_HtmlStyles[i]);
4229 _HtmlStyles.clear();
4231 std::list<CHtmlElement>::iterator it = _HtmlDOM.Children.begin();
4232 while(it != _HtmlDOM.Children.end())
4234 renderDOM(*it);
4235 ++it;
4238 endBuild();
4240 //TGameTime renderStop = CTime::getLocalTime();
4241 //nlwarning("[%s] render: %.1fms (%s)\n", _Id.c_str(), (renderStop - renderStart), _URL.c_str());
4244 // ***************************************************************************
4246 bool CGroupHTML::renderHtmlString(const std::string &html)
4248 bool success;
4250 // if we are already rendering, then queue up the next page
4251 if (_Browsing)
4253 _DocumentHtml = html;
4254 _RenderNextTime = true;
4256 return true;
4260 _DocumentUrl = _URL;
4261 _DocumentHtml = html;
4262 _NextRefreshTime = 0;
4263 _RefreshUrl.clear();
4265 if (trim(html).empty())
4267 // clear the page
4268 beginBuild();
4270 // clear previous page and state
4271 removeContent();
4273 endBuild();
4275 success = false;
4277 else
4279 // browser.css
4280 resetCssStyle();
4282 // start new rendering
4283 _HtmlDOM = CHtmlElement(CHtmlElement::NONE, "<root>");
4284 _CurrentHTMLElement = NULL;
4285 success = parseHtml(html);
4286 if (success)
4288 _WaitingForStylesheet = !_StylesheetQueue.empty();
4289 renderDocument();
4291 else
4293 std::string error = "ERROR: HTML parse failed.";
4294 error += toString("\nsize %d bytes", html.size());
4295 error += toString("\n---start---\n%s\n---end---\n", html.c_str());
4296 browseError(error.c_str());
4300 return success;
4303 // ***************************************************************************
4304 void CGroupHTML::doBrowseAnchor(const std::string &anchor)
4306 if (_Anchors.count(anchor) == 0)
4308 return;
4311 CInterfaceElement *pIE = _Anchors.find(anchor)->second;
4312 if (pIE)
4314 // hotspot depends on vertical/horizontal scrollbar
4315 CCtrlScroll *pSB = getScrollBar();
4316 if (pSB)
4318 pSB->ensureVisible(pIE, Hotspot_Tx, Hotspot_Tx);
4323 // ***************************************************************************
4325 void CGroupHTML::draw ()
4327 CGroupScrollText::draw ();
4330 // ***************************************************************************
4332 void CGroupHTML::beginBuild ()
4334 _Browsing = true;
4337 void CGroupHTML::endBuild ()
4339 // set the browser as complete
4340 _Browsing = false;
4341 updateRefreshButton();
4343 // check that the title is set, or reset it (in the case the page
4344 // does not provide a title)
4345 if (_TitleString.empty())
4347 setTitle(_TitlePrefix);
4350 invalidateCoords();
4353 // ***************************************************************************
4355 void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
4359 // ***************************************************************************
4361 void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
4365 // ***************************************************************************
4367 string CGroupHTML::home () const
4369 return Home;
4372 // ***************************************************************************
4374 void CGroupHTML::removeContent ()
4376 // Remove old document
4377 if (!_GroupListAdaptor)
4379 _GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
4380 _GroupListAdaptor->setId(getList()->getId() + ":GLA");
4381 _GroupListAdaptor->setResizeFromChildH(true);
4382 getList()->addChild (_GroupListAdaptor, true);
4385 // Group list adaptor not exist ?
4386 _GroupListAdaptor->clearGroups();
4387 _GroupListAdaptor->clearControls();
4388 _GroupListAdaptor->clearViews();
4389 CWidgetManager::getInstance()->clearViewUnders();
4390 CWidgetManager::getInstance()->clearCtrlsUnders();
4392 // Clear all the context
4393 clearContext();
4395 // Reset default background color
4396 setBackgroundColor (BgColor);
4397 setBackground ("blank.tga", true, false);
4399 paragraphChange ();
4402 // ***************************************************************************
4403 const std::string &CGroupHTML::selectTreeNodeRecurs(CGroupTree::SNode *node, const std::string &url)
4405 static std::string emptyString;
4406 if(!node)
4408 return emptyString;
4411 // if this node match
4412 if(actionLaunchUrlRecurs(node->AHName, node->AHParams, url))
4414 return node->Id;
4416 // fails => look into children
4417 else
4419 for(uint i=0;i<node->Children.size();i++)
4421 const string &childRes= selectTreeNodeRecurs(node->Children[i], url);
4422 if(!childRes.empty())
4423 return childRes;
4426 // none match...
4427 return emptyString;
4431 // ***************************************************************************
4432 bool CGroupHTML::actionLaunchUrlRecurs(const std::string &ah, const std::string &params, const std::string &url)
4434 // check if this action match
4435 if( (ah=="launch_help" || ah=="browse") && IActionHandler::getParam (params, "url") == url)
4437 return true;
4439 // can be a proc that contains launch_help/browse => look recurs
4440 else if(ah=="proc")
4442 const std::string &procName= params;
4443 // look into this proc
4444 uint numActions= CWidgetManager::getInstance()->getParser()->getProcedureNumActions(procName);
4445 for(uint i=0;i<numActions;i++)
4447 string procAh, procParams;
4448 if( CWidgetManager::getInstance()->getParser()->getProcedureAction(procName, i, procAh, procParams))
4450 // recurs proc if needed!
4451 if (actionLaunchUrlRecurs(procAh, procParams, url))
4452 return true;
4457 return false;
4460 // ***************************************************************************
4461 void CGroupHTML::clearRefresh()
4463 _URL.clear();
4464 updateRefreshButton();
4467 // ***************************************************************************
4468 void CGroupHTML::clearUndoRedo()
4470 // erase any undo/redo
4471 _BrowseUndo.clear();
4472 _BrowseRedo.clear();
4474 // update buttons validation
4475 updateUndoRedoButtons();
4478 // ***************************************************************************
4479 void CGroupHTML::pushUrlUndoRedo(const std::string &url)
4481 // if same url, no op
4482 if(url==_AskedUrl)
4483 return;
4485 // erase any redo, push undo, set current
4486 _BrowseRedo.clear();
4487 if(!_AskedUrl.empty())
4488 _BrowseUndo.push_back(_AskedUrl);
4489 _AskedUrl= url;
4491 // limit undo
4492 while(_BrowseUndo.size()>MaxUrlUndoRedo)
4493 _BrowseUndo.pop_front();
4495 // update buttons validation
4496 updateUndoRedoButtons();
4499 // ***************************************************************************
4500 void CGroupHTML::browseUndo()
4502 if(_BrowseUndo.empty())
4503 return;
4505 // push to redo, pop undo, and set current
4506 if (!_AskedUrl.empty())
4507 _BrowseRedo.push_front(_AskedUrl);
4509 _AskedUrl= _BrowseUndo.back();
4510 _BrowseUndo.pop_back();
4512 // update buttons validation
4513 updateUndoRedoButtons();
4515 // and then browse the undoed url, with no undo/redo
4516 doBrowse(_AskedUrl.c_str());
4519 // ***************************************************************************
4520 void CGroupHTML::browseRedo()
4522 if(_BrowseRedo.empty())
4523 return;
4525 // push to undo, pop redo, and set current
4526 _BrowseUndo.push_back(_AskedUrl);
4527 _AskedUrl= _BrowseRedo.front();
4528 _BrowseRedo.pop_front();
4530 // update buttons validation
4531 updateUndoRedoButtons();
4533 // and then browse the redoed url, with no undo/redo
4534 doBrowse(_AskedUrl.c_str());
4537 // ***************************************************************************
4538 void CGroupHTML::updateUndoRedoButtons()
4540 CCtrlBaseButton *butUndo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
4541 CCtrlBaseButton *butRedo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRedoButton));
4543 // gray according to list size
4544 if(butUndo)
4545 butUndo->setFrozen(_BrowseUndo.empty());
4546 if(butRedo)
4547 butRedo->setFrozen(_BrowseRedo.empty());
4550 // ***************************************************************************
4551 void CGroupHTML::updateRefreshButton()
4553 CCtrlBaseButton *butRefresh = dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
4554 if(butRefresh)
4556 // connecting, rendering, or is missing url
4557 bool frozen = _CurlWWW || _Browsing || _URL.empty();
4558 butRefresh->setFrozen(frozen);
4562 // ***************************************************************************
4564 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
4566 CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam &param)
4567 : CInterfaceGroup(param),
4568 Offset(0)
4572 xmlNodePtr CGroupHTMLInputOffset::serialize( xmlNodePtr parentNode, const char *type ) const
4574 xmlNodePtr node = CInterfaceGroup::serialize( parentNode, type );
4575 if( node == NULL )
4576 return NULL;
4578 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html_input_offset" );
4579 xmlSetProp( node, BAD_CAST "y_offset", BAD_CAST toString( Offset ).c_str() );
4581 return node;
4584 // ***************************************************************************
4585 bool CGroupHTMLInputOffset::parse(xmlNodePtr cur, CInterfaceGroup *parentGroup)
4587 if (!CInterfaceGroup::parse(cur, parentGroup)) return false;
4588 CXMLAutoPtr ptr;
4589 // Get the url
4590 ptr = xmlGetProp (cur, (xmlChar*)"y_offset");
4591 if (ptr)
4592 fromString((const char*)ptr, Offset);
4593 return true;
4596 // ***************************************************************************
4597 int CGroupHTML::luaParseHtml(CLuaState &ls)
4599 const char *funcName = "parseHtml";
4600 CLuaIHM::checkArgCount(ls, funcName, 1);
4601 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4602 std::string html = ls.toString(1);
4604 parseHtml(html);
4606 return 0;
4609 int CGroupHTML::luaClearRefresh(CLuaState &ls)
4611 const char *funcName = "clearRefresh";
4612 CLuaIHM::checkArgCount(ls, funcName, 0);
4614 clearRefresh();
4616 return 0;
4619 int CGroupHTML::luaClearUndoRedo(CLuaState &ls)
4621 const char *funcName = "clearUndoRedo";
4622 CLuaIHM::checkArgCount(ls, funcName, 0);
4624 clearUndoRedo();
4625 return 0;
4628 // ***************************************************************************
4629 int CGroupHTML::luaBrowse(CLuaState &ls)
4631 const char *funcName = "browse";
4632 CLuaIHM::checkArgCount(ls, funcName, 1);
4633 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4634 browse(ls.toString(1));
4635 return 0;
4638 // ***************************************************************************
4639 int CGroupHTML::luaRefresh(CLuaState &ls)
4641 const char *funcName = "refresh";
4642 CLuaIHM::checkArgCount(ls, funcName, 0);
4643 refresh();
4644 return 0;
4647 // ***************************************************************************
4648 int CGroupHTML::luaRemoveContent(CLuaState &ls)
4650 const char *funcName = "removeContent";
4651 CLuaIHM::checkArgCount(ls, funcName, 0);
4652 removeContent();
4653 return 0;
4656 // ***************************************************************************
4657 int CGroupHTML::luaRenderHtml(CLuaState &ls)
4659 const char *funcName = "renderHtml";
4660 CLuaIHM::checkArgCount(ls, funcName, 1);
4661 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4662 std::string html = ls.toString(1);
4664 // Always trust domain if rendered from lua
4665 _TrustedDomain = true;
4666 renderHtmlString(html);
4668 return 0;
4671 // ***************************************************************************
4672 int CGroupHTML::luaSetBackground(CLuaState &ls)
4674 const char *funcName = "setBackground";
4675 CLuaIHM::checkArgCount(ls, funcName, 3);
4676 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4677 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4678 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4679 std::string image = ls.toString(1);
4680 bool scale = ls.toBoolean(2);
4681 bool repeat = ls.toBoolean(3);
4683 setBackground(image, scale, repeat);
4685 return 0;
4688 // ***************************************************************************
4689 int CGroupHTML::luaInsertText(CLuaState &ls)
4691 const char *funcName = "insertText";
4692 CLuaIHM::checkArgCount(ls, funcName, 3);
4693 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4694 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TSTRING);
4695 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4697 string name = ls.toString(1);
4698 string text = ls.toString(2);
4700 if (!_Forms.empty())
4702 for (uint i=0; i<_Forms.back().Entries.size(); i++)
4704 if (_Forms.back().Entries[i].TextArea && _Forms.back().Entries[i].Name == name)
4706 // Get the edit box view
4707 CInterfaceGroup *group = _Forms.back().Entries[i].TextArea->getGroup ("eb");
4708 if (group)
4710 // Should be a CGroupEditBox
4711 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
4712 if (editBox)
4713 editBox->writeString(text, false, ls.toBoolean(3));
4719 return 0;
4722 // ***************************************************************************
4723 int CGroupHTML::luaAddString(CLuaState &ls)
4725 const char *funcName = "addString";
4726 CLuaIHM::checkArgCount(ls, funcName, 1);
4727 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4728 addString(ls.toString(1));
4729 return 0;
4732 // ***************************************************************************
4733 int CGroupHTML::luaAddImage(CLuaState &ls)
4735 const char *funcName = "addImage";
4736 CLuaIHM::checkArgCount(ls, funcName, 2);
4737 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4738 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4739 if (!_Paragraph)
4741 newParagraph(0);
4742 paragraphChange();
4745 CStyleParams style;
4746 style.GlobalColor = ls.toBoolean(2);
4748 string url = getLink();
4749 if (!url.empty())
4751 string params = "name=" + getId() + "|url=" + getLink ();
4752 addButton(CCtrlButton::PushButton, "", ls.toString(1), ls.toString(1),
4753 "", "browse", params.c_str(), "", style);
4755 else
4757 addImage("", ls.toString(1), false, style);
4761 return 0;
4764 // ***************************************************************************
4765 int CGroupHTML::luaShowDiv(CLuaState &ls)
4767 const char *funcName = "showDiv";
4768 CLuaIHM::checkArgCount(ls, funcName, 2);
4769 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4770 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4772 if (!_Groups.empty())
4774 for (uint i=0; i<_Groups.size(); i++)
4776 CInterfaceGroup *group = _Groups[i];
4777 if (group->getName() == ls.toString(1))
4779 group->setActive(ls.toBoolean(2));
4783 return 0;
4786 // ***************************************************************************
4787 void CGroupHTML::setURL(const std::string &url)
4789 browse(url.c_str());
4792 void CGroupHTML::setHTML(const std::string &html)
4794 renderHtmlString(html);
4797 void CGroupHTML::setHome(const std::string &home)
4799 Home = home;
4802 // ***************************************************************************
4803 void CGroupHTML::parseStylesheetFile(const std::string &fname)
4805 CIFile css;
4806 if (css.open(fname))
4808 uint32 remaining = css.getFileSize();
4809 std::string content;
4810 try {
4811 while(!css.eof() && remaining > 0)
4813 const uint BUF_SIZE = 4096;
4814 char buf[BUF_SIZE];
4816 uint32 readJustNow = std::min(remaining, BUF_SIZE);
4817 css.serialBuffer((uint8 *)&buf, readJustNow);
4818 content.append(buf, readJustNow);
4819 remaining -= readJustNow;
4822 _Style.parseStylesheet(content);
4824 catch(const Exception &e)
4826 nlwarning("exception while reading css file '%s'", e.what());
4829 else
4831 nlwarning("Stylesheet file '%s' not found (%s)", fname.c_str(), _URL.c_str());
4835 // ***************************************************************************
4836 bool CGroupHTML::parseHtml(const std::string &htmlString)
4838 CHtmlElement *parsedDOM;
4839 if (_CurrentHTMLElement == NULL)
4841 // parse under <root> element (clean dom)
4842 parsedDOM = &_HtmlDOM;
4844 else
4846 // parse under currently rendered <lua> element
4847 parsedDOM = _CurrentHTMLElement;
4850 std::vector<CHtmlParser::StyleLink> links;
4852 CHtmlParser parser;
4853 parser.getDOM(htmlString, *parsedDOM, _HtmlStyles, links);
4855 // <link> elements inserted from lua::parseHtml are ignored
4856 if (_CurrentHTMLElement == NULL && !links.empty())
4858 addStylesheetDownload(links);
4860 else if (_CurrentHTMLElement != NULL)
4862 // Called from active element (lua)
4863 // <style> order is not preserved as document is already being rendered
4864 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4866 if (!_HtmlStyles[i].empty())
4868 _Style.parseStylesheet(_HtmlStyles[i]);
4871 _HtmlStyles.clear();
4874 // this should rarely fail as first element should be <html>
4875 bool success = parsedDOM->Children.size() > 0;
4877 std::list<CHtmlElement>::iterator it = parsedDOM->Children.begin();
4878 while(it != parsedDOM->Children.end())
4880 if (it->Type == CHtmlElement::ELEMENT_NODE && it->Value == "html")
4882 // move newly parsed childs from <body> into siblings
4883 if (_CurrentHTMLElement) {
4884 std::list<CHtmlElement>::iterator it2 = it->Children.begin();
4885 while(it2 != it->Children.end())
4887 if (it2->Type == CHtmlElement::ELEMENT_NODE && it2->Value == "body")
4889 spliceFragment(it2);
4890 break;
4892 ++it2;
4894 // remove <html> fragment from current element child
4895 it = parsedDOM->Children.erase(it);
4897 else
4899 // remove link to <root> (html->parent == '<root>') or css selector matching will break
4900 it->parent = NULL;
4901 ++it;
4903 continue;
4906 // skip over other non-handled element
4907 ++it;
4910 return success;
4913 void CGroupHTML::spliceFragment(std::list<CHtmlElement>::iterator src)
4915 if(!_CurrentHTMLElement->parent)
4917 nlwarning("BUG: Current node is missing parent element. unable to splice fragment");
4918 return;
4921 // get the iterators for current element (<lua>) and next sibling
4922 std::list<CHtmlElement>::iterator currentElement;
4923 currentElement = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLElement);
4924 if (currentElement == _CurrentHTMLElement->parent->Children.end())
4926 nlwarning("BUG: unable to find current element iterator from parent");
4927 return;
4930 // where fragment should be moved
4931 std::list<CHtmlElement>::iterator insertBefore;
4932 if (_CurrentHTMLNextSibling == NULL)
4934 insertBefore = _CurrentHTMLElement->parent->Children.end();
4935 } else {
4936 // get iterator for nextSibling
4937 insertBefore = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLNextSibling);
4940 _CurrentHTMLElement->parent->Children.splice(insertBefore, src->Children);
4942 // reindex moved elements
4943 CHtmlElement *prev = NULL;
4944 uint childIndex = _CurrentHTMLElement->childIndex;
4945 while(currentElement != _CurrentHTMLElement->parent->Children.end())
4947 if (currentElement->Type == CHtmlElement::ELEMENT_NODE)
4949 if (prev != NULL)
4951 currentElement->parent = _CurrentHTMLElement->parent;
4952 currentElement->childIndex = childIndex;
4953 currentElement->previousSibling = prev;
4954 prev->nextSibling = &(*currentElement);
4957 childIndex++;
4958 prev = &(*currentElement);
4960 ++currentElement;
4964 // ***************************************************************************
4965 inline bool isDigit(char c, uint base = 16)
4967 if (c>='0' && c<='9') return true;
4968 if (base != 16) return false;
4969 if (c>='A' && c<='F') return true;
4970 if (c>='a' && c<='f') return true;
4971 return false;
4974 // ***************************************************************************
4975 inline char convertHexDigit(char c)
4977 if (c>='0' && c<='9') return c-'0';
4978 if (c>='A' && c<='F') return c-'A'+10;
4979 if (c>='a' && c<='f') return c-'a'+10;
4980 return 0;
4983 // ***************************************************************************
4984 std::string CGroupHTML::decodeHTMLEntities(const std::string &str)
4986 std::string result;
4987 result.reserve(str.size() + (str.size() >> 2));
4988 uint last, pos;
4990 for (uint i=0; i<str.length(); ++i)
4992 // HTML entity
4993 if (str[i] == '&' && (str.length()-i) >= 4)
4995 pos = i+1;
4997 // unicode character
4998 if (str[pos] == '#')
5000 ++pos;
5002 // using decimal by default
5003 uint base = 10;
5005 // using hexadecimal if &#x
5006 if (str[pos] == 'x')
5008 base = 16;
5009 ++pos;
5012 // setup "last" to point at the first character following "&#x?[0-9a-f]+"
5013 for (last = pos; last < str.length(); ++last) if (!isDigit(str[last], base)) break;
5015 // make sure that at least 1 digit was found
5016 // and have the terminating ';' to complete the token: "&#x?[0-9a-f]+;"
5017 if (last == pos || str[last] != ';')
5019 result += str[i];
5020 continue;
5023 u32char c = 0;
5025 // convert digits to unicode character
5026 while (pos < last) c = convertHexDigit(str[pos++]) + (c * u32char(base));
5028 // append our new character to the result string
5029 CUtfStringView::append(result, c);
5031 // move 'i' forward to point at the ';' .. the for(...) will increment i to point to next char
5032 i = last;
5034 continue;
5037 // special xml characters
5038 if (str.substr(i+1,5)=="quot;") { i+=5; result+='\"'; continue; }
5039 if (str.substr(i+1,4)=="amp;") { i+=4; result+='&'; continue; }
5040 if (str.substr(i+1,3)=="lt;") { i+=3; result+='<'; continue; }
5041 if (str.substr(i+1,3)=="gt;") { i+=3; result+='>'; continue; }
5044 // all the special cases are catered for... treat this as a normal character
5045 result += str[i];
5048 return result;
5051 // ***************************************************************************
5052 std::string CGroupHTML::getAbsoluteUrl(const std::string &url)
5054 CUrlParser uri(url);
5055 if (uri.isAbsolute())
5056 return url;
5058 uri.inherit(_URL);
5060 return uri.toString();
5063 // ***************************************************************************
5064 void CGroupHTML::resetCssStyle()
5066 _WaitingForStylesheet = false;
5067 _StylesheetQueue.clear();
5068 _Style.reset();
5069 _Style = _BrowserStyle;
5072 // ***************************************************************************
5073 std::string CGroupHTML::HTMLOListElement::getListMarkerText() const
5075 std::string ret;
5076 sint32 number = Value;
5078 if (Type == "disc")
5080 // (ucchar)0x2219;
5081 ret = "\xe2\x88\x99 ";
5083 else if (Type == "circle")
5085 // (uchar)0x26AA;
5086 ret = "\xe2\x9a\xaa ";
5088 else if (Type == "square")
5090 // (ucchar)0x25AA;
5091 ret = "\xe2\x96\xaa ";
5093 else if (Type == "a" || Type == "A")
5095 // @see toAlphabeticOrNumeric in WebKit
5096 static const char lower[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
5097 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
5098 static const char upper[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
5099 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
5100 uint size = 26;
5101 if (number < 1)
5103 ret = toString(number);
5105 else
5107 const char* digits = (Type == "A" ? upper : lower);
5108 while(number > 0)
5110 --number;
5111 ret.insert(ret.begin(), digits[number % size]);
5112 number /= size;
5115 ret += ". ";
5117 else if (Type == "i" || Type == "I")
5119 // @see toRoman in WebKit
5120 static const char lower[7] = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
5121 static const char upper[7] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'};
5123 if (number < 1 || number > 3999)
5125 ret = toString(number);
5127 else
5129 const char* digits = (Type == "I" ? upper : lower);
5130 uint8 i, d=0;
5133 uint32 num = number % 10;
5134 if (num % 5 < 4)
5136 for (i = num % 5; i > 0; i--)
5138 ret.insert(ret.begin(), digits[d]);
5141 if (num >= 4 && num <= 8)
5143 ret.insert(ret.begin(), digits[d + 1]);
5145 if (num == 9)
5147 ret.insert(ret.begin(), digits[d + 2]);
5149 if (num % 5 == 4)
5151 ret.insert(ret.begin(), digits[d]);
5153 number /= 10;
5154 d += 2;
5156 while (number > 0);
5158 if (Type == "I")
5160 ret = toUpper(ret);
5163 ret += ". ";
5165 else
5167 ret = toString(Value) + ". ";
5170 return ret;
5173 void CGroupHTML::HTMLMeterElement::readValues(const CHtmlElement &elm)
5175 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5176 value = 0.f;
5177 if (!elm.hasAttribute("min") || !fromString(elm.getAttribute("min"), min))
5178 min = 0.f;
5179 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5180 max = 1.f;
5182 // ensure min < max
5183 if (max < min)
5184 std::swap(min, max);
5186 if (!elm.hasAttribute("low") || !fromString(elm.getAttribute("low"), low))
5187 low = min;
5188 if (!elm.hasAttribute("high") || !fromString(elm.getAttribute("high"), high))
5189 high = max;
5191 if (!elm.hasAttribute("optimum") || !fromString(elm.getAttribute("optimum"), optimum))
5192 optimum = (max - min) / 2.f;
5194 // ensure low < high
5195 if (high < low)
5196 std::swap(low, high);
5197 if (low < min)
5198 low = min;
5199 if (high > max)
5200 high = max;
5203 float CGroupHTML::HTMLMeterElement::getValueRatio() const
5205 if (max <= min)
5206 return 0.f;
5208 return (value - min) / (max - min);
5211 CGroupHTML::HTMLMeterElement::EValueRegion CGroupHTML::HTMLMeterElement::getValueRegion() const
5213 if (optimum <= low)
5215 // low region is optimum
5216 if (value <= low)
5217 return VALUE_OPTIMUM;
5218 else if (value <= high)
5219 return VALUE_SUB_OPTIMAL;
5221 return VALUE_EVEN_LESS_GOOD;
5223 else if (optimum >= high)
5225 // high region is optimum
5226 if (value >= high)
5227 return VALUE_OPTIMUM;
5228 else if (value >= low)
5229 return VALUE_SUB_OPTIMAL;
5231 return VALUE_EVEN_LESS_GOOD;
5234 // middle region is optimum
5235 if (value >= low && value <= high)
5236 return VALUE_OPTIMUM;
5238 return VALUE_SUB_OPTIMAL;
5241 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5243 // color meter (inactive) bar segment
5244 // firefox:: meter { background:none; background-color: #555; },
5245 // webkit:: meter::-webkit-meter-bar { background:none; background-color: #555; }
5246 // webkit makes background color visible when padding is added
5247 CRGBA color(150, 150, 150, 255);
5249 // use webkit pseudo elements as thats easier than firefox pseudo classes
5250 // background-color is expected to be set from browser.css
5251 style.pushStyle();
5252 style.applyStyle(elm.getPseudo(":-webkit-meter-bar"));
5253 if(style.hasStyle("background-color"))
5254 color = style.Current.BackgroundColor;
5255 style.popStyle();
5257 return color;
5260 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5262 // background-color is expected to be set from browser.css
5263 CRGBA color;
5264 style.pushStyle();
5265 switch(getValueRegion())
5267 case VALUE_OPTIMUM:
5269 style.applyStyle(elm.getPseudo(":-webkit-meter-optimum-value"));
5270 if (style.hasStyle("background-color"))
5271 color = style.Current.BackgroundColor;
5272 break;
5274 case VALUE_SUB_OPTIMAL:
5276 style.applyStyle(elm.getPseudo(":-webkit-meter-suboptimum-value"));
5277 if (style.hasStyle("background-color"))
5278 color = style.Current.BackgroundColor;
5279 break;
5281 case VALUE_EVEN_LESS_GOOD: // fall through
5282 default:
5284 style.applyStyle(elm.getPseudo(":-webkit-meter-even-less-good-value"));
5285 if (style.hasStyle("background-color"))
5286 color = style.Current.BackgroundColor;
5287 break;
5289 }//switch
5290 style.popStyle();
5292 return color;
5295 // ****************************************************************************
5296 void CGroupHTML::HTMLProgressElement::readValues(const CHtmlElement &elm)
5298 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5299 value = 0.f;
5300 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5301 max = 1.f;
5303 if (value > max)
5304 value = max;
5307 // ****************************************************************************
5308 float CGroupHTML::HTMLProgressElement::getValueRatio() const
5310 if (max > 0.f)
5311 return value / max;
5312 return 0.f;
5315 // ****************************************************************************
5316 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5318 CRGBA color;
5320 style.pushStyle();
5321 style.applyStyle(elm.getPseudo(":-webkit-progress-bar"));
5322 if (style.hasStyle("background-color"))
5323 color = style.Current.BackgroundColor;
5324 style.popStyle();
5326 return color;
5329 // ****************************************************************************
5330 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5332 CRGBA color;
5334 style.pushStyle();
5335 style.applyStyle(elm.getPseudo(":-webkit-progress-value"));
5336 if (style.hasStyle("background-color"))
5337 color = style.Current.BackgroundColor;
5338 style.popStyle();
5340 return color;
5343 // ****************************************************************************
5344 void CGroupHTML::getCellsParameters(const CHtmlElement &elm, bool inherit)
5346 CGroupHTML::CCellParams cellParams;
5347 if (!_CellParams.empty() && inherit)
5348 cellParams = _CellParams.back();
5350 if (_Style.hasStyle("background-color"))
5351 cellParams.BgColor = _Style.Current.BackgroundColor;
5352 else if (elm.hasNonEmptyAttribute("bgcolor"))
5353 scanHTMLColor(elm.getAttribute("bgcolor").c_str(), cellParams.BgColor);
5355 if (elm.hasAttribute("nowrap") || _Style.Current.WhiteSpace == "nowrap")
5356 cellParams.NoWrap = true;
5358 if (elm.hasNonEmptyAttribute("l_margin"))
5359 fromString(elm.getAttribute("l_margin"), cellParams.LeftMargin);
5361 if (_Style.hasStyle("height"))
5362 cellParams.Height = _Style.Current.Height;
5363 else if (elm.hasNonEmptyAttribute("height"))
5364 fromString(elm.getAttribute("height"), cellParams.Height);
5367 std::string align;
5368 // having text-align on table/tr should not override td align attribute
5369 if (_Style.hasStyle("text-align"))
5370 align = _Style.Current.TextAlign;
5371 else if (elm.hasNonEmptyAttribute("align"))
5372 align = toLowerAscii(elm.getAttribute("align"));
5374 if (align == "left")
5375 cellParams.Align = CGroupCell::Left;
5376 else if (align == "center")
5377 cellParams.Align = CGroupCell::Center;
5378 else if (align == "right")
5379 cellParams.Align = CGroupCell::Right;
5383 std::string valign;
5384 if (_Style.hasStyle("vertical-align"))
5385 valign = _Style.Current.VerticalAlign;
5386 else if (elm.hasNonEmptyAttribute("valign"))
5387 valign = toLowerAscii(elm.getAttribute("valign"));
5389 if (valign == "top")
5390 cellParams.VAlign = CGroupCell::Top;
5391 else if (valign == "middle")
5392 cellParams.VAlign = CGroupCell::Middle;
5393 else if (valign == "bottom")
5394 cellParams.VAlign = CGroupCell::Bottom;
5397 _CellParams.push_back (cellParams);
5400 // ***************************************************************************
5401 void CGroupHTML::applyBackground(const CHtmlElement &elm)
5403 bool root = elm.Value == "html" || elm.Value == "body";
5405 // non-empty image
5406 if (_Style.hasStyle("background-image"))
5408 bool repeat = _Style.checkStyle("background-repeat", "repeat");
5409 bool scale = _Style.checkStyle("background-size", "100%");
5410 std::string image = _Style.getStyle("background-image");
5411 if (!image.empty())
5413 if (root)
5415 setBackground (image, scale, repeat);
5417 // TODO: else
5419 // default background color is transparent, so image does not show
5420 if (!_Style.hasStyle("background-color") || _Style.checkStyle("background-color", "transparent"))
5422 _Style.applyStyle("background-color: #fff;");
5427 if (_Style.hasStyle("background-color"))
5429 CRGBA bgColor = _Style.Current.BackgroundColor;
5430 scanHTMLColor(elm.getAttribute("bgcolor").c_str(), bgColor);
5431 if (root)
5433 setBackgroundColor(bgColor);
5435 // TODO: else
5440 // ***************************************************************************
5441 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)
5443 _FormSubmit.push_back(SFormSubmitButton(formId, name, "", "image", action));
5444 // Action handler parameters
5445 std::string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5447 // Add the ctrl button
5448 addButton (CCtrlButton::PushButton, name, src, src, over, "html_submit_form", param.c_str(), tooltip.c_str(), _Style.Current);
5451 // ***************************************************************************
5452 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)
5454 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "submit", formAction));
5455 // Action handler parameters
5456 string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5458 // Add the ctrl button
5459 if (!_Paragraph)
5461 newParagraph (0);
5462 paragraphChange ();
5465 string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup);
5466 typedef pair<string, string> TTmplParam;
5467 vector<TTmplParam> tmplParams;
5468 tmplParams.push_back(TTmplParam("id", name));
5469 tmplParams.push_back(TTmplParam("onclick", "html_submit_form"));
5470 tmplParams.push_back(TTmplParam("onclick_param", param));
5471 tmplParams.push_back(TTmplParam("active", "true"));
5472 if (minWidth > 0) tmplParams.push_back(TTmplParam("wmin", toString(minWidth)));
5473 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
5474 if (buttonGroup)
5476 // Add the ctrl button
5477 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
5478 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
5479 if (ctrlButton)
5481 ctrlButton->setModulateGlobalColorAll (_Style.Current.GlobalColor);
5483 // Translate the tooltip
5484 if (!tooltip.empty())
5486 if (CI18N::hasTranslation(tooltip))
5488 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
5490 else
5492 ctrlButton->setDefaultContextHelp(tooltip);
5496 ctrlButton->setText(value);
5498 setTextButtonStyle(ctrlButton, _Style.Current);
5500 getParagraph()->addChild (buttonGroup);
5501 paragraphChange ();
5505 // ***************************************************************************
5506 void CGroupHTML::htmlA(const CHtmlElement &elm)
5508 _A.push_back(true);
5509 _Link.push_back ("");
5510 _LinkTitle.push_back("");
5511 _LinkClass.push_back("");
5512 if (elm.hasClass("ryzom-ui-button"))
5513 _LinkClass.back() = "ryzom-ui-button";
5515 // #fragment works with both ID and NAME so register both
5516 if (elm.hasNonEmptyAttribute("name"))
5517 _AnchorName.push_back(elm.getAttribute("name"));
5518 if (elm.hasNonEmptyAttribute("title"))
5519 _LinkTitle.back() = elm.getAttribute("title");
5520 if (elm.hasNonEmptyAttribute("href"))
5522 string suri = elm.getAttribute("href");
5523 if(suri.find("ah:") == 0)
5525 if (_TrustedDomain)
5526 _Link.back() = suri;
5528 else
5530 // convert href from "?key=val" into "http://domain.com/?key=val"
5531 _Link.back() = getAbsoluteUrl(suri);
5535 renderPseudoElement(":before", elm);
5538 void CGroupHTML::htmlAend(const CHtmlElement &elm)
5540 renderPseudoElement(":after", elm);
5542 popIfNotEmpty(_A);
5543 popIfNotEmpty(_Link);
5544 popIfNotEmpty(_LinkTitle);
5545 popIfNotEmpty(_LinkClass);
5548 // ***************************************************************************
5549 void CGroupHTML::htmlBASE(const CHtmlElement &elm)
5551 if (!_ReadingHeadTag || _IgnoreBaseUrlTag)
5552 return;
5554 if (elm.hasNonEmptyAttribute("href"))
5556 CUrlParser uri(elm.getAttribute("href"));
5557 if (uri.isAbsolute())
5559 _URL = uri.toString();
5560 _IgnoreBaseUrlTag = true;
5565 // ***************************************************************************
5566 void CGroupHTML::htmlBODY(const CHtmlElement &elm)
5568 // override <body> (or <html>) css style attribute
5569 if (elm.hasNonEmptyAttribute("bgcolor"))
5571 _Style.applyStyle("background-color: " + elm.getAttribute("bgcolor"));
5574 applyBackground(elm);
5576 renderPseudoElement(":before", elm);
5579 // ***************************************************************************
5580 void CGroupHTML::htmlBR(const CHtmlElement &elm)
5582 if (!_Paragraph || _Paragraph->getNumChildren() == 0)
5584 addString("\n");
5586 else
5588 endParagraph();
5592 // ***************************************************************************
5593 void CGroupHTML::htmlBUTTON(const CHtmlElement &elm)
5595 std::string name = elm.getAttribute("name");
5596 std::string value = elm.getAttribute("value");
5597 std::string formId = elm.getAttribute("form");
5598 std::string formAction = elm.getAttribute("formaction");
5599 std::string tooltip = elm.getAttribute("tooltip");
5600 bool disabled = elm.hasAttribute("disabled");
5602 if (formId.empty() && _FormOpen)
5604 formId = _Forms.back().id;
5607 if (!formAction.empty())
5609 formAction = getAbsoluteUrl(formAction);
5612 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "text", formAction));
5613 // Action handler parameters
5614 std::string param;
5615 if (!disabled)
5617 if (elm.getAttribute("type") == "submit")
5619 param = "ah:html_submit_form&name=" + getId() + "&button=" + toString(_FormSubmit.size()-1);
5621 else
5623 param = "ah:";
5627 _A.push_back(true);
5628 _Link.push_back(param);
5629 _LinkTitle.push_back(tooltip);
5630 _LinkClass.push_back("ryzom-ui-button");
5632 // TODO: this creates separate button element
5633 //renderPseudoElement(":before", elm);
5635 void CGroupHTML::htmlBUTTONend(const CHtmlElement &elm)
5637 // TODO: this creates separate button element
5638 //renderPseudoElement(":after", elm);
5640 popIfNotEmpty(_A);
5641 popIfNotEmpty(_Link);
5642 popIfNotEmpty(_LinkTitle);
5643 popIfNotEmpty(_LinkClass);
5646 // ***************************************************************************
5647 void CGroupHTML::htmlDD(const CHtmlElement &elm)
5649 if (_DL.empty())
5650 return;
5652 // if there was no closing tag for <dt>, then remove <dt> style
5653 if (_DL.back().DT)
5655 nlwarning("BUG: nested DT in DD");
5656 _DL.back().DT = false;
5659 if (_DL.back().DD)
5661 nlwarning("BUG: nested DD in DD");
5662 _DL.back().DD = false;
5663 popIfNotEmpty(_Indent);
5666 _DL.back().DD = true;
5667 _Indent.push_back(getIndent() + ULIndent);
5669 if (!_LI)
5671 _LI = true;
5672 newParagraph(ULBeginSpace);
5674 else
5676 newParagraph(LIBeginSpace);
5679 renderPseudoElement(":before", elm);
5682 void CGroupHTML::htmlDDend(const CHtmlElement &elm)
5684 if (_DL.empty())
5685 return;
5687 renderPseudoElement(":after", elm);
5689 // parser will process two DD in a row as nested when first DD is not closed
5690 if (_DL.back().DD)
5692 _DL.back().DD = false;
5693 popIfNotEmpty(_Indent);
5697 // ***************************************************************************
5698 void CGroupHTML::htmlDIV(const CHtmlElement &elm)
5700 _DivName = elm.getAttribute("name");
5702 string instClass = elm.getAttribute("class");
5704 // use generic template system
5705 if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
5707 string style = elm.getAttribute("style");
5708 string id = elm.getAttribute("id");
5709 if (id.empty())
5710 id = "DIV" + toString(getNextAutoIdSeq());
5712 typedef pair<string, string> TTmplParam;
5713 vector<TTmplParam> tmplParams;
5715 string templateName;
5716 if (!style.empty())
5718 TStyle styles = parseStyle(style);
5719 TStyle::iterator it;
5720 for (it=styles.begin(); it != styles.end(); it++)
5722 if ((*it).first == "template")
5723 templateName = (*it).second;
5724 else
5725 tmplParams.push_back(TTmplParam((*it).first, (*it).second));
5729 if (!templateName.empty())
5731 string parentId;
5732 bool haveParentDiv = getDiv() != NULL;
5733 if (haveParentDiv)
5734 parentId = getDiv()->getId();
5735 else
5737 if (!_Paragraph)
5738 newParagraph (0);
5740 parentId = _Paragraph->getId();
5743 CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, parentId, tmplParams);
5744 if (inst)
5746 inst->setId(parentId+":"+id);
5747 inst->updateCoords();
5748 if (haveParentDiv)
5750 inst->setParent(getDiv());
5751 inst->setParentSize(getDiv());
5752 inst->setParentPos(getDiv());
5753 inst->setPosRef(Hotspot_TL);
5754 inst->setParentPosRef(Hotspot_TL);
5755 getDiv()->addGroup(inst);
5757 else
5759 getParagraph()->addChild(inst);
5760 paragraphChange();
5762 _Divs.push_back(inst);
5767 renderPseudoElement(":before", elm);
5770 void CGroupHTML::htmlDIVend(const CHtmlElement &elm)
5772 renderPseudoElement(":after", elm);
5773 _DivName.clear();
5774 popIfNotEmpty(_Divs);
5777 // ***************************************************************************
5778 void CGroupHTML::htmlDL(const CHtmlElement &elm)
5780 _DL.push_back(HTMLDListElement());
5781 _LI = _DL.size() > 1 || !_UL.empty();
5783 renderPseudoElement(":before", elm);
5786 void CGroupHTML::htmlDLend(const CHtmlElement &elm)
5788 if (_DL.empty())
5789 return;
5791 renderPseudoElement(":after", elm);
5793 // unclosed DT
5794 if (_DL.back().DT)
5796 nlwarning("BUG: unclosed DT in DL");
5799 // unclosed DD
5800 if (_DL.back().DD)
5802 popIfNotEmpty(_Indent);
5803 nlwarning("BUG: unclosed DD in DL");
5806 popIfNotEmpty (_DL);
5809 // ***************************************************************************
5810 void CGroupHTML::htmlDT(const CHtmlElement &elm)
5812 if (_DL.empty())
5813 return;
5815 // TODO: check if nested tags still happen and fix it in parser
5816 // : remove special handling for nesting and let it happen
5818 // html parser and libxml2 should prevent nested tags like these
5819 if (_DL.back().DD)
5821 nlwarning("BUG: nested DD in DT");
5823 _DL.back().DD = false;
5824 popIfNotEmpty(_Indent);
5827 // html parser and libxml2 should prevent nested tags like these
5828 if (_DL.back().DT)
5830 nlwarning("BUG: nested DT in DT");
5833 _DL.back().DT = true;
5835 if (!_LI)
5837 _LI = true;
5838 newParagraph(ULBeginSpace);
5840 else
5842 newParagraph(LIBeginSpace);
5845 renderPseudoElement(":before", elm);
5848 void CGroupHTML::htmlDTend(const CHtmlElement &elm)
5850 if (_DL.empty())
5851 return;
5853 renderPseudoElement(":after", elm);
5855 _DL.back().DT = false;
5858 // ***************************************************************************
5859 void CGroupHTML::htmlFONT(const CHtmlElement &elm)
5861 if (elm.hasNonEmptyAttribute("color"))
5863 CRGBA color;
5864 if (scanHTMLColor(elm.getAttribute("color").c_str(), color))
5865 _Style.Current.TextColor = color;
5868 if (elm.hasNonEmptyAttribute("size"))
5870 uint fontsize;
5871 fromString(elm.getAttribute("size"), fontsize);
5872 _Style.Current.FontSize = fontsize;
5876 // ***************************************************************************
5877 void CGroupHTML::htmlFORM(const CHtmlElement &elm)
5879 _FormOpen = true;
5881 // Build the form
5882 CGroupHTML::CForm form;
5883 // id check is case sensitive and auto id's are uppercase
5884 form.id = toLowerAscii(trim(elm.getAttribute("id")));
5885 if (form.id.empty())
5887 form.id = toString("FORM%d", _Forms.size());
5890 // Get the action name
5891 if (elm.hasNonEmptyAttribute("action"))
5893 form.Action = getAbsoluteUrl(elm.getAttribute("action"));
5895 else
5897 form.Action = _URL;
5900 _Forms.push_back(form);
5902 renderPseudoElement(":before", elm);
5905 void CGroupHTML::htmlFORMend(const CHtmlElement &elm)
5907 _FormOpen = false;
5908 renderPseudoElement(":after", elm);
5911 // ***************************************************************************
5912 void CGroupHTML::htmlH(const CHtmlElement &elm)
5914 newParagraph(PBeginSpace);
5915 renderPseudoElement(":before", elm);
5918 void CGroupHTML::htmlHend(const CHtmlElement &elm)
5920 renderPseudoElement(":after", elm);
5923 // ***************************************************************************
5924 void CGroupHTML::htmlHEAD(const CHtmlElement &elm)
5926 _ReadingHeadTag = !_IgnoreHeadTag;
5927 _IgnoreHeadTag = true;
5930 void CGroupHTML::htmlHEADend(const CHtmlElement &elm)
5932 _ReadingHeadTag = false;
5935 // ***************************************************************************
5936 void CGroupHTML::htmlHR(const CHtmlElement &elm)
5938 CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0);
5939 if (sep)
5941 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*>(sep->getView("hr"));
5942 if (bitmap)
5944 bitmap->setColor(_Style.Current.TextColor);
5945 if (_Style.Current.Width > 0)
5947 clamp(_Style.Current.Width, 1, 32000);
5948 bitmap->setW(_Style.Current.Width);
5949 bitmap->setSizeRef(CInterfaceElement::none);
5951 if (_Style.Current.Height > 0)
5953 clamp(_Style.Current.Height, 1, 1000);
5954 bitmap->setH(_Style.Current.Height);
5958 renderPseudoElement(":before", elm);
5959 addHtmlGroup(sep, 0);
5960 renderPseudoElement(":after", elm);
5964 // ***************************************************************************
5965 void CGroupHTML::htmlHTML(const CHtmlElement &elm)
5967 if (elm.hasNonEmptyAttribute("style"))
5969 _Style.applyStyle(elm.getAttribute("style"));
5971 _Style.Root = _Style.Current;
5972 applyBackground(elm);
5975 // ***************************************************************************
5976 void CGroupHTML::htmlI(const CHtmlElement &elm)
5978 _Localize = true;
5979 renderPseudoElement(":before", elm);
5982 void CGroupHTML::htmlIend(const CHtmlElement &elm)
5984 renderPseudoElement(":after", elm);
5985 _Localize = false;
5988 // ***************************************************************************
5989 void CGroupHTML::htmlIMG(const CHtmlElement &elm)
5991 std::string src = trim(elm.getAttribute("src"));
5992 if (src.empty())
5994 // no 'src' attribute, or empty
5995 return;
5998 float tmpf;
5999 std::string id = elm.getAttribute("id");
6001 if (elm.hasNonEmptyAttribute("width"))
6002 getPercentage(_Style.Current.Width, tmpf, elm.getAttribute("width").c_str());
6003 if (elm.hasNonEmptyAttribute("height"))
6004 getPercentage(_Style.Current.Height, tmpf, elm.getAttribute("height").c_str());
6006 // Get the global color name
6007 if (elm.hasAttribute("global_color"))
6008 _Style.Current.GlobalColor = true;
6010 // Tooltip
6011 // keep "alt" attribute for backward compatibility
6012 std::string tooltip = elm.getAttribute("alt");
6013 // tooltip
6014 if (elm.hasNonEmptyAttribute("title"))
6015 tooltip = elm.getAttribute("title");
6017 // Mouse over image
6018 string overSrc = elm.getAttribute("data-over-src");
6020 // inside a/button with valid url (ie, button is not disabled)
6021 string url = getLink();
6022 if (getA() && !url.empty() && getParent() && getParent()->getParent())
6024 string params = "name=" + getId() + "|url=" + url;
6025 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "browse", params.c_str(), tooltip, _Style.Current);
6027 else
6028 if (!tooltip.empty() || !overSrc.empty())
6030 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "", "", tooltip, _Style.Current);
6032 else
6034 // Get the option to reload (class==reload)
6035 bool reloadImg = false;
6037 if (elm.hasNonEmptyAttribute("style"))
6039 string styleString = elm.getAttribute("style");
6040 TStyle styles = parseStyle(styleString);
6041 TStyle::iterator it;
6043 it = styles.find("reload");
6044 if (it != styles.end() && (*it).second == "1")
6045 reloadImg = true;
6048 addImage(id, elm.getAttribute("src"), reloadImg, _Style.Current);
6052 // ***************************************************************************
6053 void CGroupHTML::htmlINPUT(const CHtmlElement &elm)
6055 if (_Forms.empty())
6056 return;
6058 // read general property
6059 string id = elm.getAttribute("id");
6061 // Widget template name (old)
6062 string templateName = elm.getAttribute("z_btn_tmpl");
6063 // Input name is the new
6064 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
6065 templateName = elm.getAttribute("z_input_tmpl");
6067 // Widget minimal width
6068 uint32 minWidth = 0;
6069 fromString(elm.getAttribute("z_input_width"), minWidth);
6071 // <input type="...">
6072 std::string type = trim(elm.getAttribute("type"));
6073 if (type.empty())
6075 // no 'type' attribute, or empty
6076 return;
6079 // Global color flag
6080 if (elm.hasAttribute("global_color"))
6081 _Style.Current.GlobalColor = true;
6083 // Tooltip
6084 std::string tooltip = elm.getAttribute("alt");
6086 if (type == "image")
6088 string name = elm.getAttribute("name");
6089 string src = elm.getAttribute("src");
6090 string over = elm.getAttribute("data-over-src");
6091 string formId = elm.getAttribute("form");
6092 string formAction = elm.getAttribute("formaction");
6094 if (formId.empty() && _FormOpen) {
6095 formId = _Forms.back().id;
6098 insertFormImageButton(name, tooltip, src, over, formId, formAction, minWidth, templateName);
6100 else if (type == "button" || type == "submit")
6102 string name = elm.getAttribute("name");
6103 string value = elm.getAttribute("value");
6104 string formId = elm.getAttribute("form");
6105 string formAction = elm.getAttribute("formaction");
6107 if (formId.empty() && _FormOpen) {
6108 formId = _Forms.back().id;
6111 insertFormTextButton(name, tooltip, value, formId, formAction, minWidth, templateName);
6113 else if (type == "text")
6115 // Get the string name
6116 string name = elm.getAttribute("name");
6117 string ucValue = elm.getAttribute("value");
6119 uint size = 20;
6120 uint maxlength = 1024;
6121 if (elm.hasNonEmptyAttribute("size"))
6122 fromString(elm.getAttribute("size"), size);
6123 if (elm.hasNonEmptyAttribute("maxlength"))
6124 fromString(elm.getAttribute("maxlength"), maxlength);
6126 // ryzom client used to have 'size' attribute in pixels, (12 == was default font size)
6127 if (_Style.hasStyle("-ryzom-input-size-px") && _Style.getStyle("-ryzom-input-size-px") == "true")
6128 size = size / 12;
6130 string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
6131 // Add the editbox
6132 CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size, false, ucValue, maxlength);
6133 if (textArea)
6135 // Add the text area to the form
6136 CGroupHTML::CForm::CEntry entry;
6137 entry.Name = name;
6138 entry.TextArea = textArea;
6139 _Forms.back().Entries.push_back (entry);
6142 else if (type == "checkbox" || type == "radio")
6144 renderPseudoElement(":before", elm);
6146 CCtrlButton::EType btnType;
6147 string name = elm.getAttribute("name");
6148 string normal = elm.getAttribute("src");
6149 string pushed;
6150 string over;
6151 string ucValue = "on";
6152 bool checked = elm.hasAttribute("checked");
6154 // TODO: unknown if empty attribute should override or not
6155 if (elm.hasNonEmptyAttribute("value"))
6156 ucValue = elm.getAttribute("value");
6158 if (type == "radio")
6160 btnType = CCtrlButton::RadioButton;
6161 normal = DefaultRadioButtonBitmapNormal;
6162 pushed = DefaultRadioButtonBitmapPushed;
6163 over = DefaultRadioButtonBitmapOver;
6165 else
6167 btnType = CCtrlButton::ToggleButton;
6168 normal = DefaultCheckBoxBitmapNormal;
6169 pushed = DefaultCheckBoxBitmapPushed;
6170 over = DefaultCheckBoxBitmapOver;
6173 // Add the ctrl button
6174 CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, _Style.Current);
6175 if (checkbox)
6177 if (btnType == CCtrlButton::RadioButton)
6179 // override with 'id' because radio buttons share same name
6180 if (!id.empty())
6181 checkbox->setId(id);
6183 // group together buttons with same name
6184 CForm &form = _Forms.back();
6185 bool notfound = true;
6186 for (uint i=0; i<form.Entries.size(); i++)
6188 if (form.Entries[i].Name == name && form.Entries[i].Checkbox->getType() == CCtrlButton::RadioButton)
6190 checkbox->initRBRefFromRadioButton(form.Entries[i].Checkbox);
6191 notfound = false;
6192 break;
6195 if (notfound)
6197 // this will start a new group (initRBRef() would take first button in group container otherwise)
6198 checkbox->initRBRefFromRadioButton(checkbox);
6202 checkbox->setPushed (checked);
6204 // Add the button to the form
6205 CGroupHTML::CForm::CEntry entry;
6206 entry.Name = name;
6207 entry.Value = decodeHTMLEntities(ucValue);
6208 entry.Checkbox = checkbox;
6209 _Forms.back().Entries.push_back (entry);
6211 renderPseudoElement(":after", elm);
6213 else if (type == "hidden")
6215 if (elm.hasNonEmptyAttribute("name"))
6217 // Get the name
6218 string name = elm.getAttribute("name");
6220 // Get the value
6221 string ucValue = elm.getAttribute("value");
6223 // Add an entry
6224 CGroupHTML::CForm::CEntry entry;
6225 entry.Name = name;
6226 entry.Value = decodeHTMLEntities(ucValue);
6227 _Forms.back().Entries.push_back (entry);
6232 // ***************************************************************************
6233 void CGroupHTML::htmlLI(const CHtmlElement &elm)
6235 if (_UL.empty())
6236 return;
6238 // UL, OL top margin if this is the first LI
6239 if (!_LI)
6241 _LI = true;
6242 newParagraph(ULBeginSpace);
6244 else
6246 newParagraph(LIBeginSpace);
6249 // OL list index can be overridden by <li value="1"> attribute
6250 if (elm.hasNonEmptyAttribute("value"))
6251 fromString(elm.getAttribute("value"), _UL.back().Value);
6253 string str = _UL.back().getListMarkerText();
6254 addString (str);
6256 // list-style-type: outside
6257 if (_CurrentViewLink)
6259 getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW());
6262 flushString ();
6264 // after marker
6265 renderPseudoElement(":before", elm);
6267 _UL.back().Value++;
6270 void CGroupHTML::htmlLIend(const CHtmlElement &elm)
6272 renderPseudoElement(":after", elm);
6275 // ***************************************************************************
6276 void CGroupHTML::htmlLUA(const CHtmlElement &elm)
6278 // we receive an embeded lua script
6279 _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain
6280 _LuaScript.clear();
6283 void CGroupHTML::htmlLUAend(const CHtmlElement &elm)
6285 if (_ParsingLua && _TrustedDomain)
6287 _ParsingLua = false;
6288 // execute the embeded lua script
6289 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript;
6290 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
6294 // ***************************************************************************
6295 void CGroupHTML::htmlMETA(const CHtmlElement &elm)
6297 if (!_ReadingHeadTag)
6298 return;
6300 std::string httpEquiv = elm.getAttribute("http-equiv");
6301 std::string httpContent = elm.getAttribute("content");
6302 if (httpEquiv.empty() || httpContent.empty())
6304 return;
6307 // only first http-equiv="refresh" should be handled
6308 if (_RefreshUrl.empty() && httpEquiv == "refresh")
6310 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
6311 double timeSec = times.thisFrameMs / 1000.0f;
6313 string::size_type pos = httpContent.find_first_of(";");
6314 if (pos == string::npos)
6316 fromString(httpContent, _NextRefreshTime);
6317 _RefreshUrl = _URL;
6319 else
6321 fromString(httpContent.substr(0, pos), _NextRefreshTime);
6323 pos = toLowerAscii(httpContent).find("url=");
6324 if (pos != string::npos)
6325 _RefreshUrl = getAbsoluteUrl(httpContent.substr(pos + 4));
6328 _NextRefreshTime += timeSec;
6332 // ***************************************************************************
6333 void CGroupHTML::htmlMETER(const CHtmlElement &elm)
6335 HTMLMeterElement meter;
6336 meter.readValues(elm);
6338 std::string id = "meter";
6339 if (elm.hasAttribute("id"))
6340 id = elm.getAttribute("id");
6342 // width: 5em, height: 1em
6343 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 5;
6344 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6345 // FIXME: only using border-top
6346 uint32 border = _Style.Current.BorderTopWidth > -1 ? _Style.Current.BorderTopWidth : 0;
6348 uint barw = (uint) (width * meter.getValueRatio());
6349 CRGBA bgColor = meter.getBarColor(elm, _Style);
6350 CRGBA valueColor = meter.getValueColor(elm, _Style);
6352 typedef pair<string, string> TTmplParam;
6353 vector<TTmplParam> tmplParams;
6354 tmplParams.push_back(TTmplParam("id", id));
6355 tmplParams.push_back(TTmplParam("active", "true"));
6356 tmplParams.push_back(TTmplParam("w", toString(width)));
6357 tmplParams.push_back(TTmplParam("h", toString(height)));
6358 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6359 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6360 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6361 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6362 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6363 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6365 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_meter", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6366 if (gr)
6368 renderPseudoElement(":before", elm);
6369 getParagraph()->addChild(gr);
6370 renderPseudoElement(":after", elm);
6372 // ignore any inner elements
6373 _IgnoreChildElements = true;
6377 // ***************************************************************************
6378 void CGroupHTML::htmlOBJECT(const CHtmlElement &elm)
6380 _ObjectType = elm.getAttribute("type");
6381 _ObjectData = elm.getAttribute("data");
6382 _ObjectMD5Sum = elm.getAttribute("id");
6383 _ObjectAction = elm.getAttribute("standby");
6384 _Object = true;
6387 void CGroupHTML::htmlOBJECTend(const CHtmlElement &elm)
6389 if (!_TrustedDomain)
6390 return;
6392 if (_ObjectType=="application/ryzom-data")
6394 if (!_ObjectData.empty())
6396 if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum))
6398 CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true);
6400 _ObjectScript.clear();
6403 _Object = false;
6406 // ***************************************************************************
6407 void CGroupHTML::htmlOL(const CHtmlElement &elm)
6409 sint32 start = 1;
6410 std::string type("1");
6412 if (elm.hasNonEmptyAttribute("start"))
6413 fromString(elm.getAttribute("start"), start);
6414 if (elm.hasNonEmptyAttribute("type"))
6415 type = elm.getAttribute("type");
6417 _UL.push_back(HTMLOListElement(start, type));
6418 // if LI is already present
6419 _LI = _UL.size() > 1 || _DL.size() > 1;
6420 _Indent.push_back(getIndent() + ULIndent);
6422 renderPseudoElement(":before", elm);
6425 void CGroupHTML::htmlOLend(const CHtmlElement &elm)
6427 htmlULend(elm);
6430 // ***************************************************************************
6431 void CGroupHTML::htmlOPTION(const CHtmlElement &elm)
6433 _SelectOption = true;
6434 _SelectOptionStr.clear();
6436 // Got one form ?
6437 if (_Forms.empty() || _Forms.back().Entries.empty())
6438 return;
6440 _Forms.back().Entries.back().SelectValues.push_back(elm.getAttribute("value"));
6442 if (elm.hasAttribute("selected"))
6443 _Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6445 if (elm.hasAttribute("disabled"))
6446 _Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6449 void CGroupHTML::htmlOPTIONend(const CHtmlElement &elm)
6451 if (_Forms.empty() || _Forms.back().Entries.empty())
6452 return;
6454 // use option text as value
6455 if (!elm.hasAttribute("value"))
6457 _Forms.back().Entries.back().SelectValues.back() = _SelectOptionStr;
6460 // insert the parsed text into the select control
6461 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6462 if (cb)
6464 uint lineIndex = cb->getNumTexts();
6465 cb->addText(_SelectOptionStr);
6466 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6468 cb->setGrayed(lineIndex, true);
6471 else
6473 CGroupMenu *sb = _Forms.back().Entries.back().SelectBox;
6474 if (sb)
6476 uint lineIndex = sb->getNumLine();
6477 sb->addLine(_SelectOptionStr, "", "");
6479 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6481 sb->setGrayedLine(lineIndex, true);
6483 else
6485 // create option line checkbox, CGroupMenu is taking ownership of the checbox
6486 CInterfaceGroup *ig = CWidgetManager::getInstance()->getParser()->createGroupInstance("menu_checkbox", "", NULL, 0);
6487 if (ig)
6489 CCtrlButton *cb = dynamic_cast<CCtrlButton *>(ig->getCtrl("b"));
6490 if (cb)
6492 if (_Forms.back().Entries.back().sbMultiple)
6494 cb->setType(CCtrlButton::ToggleButton);
6495 cb->setTexture(DefaultCheckBoxBitmapNormal);
6496 cb->setTexturePushed(DefaultCheckBoxBitmapPushed);
6497 cb->setTextureOver(DefaultCheckBoxBitmapOver);
6499 else
6501 cb->setType(CCtrlButton::RadioButton);
6502 cb->setTexture(DefaultRadioButtonBitmapNormal);
6503 cb->setTexturePushed(DefaultRadioButtonBitmapPushed);
6504 cb->setTextureOver(DefaultRadioButtonBitmapOver);
6506 if (_Forms.back().Entries.back().sbRBRef == NULL)
6507 _Forms.back().Entries.back().sbRBRef = cb;
6509 cb->initRBRefFromRadioButton(_Forms.back().Entries.back().sbRBRef);
6512 cb->setPushed(_Forms.back().Entries.back().InitialSelection == lineIndex);
6513 sb->setUserGroupLeft(lineIndex, ig);
6515 else
6517 nlwarning("Failed to get 'b' element from 'menu_checkbox' template");
6518 delete ig;
6526 // ***************************************************************************
6527 void CGroupHTML::htmlP(const CHtmlElement &elm)
6529 newParagraph(PBeginSpace);
6530 renderPseudoElement(":before", elm);
6533 void CGroupHTML::htmlPend(const CHtmlElement &elm)
6535 renderPseudoElement(":after", elm);
6538 // ***************************************************************************
6539 void CGroupHTML::htmlPRE(const CHtmlElement &elm)
6541 _PRE.push_back(true);
6542 newParagraph(0);
6544 renderPseudoElement(":before", elm);
6547 void CGroupHTML::htmlPREend(const CHtmlElement &elm)
6549 renderPseudoElement(":after", elm);
6551 popIfNotEmpty(_PRE);
6554 // ***************************************************************************
6555 void CGroupHTML::htmlPROGRESS(const CHtmlElement &elm)
6557 HTMLProgressElement progress;
6558 progress.readValues(elm);
6560 std::string id = "progress";
6561 if (elm.hasAttribute("id"))
6562 id = elm.getAttribute("id");
6564 // width: 10em, height: 1em
6565 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 10;
6566 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6567 // FIXME: only using border-top
6568 uint32 border = _Style.Current.BorderTopWidth > -1 ? _Style.Current.BorderTopWidth : 0;
6570 uint barw = (uint) (width * progress.getValueRatio());
6571 CRGBA bgColor = progress.getBarColor(elm, _Style);
6572 CRGBA valueColor = progress.getValueColor(elm, _Style);
6574 typedef pair<string, string> TTmplParam;
6575 vector<TTmplParam> tmplParams;
6576 tmplParams.push_back(TTmplParam("id", id));
6577 tmplParams.push_back(TTmplParam("active", "true"));
6578 tmplParams.push_back(TTmplParam("w", toString(width)));
6579 tmplParams.push_back(TTmplParam("h", toString(height)));
6580 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6581 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6582 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6583 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6584 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6585 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6587 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_progress", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6588 if (gr)
6590 renderPseudoElement(":before", elm);
6591 getParagraph()->addChild(gr);
6592 renderPseudoElement(":after", elm);
6594 // ignore any inner elements
6595 _IgnoreChildElements = true;
6599 // ***************************************************************************
6600 void CGroupHTML::htmlSCRIPT(const CHtmlElement &elm)
6602 _IgnoreText = true;
6605 void CGroupHTML::htmlSCRIPTend(const CHtmlElement &elm)
6607 _IgnoreText = false;
6610 // ***************************************************************************
6611 void CGroupHTML::htmlSELECT(const CHtmlElement &elm)
6613 if (_Forms.empty())
6614 return;
6616 // A select box
6617 string name = elm.getAttribute("name");
6618 bool multiple = elm.hasAttribute("multiple");
6619 sint32 size = 0;
6621 if (elm.hasNonEmptyAttribute("size"))
6622 fromString(elm.getAttribute("size"), size);
6624 CGroupHTML::CForm::CEntry entry;
6625 entry.Name = name;
6626 entry.sbMultiple = multiple;
6627 if (size > 1 || multiple)
6629 entry.InitialSelection = -1;
6630 CGroupMenu *sb = addSelectBox(DefaultFormSelectBoxMenuGroup, name.c_str());
6631 if (sb)
6633 if (size < 1)
6634 size = 4;
6636 if (_Style.Current.Width > -1)
6637 sb->setMinW(_Style.Current.Width);
6639 if (_Style.Current.Height > -1)
6640 sb->setMinH(_Style.Current.Height);
6642 sb->setMaxVisibleLine(size);
6643 sb->setFontSize(_Style.Current.FontSize, false);
6646 entry.SelectBox = sb;
6648 else
6650 CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
6651 entry.ComboBox = cb;
6653 if (cb)
6655 // create view text
6656 cb->updateCoords();
6657 setTextStyle(cb->getViewText(), _Style.Current);
6660 _Forms.back().Entries.push_back (entry);
6663 void CGroupHTML::htmlSELECTend(const CHtmlElement &elm)
6665 _SelectOption = false;
6666 if (_Forms.empty() || _Forms.back().Entries.empty())
6667 return;
6669 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6670 if (cb)
6672 cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection);
6673 // TODO: magic padding
6674 cb->setW(cb->evalContentWidth() + 16);
6678 // ***************************************************************************
6679 void CGroupHTML::htmlSTYLE(const CHtmlElement &elm)
6681 _IgnoreText = true;
6684 void CGroupHTML::htmlSTYLEend(const CHtmlElement &elm)
6686 _IgnoreText = false;
6689 // ***************************************************************************
6690 void CGroupHTML::htmlTABLE(const CHtmlElement &elm)
6692 // Get cells parameters
6693 getCellsParameters(elm, false);
6695 CGroupTable *table = new CGroupTable(TCtorParam());
6696 table->BgColor = _CellParams.back().BgColor;
6697 if (elm.hasNonEmptyAttribute("id"))
6698 table->setId(getCurrentGroup()->getId() + ":" + elm.getAttribute("id"));
6699 else
6700 table->setId(getCurrentGroup()->getId() + ":TABLE" + toString(getNextAutoIdSeq()));
6702 // TODO: border-spacing: 2em;
6704 if (elm.hasNonEmptyAttribute("cellspacing"))
6705 fromString(elm.getAttribute("cellspacing"), table->CellSpacing);
6707 // TODO: cssLength, horiz/vert values
6708 if (_Style.hasStyle("border-spacing"))
6709 fromString(_Style.getStyle("border-spacing"), table->CellSpacing);
6711 // overrides border-spacing if set to 'collapse'
6712 if (_Style.checkStyle("border-collapse", "collapse"))
6713 table->CellSpacing = 0;
6716 if (elm.hasNonEmptyAttribute("cellpadding"))
6717 fromString(elm.getAttribute("cellpadding"), table->CellPadding);
6719 if (_Style.hasStyle("width"))
6721 // _Style.Width does not handle '%' unit currently
6722 if (_Style.Current.Width > 0)
6724 table->ForceWidthMin = _Style.Current.Width;
6725 table->TableRatio = 0;
6727 else
6729 getPercentage (table->ForceWidthMin, table->TableRatio, _Style.getStyle("width").c_str());
6732 else if (elm.hasNonEmptyAttribute("width"))
6734 getPercentage (table->ForceWidthMin, table->TableRatio, elm.getAttribute("width").c_str());
6737 // border from css or from attribute
6739 uint32 borderWidth = 0;
6740 CRGBA borderColor = CRGBA::Transparent;
6742 if (elm.hasAttribute("border"))
6744 std::string s = elm.getAttribute("border");
6745 if (s.empty())
6746 borderWidth = 1;
6747 else
6748 fromString(elm.getAttribute("border"), borderWidth);
6750 if (elm.hasNonEmptyAttribute("bordercolor"))
6751 scanHTMLColor(elm.getAttribute("bordercolor").c_str(), borderColor);
6752 else
6753 borderColor = CRGBA(128, 128, 128, 255);
6755 table->CellBorder = (borderWidth > 0);
6756 table->Border->setWidth(borderWidth, borderWidth, borderWidth, borderWidth);
6757 table->Border->setColor(borderColor, borderColor, borderColor, borderColor);
6758 table->Border->setStyle(CSS_LINE_STYLE_OUTSET, CSS_LINE_STYLE_OUTSET, CSS_LINE_STYLE_OUTSET, CSS_LINE_STYLE_OUTSET);
6760 else
6762 table->CellBorder = false;
6765 if (_Style.hasStyle("border-top-width")) table->Border->TopWidth = _Style.Current.BorderTopWidth;
6766 if (_Style.hasStyle("border-right-width")) table->Border->RightWidth = _Style.Current.BorderRightWidth;
6767 if (_Style.hasStyle("border-bottom-width")) table->Border->BottomWidth = _Style.Current.BorderBottomWidth;
6768 if (_Style.hasStyle("border-left-width")) table->Border->LeftWidth = _Style.Current.BorderLeftWidth;
6770 if (_Style.hasStyle("border-top-color")) table->Border->TopColor = _Style.Current.BorderTopColor;
6771 if (_Style.hasStyle("border-right-color")) table->Border->RightColor = _Style.Current.BorderRightColor;
6772 if (_Style.hasStyle("border-bottom-color")) table->Border->BottomColor = _Style.Current.BorderBottomColor;
6773 if (_Style.hasStyle("border-left-color")) table->Border->LeftColor = _Style.Current.BorderLeftColor;
6775 if (_Style.hasStyle("border-top-style")) table->Border->TopStyle = _Style.Current.BorderTopStyle;
6776 if (_Style.hasStyle("border-right-style")) table->Border->RightStyle = _Style.Current.BorderRightStyle;
6777 if (_Style.hasStyle("border-bottom-style")) table->Border->BottomStyle = _Style.Current.BorderBottomStyle;
6778 if (_Style.hasStyle("border-left-style")) table->Border->LeftStyle = _Style.Current.BorderLeftStyle;
6781 if (_Style.hasStyle("background-image"))
6783 if (_Style.checkStyle("background-repeat", "repeat"))
6784 table->setTextureTile(true);
6786 if (_Style.checkStyle("background-size", "100%"))
6787 table->setTextureScale(true);
6789 string image = _Style.getStyle("background-image");
6790 addImageDownload(image, table, CStyleParams(), NormalImage, "");
6793 // setting ModulateGlobalColor must be after addImageDownload
6794 if (_Style.checkStyle("-ryzom-modulate-bgcolor", "true"))
6795 table->setModulateGlobalColor(true);
6796 table->setMarginLeft(getIndent());
6797 addHtmlGroup (table, 0);
6799 renderPseudoElement(":before", elm);
6801 _Tables.push_back(table);
6803 // Add a cell pointer
6804 _Cells.push_back(NULL);
6805 _TR.push_back(false);
6806 _Indent.push_back(0);
6809 void CGroupHTML::htmlTABLEend(const CHtmlElement &elm)
6811 popIfNotEmpty(_CellParams);
6812 popIfNotEmpty(_TR);
6813 popIfNotEmpty(_Cells);
6814 popIfNotEmpty(_Tables);
6815 popIfNotEmpty(_Indent);
6817 renderPseudoElement(":after", elm);
6820 // ***************************************************************************
6821 void CGroupHTML::htmlTD(const CHtmlElement &elm)
6823 CRGBA rowColor = CRGBA::Transparent;
6824 // remember row color so we can blend it with cell color
6825 if (!_CellParams.empty())
6826 rowColor = _CellParams.back().BgColor;
6828 // Get cells parameters
6829 getCellsParameters(elm, true);
6831 // if cell has own background,then it must be blended with row
6832 if (rowColor.A > 0 && (elm.hasNonEmptyAttribute("bgcolor") || _Style.hasStyle("background-color")))
6834 if (_CellParams.back().BgColor.A < 255)
6835 _CellParams.back().BgColor.blendFromui(rowColor, _CellParams.back().BgColor, _CellParams.back().BgColor.A);
6838 if (elm.ID == HTML_TH)
6840 if (!_Style.hasStyle("font-weight"))
6841 _Style.Current.FontWeight = FONT_WEIGHT_BOLD;
6842 // center if not specified otherwise.
6843 if (!elm.hasNonEmptyAttribute("align") && !_Style.hasStyle("text-align"))
6844 _CellParams.back().Align = CGroupCell::Center;
6847 CGroupTable *table = getTable();
6848 if (!table)
6850 // <td> appears to be outside <table>
6851 return;
6854 if (_Cells.empty())
6856 // <table> not started
6857 return;
6860 _Cells.back() = new CGroupCell(CViewBase::TCtorParam());
6861 if (elm.hasNonEmptyAttribute("id"))
6862 _Cells.back()->setId(table->getId() + ":" + elm.getAttribute("id"));
6863 else
6864 _Cells.back()->setId(table->getId() + ":TD" + toString(getNextAutoIdSeq()));
6865 // inner cell content
6866 _Cells.back()->Group->setId(_Cells.back()->getId() + ":CELL");
6868 if (_Style.checkStyle("background-repeat", "repeat"))
6869 _Cells.back()->setTextureTile(true);
6871 if (_Style.checkStyle("background-size", "100%"))
6872 _Cells.back()->setTextureScale(true);
6874 if (_Style.hasStyle("background-image"))
6876 string image = _Style.getStyle("background-image");
6877 addImageDownload(image, _Cells.back(), CStyleParams(), NormalImage, "");
6880 if (elm.hasNonEmptyAttribute("colspan"))
6881 fromString(elm.getAttribute("colspan"), _Cells.back()->ColSpan);
6882 if (elm.hasNonEmptyAttribute("rowspan"))
6883 fromString(elm.getAttribute("rowspan"), _Cells.back()->RowSpan);
6885 _Cells.back()->BgColor = _CellParams.back().BgColor;
6886 _Cells.back()->Align = _CellParams.back().Align;
6887 _Cells.back()->VAlign = _CellParams.back().VAlign;
6888 _Cells.back()->LeftMargin = _CellParams.back().LeftMargin;
6889 _Cells.back()->NoWrap = _CellParams.back().NoWrap;
6890 _Cells.back()->ColSpan = std::max(1, _Cells.back()->ColSpan);
6891 _Cells.back()->RowSpan = std::max(1, _Cells.back()->RowSpan);
6892 _Cells.back()->Height = _CellParams.back().Height;
6894 float temp;
6895 if (_Style.hasStyle("width"))
6897 // _Style.Width does not handle '%' unit currently
6898 if (_Style.Current.Width > 0)
6900 _Cells.back()->WidthWanted = _Style.Current.Width;
6901 _Cells.back()->TableRatio = 0;
6903 else
6905 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, _Style.getStyle("width").c_str());
6908 else if (elm.hasNonEmptyAttribute("width"))
6910 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, elm.getAttribute("width").c_str());
6913 _Cells.back()->NewLine = getTR();
6915 // setting ModulateGlobalColor must be after addImageDownload
6916 if (_Style.checkStyle("-ryzom-modulate-bgcolor", "true"))
6917 _Cells.back()->setModulateGlobalColor(true);
6919 // border from <table border="1">
6920 if (table->CellBorder)
6922 _Cells.back()->Border->setWidth(1, 1, 1, 1);
6923 _Cells.back()->Border->setColor(table->Border->TopColor, table->Border->RightColor, table->Border->BottomColor, table->Border->LeftColor);
6924 _Cells.back()->Border->setStyle(CSS_LINE_STYLE_INSET, CSS_LINE_STYLE_INSET, CSS_LINE_STYLE_INSET, CSS_LINE_STYLE_INSET);
6927 if (_Style.hasStyle("border-top-width")) _Cells.back()->Border->TopWidth = _Style.Current.BorderTopWidth;
6928 if (_Style.hasStyle("border-right-width")) _Cells.back()->Border->RightWidth = _Style.Current.BorderRightWidth;
6929 if (_Style.hasStyle("border-bottom-width")) _Cells.back()->Border->BottomWidth = _Style.Current.BorderBottomWidth;
6930 if (_Style.hasStyle("border-left-width")) _Cells.back()->Border->LeftWidth = _Style.Current.BorderLeftWidth;
6932 if (_Style.hasStyle("border-top-color")) _Cells.back()->Border->TopColor = _Style.Current.BorderTopColor;
6933 if (_Style.hasStyle("border-right-color")) _Cells.back()->Border->RightColor = _Style.Current.BorderRightColor;
6934 if (_Style.hasStyle("border-bottom-color")) _Cells.back()->Border->BottomColor = _Style.Current.BorderBottomColor;
6935 if (_Style.hasStyle("border-left-color")) _Cells.back()->Border->LeftColor = _Style.Current.BorderLeftColor;
6937 if (_Style.hasStyle("border-top-style")) _Cells.back()->Border->TopStyle = _Style.Current.BorderTopStyle;
6938 if (_Style.hasStyle("border-right-style")) _Cells.back()->Border->RightStyle = _Style.Current.BorderRightStyle;
6939 if (_Style.hasStyle("border-bottom-style")) _Cells.back()->Border->BottomStyle = _Style.Current.BorderBottomStyle;
6940 if (_Style.hasStyle("border-left-style")) _Cells.back()->Border->LeftStyle = _Style.Current.BorderLeftStyle;
6942 // padding from <table cellpadding="1">
6943 if (table->CellPadding)
6945 _Cells.back()->PaddingTop = table->CellPadding;
6946 _Cells.back()->PaddingRight = table->CellPadding;
6947 _Cells.back()->PaddingBottom = table->CellPadding;
6948 _Cells.back()->PaddingLeft = table->CellPadding;
6951 if (_Style.hasStyle("padding-top")) _Cells.back()->PaddingTop = _Style.Current.PaddingTop;
6952 if (_Style.hasStyle("padding-right")) _Cells.back()->PaddingRight = _Style.Current.PaddingRight;
6953 if (_Style.hasStyle("padding-bottom")) _Cells.back()->PaddingBottom = _Style.Current.PaddingBottom;
6954 if (_Style.hasStyle("padding-left")) _Cells.back()->PaddingLeft = _Style.Current.PaddingLeft;
6956 table->addChild (_Cells.back());
6958 // reusing indent pushed by table
6959 _Indent.back() = 0;
6961 newParagraph(TDBeginSpace);
6962 // indent is already 0, getParagraph()->setMarginLeft(0); // maybe setIndent(0) if LI is using one
6964 // Reset TR flag
6965 if (!_TR.empty())
6966 _TR.back() = false;
6968 renderPseudoElement(":before", elm);
6971 void CGroupHTML::htmlTDend(const CHtmlElement &elm)
6973 renderPseudoElement(":after", elm);
6975 popIfNotEmpty(_CellParams);
6976 if (!_Cells.empty())
6977 _Cells.back() = NULL;
6980 // ***************************************************************************
6981 void CGroupHTML::htmlTEXTAREA(const CHtmlElement &elm)
6983 if (_Forms.empty())
6984 return;
6986 // read general property
6987 string templateName;
6989 // Widget template name
6990 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
6991 templateName = elm.getAttribute("z_input_tmpl");
6993 // Get the string name
6994 _TextAreaName.clear();
6995 _TextAreaRow = 1;
6996 _TextAreaCols = 10;
6997 _TextAreaContent.clear();
6998 _TextAreaMaxLength = 1024;
6999 if (elm.hasNonEmptyAttribute("name"))
7000 _TextAreaName = elm.getAttribute("name");
7001 if (elm.hasNonEmptyAttribute("rows"))
7002 fromString(elm.getAttribute("rows"), _TextAreaRow);
7003 if (elm.hasNonEmptyAttribute("cols"))
7004 fromString(elm.getAttribute("cols"), _TextAreaCols);
7005 if (elm.hasNonEmptyAttribute("maxlength"))
7006 fromString(elm.getAttribute("maxlength"), _TextAreaMaxLength);
7008 _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup;
7009 _TextArea = true;
7010 _PRE.push_back(true);
7013 void CGroupHTML::htmlTEXTAREAend(const CHtmlElement &elm)
7015 if (_Forms.empty())
7016 return;
7018 CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, _TextAreaContent, _TextAreaMaxLength);
7019 if (textArea)
7021 // Add the text area to the form
7022 CGroupHTML::CForm::CEntry entry;
7023 entry.Name = _TextAreaName;
7024 entry.TextArea = textArea;
7025 _Forms.back().Entries.push_back (entry);
7028 _TextArea = false;
7029 popIfNotEmpty (_PRE);
7032 // ***************************************************************************
7033 void CGroupHTML::htmlTH(const CHtmlElement &elm)
7035 htmlTD(elm);
7038 void CGroupHTML::htmlTHend(const CHtmlElement &elm)
7040 htmlTDend(elm);
7043 // ***************************************************************************
7044 void CGroupHTML::htmlTITLE(const CHtmlElement &elm)
7046 // TODO: only from <head>
7047 // if (!_ReadingHeadTag) return;
7048 if(!_TitlePrefix.empty())
7049 _TitleString = _TitlePrefix + " - ";
7050 else
7051 _TitleString.clear();
7052 _Title = true;
7055 void CGroupHTML::htmlTITLEend(const CHtmlElement &elm)
7057 _Title = false;
7058 setTitle(_TitleString);
7061 // ***************************************************************************
7062 void CGroupHTML::htmlTR(const CHtmlElement &elm)
7064 // prevent inheriting from table
7065 if (!_CellParams.empty())
7067 _CellParams.back().BgColor = CRGBA::Transparent;
7068 _CellParams.back().Height = 0;
7071 // Get cells parameters
7072 getCellsParameters(elm, true);
7074 // TODO: this probably ends up in first cell
7075 renderPseudoElement(":before", elm);
7077 // Set TR flag
7078 if (!_TR.empty())
7079 _TR.back() = true;
7082 void CGroupHTML::htmlTRend(const CHtmlElement &elm)
7084 // TODO: this probably ends up in last cell
7085 renderPseudoElement(":after", elm);
7087 popIfNotEmpty(_CellParams);
7090 // ***************************************************************************
7091 void CGroupHTML::htmlUL(const CHtmlElement &elm)
7093 if (_UL.empty())
7094 _UL.push_back(HTMLOListElement(1, "disc"));
7095 else if (_UL.size() == 1)
7096 _UL.push_back(HTMLOListElement(1, "circle"));
7097 else
7098 _UL.push_back(HTMLOListElement(1, "square"));
7100 // if LI is already present
7101 _LI = _UL.size() > 1 || _DL.size() > 1;
7102 _Indent.push_back(getIndent() + ULIndent);
7104 renderPseudoElement(":before", elm);
7107 void CGroupHTML::htmlULend(const CHtmlElement &elm)
7109 if (_UL.empty())
7110 return;
7112 renderPseudoElement(":after", elm);
7114 popIfNotEmpty(_UL);
7115 popIfNotEmpty(_Indent);