Fix html,body background not using container opacity
[ryzomcore.git] / nel / src / gui / group_html.cpp
blob021a3691a1eb4ee6866e7e2bb6a84a1fc4f416c4
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2021 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"
61 #include "nel/gui/css_background_renderer.h"
63 #include <curl/curl.h>
65 using namespace std;
66 using namespace NLMISC;
68 #ifdef DEBUG_NEW
69 #define new DEBUG_NEW
70 #endif
72 // Default maximum time the request is allowed to take
73 #define DEFAULT_RYZOM_CONNECTION_TIMEOUT (300.0)
74 // Allow up to 10 redirects, then give up
75 #define DEFAULT_RYZOM_REDIRECT_LIMIT (10)
77 #define FONT_WEIGHT_NORMAL 400
78 #define FONT_WEIGHT_BOLD 700
80 namespace NLGUI
83 // Uncomment nlwarning() to see the log about curl downloads
84 #define LOG_DL(fmt, ...) //nlwarning(fmt, ## __VA_ARGS__)
85 // Uncomment to log curl progess
86 //#define LOG_CURL_PROGRESS 1
88 CGroupHTML::SWebOptions CGroupHTML::options;
90 // Return URL with https is host is in HSTS list
91 static std::string upgradeInsecureUrl(const std::string &url)
93 if (toLowerAscii(url.substr(0, 7)) != "http://") {
94 return url;
97 CUrlParser uri(url);
98 if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){
99 return url;
102 LOG_DL("HSTS url : '%s', using https", url.c_str());
103 uri.scheme = "https";
105 return uri.toString();
108 // Active cURL www transfer
109 class CCurlWWWData
111 public:
112 CCurlWWWData(CURL *curl, const std::string &url)
113 : Request(curl), Url(url), Content(""), HeadersSent(NULL)
116 ~CCurlWWWData()
118 if (Request)
119 curl_easy_cleanup(Request);
121 if (HeadersSent)
122 curl_slist_free_all(HeadersSent);
125 void sendHeaders(const std::vector<std::string> headers)
127 for(uint i = 0; i < headers.size(); ++i)
129 HeadersSent = curl_slist_append(HeadersSent, headers[i].c_str());
131 curl_easy_setopt(Request, CURLOPT_HTTPHEADER, HeadersSent);
134 void setRecvHeader(const std::string &header)
136 size_t pos = header.find(": ");
137 if (pos == std::string::npos)
138 return;
140 std::string key = toLowerAscii(header.substr(0, pos));
141 if (pos != std::string::npos)
143 HeadersRecv[key] = header.substr(pos + 2);
144 //nlinfo(">> received header '%s' = '%s'", key.c_str(), HeadersRecv[key].c_str());
148 // return last received "Location: <url>" header or empty string if no header set
149 const std::string getLocationHeader()
151 if (HeadersRecv.count("location") > 0)
152 return HeadersRecv["location"];
154 return "";
157 const uint32 getExpires()
159 time_t ret = 0;
160 if (HeadersRecv.count("expires") > 0)
161 ret = curl_getdate(HeadersRecv["expires"].c_str(), NULL);
163 return ret > -1 ? ret : 0;
166 const std::string getLastModified()
168 if (HeadersRecv.count("last-modified") > 0)
170 return HeadersRecv["last-modified"];
173 return "";
176 const std::string getEtag()
178 if (HeadersRecv.count("etag") > 0)
180 return HeadersRecv["etag"];
183 return "";
186 bool hasHSTSHeader()
188 // ignore header if not secure connection
189 if (toLowerAscii(Url.substr(0, 8)) != "https://")
191 return false;
194 return HeadersRecv.count("strict-transport-security") > 0;
197 const std::string getHSTSHeader()
199 if (hasHSTSHeader())
201 return HeadersRecv["strict-transport-security"];
204 return "";
207 public:
208 CURL *Request;
210 std::string Url;
211 std::string Content;
213 private:
214 // headers sent with curl request, must be released after transfer
215 curl_slist * HeadersSent;
217 // headers received from curl transfer
218 std::map<std::string, std::string> HeadersRecv;
221 // cURL transfer callbacks
222 // ***************************************************************************
223 static size_t curlHeaderCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
225 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
226 if (me)
228 std::string header;
229 header.append(buffer, size * nmemb);
230 me->setRecvHeader(header.substr(0, header.find_first_of("\n\r")));
233 return size * nmemb;
236 // ***************************************************************************
237 static size_t curlDataCallback(char *buffer, size_t size, size_t nmemb, void *pCCurlWWWData)
239 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
240 if (me)
241 me->Content.append(buffer, size * nmemb);
243 return size * nmemb;
246 // ***************************************************************************
247 static size_t curlProgressCallback(void *pCCurlWWWData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
249 CCurlWWWData * me = static_cast<CCurlWWWData *>(pCCurlWWWData);
250 if (me)
252 if (dltotal > 0 || dlnow > 0 || ultotal > 0 || ulnow > 0)
254 #ifdef LOG_CURL_PROGRESS
255 nlwarning("> dltotal %ld, dlnow %ld, ultotal %ld, ulnow %ld, url '%s'", dltotal, dlnow, ultotal, ulnow, me->Url.c_str());
256 #endif
260 // return 1 to cancel download
261 return 0;
264 CGroupHTML::CDataDownload::~CDataDownload()
266 delete data;
267 data = NULL;
270 void CGroupHTML::StylesheetDownloadCB::finish()
272 if (CFile::fileExists(tmpdest))
274 if (CFile::fileExists(dest))
276 CFile::deleteFile(dest);
278 CFile::moveFile(dest, tmpdest);
280 Parent->cssDownloadFinished(url, dest);
283 void CGroupHTML::ImageDownloadCB::addImage(CViewBase *img, const CStyleParams &style, TImageType type)
285 Images.push_back(SImageInfo(img, style, type));
288 void CGroupHTML::ImageDownloadCB::removeImage(CViewBase *img)
290 for(std::vector<SImageInfo>::iterator it = Images.begin(); it != Images.end(); ++it)
292 if (it->Image == img)
294 Images.erase(it);
295 break;
300 void CGroupHTML::ImageDownloadCB::finish()
302 // tmpdest file does not exist if download skipped (ie cache was used)
303 if (CFile::fileExists(tmpdest) || CFile::getFileSize(tmpdest) == 0)
305 try {
306 // verify that image is not corrupted
307 uint32 w, h;
308 CBitmap::loadSize(tmpdest, w, h);
309 if (w != 0 && h != 0)
311 if (CFile::fileExists(dest))
312 CFile::deleteFile(dest);
315 catch(const NLMISC::Exception &e)
317 // exception message has .tmp file name, so keep it for further analysis
318 nlwarning("Invalid image (%s) from url (%s): %s", tmpdest.c_str(), url.c_str(), e.what());
321 // to reload image on page, the easiest seems to be changing texture
322 // to temp file temporarily. that forces driver to reload texture from disk
323 // ITexture::touch() seem not to do this.
324 // cache was updated, first set texture as temp file
325 for(std::vector<SImageInfo>::iterator it = Images.begin(); it != Images.end(); ++it)
327 SImageInfo &img = *it;
328 Parent->setImage(img.Image, tmpdest, img.Type);
329 Parent->setImageSize(img.Image, img.Style);
332 CFile::moveFile(dest, tmpdest);
335 if (!CFile::fileExists(dest) || CFile::getFileSize(dest) == 0)
337 // placeholder if cached image failed
338 dest = "web_del.tga";
341 // even if image was cached, incase there was 'http://' image set to CViewBitmap
342 for(std::vector<SImageInfo>::iterator it = Images.begin(); it != Images.end(); ++it)
344 SImageInfo &img = *it;
345 Parent->setImage(img.Image, dest, img.Type);
346 Parent->setImageSize(img.Image, img.Style);
350 void CGroupHTML::TextureDownloadCB::finish()
352 // tmpdest file does not exist if download skipped (ie cache was used)
353 if (CFile::fileExists(tmpdest) && CFile::getFileSize(tmpdest) > 0)
355 if (CFile::fileExists(dest))
356 CFile::deleteFile(dest);
358 CFile::moveFile(dest, tmpdest);
361 CViewRenderer &rVR = *CViewRenderer::getInstance();
362 for(uint i = 0; i < TextureIds.size(); i++)
364 rVR.reloadTexture(TextureIds[i].first, dest);
365 TextureIds[i].second->invalidateCoords();
369 void CGroupHTML::BnpDownloadCB::finish()
371 bool verified = false;
372 // no tmpfile if file was already in cache
373 if (CFile::fileExists(tmpdest))
375 verified = m_md5sum.empty() || (m_md5sum != getMD5(tmpdest).toString());
376 if (verified)
378 if (CFile::fileExists(dest))
380 CFile::deleteFile(dest);
382 CFile::moveFile(dest, tmpdest);
384 else
386 CFile::deleteFile(tmpdest);
389 else if (CFile::fileExists(dest))
391 verified = m_md5sum.empty() || (m_md5sum != getMD5(dest).toString());
394 if (!m_lua.empty())
396 std::string script = "\nlocal __CURRENT_WINDOW__ = \""+Parent->getId()+"\"";
397 script += toString("\nlocal __DOWNLOAD_STATUS__ = %s\n", verified ? "true" : "false");
398 script += m_lua;
399 CLuaManager::getInstance().executeLuaScript(script, true );
403 // Check if domain is on TrustedDomain
404 bool CGroupHTML::isTrustedDomain(const string &domain)
406 vector<string>::iterator it;
407 it = find ( options.trustedDomains.begin(), options.trustedDomains.end(), domain);
408 return it != options.trustedDomains.end();
411 // Update view after download has finished
412 void CGroupHTML::setImage(CViewBase * view, const string &file, const TImageType type)
414 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
415 if(btn)
417 if (type == NormalImage)
419 btn->setTexture (file);
420 btn->setTexturePushed(file);
421 btn->invalidateCoords();
422 btn->invalidateContent();
423 paragraphChange();
425 else
427 btn->setTextureOver(file);
430 return;
433 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
434 if(btm)
436 btm->setTexture (file);
437 btm->invalidateCoords();
438 btm->invalidateContent();
439 paragraphChange();
441 return;
444 CGroupCell *btgc = dynamic_cast<CGroupCell*>(view);
445 if(btgc)
447 btgc->setTexture (file);
448 btgc->invalidateCoords();
449 btgc->invalidateContent();
450 paragraphChange();
452 return;
455 CGroupTable *table = dynamic_cast<CGroupTable*>(view);
456 if (table)
458 table->setTexture(file);
460 return;
464 // Force image width, height
465 void CGroupHTML::setImageSize(CViewBase *view, const CStyleParams &style)
467 sint32 width = style.Width;
468 sint32 height = style.Height;
469 sint32 maxw = style.MaxWidth;
470 sint32 maxh = style.MaxHeight;
472 sint32 imageWidth, imageHeight;
473 bool changed = true;
475 // get image texture size
476 // if image is being downloaded, then correct size is set after thats done
477 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
478 if(btn)
480 btn->fitTexture();
481 imageWidth = btn->getW(false);
482 imageHeight = btn->getH(false);
484 else
486 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
487 if(btm)
489 btm->fitTexture();
490 imageWidth = btm->getW(false);
491 imageHeight = btm->getH(false);
493 else
495 // not supported
496 return;
500 // if width/height is not requested, then use image size
501 // else recalculate missing value, keep image ratio
502 if (width == -1 && height == -1)
504 width = imageWidth;
505 height = imageHeight;
507 changed = false;
509 else
510 if (width == -1 || height == -1) {
511 float ratio = (float) imageWidth / std::max(1, imageHeight);
512 if (width == -1)
513 width = height * ratio;
514 else
515 height = width / ratio;
518 // apply max-width, max-height rules if asked
519 if (maxw > -1 || maxh > -1)
521 _Style.applyCssMinMax(width, height, 0, 0, maxw, maxh);
522 changed = true;
525 if (changed)
527 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
528 if(btn)
530 btn->setScale(true);
531 btn->setW(width);
532 btn->setH(height);
534 else
536 CViewBitmap *image = dynamic_cast<CViewBitmap*>(view);
537 if(image)
539 image->setScale(true);
540 image->setW(width);
541 image->setH(height);
547 void CGroupHTML::setTextButtonStyle(CCtrlTextButton *ctrlButton, const CStyleParams &style)
549 // this will also set size for <a class="ryzom-ui-button"> treating it like "display: inline-block;"
550 if (style.Width > 0) ctrlButton->setWMin(style.Width);
551 if (style.Height > 0) ctrlButton->setHMin(style.Height);
553 CViewText *pVT = ctrlButton->getViewText();
554 if (pVT)
556 setTextStyle(pVT, style);
559 if (style.hasStyle("background-color"))
561 ctrlButton->setColor(style.Background.color);
562 if (style.hasStyle("-ryzom-background-color-over"))
564 ctrlButton->setColorOver(style.BackgroundColorOver);
566 else
568 ctrlButton->setColorOver(style.Background.color);
570 ctrlButton->setTexture("", "blank.tga", "", false);
571 ctrlButton->setTextureOver("", "blank.tga", "");
572 ctrlButton->setProperty("force_text_over", "true");
574 else if (style.hasStyle("-ryzom-background-color-over"))
576 ctrlButton->setColorOver(style.BackgroundColorOver);
577 ctrlButton->setProperty("force_text_over", "true");
578 ctrlButton->setTextureOver("blank.tga", "blank.tga", "blank.tga");
582 void CGroupHTML::setTextStyle(CViewText *pVT, const CStyleParams &style)
584 if (pVT)
586 pVT->setColor(style.TextColor);
587 pVT->setFontName(style.FontFamily);
588 pVT->setFontSize(style.FontSize, false);
589 pVT->setEmbolden(style.FontWeight >= FONT_WEIGHT_BOLD);
590 pVT->setOblique(style.FontOblique);
591 pVT->setUnderlined(style.Underlined);
592 pVT->setStrikeThrough(style.StrikeThrough);
593 if (style.TextShadow.Enabled)
595 pVT->setShadow(true);
596 pVT->setShadowColor(style.TextShadow.Color);
597 pVT->setShadowOutline(style.TextShadow.Outline);
598 pVT->setShadowOffset(style.TextShadow.X, style.TextShadow.Y);
603 // Get an url and return the local filename with the path where the url image should be
604 string CGroupHTML::localImageName(const string &url)
606 string dest = "cache/";
607 dest += getMD5((uint8 *)url.c_str(), (uint32)url.size()).toString();
608 dest += ".cache";
609 return dest;
612 void CGroupHTML::pumpCurlQueue()
614 if (RunningCurls < options.curlMaxConnections)
616 std::list<CDataDownload*>::iterator it=Curls.begin();
617 while(it != Curls.end() && RunningCurls < options.curlMaxConnections)
619 if ((*it)->data == NULL)
621 LOG_DL("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str());
622 if (!startCurlDownload(*it))
624 LOG_DL("(%s) failed to start '%s)'", _Id.c_str(), it->url.c_str());
625 finishCurlDownload(*it);
627 it = Curls.erase(it);
628 continue;
632 ++it;
636 if (RunningCurls > 0 || !Curls.empty())
637 LOG_DL("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size());
640 // Add url to MultiCurl queue and return cURL handle
641 bool CGroupHTML::startCurlDownload(CDataDownload *download)
643 if (!MultiCurl)
645 nlwarning("Invalid MultiCurl handle, unable to download '%s'", download->url.c_str());
646 return false;
649 time_t currentTime;
650 time(&currentTime);
652 CHttpCacheObject cache;
653 if (CFile::fileExists(download->dest))
654 cache = CHttpCache::getInstance()->lookup(download->dest);
656 if (cache.Expires > currentTime)
658 LOG_DL("Cache for (%s) is not expired (%s, expires:%d)", download->url.c_str(), download->dest.c_str(), cache.Expires - currentTime);
659 return false;
662 // use browser Id so that two browsers would not use same temp file
663 download->tmpdest = localImageName(_Id + download->dest) + ".tmp";
665 // erase the tmp file if exists
666 if (CFile::fileExists(download->tmpdest))
668 CFile::deleteFile(download->tmpdest);
671 FILE *fp = nlfopen (download->tmpdest, "wb");
672 if (fp == NULL)
674 nlwarning("Can't open file '%s' for writing: code=%d '%s'", download->tmpdest.c_str (), errno, strerror(errno));
675 return false;
678 CURL *curl = curl_easy_init();
679 if (!curl)
681 fclose(fp);
682 CFile::deleteFile(download->tmpdest);
684 nlwarning("Creating cURL handle failed, unable to download '%s'", download->url.c_str());
685 return false;
687 LOG_DL("curl easy handle %p created for '%s'", curl, download->url.c_str());
689 // https://
690 if (toLowerAscii(download->url.substr(0, 8)) == "https://")
692 // if supported, use custom SSL context function to load certificates
693 NLWEB::CCurlCertificates::useCertificates(curl);
696 download->data = new CCurlWWWData(curl, download->url);
697 download->fp = fp;
699 // initial connection timeout, curl default is 300sec
700 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, download->ConnectionTimeout);
702 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true);
703 curl_easy_setopt(curl, CURLOPT_URL, download->url.c_str());
705 // limit curl to HTTP and HTTPS protocols only
706 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
707 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
709 std::vector<std::string> headers;
710 if (!cache.Etag.empty())
711 headers.push_back("If-None-Match: " + cache.Etag);
713 if (!cache.LastModified.empty())
714 headers.push_back("If-Modified-Since: " + cache.LastModified);
716 if (headers.size() > 0)
717 download->data->sendHeaders(headers);
719 // catch headers
720 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
721 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, download->data);
723 std::string userAgent = options.appName + "/" + options.appVersion;
724 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
726 CUrlParser uri(download->url);
727 if (!uri.host.empty())
728 sendCookies(curl, uri.host, isTrustedDomain(uri.host));
730 curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
731 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
733 CURLMcode ret = curl_multi_add_handle(MultiCurl, curl);
734 if (ret != CURLM_OK)
736 nlwarning("cURL multi handle %p error %d on '%s'", curl, ret, download->url.c_str());
737 return false;
740 RunningCurls++;
741 return true;
744 void CGroupHTML::finishCurlDownload(CDataDownload *download)
746 if (download)
748 download->finish();
749 delete download;
751 else
753 nlwarning("Unknown CURL download (nullptr)");
757 // Add a image download request in the multi_curl
758 // return new textureId and download callback
759 ICurlDownloadCB *CGroupHTML::addTextureDownload(const string &url, sint32 &texId, CViewBase *view)
761 CViewRenderer &rVR = *CViewRenderer::getInstance();
762 // data:image/png;base64,AA...==
763 if (startsWith(url, "data:image/"))
765 texId = rVR.createTextureFromDataURL(url);
766 return NULL;
769 std::string finalUrl;
770 // load the image from local files/bnp
771 if (lookupLocalFile(finalUrl, std::string(CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga").c_str(), false))
773 texId = rVR.createTexture(finalUrl);
774 return NULL;
777 finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
779 // use requested url for local name (cache)
780 string dest = localImageName(url);
781 LOG_DL("add to download '%s' dest '%s'", finalUrl.c_str(), dest.c_str());
783 if (CFile::fileExists(dest) && CFile::getFileSize(dest) > 0)
784 texId = rVR.createTexture(dest);
785 else
786 texId = rVR.newTextureId(dest);
788 // Search if we are not already downloading this url.
789 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
791 if((*it)->url == finalUrl)
793 LOG_DL("already downloading '%s' img %p", finalUrl.c_str(), img);
794 TextureDownloadCB *cb = dynamic_cast<TextureDownloadCB*>(*it);
795 if (cb)
797 cb->addTexture(texId, view);
798 // return pointer to shared ImageDownloadCB
799 return cb;
801 else
803 nlwarning("Found texture download '%s', but casting to TextureDownloadCB failed", finalUrl.c_str());
808 Curls.push_back(new TextureDownloadCB(finalUrl, dest, texId, this));
809 // as we return pointer to callback, skip starting downloads just now
810 //pumpCurlQueue();
811 return Curls.back();
814 // Add a image download request in the multi_curl
815 ICurlDownloadCB *CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type, const std::string &placeholder)
817 std::string finalUrl;
818 img->setModulateGlobalColor(style.GlobalColor);
820 // data:image/png;base64,AA...==
821 if (startsWith(url, "data:image/"))
823 setImage(img, decodeURIComponent(url), type);
824 setImageSize(img, style);
825 return NULL;
828 // load the image from local files/bnp
829 std::string image = CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga";
830 if (lookupLocalFile(finalUrl, image.c_str(), false))
832 setImage(img, image, type);
833 setImageSize(img, style);
834 return NULL;
837 finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
839 // use requested url for local name (cache)
840 string dest = localImageName(url);
841 LOG_DL("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
843 // Display cached image while downloading new
844 if (type != OverImage)
846 std::string temp = dest;
847 if (!CFile::fileExists(temp) || CFile::getFileSize(temp) == 0)
849 temp = placeholder;
851 setImage(img, temp, type);
852 setImageSize(img, style);
855 // Search if we are not already downloading this url.
856 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
858 if((*it)->url == finalUrl)
860 LOG_DL("already downloading '%s' img %p", finalUrl.c_str(), img);
861 ImageDownloadCB *cb = dynamic_cast<ImageDownloadCB*>(*it);
862 if (cb)
864 cb->addImage(img, style, type);
865 // return pointer to shared ImageDownloadCB
866 return cb;
868 else
870 nlwarning("Found image download '%s', but casting to ImageDownloadCB failed", finalUrl.c_str());
875 Curls.push_back(new ImageDownloadCB(finalUrl, dest, img, style, type, this));
876 // as we return pointer to callback, skip starting downloads just now
877 //pumpCurlQueue();
878 return Curls.back();
881 void CGroupHTML::removeImageDownload(ICurlDownloadCB *handle, CViewBase *img)
883 ImageDownloadCB *cb = dynamic_cast<ImageDownloadCB*>(handle);
884 if (!cb) {
885 nlwarning("Trying to remove image from downloads, but ICurlDownloadCB pointer did not cast to ImageDownloadCB");
886 return;
888 // image will be removed from handle, but handle is kept and image will be downloaded
889 cb->removeImage(img);
892 void CGroupHTML::initImageDownload()
894 LOG_DL("Init Image Download");
896 string pathName = "cache";
897 if ( ! CFile::isExists( pathName ) )
898 CFile::createDirectory( pathName );
902 // Get an url and return the local filename with the path where the bnp should be
903 string CGroupHTML::localBnpName(const string &url)
905 size_t lastIndex = url.find_last_of("/");
906 string dest = "user/"+url.substr(lastIndex+1);
907 return dest;
910 // Add a bnp download request in the multi_curl, return true if already downloaded
911 bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum)
913 url = upgradeInsecureUrl(getAbsoluteUrl(url));
915 // Search if we are not already downloading this url.
916 for(std::list<CDataDownload*>::const_iterator it = Curls.begin(); it != Curls.end(); ++it)
918 if((*it)->url == url)
920 LOG_DL("already downloading '%s'", url.c_str());
921 return false;
925 string dest = localBnpName(url);
926 LOG_DL("add to download '%s' dest '%s'", url.c_str(), dest.c_str());
928 // create/delete the local file
929 if (NLMISC::CFile::fileExists(dest))
931 if (action == "override" || action == "delete")
933 CFile::setRWAccess(dest);
934 NLMISC::CFile::deleteFile(dest);
936 else
938 return true;
941 if (action != "delete")
943 Curls.push_back(new BnpDownloadCB(url, dest, md5sum, script, this));
944 pumpCurlQueue();
946 else
947 return true;
949 return false;
952 void CGroupHTML::initBnpDownload()
954 if (!_TrustedDomain)
955 return;
957 LOG_DL("Init Bnp Download");
958 string pathName = "user";
959 if ( ! CFile::isExists( pathName ) )
960 CFile::createDirectory( pathName );
963 void CGroupHTML::addStylesheetDownload(const std::vector<CHtmlParser::StyleLink> links)
965 for(uint i = 0; i < links.size(); ++i)
967 _StylesheetQueue.push_back(links[i]);
968 std::string url = getAbsoluteUrl(links[i].Url);
969 _StylesheetQueue.back().Url = url;
971 // push to the front of the queue
972 Curls.push_front(new StylesheetDownloadCB(url, localImageName(url), this));
974 pumpCurlQueue();
977 // Call this evenly to check if an element is downloaded and then manage it
978 void CGroupHTML::checkDownloads()
980 //nlassert(_CrtCheckMemory());
982 if(Curls.empty() && _CurlWWW == NULL)
984 return;
987 int NewRunningCurls = 0;
988 while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(MultiCurl, &NewRunningCurls))
990 LOG_DL("more to do now %d - %d curls", NewRunningCurls, Curls.size());
993 LOG_DL("NewRunningCurls:%d, RunningCurls:%d", NewRunningCurls, RunningCurls);
995 // check which downloads are done
996 CURLMsg *msg;
997 int msgs_left;
998 while ((msg = curl_multi_info_read(MultiCurl, &msgs_left)))
1000 LOG_DL("> (%s) msgs_left %d", _Id.c_str(), msgs_left);
1001 if (msg->msg == CURLMSG_DONE)
1003 if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle)
1005 std::string error;
1006 bool success = msg->data.result == CURLE_OK;
1007 if (!success)
1009 error = curl_easy_strerror(msg->data.result);
1011 LOG_DL("html download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
1012 htmlDownloadFinished(success, error);
1014 else
1016 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
1018 if((*it)->data && (*it)->data->Request == msg->easy_handle)
1020 std::string error;
1021 bool success = msg->data.result == CURLE_OK;
1022 if (!success)
1024 error = curl_easy_strerror(msg->data.result);
1026 LOG_DL("data download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
1027 dataDownloadFinished(success, error, *it);
1029 Curls.erase(it);
1030 break;
1037 RunningCurls = NewRunningCurls;
1038 pumpCurlQueue();
1042 void CGroupHTML::releaseDownloads()
1044 LOG_DL("Release Downloads");
1046 if (_CurlWWW)
1048 LOG_DL("(%s) stop html url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
1049 if (MultiCurl)
1050 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
1052 delete _CurlWWW;
1053 _CurlWWW = NULL;
1056 // remove all queued and already started downloads
1057 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
1059 CDataDownload &dl = *(*it);
1060 if (dl.data)
1062 LOG_DL("(%s) stop data url '%s'", _Id.c_str(), dl.url.c_str());
1063 if (MultiCurl)
1065 curl_multi_remove_handle(MultiCurl, dl.data->Request);
1068 // close and remove temp file
1069 if (dl.fp)
1071 fclose(dl.fp);
1073 if (CFile::fileExists(dl.tmpdest))
1075 CFile::deleteFile(dl.tmpdest);
1079 // release CDataDownload
1080 delete *it;
1082 Curls.clear();
1084 // also clear css queue as it depends on Curls
1085 _StylesheetQueue.clear();
1088 class CGroupListAdaptor : public CInterfaceGroup
1090 public:
1091 CGroupListAdaptor(const TCtorParam &param)
1092 : CInterfaceGroup(param)
1095 private:
1096 void updateCoords()
1098 if (_Parent)
1100 // Get the W max from the parent
1101 _W = std::min(_Parent->getMaxWReal(), _Parent->getWReal());
1102 _WReal = _W;
1104 CInterfaceGroup::updateCoords();
1108 // ***************************************************************************
1110 template<class A> void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); }
1112 // ***************************************************************************
1113 TStyle CGroupHTML::parseStyle (const string &str_styles)
1115 TStyle styles;
1116 vector<string> elements;
1117 NLMISC::splitString(str_styles, ";", elements);
1119 for(uint i = 0; i < elements.size(); ++i)
1121 vector<string> style;
1122 NLMISC::splitString(elements[i], ":", style);
1123 if (style.size() >= 2)
1125 string fullstyle = style[1];
1126 for (uint j=2; j < style.size(); j++)
1127 fullstyle += ":"+style[j];
1128 styles[trim(style[0])] = trimSeparators(fullstyle);
1132 return styles;
1135 // ***************************************************************************
1137 void CGroupHTML::addText (const char *buf, int len)
1139 if (_Browsing)
1141 if (_IgnoreText)
1142 return;
1144 // Build a UTF8 string
1145 if (_ParsingLua && _TrustedDomain)
1147 // we are parsing a lua script
1148 _LuaScript += string(buf, buf + len);
1149 // no more to do
1150 return;
1153 // Build a unicode string
1154 CUtfStringView inputStringView(buf, len);
1156 // Build the final unicode string
1157 string tmp;
1158 tmp.reserve(len);
1159 u32char lastChar = 0;
1160 u32char inputStringView0 = *inputStringView.begin();
1161 for (CUtfStringView::iterator it(inputStringView.begin()), end(inputStringView.end()); it != end; ++it)
1163 u32char output;
1164 bool keep;
1165 // special treatment for 'nbsp' (which is returned as a discreet space)
1166 if (len == 1 && inputStringView0 == 32)
1168 // this is a nbsp entity
1169 output = *it;
1170 keep = true;
1172 else
1174 // not nbsp, use normal white space removal routine
1175 keep = translateChar (output, *it, lastChar);
1178 if (keep)
1180 CUtfStringView::append(tmp, output);
1181 lastChar = output;
1185 if (!tmp.empty())
1186 addString(tmp);
1190 // ***************************************************************************
1192 #define registerAnchorName(prefix) \
1194 if (present[prefix##_ID] && value[prefix##_ID]) \
1195 _AnchorName.push_back(value[prefix##_ID]); \
1198 // ***************************************************************************
1199 void CGroupHTML::beginElement (CHtmlElement &elm)
1201 _Style.pushStyle();
1202 _CurrentHTMLElement = &elm;
1203 _CurrentHTMLNextSibling = elm.nextSibling;
1205 // set element style from css and style attribute
1206 _Style.getStyleFor(elm);
1207 if (!elm.Style.empty())
1209 _Style.applyStyle(elm.Style);
1212 if (elm.hasNonEmptyAttribute("name"))
1214 _AnchorName.push_back(elm.getAttribute("name"));
1216 if (elm.hasNonEmptyAttribute("id"))
1218 _AnchorName.push_back(elm.getAttribute("id"));
1221 if (_Style.Current.DisplayBlock)
1223 endParagraph();
1226 switch(elm.ID)
1228 case HTML_A: htmlA(elm); break;
1229 case HTML_BASE: htmlBASE(elm); break;
1230 case HTML_BODY: htmlBODY(elm); break;
1231 case HTML_BR: htmlBR(elm); break;
1232 case HTML_BUTTON: htmlBUTTON(elm); break;
1233 case HTML_DD: htmlDD(elm); break;
1234 case HTML_DEL: renderPseudoElement(":before", elm); break;
1235 case HTML_DIV: htmlDIV(elm); break;
1236 case HTML_DL: htmlDL(elm); break;
1237 case HTML_DT: htmlDT(elm); break;
1238 case HTML_EM: renderPseudoElement(":before", elm); break;
1239 case HTML_FONT: htmlFONT(elm); break;
1240 case HTML_FORM: htmlFORM(elm); break;
1241 case HTML_H1://no-break
1242 case HTML_H2://no-break
1243 case HTML_H3://no-break
1244 case HTML_H4://no-break
1245 case HTML_H5://no-break
1246 case HTML_H6: htmlH(elm); break;
1247 case HTML_HEAD: htmlHEAD(elm); break;
1248 case HTML_HR: htmlHR(elm); break;
1249 case HTML_HTML: htmlHTML(elm); break;
1250 case HTML_I: htmlI(elm); break;
1251 case HTML_IMG: htmlIMG(elm); break;
1252 case HTML_INPUT: htmlINPUT(elm); break;
1253 case HTML_LI: htmlLI(elm); break;
1254 case HTML_LUA: htmlLUA(elm); break;
1255 case HTML_META: htmlMETA(elm); break;
1256 case HTML_METER: htmlMETER(elm); break;
1257 case HTML_OBJECT: htmlOBJECT(elm); break;
1258 case HTML_OL: htmlOL(elm); break;
1259 case HTML_OPTION: htmlOPTION(elm); break;
1260 case HTML_P: htmlP(elm); break;
1261 case HTML_PRE: htmlPRE(elm); break;
1262 case HTML_PROGRESS: htmlPROGRESS(elm); break;
1263 case HTML_SCRIPT: htmlSCRIPT(elm); break;
1264 case HTML_SELECT: htmlSELECT(elm); break;
1265 case HTML_SMALL: renderPseudoElement(":before", elm); break;
1266 case HTML_SPAN: renderPseudoElement(":before", elm); break;
1267 case HTML_STRONG: renderPseudoElement(":before", elm); break;
1268 case HTML_STYLE: htmlSTYLE(elm); break;
1269 case HTML_TABLE: htmlTABLE(elm); break;
1270 case HTML_TBODY: renderPseudoElement(":before", elm); break;
1271 case HTML_TD: htmlTD(elm); break;
1272 case HTML_TEXTAREA: htmlTEXTAREA(elm); break;
1273 case HTML_TFOOT: renderPseudoElement(":before", elm); break;
1274 case HTML_TH: htmlTH(elm); break;
1275 case HTML_TITLE: htmlTITLE(elm); break;
1276 case HTML_TR: htmlTR(elm); break;
1277 case HTML_U: renderPseudoElement(":before", elm); break;
1278 case HTML_UL: htmlUL(elm); break;
1279 default:
1280 renderPseudoElement(":before", elm);
1281 break;
1285 // ***************************************************************************
1286 void CGroupHTML::endElement(CHtmlElement &elm)
1288 _CurrentHTMLElement = &elm;
1290 switch(elm.ID)
1292 case HTML_A: htmlAend(elm); break;
1293 case HTML_BASE: break;
1294 case HTML_BODY: renderPseudoElement(":after", elm); break;
1295 case HTML_BR: break;
1296 case HTML_BUTTON: htmlBUTTONend(elm); break;
1297 case HTML_DD: htmlDDend(elm); break;
1298 case HTML_DEL: renderPseudoElement(":after", elm); break;
1299 case HTML_DIV: htmlDIVend(elm); break;
1300 case HTML_DL: htmlDLend(elm); break;
1301 case HTML_DT: htmlDTend(elm); break;
1302 case HTML_EM: renderPseudoElement(":after", elm);break;
1303 case HTML_FONT: break;
1304 case HTML_FORM: htmlFORMend(elm); break;
1305 case HTML_H1://no-break
1306 case HTML_H2://no-break
1307 case HTML_H3://no-break
1308 case HTML_H4://no-break
1309 case HTML_H5://no-break
1310 case HTML_H6: htmlHend(elm); break;
1311 case HTML_HEAD: htmlHEADend(elm); break;
1312 case HTML_HR: break;
1313 case HTML_HTML: break;
1314 case HTML_I: htmlIend(elm); break;
1315 case HTML_IMG: break;
1316 case HTML_INPUT: break;
1317 case HTML_LI: htmlLIend(elm); break;
1318 case HTML_LUA: htmlLUAend(elm); break;
1319 case HTML_META: break;
1320 case HTML_METER: break;
1321 case HTML_OBJECT: htmlOBJECTend(elm); break;
1322 case HTML_OL: htmlOLend(elm); break;
1323 case HTML_OPTION: htmlOPTIONend(elm); break;
1324 case HTML_P: htmlPend(elm); break;
1325 case HTML_PRE: htmlPREend(elm); break;
1326 case HTML_SCRIPT: htmlSCRIPTend(elm); break;
1327 case HTML_SELECT: htmlSELECTend(elm); break;
1328 case HTML_SMALL: renderPseudoElement(":after", elm);break;
1329 case HTML_SPAN: renderPseudoElement(":after", elm);break;
1330 case HTML_STRONG: renderPseudoElement(":after", elm);break;
1331 case HTML_STYLE: htmlSTYLEend(elm); break;
1332 case HTML_TABLE: htmlTABLEend(elm); break;
1333 case HTML_TD: htmlTDend(elm); break;
1334 case HTML_TBODY: renderPseudoElement(":after", elm); break;
1335 case HTML_TEXTAREA: break;
1336 case HTML_TFOOT: renderPseudoElement(":after", elm); break;
1337 case HTML_TH: htmlTHend(elm); break;
1338 case HTML_TITLE: break;
1339 case HTML_TR: htmlTRend(elm); break;
1340 case HTML_U: renderPseudoElement(":after", elm); break;
1341 case HTML_UL: htmlULend(elm); break;
1342 default:
1343 renderPseudoElement(":after", elm);
1344 break;
1347 if (_Style.Current.DisplayBlock)
1349 endParagraph();
1352 _Style.popStyle();
1355 // ***************************************************************************
1356 void CGroupHTML::renderPseudoElement(const std::string &pseudo, const CHtmlElement &elm)
1358 if (pseudo != ":before" && pseudo != ":after")
1359 return;
1361 if (!elm.hasPseudo(pseudo))
1362 return;
1364 _Style.pushStyle();
1365 _Style.applyStyle(elm.getPseudo(pseudo));
1367 // TODO: 'content' should already be tokenized in css parser as it has all the functions for that
1368 std::string content = trim(_Style.getStyle("content"));
1369 if (toLowerAscii(content) == "none" || toLowerAscii(content) == "normal")
1371 _Style.popStyle();
1372 return;
1375 std::string::size_type pos = 0;
1376 // TODO: tokenize by whitespace
1377 while(pos < content.size())
1379 std::string::size_type start;
1380 std::string token;
1382 // not supported
1383 // counter, open-quote, close-quote, no-open-quote, no-close-quote
1384 if (content[pos] == '"' || content[pos] == '\'')
1386 char quote = content[pos];
1387 pos++;
1388 start = pos;
1389 while(pos < content.size() && content[pos] != quote)
1391 if (content[pos] == '\\') pos++;
1392 pos++;
1394 token = content.substr(start, pos - start);
1395 addString(token);
1397 // skip closing quote
1398 pos++;
1400 else if (content[pos] == 'u' && pos < content.size() - 6 && toLowerAscii(content.substr(pos, 4)) == "url(")
1402 // url(/path-to/image.jpg) / "Alt!"
1403 // url("/path to/image.jpg") / "Alt!"
1404 std::string tooltip;
1406 start = pos + 4;
1407 // fails if url contains ')'
1408 pos = content.find(")", start);
1409 if (pos == std::string::npos)
1410 break;
1412 token = trim(content.substr(start, pos - start));
1413 // skip ')'
1414 pos++;
1416 // scan for tooltip
1417 start = pos;
1418 while(pos < content.size() && content[pos] == ' ' && content[pos] != '/')
1420 pos++;
1422 if (pos < content.size() && content[pos] == '/')
1424 // skip '/'
1425 pos++;
1427 // skip whitespace
1428 while(pos < content.size() && content[pos] == ' ')
1430 pos++;
1432 if (pos < content.size() && (content[pos] == '\'' || content[pos] == '"'))
1434 char openQuote = content[pos];
1435 pos++;
1436 start = pos;
1437 while(pos < content.size() && content[pos] != openQuote)
1439 if (content[pos] == '\\') pos++;
1440 pos++;
1442 tooltip = content.substr(start, pos - start);
1444 // skip closing quote
1445 pos++;
1447 else
1449 // tooltip should be quoted
1450 pos = start;
1451 tooltip.clear();
1454 else
1456 // no tooltip
1457 pos = start;
1460 if (tooltip.empty())
1462 addImage(getId() + pseudo, token, false, _Style.Current);
1464 else
1466 tooltip = trimQuotes(tooltip);
1467 addButton(CCtrlButton::PushButton, getId() + pseudo, token, token, "", "", "", tooltip.c_str(), _Style.Current);
1470 else if (content[pos] == 'a' && pos < content.size() - 7)
1472 // attr(title)
1473 start = pos + 5;
1474 std::string::size_type end = 0;
1475 end = content.find(")", start);
1476 if (end != std::string::npos)
1478 token = content.substr(start, end - start);
1479 // skip ')'
1480 pos = end + 1;
1481 if (elm.hasAttribute(token))
1483 addString(elm.getAttribute(token));
1486 else
1488 // skip over 'a'
1489 pos++;
1492 else
1494 pos++;
1498 _Style.popStyle();
1501 // ***************************************************************************
1502 void CGroupHTML::renderDOM(CHtmlElement &elm)
1504 if (elm.Type == CHtmlElement::TEXT_NODE)
1506 addText(elm.Value.c_str(), elm.Value.size());
1508 else
1510 beginElement(elm);
1512 if (!_IgnoreChildElements)
1514 std::list<CHtmlElement>::iterator it = elm.Children.begin();
1515 while(it != elm.Children.end())
1517 renderDOM(*it);
1519 ++it;
1522 _IgnoreChildElements = false;
1524 endElement(elm);
1528 // ***************************************************************************
1529 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html");
1532 // ***************************************************************************
1533 uint32 CGroupHTML::_GroupHtmlUIDPool= 0;
1534 CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID;
1537 // ***************************************************************************
1538 CGroupHTML::CGroupHTML(const TCtorParam &param)
1539 : CGroupScrollText(param),
1540 _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT),
1541 _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT),
1542 _CurrentHTMLElement(NULL)
1544 // add it to map of group html created
1545 _GroupHtmlUID= ++_GroupHtmlUIDPool; // valid assigned Id begin to 1!
1546 _GroupHtmlByUID[_GroupHtmlUID]= this;
1548 // init
1549 _TrustedDomain = false;
1550 _ParsingLua = false;
1551 _LuaHrefHack = false;
1552 _IgnoreText = false;
1553 _IgnoreChildElements = false;
1554 _BrowseNextTime = false;
1555 _PostNextTime = false;
1556 _Browsing = false;
1557 _CurrentViewLink = NULL;
1558 _CurrentViewImage = NULL;
1559 _Indent.clear();
1560 _LI = false;
1561 _SelectOption = false;
1562 _GroupListAdaptor = NULL;
1563 _UrlFragment.clear();
1564 _RefreshUrl.clear();
1565 _NextRefreshTime = 0.0;
1566 _LastRefreshTime = 0.0;
1567 _RenderNextTime = false;
1568 _WaitingForStylesheet = false;
1569 _AutoIdSeq = 0;
1570 _FormOpen = false;
1572 // Register
1573 CWidgetManager::getInstance()->registerClockMsgTarget(this);
1575 // HTML parameters
1576 ErrorColor = CRGBA(255, 0, 0);
1577 LinkColor = CRGBA(0, 0, 255);
1578 ErrorColorGlobalColor = false;
1579 LinkColorGlobalColor = false;
1580 TextColorGlobalColor = false;
1581 LIBeginSpace = 4;
1582 ULBeginSpace = 12;
1583 PBeginSpace = 12;
1584 TDBeginSpace = 0;
1585 ULIndent = 30;
1586 LineSpaceFontFactor = 0.5f;
1587 DefaultButtonGroup = "html_text_button";
1588 DefaultFormTextGroup = "edit_box_widget";
1589 DefaultFormTextAreaGroup = "edit_box_widget_multiline";
1590 DefaultFormSelectGroup = "html_form_select_widget";
1591 DefaultFormSelectBoxMenuGroup = "html_form_select_box_menu_widget";
1592 DefaultCheckBoxBitmapNormal = "checkbox_normal.tga";
1593 DefaultCheckBoxBitmapPushed = "checkbox_pushed.tga";
1594 DefaultCheckBoxBitmapOver = "checkbox_over.tga";
1595 DefaultRadioButtonBitmapNormal = "w_radiobutton.png";
1596 DefaultRadioButtonBitmapPushed = "w_radiobutton_pushed.png";
1597 DefaultBackgroundBitmapView = "bg";
1598 clearContext();
1600 MultiCurl = curl_multi_init();
1601 #ifdef CURLMOPT_MAX_HOST_CONNECTIONS
1602 if (MultiCurl)
1604 // added in libcurl 7.30.0
1605 curl_multi_setopt(MultiCurl, CURLMOPT_MAX_HOST_CONNECTIONS, options.curlMaxConnections);
1606 curl_multi_setopt(MultiCurl, CURLMOPT_PIPELINING, 1);
1608 #endif
1609 RunningCurls = 0;
1610 _CurlWWW = NULL;
1612 initImageDownload();
1613 initBnpDownload();
1615 // setup default browser style
1616 setProperty("browser_css_file", "browser.css");
1619 // ***************************************************************************
1621 CGroupHTML::~CGroupHTML()
1623 //releaseImageDownload();
1625 // TestYoyo
1626 //nlinfo("** CGroupHTML Destroy: %x, %s, uid%d", this, _Id.c_str(), _GroupHtmlUID);
1628 /* Erase from map of Group HTML (thus requestTerminated() callback won't be called)
1629 Do it first, just because don't want requestTerminated() to be called while I'm destroying
1630 (useless and may be dangerous)
1632 _GroupHtmlByUID.erase(_GroupHtmlUID);
1634 clearContext();
1635 releaseDownloads();
1637 if (_CurlWWW)
1638 delete _CurlWWW;
1640 if(MultiCurl)
1641 curl_multi_cleanup(MultiCurl);
1644 std::string CGroupHTML::getProperty( const std::string &name ) const
1646 if( name == "url" )
1648 return _URL;
1650 else
1651 if( name == "title_prefix" )
1653 return _TitlePrefix;
1655 else
1656 if( name == "error_color" )
1658 return toString( ErrorColor );
1660 else
1661 if( name == "link_color" )
1663 return toString( LinkColor );
1665 else
1666 if( name == "error_color_global_color" )
1668 return toString( ErrorColorGlobalColor );
1670 else
1671 if( name == "link_color_global_color" )
1673 return toString( LinkColorGlobalColor );
1675 else
1676 if( name == "text_color_global_color" )
1678 return toString( TextColorGlobalColor );
1680 else
1681 if( name == "td_begin_space" )
1683 return toString( TDBeginSpace );
1685 else
1686 if( name == "paragraph_begin_space" )
1688 return toString( PBeginSpace );
1690 else
1691 if( name == "li_begin_space" )
1693 return toString( LIBeginSpace );
1695 else
1696 if( name == "ul_begin_space" )
1698 return toString( ULBeginSpace );
1700 else
1701 if( name == "ul_indent" )
1703 return toString( ULIndent );
1705 else
1706 if( name == "multi_line_space_factor" )
1708 return toString( LineSpaceFontFactor );
1710 else
1711 if( name == "form_text_area_group" )
1713 return DefaultFormTextGroup;
1715 else
1716 if( name == "form_select_group" )
1718 return DefaultFormSelectGroup;
1720 else
1721 if( name == "checkbox_bitmap_normal" )
1723 return DefaultCheckBoxBitmapNormal;
1725 else
1726 if( name == "checkbox_bitmap_pushed" )
1728 return DefaultCheckBoxBitmapPushed;
1730 else
1731 if( name == "checkbox_bitmap_over" )
1733 return DefaultCheckBoxBitmapOver;
1735 else
1736 if( name == "radiobutton_bitmap_normal" )
1738 return DefaultRadioButtonBitmapNormal;
1740 else
1741 if( name == "radiobutton_bitmap_pushed" )
1743 return DefaultRadioButtonBitmapPushed;
1745 else
1746 if( name == "radiobutton_bitmap_over" )
1748 return DefaultRadioButtonBitmapOver;
1750 else
1751 if( name == "background_bitmap_view" )
1753 return DefaultBackgroundBitmapView;
1755 else
1756 if( name == "home" )
1758 return Home;
1760 else
1761 if( name == "browse_next_time" )
1763 return toString( _BrowseNextTime );
1765 else
1766 if( name == "browse_tree" )
1768 return _BrowseTree;
1770 else
1771 if( name == "browse_undo" )
1773 return _BrowseUndoButton;
1775 else
1776 if( name == "browse_redo" )
1778 return _BrowseRedoButton;
1780 else
1781 if( name == "browse_refresh" )
1783 return _BrowseRefreshButton;
1785 else
1786 if( name == "timeout" )
1788 return toString( _TimeoutValue );
1790 else
1791 if( name == "browser_css_file" )
1793 return _BrowserCssFile;
1795 else
1796 return CGroupScrollText::getProperty( name );
1799 void CGroupHTML::setProperty( const std::string &name, const std::string &value )
1801 if( name == "url" )
1803 _URL = value;
1804 return;
1806 else
1807 if( name == "title_prefix" )
1809 _TitlePrefix = value;
1810 return;
1812 else
1813 if( name == "error_color" )
1815 CRGBA c;
1816 if( fromString( value, c ) )
1817 ErrorColor = c;
1818 return;
1820 else
1821 if( name == "link_color" )
1823 CRGBA c;
1824 if( fromString( value, c ) )
1825 LinkColor = c;
1826 return;
1828 else
1829 if( name == "error_color_global_color" )
1831 bool b;
1832 if( fromString( value, b ) )
1833 ErrorColorGlobalColor = b;
1834 return;
1836 else
1837 if( name == "link_color_global_color" )
1839 bool b;
1840 if( fromString( value, b ) )
1841 LinkColorGlobalColor = b;
1842 return;
1844 else
1845 if( name == "text_color_global_color" )
1847 bool b;
1848 if( fromString( value, b ) )
1849 TextColorGlobalColor = b;
1850 return;
1852 else
1853 if( name == "td_begin_space" )
1855 uint i;
1856 if( fromString( value, i ) )
1857 TDBeginSpace = i;
1858 return;
1860 else
1861 if( name == "paragraph_begin_space" )
1863 uint i;
1864 if( fromString( value, i ) )
1865 PBeginSpace = i;
1866 return;
1868 else
1869 if( name == "li_begin_space" )
1871 uint i;
1872 if( fromString( value, i ) )
1873 LIBeginSpace = i;
1874 return;
1876 else
1877 if( name == "ul_begin_space" )
1879 uint i;
1880 if( fromString( value, i ) )
1881 ULBeginSpace = i;
1882 return;
1884 else
1885 if( name == "ul_indent" )
1887 uint i;
1888 if( fromString( value, i ) )
1889 ULIndent = i;
1890 return;
1892 else
1893 if( name == "multi_line_space_factor" )
1895 float f;
1896 if( fromString( value, f ) )
1897 LineSpaceFontFactor = f;
1898 return;
1900 else
1901 if( name == "form_text_area_group" )
1903 DefaultFormTextGroup = value;
1904 return;
1906 else
1907 if( name == "form_select_group" )
1909 DefaultFormSelectGroup = value;
1910 return;
1912 else
1913 if( name == "checkbox_bitmap_normal" )
1915 DefaultCheckBoxBitmapNormal = value;
1916 return;
1918 else
1919 if( name == "checkbox_bitmap_pushed" )
1921 DefaultCheckBoxBitmapPushed = value;
1922 return;
1924 else
1925 if( name == "checkbox_bitmap_over" )
1927 DefaultCheckBoxBitmapOver = value;
1928 return;
1930 else
1931 if( name == "radiobutton_bitmap_normal" )
1933 DefaultRadioButtonBitmapNormal = value;
1934 return;
1936 else
1937 if( name == "radiobutton_bitmap_pushed" )
1939 DefaultRadioButtonBitmapPushed = value;
1940 return;
1942 else
1943 if( name == "radiobutton_bitmap_over" )
1945 DefaultRadioButtonBitmapOver = value;
1946 return;
1948 else
1949 if( name == "background_bitmap_view" )
1951 DefaultBackgroundBitmapView = value;
1952 return;
1954 else
1955 if( name == "home" )
1957 Home = value;
1958 return;
1960 else
1961 if( name == "browse_next_time" )
1963 bool b;
1964 if( fromString( value, b ) )
1965 _BrowseNextTime = b;
1966 return;
1968 else
1969 if( name == "browse_tree" )
1971 _BrowseTree = value;
1972 return;
1974 else
1975 if( name == "browse_undo" )
1977 _BrowseUndoButton = value;
1978 return;
1980 else
1981 if( name == "browse_redo" )
1983 _BrowseRedoButton = value;
1984 return;
1986 else
1987 if( name == "browse_refresh" )
1989 _BrowseRefreshButton = value;
1990 return;
1992 else
1993 if( name == "timeout" )
1995 double d;
1996 if( fromString( value, d ) )
1997 _TimeoutValue = d;
1998 return;
2000 else
2001 if( name == "browser_css_file")
2003 _BrowserStyle.reset();
2004 _BrowserCssFile = value;
2005 if (!_BrowserCssFile.empty())
2007 std::string filename = CPath::lookup(_BrowserCssFile, false, true, true);
2008 if (!filename.empty())
2010 CIFile in;
2011 if (in.open(filename))
2013 std::string css;
2014 if (in.readAll(css))
2015 _BrowserStyle.parseStylesheet(css);
2016 else
2017 nlwarning("Failed to read browser css from '%s'", filename.c_str());
2019 else
2021 nlwarning("Failed to open browser css file '%s'", filename.c_str());
2024 else
2026 nlwarning("Browser css file '%s' not found", _BrowserCssFile.c_str());
2030 else
2031 CGroupScrollText::setProperty( name, value );
2034 xmlNodePtr CGroupHTML::serialize( xmlNodePtr parentNode, const char *type ) const
2036 xmlNodePtr node = CGroupScrollText::serialize( parentNode, type );
2037 if( node == NULL )
2038 return NULL;
2040 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html" );
2041 xmlSetProp( node, BAD_CAST "url", BAD_CAST _URL.c_str() );
2042 xmlSetProp( node, BAD_CAST "title_prefix", BAD_CAST _TitlePrefix.c_str() );
2043 xmlSetProp( node, BAD_CAST "error_color", BAD_CAST toString( ErrorColor ).c_str() );
2044 xmlSetProp( node, BAD_CAST "link_color", BAD_CAST toString( LinkColor ).c_str() );
2046 xmlSetProp( node, BAD_CAST "error_color_global_color",
2047 BAD_CAST toString( ErrorColorGlobalColor ).c_str() );
2048 xmlSetProp( node, BAD_CAST "link_color_global_color",
2049 BAD_CAST toString( LinkColorGlobalColor ).c_str() );
2050 xmlSetProp( node, BAD_CAST "text_color_global_color",
2051 BAD_CAST toString( TextColorGlobalColor ).c_str() );
2053 xmlSetProp( node, BAD_CAST "td_begin_space", BAD_CAST toString( TDBeginSpace ).c_str() );
2054 xmlSetProp( node, BAD_CAST "paragraph_begin_space", BAD_CAST toString( PBeginSpace ).c_str() );
2055 xmlSetProp( node, BAD_CAST "li_begin_space", BAD_CAST toString( LIBeginSpace ).c_str() );
2056 xmlSetProp( node, BAD_CAST "ul_begin_space", BAD_CAST toString( ULBeginSpace ).c_str() );
2057 xmlSetProp( node, BAD_CAST "ul_indent", BAD_CAST toString( ULIndent ).c_str() );
2058 xmlSetProp( node, BAD_CAST "multi_line_space_factor", BAD_CAST toString( LineSpaceFontFactor ).c_str() );
2059 xmlSetProp( node, BAD_CAST "form_text_area_group", BAD_CAST DefaultFormTextGroup.c_str() );
2060 xmlSetProp( node, BAD_CAST "form_select_group", BAD_CAST DefaultFormSelectGroup.c_str() );
2061 xmlSetProp( node, BAD_CAST "checkbox_bitmap_normal", BAD_CAST DefaultCheckBoxBitmapNormal.c_str() );
2062 xmlSetProp( node, BAD_CAST "checkbox_bitmap_pushed", BAD_CAST DefaultCheckBoxBitmapPushed.c_str() );
2063 xmlSetProp( node, BAD_CAST "checkbox_bitmap_over", BAD_CAST DefaultCheckBoxBitmapOver.c_str() );
2064 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_normal", BAD_CAST DefaultRadioButtonBitmapNormal.c_str() );
2065 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_pushed", BAD_CAST DefaultRadioButtonBitmapPushed.c_str() );
2066 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_over", BAD_CAST DefaultRadioButtonBitmapOver.c_str() );
2067 xmlSetProp( node, BAD_CAST "background_bitmap_view", BAD_CAST DefaultBackgroundBitmapView.c_str() );
2068 xmlSetProp( node, BAD_CAST "home", BAD_CAST Home.c_str() );
2069 xmlSetProp( node, BAD_CAST "browse_next_time", BAD_CAST toString( _BrowseNextTime ).c_str() );
2070 xmlSetProp( node, BAD_CAST "browse_tree", BAD_CAST _BrowseTree.c_str() );
2071 xmlSetProp( node, BAD_CAST "browse_undo", BAD_CAST _BrowseUndoButton.c_str() );
2072 xmlSetProp( node, BAD_CAST "browse_redo", BAD_CAST _BrowseRedoButton.c_str() );
2073 xmlSetProp( node, BAD_CAST "browse_refresh", BAD_CAST _BrowseRefreshButton.c_str() );
2074 xmlSetProp( node, BAD_CAST "timeout", BAD_CAST toString( _TimeoutValue ).c_str() );
2075 xmlSetProp( node, BAD_CAST "browser_css_file", BAD_CAST _BrowserCssFile.c_str() );
2077 return node;
2080 // ***************************************************************************
2082 bool CGroupHTML::parse(xmlNodePtr cur,CInterfaceGroup *parentGroup)
2084 nlassert( CWidgetManager::getInstance()->isClockMsgTarget(this));
2087 if(!CGroupScrollText::parse(cur, parentGroup))
2088 return false;
2090 // TestYoyo
2091 //nlinfo("** CGroupHTML parsed Ok: %x, %s, %s, uid%d", this, _Id.c_str(), typeid(this).name(), _GroupHtmlUID);
2093 CXMLAutoPtr ptr;
2095 // Get the url
2096 ptr = xmlGetProp (cur, (xmlChar*)"url");
2097 if (ptr)
2098 _URL = (const char*)ptr;
2100 // Bkup default for undo/redo
2101 _AskedUrl= _URL;
2103 ptr = xmlGetProp (cur, (xmlChar*)"title_prefix");
2104 if (ptr)
2105 _TitlePrefix = CI18N::get((const char*)ptr);
2107 // Parameters
2108 ptr = xmlGetProp (cur, (xmlChar*)"error_color");
2109 if (ptr)
2110 ErrorColor = convertColor(ptr);
2111 ptr = xmlGetProp (cur, (xmlChar*)"link_color");
2112 if (ptr)
2113 LinkColor = convertColor(ptr);
2114 ptr = xmlGetProp (cur, (xmlChar*)"error_color_global_color");
2115 if (ptr)
2116 ErrorColorGlobalColor = convertBool(ptr);
2117 ptr = xmlGetProp (cur, (xmlChar*)"link_color_global_color");
2118 if (ptr)
2119 LinkColorGlobalColor = convertBool(ptr);
2120 ptr = xmlGetProp (cur, (xmlChar*)"text_color_global_color");
2121 if (ptr)
2122 TextColorGlobalColor = convertBool(ptr);
2123 ptr = xmlGetProp (cur, (xmlChar*)"td_begin_space");
2124 if (ptr)
2125 fromString((const char*)ptr, TDBeginSpace);
2126 ptr = xmlGetProp (cur, (xmlChar*)"paragraph_begin_space");
2127 if (ptr)
2128 fromString((const char*)ptr, PBeginSpace);
2129 ptr = xmlGetProp (cur, (xmlChar*)"li_begin_space");
2130 if (ptr)
2131 fromString((const char*)ptr, LIBeginSpace);
2132 ptr = xmlGetProp (cur, (xmlChar*)"ul_begin_space");
2133 if (ptr)
2134 fromString((const char*)ptr, ULBeginSpace);
2135 ptr = xmlGetProp (cur, (xmlChar*)"ul_indent");
2136 if (ptr)
2137 fromString((const char*)ptr, ULIndent);
2138 ptr = xmlGetProp (cur, (xmlChar*)"multi_line_space_factor");
2139 if (ptr)
2140 fromString((const char*)ptr, LineSpaceFontFactor);
2141 ptr = xmlGetProp (cur, (xmlChar*)"form_text_group");
2142 if (ptr)
2143 DefaultFormTextGroup = (const char*)(ptr);
2144 ptr = xmlGetProp (cur, (xmlChar*)"form_text_area_group");
2145 if (ptr)
2146 DefaultFormTextAreaGroup = (const char*)(ptr);
2147 ptr = xmlGetProp (cur, (xmlChar*)"form_select_group");
2148 if (ptr)
2149 DefaultFormSelectGroup = (const char*)(ptr);
2150 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_normal");
2151 if (ptr)
2152 DefaultCheckBoxBitmapNormal = (const char*)(ptr);
2153 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_pushed");
2154 if (ptr)
2155 DefaultCheckBoxBitmapPushed = (const char*)(ptr);
2156 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_over");
2157 if (ptr)
2158 DefaultCheckBoxBitmapOver = (const char*)(ptr);
2159 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_normal");
2160 if (ptr)
2161 DefaultRadioButtonBitmapNormal = (const char*)(ptr);
2162 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_pushed");
2163 if (ptr)
2164 DefaultRadioButtonBitmapPushed = (const char*)(ptr);
2165 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_over");
2166 if (ptr)
2167 DefaultRadioButtonBitmapOver = (const char*)(ptr);
2168 ptr = xmlGetProp (cur, (xmlChar*)"background_bitmap_view");
2169 if (ptr)
2170 DefaultBackgroundBitmapView = (const char*)(ptr);
2171 ptr = xmlGetProp (cur, (xmlChar*)"home");
2172 if (ptr)
2173 Home = (const char*)(ptr);
2174 ptr = xmlGetProp (cur, (xmlChar*)"browse_next_time");
2175 if (ptr)
2176 _BrowseNextTime = convertBool(ptr);
2177 ptr = xmlGetProp (cur, (xmlChar*)"browse_tree");
2178 if(ptr)
2179 _BrowseTree = (const char*)ptr;
2180 ptr = xmlGetProp (cur, (xmlChar*)"browse_undo");
2181 if(ptr)
2182 _BrowseUndoButton= (const char*)ptr;
2183 ptr = xmlGetProp (cur, (xmlChar*)"browse_redo");
2184 if(ptr)
2185 _BrowseRedoButton = (const char*)ptr;
2186 ptr = xmlGetProp (cur, (xmlChar*)"browse_refresh");
2187 if(ptr)
2188 _BrowseRefreshButton = (const char*)ptr;
2189 ptr = xmlGetProp (cur, (xmlChar*)"timeout");
2190 if(ptr)
2191 fromString((const char*)ptr, _TimeoutValue);
2193 ptr = xmlGetProp (cur, (xmlChar*)"browser_css_file");
2194 if (ptr)
2196 setProperty("browser_css_file", (const char *)ptr);
2199 return true;
2202 // ***************************************************************************
2204 bool CGroupHTML::handleEvent (const NLGUI::CEventDescriptor& eventDesc)
2206 bool traited = false;
2208 if (eventDesc.getType() == NLGUI::CEventDescriptor::mouse)
2210 const NLGUI::CEventDescriptorMouse &mouseEvent = (const NLGUI::CEventDescriptorMouse &)eventDesc;
2211 if (mouseEvent.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mousewheel)
2213 // Check if mouse wheel event was on any of multiline select box widgets
2214 // Must do this before CGroupScrollText
2215 for (uint i=0; i<_Forms.size() && !traited; i++)
2217 for (uint j=0; j<_Forms[i].Entries.size() && !traited; j++)
2219 if (_Forms[i].Entries[j].SelectBox)
2221 if (_Forms[i].Entries[j].SelectBox->handleEvent(eventDesc))
2223 traited = true;
2224 break;
2232 if (!traited)
2233 traited = CGroupScrollText::handleEvent (eventDesc);
2235 if (eventDesc.getType() == NLGUI::CEventDescriptor::system)
2237 const NLGUI::CEventDescriptorSystem &systemEvent = (const NLGUI::CEventDescriptorSystem &) eventDesc;
2238 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::clocktick)
2240 // Handle now
2241 handle ();
2243 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::activecalledonparent)
2245 if (!((NLGUI::CEventDescriptorActiveCalledOnParent &) systemEvent).getActive())
2247 // stop refresh when window gets hidden
2248 _NextRefreshTime = 0;
2252 return traited;
2255 // ***************************************************************************
2257 void CGroupHTML::endParagraph()
2259 _Paragraph = NULL;
2261 paragraphChange ();
2264 // ***************************************************************************
2266 void CGroupHTML::newParagraph(uint beginSpace)
2268 // Add a new paragraph
2269 CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam());
2270 newParagraph->setId(getCurrentGroup()->getId() + ":PARAGRAPH" + toString(getNextAutoIdSeq()));
2271 newParagraph->setResizeFromChildH(true);
2273 newParagraph->setMarginLeft(getIndent());
2274 if (!_Style.Current.TextAlign.empty())
2276 if (_Style.Current.TextAlign == "left")
2277 newParagraph->setTextAlign(CGroupParagraph::AlignLeft);
2278 else if (_Style.Current.TextAlign == "center")
2279 newParagraph->setTextAlign(CGroupParagraph::AlignCenter);
2280 else if (_Style.Current.TextAlign == "right")
2281 newParagraph->setTextAlign(CGroupParagraph::AlignRight);
2282 else if (_Style.Current.TextAlign == "justify")
2283 newParagraph->setTextAlign(CGroupParagraph::AlignJustify);
2286 // Add to the group
2287 addHtmlGroup (newParagraph, beginSpace);
2288 _Paragraph = newParagraph;
2290 paragraphChange ();
2293 // ***************************************************************************
2295 void CGroupHTML::browse(const char *url)
2297 // modify undo/redo
2298 pushUrlUndoRedo(url);
2300 // do the browse, with no undo/redo
2301 doBrowse(url);
2304 // ***************************************************************************
2305 void CGroupHTML::refresh()
2307 if (!_URL.empty())
2308 doBrowse(_URL.c_str(), true);
2311 // ***************************************************************************
2312 void CGroupHTML::doBrowse(const char *url, bool force)
2314 LOG_DL("(%s) Browsing URL : '%s'", _Id.c_str(), url);
2316 CUrlParser uri(url);
2317 if (!uri.hash.empty())
2319 // Anchor to scroll after page has loaded
2320 _UrlFragment = uri.hash;
2322 uri.inherit(_DocumentUrl);
2323 uri.hash.clear();
2325 // compare urls and see if we only navigating to new anchor
2326 if (!force && _DocumentUrl == uri.toString())
2328 // scroll happens in updateCoords()
2329 invalidateCoords();
2330 return;
2333 else
2334 _UrlFragment.clear();
2336 // go
2337 _URL = uri.toString();
2338 _BrowseNextTime = true;
2339 _WaitingForStylesheet = false;
2341 // if a BrowseTree is bound to us, try to select the node that opens this URL (auto-locate)
2342 if(!_BrowseTree.empty())
2344 CGroupTree *groupTree=dynamic_cast<CGroupTree*>(CWidgetManager::getInstance()->getElementFromId(_BrowseTree));
2345 if(groupTree)
2347 string nodeId= selectTreeNodeRecurs(groupTree->getRootNode(), url);
2348 // select the node
2349 if(!nodeId.empty())
2351 groupTree->selectNodeById(nodeId);
2357 // ***************************************************************************
2359 void CGroupHTML::browseError (const char *msg)
2361 releaseDownloads();
2363 // Get the list group from CGroupScrollText
2364 removeContent();
2365 newParagraph(0);
2366 CViewText *viewText = new CViewText ("", (string("Error : ")+msg).c_str());
2367 viewText->setColor (ErrorColor);
2368 viewText->setModulateGlobalColor(ErrorColorGlobalColor);
2369 viewText->setMultiLine (true);
2370 getParagraph()->addChild (viewText);
2371 if(!_TitlePrefix.empty())
2372 setTitle (_TitlePrefix);
2374 updateRefreshButton();
2375 invalidateCoords();
2378 void CGroupHTML::browseErrorHtml(const std::string &html)
2380 releaseDownloads();
2381 removeContent();
2383 renderHtmlString(html);
2385 updateRefreshButton();
2386 invalidateCoords();
2389 // ***************************************************************************
2391 bool CGroupHTML::isBrowsing()
2393 // do not show spinning cursor for image downloads (!Curls.empty())
2394 return _BrowseNextTime || _PostNextTime || _RenderNextTime ||
2395 _Browsing || _WaitingForStylesheet ||
2396 _CurlWWW;
2399 // ***************************************************************************
2401 void CGroupHTML::updateCoords()
2403 CGroupScrollText::updateCoords();
2405 // all elements are in their correct place, tell scrollbar to scroll to anchor
2406 if (!_Browsing && !_UrlFragment.empty())
2408 doBrowseAnchor(_UrlFragment);
2409 _UrlFragment.clear();
2412 if (!m_HtmlBackground.isEmpty() || !m_BodyBackground.isEmpty())
2414 // get scroll offset from list
2415 CGroupList *list = getList();
2416 if (list)
2418 CInterfaceElement* vp = list->getParentPos() ? list->getParentPos() : this;
2419 sint htmlW = std::max(vp->getWReal(), list->getWReal());
2420 sint htmlH = list->getHReal();
2421 sint htmlX = list->getXReal() + list->getOfsX();
2422 sint htmlY = list->getYReal() + list->getOfsY();
2424 if (!m_HtmlBackground.isEmpty())
2426 m_HtmlBackground.setFillViewport(true);
2427 m_HtmlBackground.setBorderArea(htmlX, htmlY, htmlW, htmlH);
2428 m_HtmlBackground.setPaddingArea(htmlX, htmlY, htmlW, htmlH);
2429 m_HtmlBackground.setContentArea(htmlX, htmlY, htmlW, htmlH);
2432 if (!m_BodyBackground.isEmpty())
2434 // TODO: html padding + html border
2435 m_BodyBackground.setBorderArea(htmlX, htmlY, htmlW, htmlH);
2436 // TODO: html padding + html border + body border
2437 m_BodyBackground.setPaddingArea(htmlX, htmlY, htmlW, htmlH);
2438 // TODO: html padding + html_border + body padding
2439 m_BodyBackground.setContentArea(htmlX, htmlY, htmlW, htmlH);
2445 // ***************************************************************************
2447 bool CGroupHTML::translateChar(u32char &output, u32char input, u32char lastCharParam) const
2449 // Keep this char ?
2450 bool keep = true;
2452 // char is between table elements
2453 // TODO: only whitespace is handled, text is added to either TD, or after TABLE (should be before)
2454 bool tableWhitespace = getTable() && (_Cells.empty() || _Cells.back() == NULL);
2456 switch (input)
2458 // Return / tab only in <PRE> mode
2459 case '\t':
2460 case '\n':
2462 if (tableWhitespace)
2464 keep = false;
2466 else
2468 // Get the last char
2469 u32char lastChar = lastCharParam;
2470 if (lastChar == 0)
2471 lastChar = getLastChar();
2472 keep = ((lastChar != (u32char)' ') &&
2473 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2474 if(!getPRE())
2475 input = (u32char)' ';
2478 break;
2479 case ' ':
2481 if (tableWhitespace)
2483 keep = false;
2485 else
2487 // Get the last char
2488 u32char lastChar = lastCharParam;
2489 if (lastChar == 0)
2490 lastChar = getLastChar();
2491 keep = ((lastChar != (u32char)' ') &&
2492 (lastChar != (u32char)'\n') &&
2493 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2496 break;
2497 case 0xd:
2498 keep = false;
2499 break;
2502 if (keep)
2504 output = input;
2507 return keep;
2510 // ***************************************************************************
2512 void CGroupHTML::registerAnchor(CInterfaceElement* elm)
2514 if (!_AnchorName.empty())
2516 for(uint32 i=0; i < _AnchorName.size(); ++i)
2518 // filter out duplicates and register only first
2519 if (!_AnchorName[i].empty() && _Anchors.count(_AnchorName[i]) == 0)
2521 _Anchors[_AnchorName[i]] = elm;
2525 _AnchorName.clear();
2529 // ***************************************************************************
2530 bool CGroupHTML::isSameStyle(CViewLink *text, const CStyleParams &style) const
2532 if (!text) return false;
2534 bool embolden = style.FontWeight >= FONT_WEIGHT_BOLD;
2535 bool sameShadow = style.TextShadow.Enabled && text->getShadow();
2536 if (sameShadow && style.TextShadow.Enabled)
2538 sint sx, sy;
2539 text->getShadowOffset(sx, sy);
2540 sameShadow = (style.TextShadow.Color == text->getShadowColor());
2541 sameShadow = sameShadow && (style.TextShadow.Outline == text->getShadowOutline());
2542 sameShadow = sameShadow && (style.TextShadow.X == sx) && (style.TextShadow.Y == sy);
2544 // Compatible with current parameters ?
2545 return sameShadow &&
2546 (style.TextColor == text->getColor()) &&
2547 (style.FontFamily == text->getFontName()) &&
2548 (style.FontSize == (uint)text->getFontSize()) &&
2549 (style.Underlined == text->getUnderlined()) &&
2550 (style.StrikeThrough == text->getStrikeThrough()) &&
2551 (embolden == text->getEmbolden()) &&
2552 (style.FontOblique == text->getOblique()) &&
2553 (getLink() == text->Link) &&
2554 (style.GlobalColorText == text->getModulateGlobalColor());
2557 // ***************************************************************************
2558 void CGroupHTML::newTextButton(const std::string &text, const std::string &tpl)
2560 _CurrentViewLink = NULL;
2561 _CurrentViewImage = NULL;
2563 // Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
2564 string param = "name=" + this->_Id + "|url=" + getLink();
2565 string name;
2566 if (!_AnchorName.empty())
2567 name = _AnchorName.back();
2569 typedef pair<string, string> TTmplParam;
2570 vector<TTmplParam> tmplParams;
2571 tmplParams.push_back(TTmplParam("id", ""));
2572 tmplParams.push_back(TTmplParam("onclick", "browse"));
2573 tmplParams.push_back(TTmplParam("onclick_param", param));
2574 tmplParams.push_back(TTmplParam("active", "true"));
2575 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(tpl, getId()+":"+name, tmplParams);
2576 if (!buttonGroup)
2578 nlinfo("Text button template '%s' not found", tpl.c_str());
2579 return;
2581 buttonGroup->setId(getId()+":"+name);
2583 // Add the ctrl button
2584 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
2585 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
2586 if (!ctrlButton)
2588 nlinfo("Text button template '%s' is missing :button or :b text element", tpl.c_str());
2589 return;
2591 ctrlButton->setModulateGlobalColorAll(_Style.Current.GlobalColor);
2592 ctrlButton->setTextModulateGlobalColorNormal(_Style.Current.GlobalColorText);
2593 ctrlButton->setTextModulateGlobalColorOver(_Style.Current.GlobalColorText);
2594 ctrlButton->setTextModulateGlobalColorPushed(_Style.Current.GlobalColorText);
2596 // Translate the tooltip
2597 ctrlButton->setText(text);
2598 ctrlButton->setDefaultContextHelp(std::string(getLinkTitle()));
2599 // empty url / button disabled
2600 ctrlButton->setFrozen(*getLink() == '\0');
2602 setTextButtonStyle(ctrlButton, _Style.Current);
2604 _Paragraph->addChild(buttonGroup);
2607 // ***************************************************************************
2608 void CGroupHTML::newTextLink(const std::string &text)
2610 CViewLink *newLink = new CViewLink(CViewBase::TCtorParam());
2611 if (getA())
2613 newLink->Link = getLink();
2614 newLink->LinkTitle = getLinkTitle();
2615 if (!newLink->Link.empty())
2617 newLink->setHTMLView (this);
2618 newLink->setActionOnLeftClick("browse");
2619 newLink->setParamsOnLeftClick("name=" + getId() + "|url=" + newLink->Link);
2622 newLink->setText(text);
2623 newLink->setMultiLineSpace((uint)((float)(_Style.Current.FontSize)*LineSpaceFontFactor));
2624 newLink->setMultiLine(true);
2625 newLink->setModulateGlobalColor(_Style.Current.GlobalColorText);
2626 setTextStyle(newLink, _Style.Current);
2628 registerAnchor(newLink);
2630 if (getA() && !newLink->Link.empty())
2631 getParagraph()->addChildLink(newLink);
2632 else
2633 getParagraph()->addChild(newLink);
2635 _CurrentViewLink = newLink;
2636 _CurrentViewImage = NULL;
2639 // ***************************************************************************
2641 void CGroupHTML::addString(const std::string &str)
2643 string tmpStr = str;
2645 if (_Localize)
2647 string _str = tmpStr;
2648 string::size_type p = _str.find('#');
2649 if (p == string::npos)
2651 tmpStr = CI18N::get(_str);
2653 else
2655 string cmd = _str.substr(0, p);
2656 string arg = _str.substr(p+1);
2658 if (cmd == "date")
2660 uint year, month, day;
2661 sscanf(arg.c_str(), "%d/%d/%d", &year, &month, &day);
2662 tmpStr = CI18N::get( "uiMFIDate");
2664 year += (year > 70 ? 1900 : 2000);
2666 strFindReplace(tmpStr, "%year", toString("%d", year) );
2667 strFindReplace(tmpStr, "%month", CI18N::get(toString("uiMonth%02d", month)) );
2668 strFindReplace(tmpStr, "%day", toString("%d", day) );
2670 else
2672 tmpStr = arg;
2677 // In title ?
2678 if (_Object)
2680 _ObjectScript += tmpStr;
2682 else if (_SelectOption)
2684 if (!(_Forms.empty()))
2686 if (!_Forms.back().Entries.empty())
2688 _SelectOptionStr += tmpStr;
2692 else
2694 // In a paragraph ?
2695 if (!_Paragraph)
2697 newParagraph (0);
2698 paragraphChange ();
2701 CStyleParams &style = _Style.Current;
2703 // Text added ?
2704 bool added = false;
2706 if (_CurrentViewLink)
2708 bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == '\n';
2709 if (!skipLine && isSameStyle(_CurrentViewLink, style))
2711 // Concat the text
2712 _CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
2713 _CurrentViewLink->invalidateContent();
2714 added = true;
2718 // Not added ?
2719 if (!added)
2721 if (getA() && string(getLinkClass()) == "ryzom-ui-button")
2722 newTextButton(tmpStr, DefaultButtonGroup);
2723 else
2724 newTextLink(tmpStr);
2729 // ***************************************************************************
2731 void CGroupHTML::addImage(const std::string &id, const std::string &img, bool reloadImg, const CStyleParams &style)
2733 // In a paragraph ?
2734 if (!_Paragraph)
2736 newParagraph (0);
2737 paragraphChange ();
2740 // No more text in this text view
2741 _CurrentViewLink = NULL;
2743 // Not added ?
2744 CViewBitmap *newImage = new CViewBitmap (TCtorParam());
2745 newImage->setId(id);
2747 addImageDownload(img, newImage, style, NormalImage);
2748 newImage->setRenderLayer(getRenderLayer()+1);
2750 getParagraph()->addChild(newImage);
2751 paragraphChange ();
2753 setImageSize(newImage, style);
2756 // ***************************************************************************
2758 CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const std::string &content, uint maxlength)
2760 // In a paragraph ?
2761 if (!_Paragraph)
2763 newParagraph (0);
2764 paragraphChange ();
2767 // No more text in this text view
2768 _CurrentViewLink = NULL;
2770 CStyleParams &style = _Style.Current;
2772 // override cols/rows values from style
2773 if (style.Width > 0) cols = style.Width / style.FontSize;
2774 if (style.Height > 0) rows = style.Height / style.FontSize;
2776 // Not added ?
2777 std::vector<std::pair<std::string,std::string> > templateParams;
2778 templateParams.push_back (std::pair<std::string,std::string> ("w", toString (cols*style.FontSize)));
2779 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
2780 templateParams.push_back (std::pair<std::string,std::string> ("prompt", ""));
2781 templateParams.push_back (std::pair<std::string,std::string> ("multiline", multiLine?"true":"false"));
2782 templateParams.push_back (std::pair<std::string,std::string> ("fontsize", toString (style.FontSize)));
2783 templateParams.push_back (std::pair<std::string,std::string> ("color", style.TextColor.toString()));
2784 if (style.FontWeight >= FONT_WEIGHT_BOLD)
2785 templateParams.push_back (std::pair<std::string,std::string> ("fontweight", "bold"));
2786 if (style.FontOblique)
2787 templateParams.push_back (std::pair<std::string,std::string> ("fontstyle", "oblique"));
2788 if (multiLine)
2789 templateParams.push_back (std::pair<std::string,std::string> ("multi_min_line", toString(rows)));
2790 templateParams.push_back (std::pair<std::string,std::string> ("want_return", multiLine?"true":"false"));
2791 templateParams.push_back (std::pair<std::string,std::string> ("onenter", ""));
2792 templateParams.push_back (std::pair<std::string,std::string> ("enter_recover_focus", "false"));
2793 if (maxlength > 0)
2794 templateParams.push_back (std::pair<std::string,std::string> ("max_num_chars", toString(maxlength)));
2795 templateParams.push_back (std::pair<std::string,std::string> ("shadow", toString(style.TextShadow.Enabled)));
2796 if (style.TextShadow.Enabled)
2798 templateParams.push_back (std::pair<std::string,std::string> ("shadow_x", toString(style.TextShadow.X)));
2799 templateParams.push_back (std::pair<std::string,std::string> ("shadow_y", toString(style.TextShadow.Y)));
2800 templateParams.push_back (std::pair<std::string,std::string> ("shadow_color", style.TextShadow.Color.toString()));
2801 templateParams.push_back (std::pair<std::string,std::string> ("shadow_outline", toString(style.TextShadow.Outline)));
2804 CInterfaceGroup *textArea = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
2805 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
2807 // Group created ?
2808 if (textArea)
2810 // Set the content
2811 CGroupEditBox *eb = dynamic_cast<CGroupEditBox*>(textArea->getGroup("eb"));
2812 if (eb)
2814 eb->setInputString(decodeHTMLEntities(content));
2815 if (style.hasStyle("background-color"))
2817 CViewBitmap *bg = dynamic_cast<CViewBitmap*>(eb->getView("bg"));
2818 if (bg)
2820 bg->setTexture("blank.tga");
2821 bg->setColor(style.Background.color);
2826 textArea->invalidateCoords();
2827 getParagraph()->addChild (textArea);
2828 paragraphChange ();
2830 return textArea;
2834 // Not group created
2835 return NULL;
2838 // ***************************************************************************
2839 CDBGroupComboBox *CGroupHTML::addComboBox(const std::string &templateName, const char *name)
2841 // In a paragraph ?
2842 if (!_Paragraph)
2844 newParagraph (0);
2845 paragraphChange ();
2850 // Not added ?
2851 std::vector<std::pair<std::string,std::string> > templateParams;
2852 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
2853 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
2854 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
2856 // Group created ?
2857 if (group)
2859 // Set the content
2860 CDBGroupComboBox *cb = dynamic_cast<CDBGroupComboBox *>(group);
2861 if (!cb)
2863 nlwarning("'%s' template has bad type, combo box expected", templateName.c_str());
2864 delete cb;
2865 return NULL;
2867 else
2869 getParagraph()->addChild (cb);
2870 paragraphChange ();
2871 return cb;
2876 // Not group created
2877 return NULL;
2880 // ***************************************************************************
2881 CGroupMenu *CGroupHTML::addSelectBox(const std::string &templateName, const char *name)
2883 // In a paragraph ?
2884 if (!_Paragraph)
2886 newParagraph (0);
2887 paragraphChange ();
2890 // Not added ?
2891 std::vector<std::pair<std::string,std::string> > templateParams;
2892 templateParams.push_back(std::pair<std::string,std::string> ("id", name));
2893 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName.c_str(),
2894 getParagraph()->getId(), &(templateParams[0]), (uint)templateParams.size());
2896 // Group created ?
2897 if (group)
2899 // Set the content
2900 CGroupMenu *sb = dynamic_cast<CGroupMenu *>(group);
2901 if (!sb)
2903 nlwarning("'%s' template has bad type, CGroupMenu expected", templateName.c_str());
2904 delete sb;
2905 return NULL;
2907 else
2909 getParagraph()->addChild (sb);
2910 paragraphChange ();
2911 return sb;
2915 // No group created
2916 return NULL;
2919 // ***************************************************************************
2921 CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &name, const std::string &normalBitmap, const std::string &pushedBitmap,
2922 const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams,
2923 const std::string &tooltip, const CStyleParams &style)
2925 // In a paragraph ?
2926 if (!_Paragraph)
2928 newParagraph (0);
2929 paragraphChange ();
2932 // Add the ctrl button
2933 CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
2934 if (!name.empty())
2936 ctrlButton->setId(name);
2939 std::string normal;
2940 if (startsWith(normalBitmap, "data:image/"))
2942 normal = decodeURIComponent(normalBitmap);
2944 else
2946 // Load only tga files.. (conversion in dds filename is done in the lookup procedure)
2947 normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
2949 // if the image doesn't exist on local, we check in the cache
2950 if(!CPath::exists(normal))
2952 // search in the compressed texture
2953 CViewRenderer &rVR = *CViewRenderer::getInstance();
2954 sint32 id = rVR.getTextureIdFromName(normal);
2955 if(id == -1)
2957 normal = localImageName(normalBitmap);
2958 addImageDownload(normalBitmap, ctrlButton, style);
2963 std::string pushed;
2964 if (startsWith(pushedBitmap, "data:image/"))
2966 pushed = decodeURIComponent(pushedBitmap);
2968 else
2970 pushed = pushedBitmap.empty()?"":CFile::getPath(pushedBitmap) + CFile::getFilenameWithoutExtension(pushedBitmap) + ".tga";
2971 // if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it
2972 if(!CPath::exists(pushed))
2974 // search in the compressed texture
2975 CViewRenderer &rVR = *CViewRenderer::getInstance();
2976 sint32 id = rVR.getTextureIdFromName(pushed);
2977 if(id == -1)
2979 pushed = localImageName(pushedBitmap);
2984 std::string over;
2985 if (startsWith(overBitmap, "data:image/"))
2987 over = decodeURIComponent(overBitmap);
2989 else
2991 over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga";
2992 // schedule mouseover bitmap for download if its different from normal
2993 if (!over.empty() && !CPath::exists(over))
2995 if (overBitmap != normalBitmap)
2997 over = localImageName(overBitmap);
2998 addImageDownload(overBitmap, ctrlButton, style, OverImage);
3003 ctrlButton->setType (type);
3004 if (!normal.empty())
3005 ctrlButton->setTexture (normal);
3006 if (!pushed.empty())
3007 ctrlButton->setTexturePushed (pushed);
3008 if (!over.empty())
3009 ctrlButton->setTextureOver (over);
3010 ctrlButton->setModulateGlobalColorAll (style.GlobalColor);
3011 ctrlButton->setActionOnLeftClick (actionHandler);
3012 ctrlButton->setParamsOnLeftClick (actionHandlerParams);
3014 // Translate the tooltip or display raw text (tooltip from webig)
3015 if (!tooltip.empty())
3017 if (CI18N::hasTranslation(tooltip))
3019 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
3020 //ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
3022 else
3024 ctrlButton->setDefaultContextHelp(tooltip);
3025 //ctrlButton->setOnContextHelp(string(tooltip));
3028 ctrlButton->setInstantContextHelp(true);
3029 ctrlButton->setToolTipParent(TTMouse);
3030 ctrlButton->setToolTipParentPosRef(Hotspot_TTAuto);
3031 ctrlButton->setToolTipPosRef(Hotspot_TTAuto);
3034 getParagraph()->addChild (ctrlButton);
3035 paragraphChange ();
3037 setImageSize(ctrlButton, style);
3039 return ctrlButton;
3042 // ***************************************************************************
3044 void CGroupHTML::flushString()
3046 _CurrentViewLink = NULL;
3049 // ***************************************************************************
3051 void CGroupHTML::clearContext()
3053 _Paragraph = NULL;
3054 _PRE.clear();
3055 _Indent.clear();
3056 _LI = false;
3057 _UL.clear();
3058 _DL.clear();
3059 _A.clear();
3060 _Link.clear();
3061 _LinkTitle.clear();
3062 _Tables.clear();
3063 _Cells.clear();
3064 _TR.clear();
3065 _Forms.clear();
3066 _FormOpen = false;
3067 _FormSubmit.clear();
3068 _Groups.clear();
3069 _Divs.clear();
3070 _Anchors.clear();
3071 _AnchorName.clear();
3072 _CellParams.clear();
3073 _Object = false;
3074 _Localize = false;
3075 _ReadingHeadTag = false;
3076 _IgnoreHeadTag = false;
3077 _IgnoreBaseUrlTag = false;
3078 _AutoIdSeq = 0;
3080 paragraphChange ();
3082 // clear the pointer to the current image download since all the button are deleted
3083 LOG_DL("Clear pointers to %d curls", Curls.size());
3085 // remove image refs from downloads
3086 /*for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
3088 it->imgs.clear();
3092 // ***************************************************************************
3094 u32char CGroupHTML::getLastChar() const
3096 if (_CurrentViewLink)
3098 ::u32string str = CUtfStringView(_CurrentViewLink->getText()).toUtf32(); // FIXME: Optimize reverse UTF iteration
3099 if (!str.empty())
3100 return str[str.length()-1];
3102 return 0;
3105 // ***************************************************************************
3107 void CGroupHTML::paragraphChange ()
3109 _CurrentViewLink = NULL;
3110 _CurrentViewImage = NULL;
3111 CGroupParagraph *paragraph = getParagraph();
3112 if (paragraph)
3114 // Number of child in this paragraph
3115 uint numChild = paragraph->getNumChildren();
3116 if (numChild)
3118 // Get the last child
3119 CViewBase *child = paragraph->getChild(numChild-1);
3121 // Is this a string view ?
3122 _CurrentViewLink = dynamic_cast<CViewLink*>(child);
3123 _CurrentViewImage = dynamic_cast<CViewBitmap*>(child);
3128 // ***************************************************************************
3130 CInterfaceGroup *CGroupHTML::getCurrentGroup()
3132 if (!_Cells.empty() && _Cells.back())
3133 return _Cells.back()->Group;
3134 else
3135 return _GroupListAdaptor;
3138 // ***************************************************************************
3140 void CGroupHTML::addHtmlGroup (CInterfaceGroup *group, uint beginSpace)
3142 if (!group)
3143 return;
3145 registerAnchor(group);
3147 if (!_DivName.empty())
3149 group->setName(_DivName);
3150 _Groups.push_back(group);
3153 group->setSizeRef(CInterfaceElement::width);
3155 // Compute begin space between paragraph and tables
3156 // * If first in group, no begin space
3158 // Pointer on the current paragraph (can be a table too)
3159 CGroupParagraph *p = dynamic_cast<CGroupParagraph*>(group);
3161 CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
3162 const std::vector<CInterfaceGroup*> &groups = parentGroup->getGroups ();
3163 group->setParent(parentGroup);
3164 group->setParentSize(parentGroup);
3165 if (groups.empty())
3167 group->setParentPos(parentGroup);
3168 group->setPosRef(Hotspot_TL);
3169 group->setParentPosRef(Hotspot_TL);
3170 beginSpace = 0;
3172 else
3174 // Last is a paragraph ?
3175 group->setParentPos(groups.back());
3176 group->setPosRef(Hotspot_TL);
3177 group->setParentPosRef(Hotspot_BL);
3180 // Set the begin space
3181 if (p)
3182 p->setTopSpace(beginSpace);
3183 else
3184 group->setY(-(sint32)beginSpace);
3185 parentGroup->addGroup (group);
3188 // ***************************************************************************
3190 void CGroupHTML::setContainerTitle (const std::string &title)
3192 CInterfaceElement *parent = getParent();
3193 if (parent)
3195 if ((parent = parent->getParent()))
3197 CGroupContainer *container = dynamic_cast<CGroupContainer*>(parent);
3198 if (container)
3200 container->setTitle(title);
3206 void CGroupHTML::setTitle(const std::string &title)
3208 if(_TitlePrefix.empty())
3209 _TitleString = title;
3210 else
3211 _TitleString = _TitlePrefix + " - " + title;
3213 setContainerTitle(_TitleString);
3216 std::string CGroupHTML::getTitle() const {
3217 return _TitleString;
3220 // ***************************************************************************
3222 bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
3224 result = url;
3225 string tmp;
3227 if (toLowerAscii(result).find("file:") == 0 && result.size() > 5)
3229 result = result.substr(5, result.size()-5);
3231 else if (result.find("://") != string::npos || result.find("//") == 0)
3233 // http://, https://, etc or protocol-less url "//domain.com/image.png"
3234 return false;
3237 tmp = CPath::lookup (CFile::getFilename(result), false, false, false);
3238 if (tmp.empty())
3240 // try to find in local directory
3241 tmp = CPath::lookup (result, false, false, true);
3244 if (!tmp.empty())
3246 // Normalize the path
3247 if (isUrl)
3248 //result = "file:"+toLowerAscii(CPath::standardizePath (CPath::getFullPath (CFile::getPath(result)))+CFile::getFilename(result));*/
3249 result = "file:/"+tmp;
3250 else
3251 result = tmp;
3252 return true;
3254 else
3256 // Is it a texture in the big texture ?
3257 if (CViewRenderer::getInstance()->getTextureIdFromName (result) >= 0)
3259 return true;
3261 else
3263 // This is not a file in the CPath, let libwww open this URL
3264 result = url;
3265 return false;
3270 // ***************************************************************************
3272 void CGroupHTML::submitForm(uint button, sint32 x, sint32 y)
3274 if (button >= _FormSubmit.size())
3275 return;
3277 for(uint formId = 0; formId < _Forms.size(); formId++)
3279 // case sensitive search (user id is lowecase, auto id is uppercase)
3280 if (_Forms[formId].id == _FormSubmit[button].form)
3282 _PostNextTime = true;
3283 _PostFormId = formId;
3284 _PostFormAction = _FormSubmit[button].formAction;
3285 _PostFormSubmitType = _FormSubmit[button].type;
3286 _PostFormSubmitButton = _FormSubmit[button].name;
3287 _PostFormSubmitValue = _FormSubmit[button].value;
3288 _PostFormSubmitX = x;
3289 _PostFormSubmitY = y;
3291 return;
3295 nlwarning("Unable to find form '%s' to submit (button '%s')", _FormSubmit[button].form.c_str(), _FormSubmit[button].name.c_str());
3298 // ***************************************************************************
3300 void CGroupHTML::setupBackground(CSSBackgroundRenderer *bg)
3302 if (!bg) return;
3304 bg->setModulateGlobalColor(_Style.Current.GlobalColor);
3305 bg->setBackground(_Style.Current.Background);
3306 bg->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
3308 bg->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
3310 if (!_Style.Current.Background.image.empty())
3311 addTextureDownload(_Style.Current.Background.image, bg->TextureId, this);
3314 // ***************************************************************************
3316 void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
3318 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3319 CViewBase *view = getView (DefaultBackgroundBitmapView);
3320 if (view)
3321 view->setActive(false);
3323 m_HtmlBackground.setColor(bgcolor);
3326 // ***************************************************************************
3328 void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
3330 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3331 CViewBase *view = getView (DefaultBackgroundBitmapView);
3332 if (view)
3333 view->setActive(false);
3335 m_HtmlBackground.setImage(bgtex);
3336 m_HtmlBackground.setImageRepeat(tile);
3337 m_HtmlBackground.setImageCover(scale);
3339 if (!bgtex.empty())
3340 addTextureDownload(bgtex, m_HtmlBackground.TextureId, this);
3344 struct CButtonFreezer : public CInterfaceElementVisitor
3346 virtual void visitCtrl(CCtrlBase *ctrl)
3348 CCtrlBaseButton *textButt = dynamic_cast<CCtrlTextButton *>(ctrl);
3349 if (textButt)
3351 textButt->setFrozen(true);
3356 // ***************************************************************************
3358 void CGroupHTML::handle ()
3360 H_AUTO(RZ_Interface_Html_handle)
3362 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
3364 // handle curl downloads
3365 checkDownloads();
3367 // handle refresh timer
3368 if (_NextRefreshTime > 0 && _NextRefreshTime <= (times.thisFrameMs / 1000.0f) )
3370 // there might be valid uses for 0sec refresh, but two in a row is probably a mistake
3371 if (_NextRefreshTime - _LastRefreshTime >= 1.0)
3373 _LastRefreshTime = _NextRefreshTime;
3374 doBrowse(_RefreshUrl.c_str());
3376 else
3377 nlwarning("Ignore second 0sec http-equiv refresh in a row (url '%s')", _URL.c_str());
3379 _NextRefreshTime = 0;
3382 if (_CurlWWW)
3384 // still transfering html page
3385 if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
3387 browseError(("Connection timeout : "+_URL).c_str());
3390 else
3391 if (_RenderNextTime)
3393 _RenderNextTime = false;
3394 renderHtmlString(_DocumentHtml);
3396 else
3397 if (_WaitingForStylesheet)
3399 renderDocument();
3401 else
3402 if (_BrowseNextTime || _PostNextTime)
3404 // Set timeout
3405 _ConnectingTimeout = ( times.thisFrameMs / 1000.0f ) + _TimeoutValue;
3407 // freeze form buttons
3408 CButtonFreezer freezer;
3409 this->visit(&freezer);
3411 // Home ?
3412 if (_URL == "home")
3413 _URL = home();
3415 string finalUrl;
3416 bool isLocal = lookupLocalFile (finalUrl, _URL.c_str(), true);
3418 _URL = finalUrl;
3420 CUrlParser uri (_URL);
3421 _TrustedDomain = isTrustedDomain(uri.host);
3422 _DocumentDomain = uri.host;
3424 // file is probably from bnp (ingame help)
3425 if (isLocal)
3427 doBrowseLocalFile(finalUrl);
3429 else
3431 SFormFields formfields;
3432 if (_PostNextTime)
3434 buildHTTPPostParams(formfields);
3435 // _URL is set from form.Action
3436 finalUrl = _URL;
3438 else
3440 // Add custom get params from child classes
3441 addHTTPGetParams (finalUrl, _TrustedDomain);
3444 doBrowseRemoteUrl(finalUrl, "", _PostNextTime, formfields);
3447 _BrowseNextTime = false;
3448 _PostNextTime = false;
3452 // ***************************************************************************
3453 void CGroupHTML::buildHTTPPostParams (SFormFields &formfields)
3455 // Add text area text
3456 uint i;
3458 if (_PostFormId >= _Forms.size())
3460 nlwarning("(%s) invalid form index %d, _Forms %d", _Id.c_str(), _PostFormId, _Forms.size());
3461 return;
3463 // Ref the form
3464 CForm &form = _Forms[_PostFormId];
3466 // button can override form action url (and methor, but we only do POST)
3467 _URL = _PostFormAction.empty() ? form.Action : _PostFormAction;
3469 CUrlParser uri(_URL);
3470 _TrustedDomain = isTrustedDomain(uri.host);
3471 _DocumentDomain = uri.host;
3473 for (i=0; i<form.Entries.size(); i++)
3475 // Text area ?
3476 bool addEntry = false;
3477 string entryData;
3478 if (form.Entries[i].TextArea)
3480 // Get the edit box view
3481 CInterfaceGroup *group = form.Entries[i].TextArea->getGroup ("eb");
3482 if (group)
3484 // Should be a CGroupEditBox
3485 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
3486 if (editBox)
3488 entryData = editBox->getViewText()->getText();
3489 addEntry = true;
3493 else if (form.Entries[i].Checkbox)
3495 // todo handle unicode POST here
3496 if (form.Entries[i].Checkbox->getPushed ())
3498 entryData = form.Entries[i].Value;
3499 addEntry = true;
3502 else if (form.Entries[i].ComboBox)
3504 CDBGroupComboBox *cb = form.Entries[i].ComboBox;
3505 entryData = form.Entries[i].SelectValues[cb->getSelection()];
3506 addEntry = true;
3508 else if (form.Entries[i].SelectBox)
3510 CGroupMenu *sb = form.Entries[i].SelectBox;
3511 CGroupSubMenu *rootMenu = sb->getRootMenu();
3512 if (rootMenu)
3514 for(uint j=0; j<rootMenu->getNumLine(); ++j)
3516 CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
3517 if (ig)
3519 CCtrlBaseButton *cb = dynamic_cast<CCtrlBaseButton *>(ig->getCtrl("b"));
3520 if (cb && cb->getPushed())
3521 formfields.add(form.Entries[i].Name, form.Entries[i].SelectValues[j]);
3526 // This is a hidden value
3527 else
3529 entryData = form.Entries[i].Value;
3530 addEntry = true;
3533 // Add this entry
3534 if (addEntry)
3536 formfields.add(form.Entries[i].Name, entryData);
3540 if (_PostFormSubmitType == "image")
3542 // Add the button coordinates
3543 if (_PostFormSubmitButton.find_first_of("[") == string::npos)
3545 formfields.add(_PostFormSubmitButton + "_x", NLMISC::toString(_PostFormSubmitX));
3546 formfields.add(_PostFormSubmitButton + "_y", NLMISC::toString(_PostFormSubmitY));
3548 else
3550 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitX));
3551 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitY));
3554 else
3555 formfields.add(_PostFormSubmitButton, _PostFormSubmitValue);
3557 // Add custom params from child classes
3558 addHTTPPostParams(formfields, _TrustedDomain);
3561 // ***************************************************************************
3562 void CGroupHTML::doBrowseLocalFile(const std::string &uri)
3564 releaseDownloads();
3565 updateRefreshButton();
3567 std::string filename;
3568 if (toLowerAscii(uri).find("file:/") == 0)
3570 filename = uri.substr(6, uri.size() - 6);
3572 else
3574 filename = uri;
3577 LOG_DL("browse local file '%s'", filename.c_str());
3579 _TrustedDomain = true;
3580 _DocumentDomain = "localhost";
3582 CIFile in;
3583 if (in.open(filename))
3585 std::string html;
3586 while(!in.eof())
3588 char buf[1024];
3589 in.getline(buf, 1024);
3590 html += std::string(buf) + "\n";
3592 in.close();
3594 if (!renderHtmlString(html))
3596 browseError((string("Failed to parse html from file : ")+filename).c_str());
3599 else
3601 browseError((string("The page address is malformed : ")+filename).c_str());
3605 // ***************************************************************************
3606 void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
3608 // stop all downloads from previous page
3609 releaseDownloads();
3610 updateRefreshButton();
3612 // Reset the title
3613 if(_TitlePrefix.empty())
3614 setTitle (CI18N::get("uiPleaseWait"));
3615 else
3616 setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
3618 url = upgradeInsecureUrl(url);
3620 LOG_DL("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d",
3621 _Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size());
3623 if (!MultiCurl)
3625 browseError(string("Invalid MultCurl handle, loading url failed : "+url).c_str());
3626 return;
3629 CURL *curl = curl_easy_init();
3630 if (!curl)
3632 nlwarning("(%s) failed to create curl handle", _Id.c_str());
3633 browseError(string("Failed to create cURL handle : " + url).c_str());
3634 return;
3637 // https://
3638 if (toLowerAscii(url.substr(0, 8)) == "https://")
3640 // if supported, use custom SSL context function to load certificates
3641 NLWEB::CCurlCertificates::useCertificates(curl);
3644 // do not follow redirects, we have own handler
3645 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
3646 // after redirect
3647 curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
3649 // tell curl to use compression if possible (gzip, deflate)
3650 // leaving this empty allows all encodings that curl supports
3651 //curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
3653 // limit curl to HTTP and HTTPS protocols only
3654 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3655 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3657 // Destination
3658 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
3660 // User-Agent:
3661 std::string userAgent = options.appName + "/" + options.appVersion;
3662 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
3664 // Cookies
3665 sendCookies(curl, _DocumentDomain, _TrustedDomain);
3667 // Referer
3668 if (!referer.empty())
3670 curl_easy_setopt(curl, CURLOPT_REFERER, referer.c_str());
3671 LOG_DL("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
3674 if (doPost)
3676 // serialize form data and add it to curl
3677 std::string data;
3678 for(uint i=0; i<formfields.Values.size(); ++i)
3680 char * escapedName = curl_easy_escape(curl, formfields.Values[i].name.c_str(), formfields.Values[i].name.size());
3681 char * escapedValue = curl_easy_escape(curl, formfields.Values[i].value.c_str(), formfields.Values[i].value.size());
3683 if (i>0)
3684 data += "&";
3686 data += std::string(escapedName) + "=" + escapedValue;
3688 curl_free(escapedName);
3689 curl_free(escapedValue);
3691 curl_easy_setopt(curl, CURLOPT_POST, 1);
3692 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
3693 curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data.c_str());
3695 else
3697 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
3700 // transfer handle
3701 _CurlWWW = new CCurlWWWData(curl, url);
3703 // set the language code used by the client
3704 std::vector<std::string> headers;
3705 headers.push_back("Accept-Language: "+options.languageCode);
3706 headers.push_back("Accept-Charset: utf-8");
3707 _CurlWWW->sendHeaders(headers);
3709 // catch headers for redirect
3710 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
3711 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
3713 // catch body
3714 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NLGUI::curlDataCallback);
3715 curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
3717 #ifdef LOG_CURL_PROGRESS
3718 // progress callback
3719 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
3720 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, NLGUI::curlProgressCallback);
3721 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, _CurlWWW);
3722 #else
3723 // progress off
3724 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
3725 #endif
3728 curl_multi_add_handle(MultiCurl, curl);
3730 // start the transfer
3731 int NewRunningCurls = 0;
3732 curl_multi_perform(MultiCurl, &NewRunningCurls);
3733 RunningCurls++;
3735 _RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
3738 // ***************************************************************************
3739 void CGroupHTML::htmlDownloadFinished(bool success, const std::string &error)
3741 if (!success)
3743 CUrlParser uri(_CurlWWW->Url);
3745 // potentially unwanted chars
3746 std::string url = _CurlWWW->Url;
3747 url = strFindReplaceAll(url, string("<"), string("%3C"));
3748 url = strFindReplaceAll(url, string(">"), string("%3E"));
3749 url = strFindReplaceAll(url, string("\""), string("%22"));
3750 url = strFindReplaceAll(url, string("'"), string("%27"));
3752 std::string err;
3753 err = "<html><head><title>cURL error</title></head><body>";
3754 err += "<h1>Connection failed with cURL error</h1>";
3755 err += error;
3756 err += "<hr>(" + uri.scheme + "://" + uri.host + ") <a href=\"" + url + "\">reload</a>";
3757 err += "</body></html>";
3758 browseErrorHtml(err);
3759 return;
3762 // received content from remote
3763 std::string content = trim(_CurlWWW->Content);
3765 // save HSTS header from all requests regardless of HTTP code
3766 if (_CurlWWW->hasHSTSHeader())
3768 CUrlParser uri(_CurlWWW->Url);
3769 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
3772 receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
3774 long code;
3775 curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
3776 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());
3778 if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3780 if (_RedirectsRemaining < 0)
3782 browseError(string("Redirect limit reached : " + _URL).c_str());
3783 return;
3786 // redirect, get the location and try browse again
3787 // we cant use curl redirection because 'addHTTPGetParams()' must be called on new destination
3788 std::string location(_CurlWWW->getLocationHeader());
3789 if (location.empty())
3791 browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
3792 return;
3795 LOG_DL("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
3796 location = getAbsoluteUrl(location);
3798 _PostNextTime = false;
3799 _RedirectsRemaining--;
3801 doBrowse(location.c_str());
3803 else if ( (code < 200 || code >= 300) )
3805 // catches 304 not modified, but html is not in cache anyway
3806 // if server did not send any error back
3807 if (content.empty())
3809 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>");
3813 char *ch;
3814 std::string contentType;
3815 CURLcode res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
3816 if (res == CURLE_OK && ch != NULL)
3818 contentType = ch;
3821 htmlDownloadFinished(content, contentType, code);
3823 // clear curl handler
3824 if (MultiCurl)
3826 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
3829 delete _CurlWWW;
3830 _CurlWWW = NULL;
3832 // refresh button uses _CurlWWW. refresh button may stay disabled if
3833 // there is no css files to download and page is rendered before _CurlWWW is freed
3834 updateRefreshButton();
3837 void CGroupHTML::dataDownloadFinished(bool success, const std::string &error, CDataDownload *data)
3839 fclose(data->fp);
3841 CUrlParser uri(data->url);
3842 if (!uri.host.empty())
3844 receiveCookies(data->data->Request, uri.host, isTrustedDomain(uri.host));
3847 long code = -1;
3848 curl_easy_getinfo(data->data->Request, CURLINFO_RESPONSE_CODE, &code);
3850 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());
3851 curl_multi_remove_handle(MultiCurl, data->data->Request);
3853 // save HSTS header from all requests regardless of HTTP code
3854 if (success)
3856 if (data->data->hasHSTSHeader())
3858 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, data->data->getHSTSHeader());
3861 // 2XX success, 304 Not Modified
3862 if ((code >= 200 && code <= 204) || code == 304)
3864 CHttpCacheObject obj;
3865 obj.Expires = data->data->getExpires();
3866 obj.Etag = data->data->getEtag();
3867 obj.LastModified = data->data->getLastModified();
3869 CHttpCache::getInstance()->store(data->dest, obj);
3870 if (code == 304 && CFile::fileExists(data->tmpdest))
3872 CFile::deleteFile(data->tmpdest);
3875 else if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3877 if (data->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT)
3879 std::string location(data->data->getLocationHeader());
3880 if (!location.empty())
3882 CUrlParser uri(location);
3883 if (!uri.isAbsolute())
3885 uri.inherit(data->url);
3886 location = uri.toString();
3889 // clear old request state, and curl easy handle
3890 delete data->data;
3891 data->data = NULL;
3892 data->fp = NULL;
3893 data->url = location;
3894 data->redirects++;
3896 // push same request in the front of the queue
3897 // cache filename is based of original url
3898 Curls.push_front(data);
3900 LOG_DL("Redirect '%s'", location.c_str());
3901 // no finished callback called, so cleanup old temp
3902 if (CFile::fileExists(data->tmpdest))
3904 CFile::deleteFile(data->tmpdest);
3906 return;
3909 nlwarning("Redirected to empty url '%s'", data->url.c_str());
3911 else
3913 nlwarning("Redirect limit reached for '%s'", data->url.c_str());
3916 else
3918 nlwarning("HTTP request failed with code [%d] for '%s'\n",code, data->url.c_str());
3919 // 404, 500, etc
3920 if (CFile::fileExists(data->dest))
3922 CFile::deleteFile(data->dest);
3926 else
3928 nlwarning("DATA download failed '%s', error '%s'", data->url.c_str(), error.c_str());
3931 finishCurlDownload(data);
3934 void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
3936 LOG_DL("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
3938 // create <html> markup for image downloads
3939 if (type.find("image/") == 0 && !content.empty())
3943 std::string dest = localImageName(_URL);
3944 COFile out;
3945 out.open(dest);
3946 out.serialBuffer((uint8 *)(content.c_str()), content.size());
3947 out.close();
3948 LOG_DL("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
3950 catch(...) { }
3952 // create html code with image url inside and do the request again
3953 renderHtmlString("<html><head><title>"+_URL+"</title></head><body><img src=\"" + _URL + "\"></body></html>");
3955 else if (_TrustedDomain && type.find("text/lua") == 0)
3957 setTitle(_TitleString);
3959 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+content;
3960 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
3961 _LuaScript.clear();
3963 // disable refresh button
3964 clearRefresh();
3965 // disable redo into this url
3966 _AskedUrl.clear();
3968 else
3970 // Sanitize downloaded HTML UTF-8 encoding, and render
3971 renderHtmlString(CUtfStringView(content).toUtf8(true));
3975 // ***************************************************************************
3976 void CGroupHTML::cssDownloadFinished(const std::string &url, const std::string &local)
3978 for(std::vector<CHtmlParser::StyleLink>::iterator it = _StylesheetQueue.begin();
3979 it != _StylesheetQueue.end(); ++it)
3981 if (it->Url == url)
3983 // read downloaded file into HtmlStyles
3984 if (CFile::fileExists(local) && it->Index < _HtmlStyles.size())
3986 CIFile in;
3987 if (in.open(local))
3989 if (!in.readAll(_HtmlStyles[it->Index]))
3991 nlwarning("Failed to read downloaded css file(%s), url(%s)", local.c_str(), url.c_str());
3996 _StylesheetQueue.erase(it);
3997 break;
4002 void CGroupHTML::renderDocument()
4004 if (!Curls.empty() && !_StylesheetQueue.empty())
4006 // waiting for stylesheets to finish downloading
4007 return;
4009 _WaitingForStylesheet = false;
4011 //TGameTime renderStart = CTime::getLocalTime();
4013 // clear previous state and page
4014 beginBuild();
4015 removeContent();
4017 // process all <style> and <link rel=stylesheet> elements
4018 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4020 if (!_HtmlStyles[i].empty())
4022 _Style.parseStylesheet(_HtmlStyles[i]);
4025 _HtmlStyles.clear();
4027 std::list<CHtmlElement>::iterator it = _HtmlDOM.Children.begin();
4028 while(it != _HtmlDOM.Children.end())
4030 renderDOM(*it);
4031 ++it;
4034 endBuild();
4036 //TGameTime renderStop = CTime::getLocalTime();
4037 //nlwarning("[%s] render: %.1fms (%s)\n", _Id.c_str(), (renderStop - renderStart), _URL.c_str());
4040 // ***************************************************************************
4042 bool CGroupHTML::renderHtmlString(const std::string &html)
4044 bool success;
4046 // if we are already rendering, then queue up the next page
4047 if (_Browsing)
4049 _DocumentHtml = html;
4050 _RenderNextTime = true;
4052 return true;
4056 _DocumentUrl = _URL;
4057 _DocumentHtml = html;
4058 _NextRefreshTime = 0;
4059 _RefreshUrl.clear();
4061 if (trim(html).empty())
4063 // clear the page
4064 beginBuild();
4066 // clear previous page and state
4067 removeContent();
4069 endBuild();
4071 success = false;
4073 else
4075 // browser.css
4076 resetCssStyle();
4078 // start new rendering
4079 _HtmlDOM = CHtmlElement(CHtmlElement::NONE, "<root>");
4080 _CurrentHTMLElement = NULL;
4081 success = parseHtml(html);
4082 if (success)
4084 _WaitingForStylesheet = !_StylesheetQueue.empty();
4085 renderDocument();
4087 else
4089 std::string error = "ERROR: HTML parse failed.";
4090 error += toString("\nsize %d bytes", html.size());
4091 error += toString("\n---start---\n%s\n---end---\n", html.c_str());
4092 browseError(error.c_str());
4096 return success;
4099 // ***************************************************************************
4100 void CGroupHTML::doBrowseAnchor(const std::string &anchor)
4102 if (_Anchors.count(anchor) == 0)
4104 return;
4107 CInterfaceElement *pIE = _Anchors.find(anchor)->second;
4108 if (pIE)
4110 // hotspot depends on vertical/horizontal scrollbar
4111 CCtrlScroll *pSB = getScrollBar();
4112 if (pSB)
4114 pSB->ensureVisible(pIE, Hotspot_Tx, Hotspot_Tx);
4119 // ***************************************************************************
4121 void CGroupHTML::draw ()
4123 uint8 CurrentAlpha = 255;
4124 // search a parent container
4125 CInterfaceGroup *gr = getParent();
4126 while (gr)
4128 if (gr->isGroupContainer())
4130 CGroupContainer *gc = static_cast<CGroupContainer *>(gr);
4131 CurrentAlpha = gc->getCurrentContainerAlpha();
4132 break;
4134 gr = gr->getParent();
4136 m_HtmlBackground.CurrentAlpha = CurrentAlpha;
4137 m_BodyBackground.CurrentAlpha = CurrentAlpha;
4139 m_HtmlBackground.draw();
4140 m_BodyBackground.draw();
4141 CGroupScrollText::draw ();
4144 // ***************************************************************************
4146 void CGroupHTML::beginBuild ()
4148 _Browsing = true;
4151 void CGroupHTML::endBuild ()
4153 // set the browser as complete
4154 _Browsing = false;
4155 updateRefreshButton();
4157 // check that the title is set, or reset it (in the case the page
4158 // does not provide a title)
4159 if (_TitleString.empty())
4161 setTitle(_TitlePrefix);
4164 invalidateCoords();
4167 // ***************************************************************************
4169 void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
4173 // ***************************************************************************
4175 void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
4179 // ***************************************************************************
4181 string CGroupHTML::home () const
4183 return Home;
4186 // ***************************************************************************
4188 void CGroupHTML::removeContent ()
4190 // Remove old document
4191 if (!_GroupListAdaptor)
4193 _GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
4194 _GroupListAdaptor->setId(getList()->getId() + ":GLA");
4195 _GroupListAdaptor->setResizeFromChildH(true);
4196 getList()->addChild (_GroupListAdaptor, true);
4199 // Group list adaptor not exist ?
4200 _GroupListAdaptor->clearGroups();
4201 _GroupListAdaptor->clearControls();
4202 _GroupListAdaptor->clearViews();
4203 CWidgetManager::getInstance()->clearViewUnders();
4204 CWidgetManager::getInstance()->clearCtrlsUnders();
4206 // Clear all the context
4207 clearContext();
4209 // Reset default background
4210 m_HtmlBackground.clear();
4211 m_BodyBackground.clear();
4213 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
4214 CViewBase *view = getView (DefaultBackgroundBitmapView);
4215 if (view)
4216 view->setActive(false);
4218 paragraphChange ();
4221 // ***************************************************************************
4222 const std::string &CGroupHTML::selectTreeNodeRecurs(CGroupTree::SNode *node, const std::string &url)
4224 static std::string emptyString;
4225 if(!node)
4227 return emptyString;
4230 // if this node match
4231 if(actionLaunchUrlRecurs(node->AHName, node->AHParams, url))
4233 return node->Id;
4235 // fails => look into children
4236 else
4238 for(uint i=0;i<node->Children.size();i++)
4240 const string &childRes= selectTreeNodeRecurs(node->Children[i], url);
4241 if(!childRes.empty())
4242 return childRes;
4245 // none match...
4246 return emptyString;
4250 // ***************************************************************************
4251 bool CGroupHTML::actionLaunchUrlRecurs(const std::string &ah, const std::string &params, const std::string &url)
4253 // check if this action match
4254 if( (ah=="launch_help" || ah=="browse") && IActionHandler::getParam (params, "url") == url)
4256 return true;
4258 // can be a proc that contains launch_help/browse => look recurs
4259 else if(ah=="proc")
4261 const std::string &procName= params;
4262 // look into this proc
4263 uint numActions= CWidgetManager::getInstance()->getParser()->getProcedureNumActions(procName);
4264 for(uint i=0;i<numActions;i++)
4266 string procAh, procParams;
4267 if( CWidgetManager::getInstance()->getParser()->getProcedureAction(procName, i, procAh, procParams))
4269 // recurs proc if needed!
4270 if (actionLaunchUrlRecurs(procAh, procParams, url))
4271 return true;
4276 return false;
4279 // ***************************************************************************
4280 void CGroupHTML::clearRefresh()
4282 _URL.clear();
4283 updateRefreshButton();
4286 // ***************************************************************************
4287 void CGroupHTML::clearUndoRedo()
4289 // erase any undo/redo
4290 _BrowseUndo.clear();
4291 _BrowseRedo.clear();
4293 // update buttons validation
4294 updateUndoRedoButtons();
4297 // ***************************************************************************
4298 void CGroupHTML::pushUrlUndoRedo(const std::string &url)
4300 // if same url, no op
4301 if(url==_AskedUrl)
4302 return;
4304 // erase any redo, push undo, set current
4305 _BrowseRedo.clear();
4306 if(!_AskedUrl.empty())
4307 _BrowseUndo.push_back(_AskedUrl);
4308 _AskedUrl= url;
4310 // limit undo
4311 while(_BrowseUndo.size()>MaxUrlUndoRedo)
4312 _BrowseUndo.pop_front();
4314 // update buttons validation
4315 updateUndoRedoButtons();
4318 // ***************************************************************************
4319 void CGroupHTML::browseUndo()
4321 if(_BrowseUndo.empty())
4322 return;
4324 // push to redo, pop undo, and set current
4325 if (!_AskedUrl.empty())
4326 _BrowseRedo.push_front(_AskedUrl);
4328 _AskedUrl= _BrowseUndo.back();
4329 _BrowseUndo.pop_back();
4331 // update buttons validation
4332 updateUndoRedoButtons();
4334 // and then browse the undoed url, with no undo/redo
4335 doBrowse(_AskedUrl.c_str());
4338 // ***************************************************************************
4339 void CGroupHTML::browseRedo()
4341 if(_BrowseRedo.empty())
4342 return;
4344 // push to undo, pop redo, and set current
4345 _BrowseUndo.push_back(_AskedUrl);
4346 _AskedUrl= _BrowseRedo.front();
4347 _BrowseRedo.pop_front();
4349 // update buttons validation
4350 updateUndoRedoButtons();
4352 // and then browse the redoed url, with no undo/redo
4353 doBrowse(_AskedUrl.c_str());
4356 // ***************************************************************************
4357 void CGroupHTML::updateUndoRedoButtons()
4359 CCtrlBaseButton *butUndo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
4360 CCtrlBaseButton *butRedo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRedoButton));
4362 // gray according to list size
4363 if(butUndo)
4364 butUndo->setFrozen(_BrowseUndo.empty());
4365 if(butRedo)
4366 butRedo->setFrozen(_BrowseRedo.empty());
4369 // ***************************************************************************
4370 void CGroupHTML::updateRefreshButton()
4372 CCtrlBaseButton *butRefresh = dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
4373 if(butRefresh)
4375 // connecting, rendering, or is missing url
4376 bool frozen = _CurlWWW || _Browsing || _URL.empty();
4377 butRefresh->setFrozen(frozen);
4381 // ***************************************************************************
4383 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
4385 CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam &param)
4386 : CInterfaceGroup(param),
4387 Offset(0)
4391 xmlNodePtr CGroupHTMLInputOffset::serialize( xmlNodePtr parentNode, const char *type ) const
4393 xmlNodePtr node = CInterfaceGroup::serialize( parentNode, type );
4394 if( node == NULL )
4395 return NULL;
4397 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html_input_offset" );
4398 xmlSetProp( node, BAD_CAST "y_offset", BAD_CAST toString( Offset ).c_str() );
4400 return node;
4403 // ***************************************************************************
4404 bool CGroupHTMLInputOffset::parse(xmlNodePtr cur, CInterfaceGroup *parentGroup)
4406 if (!CInterfaceGroup::parse(cur, parentGroup)) return false;
4407 CXMLAutoPtr ptr;
4408 // Get the url
4409 ptr = xmlGetProp (cur, (xmlChar*)"y_offset");
4410 if (ptr)
4411 fromString((const char*)ptr, Offset);
4412 return true;
4415 // ***************************************************************************
4416 int CGroupHTML::luaParseHtml(CLuaState &ls)
4418 const char *funcName = "parseHtml";
4419 CLuaIHM::checkArgCount(ls, funcName, 1);
4420 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4421 std::string html = ls.toString(1);
4423 parseHtml(html);
4425 return 0;
4428 int CGroupHTML::luaClearRefresh(CLuaState &ls)
4430 const char *funcName = "clearRefresh";
4431 CLuaIHM::checkArgCount(ls, funcName, 0);
4433 clearRefresh();
4435 return 0;
4438 int CGroupHTML::luaClearUndoRedo(CLuaState &ls)
4440 const char *funcName = "clearUndoRedo";
4441 CLuaIHM::checkArgCount(ls, funcName, 0);
4443 clearUndoRedo();
4444 return 0;
4447 // ***************************************************************************
4448 int CGroupHTML::luaBrowse(CLuaState &ls)
4450 const char *funcName = "browse";
4451 CLuaIHM::checkArgCount(ls, funcName, 1);
4452 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4453 browse(ls.toString(1));
4454 return 0;
4457 // ***************************************************************************
4458 int CGroupHTML::luaRefresh(CLuaState &ls)
4460 const char *funcName = "refresh";
4461 CLuaIHM::checkArgCount(ls, funcName, 0);
4462 refresh();
4463 return 0;
4466 // ***************************************************************************
4467 int CGroupHTML::luaRemoveContent(CLuaState &ls)
4469 const char *funcName = "removeContent";
4470 CLuaIHM::checkArgCount(ls, funcName, 0);
4471 removeContent();
4472 return 0;
4475 // ***************************************************************************
4476 int CGroupHTML::luaRenderHtml(CLuaState &ls)
4478 const char *funcName = "renderHtml";
4479 CLuaIHM::checkArgCount(ls, funcName, 1);
4480 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4481 std::string html = ls.toString(1);
4483 // Always trust domain if rendered from lua
4484 _TrustedDomain = true;
4485 renderHtmlString(html);
4487 return 0;
4490 // ***************************************************************************
4491 int CGroupHTML::luaSetBackground(CLuaState &ls)
4493 const char *funcName = "setBackground";
4494 CLuaIHM::checkArgCount(ls, funcName, 3);
4495 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4496 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4497 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4498 std::string image = ls.toString(1);
4499 bool scale = ls.toBoolean(2);
4500 bool repeat = ls.toBoolean(3);
4502 setBackground(image, scale, repeat);
4504 return 0;
4507 // ***************************************************************************
4508 int CGroupHTML::luaInsertText(CLuaState &ls)
4510 const char *funcName = "insertText";
4511 CLuaIHM::checkArgCount(ls, funcName, 3);
4512 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4513 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TSTRING);
4514 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4516 string name = ls.toString(1);
4517 string text = ls.toString(2);
4519 if (!_Forms.empty())
4521 for (uint i=0; i<_Forms.back().Entries.size(); i++)
4523 if (_Forms.back().Entries[i].TextArea && _Forms.back().Entries[i].Name == name)
4525 // Get the edit box view
4526 CInterfaceGroup *group = _Forms.back().Entries[i].TextArea->getGroup ("eb");
4527 if (group)
4529 // Should be a CGroupEditBox
4530 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
4531 if (editBox)
4532 editBox->writeString(text, false, ls.toBoolean(3));
4538 return 0;
4541 // ***************************************************************************
4542 int CGroupHTML::luaAddString(CLuaState &ls)
4544 const char *funcName = "addString";
4545 CLuaIHM::checkArgCount(ls, funcName, 1);
4546 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4547 addString(ls.toString(1));
4548 return 0;
4551 // ***************************************************************************
4552 int CGroupHTML::luaAddImage(CLuaState &ls)
4554 const char *funcName = "addImage";
4555 CLuaIHM::checkArgCount(ls, funcName, 2);
4556 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4557 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4558 if (!_Paragraph)
4560 newParagraph(0);
4561 paragraphChange();
4564 CStyleParams style;
4565 style.GlobalColor = ls.toBoolean(2);
4567 string url = getLink();
4568 if (!url.empty())
4570 string params = "name=" + getId() + "|url=" + getLink ();
4571 addButton(CCtrlButton::PushButton, "", ls.toString(1), ls.toString(1),
4572 "", "browse", params.c_str(), "", style);
4574 else
4576 addImage("", ls.toString(1), false, style);
4580 return 0;
4583 // ***************************************************************************
4584 int CGroupHTML::luaShowDiv(CLuaState &ls)
4586 const char *funcName = "showDiv";
4587 CLuaIHM::checkArgCount(ls, funcName, 2);
4588 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4589 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4591 if (!_Groups.empty())
4593 for (uint i=0; i<_Groups.size(); i++)
4595 CInterfaceGroup *group = _Groups[i];
4596 if (group->getName() == ls.toString(1))
4598 group->setActive(ls.toBoolean(2));
4602 return 0;
4605 // ***************************************************************************
4606 void CGroupHTML::setURL(const std::string &url)
4608 browse(url.c_str());
4611 void CGroupHTML::setHTML(const std::string &html)
4613 renderHtmlString(html);
4616 void CGroupHTML::setHome(const std::string &home)
4618 Home = home;
4621 // ***************************************************************************
4622 void CGroupHTML::parseStylesheetFile(const std::string &fname)
4624 CIFile css;
4625 if (css.open(fname))
4627 uint32 remaining = css.getFileSize();
4628 std::string content;
4629 try {
4630 while(!css.eof() && remaining > 0)
4632 const uint BUF_SIZE = 4096;
4633 char buf[BUF_SIZE];
4635 uint32 readJustNow = std::min(remaining, BUF_SIZE);
4636 css.serialBuffer((uint8 *)&buf, readJustNow);
4637 content.append(buf, readJustNow);
4638 remaining -= readJustNow;
4641 _Style.parseStylesheet(content);
4643 catch(const Exception &e)
4645 nlwarning("exception while reading css file '%s'", e.what());
4648 else
4650 nlwarning("Stylesheet file '%s' not found (%s)", fname.c_str(), _URL.c_str());
4654 // ***************************************************************************
4655 bool CGroupHTML::parseHtml(const std::string &htmlString)
4657 CHtmlElement *parsedDOM;
4658 if (_CurrentHTMLElement == NULL)
4660 // parse under <root> element (clean dom)
4661 parsedDOM = &_HtmlDOM;
4663 else
4665 // parse under currently rendered <lua> element
4666 parsedDOM = _CurrentHTMLElement;
4669 std::vector<CHtmlParser::StyleLink> links;
4671 CHtmlParser parser;
4672 parser.getDOM(htmlString, *parsedDOM, _HtmlStyles, links);
4674 // <link> elements inserted from lua::parseHtml are ignored
4675 if (_CurrentHTMLElement == NULL && !links.empty())
4677 addStylesheetDownload(links);
4679 else if (_CurrentHTMLElement != NULL)
4681 // Called from active element (lua)
4682 // <style> order is not preserved as document is already being rendered
4683 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4685 if (!_HtmlStyles[i].empty())
4687 _Style.parseStylesheet(_HtmlStyles[i]);
4690 _HtmlStyles.clear();
4693 // this should rarely fail as first element should be <html>
4694 bool success = parsedDOM->Children.size() > 0;
4696 std::list<CHtmlElement>::iterator it = parsedDOM->Children.begin();
4697 while(it != parsedDOM->Children.end())
4699 if (it->Type == CHtmlElement::ELEMENT_NODE && it->Value == "html")
4701 // move newly parsed childs from <body> into siblings
4702 if (_CurrentHTMLElement) {
4703 std::list<CHtmlElement>::iterator it2 = it->Children.begin();
4704 while(it2 != it->Children.end())
4706 if (it2->Type == CHtmlElement::ELEMENT_NODE && it2->Value == "body")
4708 spliceFragment(it2);
4709 break;
4711 ++it2;
4713 // remove <html> fragment from current element child
4714 it = parsedDOM->Children.erase(it);
4716 else
4718 // remove link to <root> (html->parent == '<root>') or css selector matching will break
4719 it->parent = NULL;
4720 ++it;
4722 continue;
4725 // skip over other non-handled element
4726 ++it;
4729 return success;
4732 void CGroupHTML::spliceFragment(std::list<CHtmlElement>::iterator src)
4734 if(!_CurrentHTMLElement->parent)
4736 nlwarning("BUG: Current node is missing parent element. unable to splice fragment");
4737 return;
4740 // get the iterators for current element (<lua>) and next sibling
4741 std::list<CHtmlElement>::iterator currentElement;
4742 currentElement = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLElement);
4743 if (currentElement == _CurrentHTMLElement->parent->Children.end())
4745 nlwarning("BUG: unable to find current element iterator from parent");
4746 return;
4749 // where fragment should be moved
4750 std::list<CHtmlElement>::iterator insertBefore;
4751 if (_CurrentHTMLNextSibling == NULL)
4753 insertBefore = _CurrentHTMLElement->parent->Children.end();
4754 } else {
4755 // get iterator for nextSibling
4756 insertBefore = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLNextSibling);
4759 _CurrentHTMLElement->parent->Children.splice(insertBefore, src->Children);
4761 // reindex moved elements
4762 CHtmlElement *prev = NULL;
4763 uint childIndex = _CurrentHTMLElement->childIndex;
4764 while(currentElement != _CurrentHTMLElement->parent->Children.end())
4766 if (currentElement->Type == CHtmlElement::ELEMENT_NODE)
4768 if (prev != NULL)
4770 currentElement->parent = _CurrentHTMLElement->parent;
4771 currentElement->childIndex = childIndex;
4772 currentElement->previousSibling = prev;
4773 prev->nextSibling = &(*currentElement);
4776 childIndex++;
4777 prev = &(*currentElement);
4779 ++currentElement;
4783 // ***************************************************************************
4784 inline bool isDigit(char c, uint base = 16)
4786 if (c>='0' && c<='9') return true;
4787 if (base != 16) return false;
4788 if (c>='A' && c<='F') return true;
4789 if (c>='a' && c<='f') return true;
4790 return false;
4793 // ***************************************************************************
4794 inline char convertHexDigit(char c)
4796 if (c>='0' && c<='9') return c-'0';
4797 if (c>='A' && c<='F') return c-'A'+10;
4798 if (c>='a' && c<='f') return c-'a'+10;
4799 return 0;
4802 // ***************************************************************************
4803 std::string CGroupHTML::decodeHTMLEntities(const std::string &str)
4805 std::string result;
4806 result.reserve(str.size() + (str.size() >> 2));
4807 uint last, pos;
4809 for (uint i=0; i<str.length(); ++i)
4811 // HTML entity
4812 if (str[i] == '&' && (str.length()-i) >= 4)
4814 pos = i+1;
4816 // unicode character
4817 if (str[pos] == '#')
4819 ++pos;
4821 // using decimal by default
4822 uint base = 10;
4824 // using hexadecimal if &#x
4825 if (str[pos] == 'x')
4827 base = 16;
4828 ++pos;
4831 // setup "last" to point at the first character following "&#x?[0-9a-f]+"
4832 for (last = pos; last < str.length(); ++last) if (!isDigit(str[last], base)) break;
4834 // make sure that at least 1 digit was found
4835 // and have the terminating ';' to complete the token: "&#x?[0-9a-f]+;"
4836 if (last == pos || str[last] != ';')
4838 result += str[i];
4839 continue;
4842 u32char c = 0;
4844 // convert digits to unicode character
4845 while (pos < last) c = convertHexDigit(str[pos++]) + (c * u32char(base));
4847 // append our new character to the result string
4848 CUtfStringView::append(result, c);
4850 // move 'i' forward to point at the ';' .. the for(...) will increment i to point to next char
4851 i = last;
4853 continue;
4856 // special xml characters
4857 if (str.substr(i+1,5)=="quot;") { i+=5; result+='\"'; continue; }
4858 if (str.substr(i+1,4)=="amp;") { i+=4; result+='&'; continue; }
4859 if (str.substr(i+1,3)=="lt;") { i+=3; result+='<'; continue; }
4860 if (str.substr(i+1,3)=="gt;") { i+=3; result+='>'; continue; }
4863 // all the special cases are catered for... treat this as a normal character
4864 result += str[i];
4867 return result;
4870 // ***************************************************************************
4871 std::string CGroupHTML::getAbsoluteUrl(const std::string &url)
4873 CUrlParser uri(url);
4874 if (uri.isAbsolute())
4875 return url;
4877 uri.inherit(_URL);
4879 return uri.toString();
4882 // ***************************************************************************
4883 void CGroupHTML::resetCssStyle()
4885 _WaitingForStylesheet = false;
4886 _StylesheetQueue.clear();
4887 _Style.reset();
4888 _Style = _BrowserStyle;
4891 // ***************************************************************************
4892 std::string CGroupHTML::HTMLOListElement::getListMarkerText() const
4894 std::string ret;
4895 sint32 number = Value;
4897 if (Type == "disc")
4899 // (ucchar)0x2219;
4900 ret = "\xe2\x88\x99 ";
4902 else if (Type == "circle")
4904 // (uchar)0x26AA;
4905 ret = "\xe2\x9a\xaa ";
4907 else if (Type == "square")
4909 // (ucchar)0x25AA;
4910 ret = "\xe2\x96\xaa ";
4912 else if (Type == "a" || Type == "A")
4914 // @see toAlphabeticOrNumeric in WebKit
4915 static const char lower[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
4916 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
4917 static const char upper[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
4918 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
4919 uint size = 26;
4920 if (number < 1)
4922 ret = toString(number);
4924 else
4926 const char* digits = (Type == "A" ? upper : lower);
4927 while(number > 0)
4929 --number;
4930 ret.insert(ret.begin(), digits[number % size]);
4931 number /= size;
4934 ret += ". ";
4936 else if (Type == "i" || Type == "I")
4938 // @see toRoman in WebKit
4939 static const char lower[7] = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
4940 static const char upper[7] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'};
4942 if (number < 1 || number > 3999)
4944 ret = toString(number);
4946 else
4948 const char* digits = (Type == "I" ? upper : lower);
4949 uint8 i, d=0;
4952 uint32 num = number % 10;
4953 if (num % 5 < 4)
4955 for (i = num % 5; i > 0; i--)
4957 ret.insert(ret.begin(), digits[d]);
4960 if (num >= 4 && num <= 8)
4962 ret.insert(ret.begin(), digits[d + 1]);
4964 if (num == 9)
4966 ret.insert(ret.begin(), digits[d + 2]);
4968 if (num % 5 == 4)
4970 ret.insert(ret.begin(), digits[d]);
4972 number /= 10;
4973 d += 2;
4975 while (number > 0);
4977 if (Type == "I")
4979 ret = toUpper(ret);
4982 ret += ". ";
4984 else
4986 ret = toString(Value) + ". ";
4989 return ret;
4992 void CGroupHTML::HTMLMeterElement::readValues(const CHtmlElement &elm)
4994 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
4995 value = 0.f;
4996 if (!elm.hasAttribute("min") || !fromString(elm.getAttribute("min"), min))
4997 min = 0.f;
4998 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
4999 max = 1.f;
5001 // ensure min < max
5002 if (max < min)
5003 std::swap(min, max);
5005 if (!elm.hasAttribute("low") || !fromString(elm.getAttribute("low"), low))
5006 low = min;
5007 if (!elm.hasAttribute("high") || !fromString(elm.getAttribute("high"), high))
5008 high = max;
5010 if (!elm.hasAttribute("optimum") || !fromString(elm.getAttribute("optimum"), optimum))
5011 optimum = (max - min) / 2.f;
5013 // ensure low < high
5014 if (high < low)
5015 std::swap(low, high);
5016 if (low < min)
5017 low = min;
5018 if (high > max)
5019 high = max;
5022 float CGroupHTML::HTMLMeterElement::getValueRatio() const
5024 if (max <= min)
5025 return 0.f;
5027 return (value - min) / (max - min);
5030 CGroupHTML::HTMLMeterElement::EValueRegion CGroupHTML::HTMLMeterElement::getValueRegion() const
5032 if (optimum <= low)
5034 // low region is optimum
5035 if (value <= low)
5036 return VALUE_OPTIMUM;
5037 else if (value <= high)
5038 return VALUE_SUB_OPTIMAL;
5040 return VALUE_EVEN_LESS_GOOD;
5042 else if (optimum >= high)
5044 // high region is optimum
5045 if (value >= high)
5046 return VALUE_OPTIMUM;
5047 else if (value >= low)
5048 return VALUE_SUB_OPTIMAL;
5050 return VALUE_EVEN_LESS_GOOD;
5053 // middle region is optimum
5054 if (value >= low && value <= high)
5055 return VALUE_OPTIMUM;
5057 return VALUE_SUB_OPTIMAL;
5060 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5062 // color meter (inactive) bar segment
5063 // firefox:: meter { background:none; background-color: #555; },
5064 // webkit:: meter::-webkit-meter-bar { background:none; background-color: #555; }
5065 // webkit makes background color visible when padding is added
5066 CRGBA color(150, 150, 150, 255);
5068 // use webkit pseudo elements as thats easier than firefox pseudo classes
5069 // background-color is expected to be set from browser.css
5070 style.pushStyle();
5071 style.applyStyle(elm.getPseudo(":-webkit-meter-bar"));
5072 if(style.hasStyle("background-color"))
5073 color = style.Current.Background.color;
5074 style.popStyle();
5076 return color;
5079 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5081 // background-color is expected to be set from browser.css
5082 CRGBA color;
5083 style.pushStyle();
5084 switch(getValueRegion())
5086 case VALUE_OPTIMUM:
5088 style.applyStyle(elm.getPseudo(":-webkit-meter-optimum-value"));
5089 if (style.hasStyle("background-color"))
5090 color = style.Current.Background.color;
5091 break;
5093 case VALUE_SUB_OPTIMAL:
5095 style.applyStyle(elm.getPseudo(":-webkit-meter-suboptimum-value"));
5096 if (style.hasStyle("background-color"))
5097 color = style.Current.Background.color;
5098 break;
5100 case VALUE_EVEN_LESS_GOOD: // fall through
5101 default:
5103 style.applyStyle(elm.getPseudo(":-webkit-meter-even-less-good-value"));
5104 if (style.hasStyle("background-color"))
5105 color = style.Current.Background.color;
5106 break;
5108 }//switch
5109 style.popStyle();
5111 return color;
5114 // ****************************************************************************
5115 void CGroupHTML::HTMLProgressElement::readValues(const CHtmlElement &elm)
5117 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5118 value = 0.f;
5119 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5120 max = 1.f;
5122 if (value > max)
5123 value = max;
5126 // ****************************************************************************
5127 float CGroupHTML::HTMLProgressElement::getValueRatio() const
5129 if (max > 0.f)
5130 return value / max;
5131 return 0.f;
5134 // ****************************************************************************
5135 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5137 CRGBA color;
5139 style.pushStyle();
5140 style.applyStyle(elm.getPseudo(":-webkit-progress-bar"));
5141 if (style.hasStyle("background-color"))
5142 color = style.Current.Background.color;
5143 style.popStyle();
5145 return color;
5148 // ****************************************************************************
5149 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5151 CRGBA color;
5153 style.pushStyle();
5154 style.applyStyle(elm.getPseudo(":-webkit-progress-value"));
5155 if (style.hasStyle("background-color"))
5156 color = style.Current.Background.color;
5157 style.popStyle();
5159 return color;
5162 // ****************************************************************************
5163 void CGroupHTML::getCellsParameters(const CHtmlElement &elm, bool inherit)
5165 CGroupHTML::CCellParams cellParams;
5166 if (!_CellParams.empty() && inherit)
5167 cellParams = _CellParams.back();
5169 if (!_Style.hasStyle("background-color") && elm.hasNonEmptyAttribute("bgcolor"))
5171 CRGBA c;
5172 if (scanHTMLColor(elm.getAttribute("bgcolor").c_str(), c))
5173 _Style.Current.Background.color = c;
5175 cellParams.BgColor = _Style.Current.Background.color;
5177 if (elm.hasAttribute("nowrap") || _Style.Current.WhiteSpace == "nowrap")
5178 cellParams.NoWrap = true;
5180 if (elm.hasNonEmptyAttribute("l_margin"))
5181 fromString(elm.getAttribute("l_margin"), cellParams.LeftMargin);
5183 if (_Style.hasStyle("height"))
5184 cellParams.Height = _Style.Current.Height;
5185 else if (elm.hasNonEmptyAttribute("height"))
5186 fromString(elm.getAttribute("height"), cellParams.Height);
5189 std::string align;
5190 // having text-align on table/tr should not override td align attribute
5191 if (_Style.hasStyle("text-align"))
5192 align = _Style.Current.TextAlign;
5193 else if (elm.hasNonEmptyAttribute("align"))
5194 align = toLowerAscii(elm.getAttribute("align"));
5196 if (align == "left")
5197 cellParams.Align = CGroupCell::Left;
5198 else if (align == "center")
5199 cellParams.Align = CGroupCell::Center;
5200 else if (align == "right")
5201 cellParams.Align = CGroupCell::Right;
5202 else if (align != "justify")
5203 align.clear();
5205 // copy td align (can be empty) attribute back into css
5206 _Style.Current.TextAlign = align;
5210 std::string valign;
5211 if (_Style.hasStyle("vertical-align"))
5212 valign = _Style.Current.VerticalAlign;
5213 else if (elm.hasNonEmptyAttribute("valign"))
5214 valign = toLowerAscii(elm.getAttribute("valign"));
5216 if (valign == "top")
5217 cellParams.VAlign = CGroupCell::Top;
5218 else if (valign == "middle")
5219 cellParams.VAlign = CGroupCell::Middle;
5220 else if (valign == "bottom")
5221 cellParams.VAlign = CGroupCell::Bottom;
5224 _CellParams.push_back (cellParams);
5227 // ***************************************************************************
5228 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)
5230 _FormSubmit.push_back(SFormSubmitButton(formId, name, "", "image", action));
5231 // Action handler parameters
5232 std::string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5234 // Add the ctrl button
5235 addButton (CCtrlButton::PushButton, name, src, src, over, "html_submit_form", param.c_str(), tooltip.c_str(), _Style.Current);
5238 // ***************************************************************************
5239 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)
5241 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "submit", formAction));
5242 // Action handler parameters
5243 string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5245 // Add the ctrl button
5246 if (!_Paragraph)
5248 newParagraph (0);
5249 paragraphChange ();
5252 string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup);
5253 typedef pair<string, string> TTmplParam;
5254 vector<TTmplParam> tmplParams;
5255 tmplParams.push_back(TTmplParam("id", name));
5256 tmplParams.push_back(TTmplParam("onclick", "html_submit_form"));
5257 tmplParams.push_back(TTmplParam("onclick_param", param));
5258 tmplParams.push_back(TTmplParam("active", "true"));
5259 if (minWidth > 0) tmplParams.push_back(TTmplParam("wmin", toString(minWidth)));
5260 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
5261 if (buttonGroup)
5263 // Add the ctrl button
5264 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
5265 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
5266 if (ctrlButton)
5268 ctrlButton->setModulateGlobalColorAll (_Style.Current.GlobalColor);
5269 ctrlButton->setTextModulateGlobalColorNormal(_Style.Current.GlobalColorText);
5270 ctrlButton->setTextModulateGlobalColorOver(_Style.Current.GlobalColorText);
5271 ctrlButton->setTextModulateGlobalColorPushed(_Style.Current.GlobalColorText);
5273 // Translate the tooltip
5274 if (!tooltip.empty())
5276 if (CI18N::hasTranslation(tooltip))
5278 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
5280 else
5282 ctrlButton->setDefaultContextHelp(tooltip);
5286 ctrlButton->setText(value);
5288 setTextButtonStyle(ctrlButton, _Style.Current);
5290 getParagraph()->addChild (buttonGroup);
5291 paragraphChange ();
5295 // ***************************************************************************
5296 void CGroupHTML::htmlA(const CHtmlElement &elm)
5298 _A.push_back(true);
5299 _Link.push_back ("");
5300 _LinkTitle.push_back("");
5301 _LinkClass.push_back("");
5302 if (elm.hasClass("ryzom-ui-button"))
5303 _LinkClass.back() = "ryzom-ui-button";
5305 // #fragment works with both ID and NAME so register both
5306 if (elm.hasNonEmptyAttribute("name"))
5307 _AnchorName.push_back(elm.getAttribute("name"));
5308 if (elm.hasNonEmptyAttribute("title"))
5309 _LinkTitle.back() = elm.getAttribute("title");
5310 if (elm.hasNonEmptyAttribute("href"))
5312 string suri = elm.getAttribute("href");
5313 if(suri.find("ah:") == 0)
5315 if (_TrustedDomain)
5316 _Link.back() = suri;
5318 else
5320 // convert href from "?key=val" into "http://domain.com/?key=val"
5321 _Link.back() = getAbsoluteUrl(suri);
5325 renderPseudoElement(":before", elm);
5328 void CGroupHTML::htmlAend(const CHtmlElement &elm)
5330 renderPseudoElement(":after", elm);
5332 popIfNotEmpty(_A);
5333 popIfNotEmpty(_Link);
5334 popIfNotEmpty(_LinkTitle);
5335 popIfNotEmpty(_LinkClass);
5338 // ***************************************************************************
5339 void CGroupHTML::htmlBASE(const CHtmlElement &elm)
5341 if (!_ReadingHeadTag || _IgnoreBaseUrlTag)
5342 return;
5344 if (elm.hasNonEmptyAttribute("href"))
5346 CUrlParser uri(elm.getAttribute("href"));
5347 if (uri.isAbsolute())
5349 _URL = uri.toString();
5350 _IgnoreBaseUrlTag = true;
5355 // ***************************************************************************
5356 void CGroupHTML::htmlBODY(const CHtmlElement &elm)
5358 // override <body> (or <html>) css style attribute
5359 if (elm.hasNonEmptyAttribute("bgcolor"))
5360 _Style.applyStyle("background-color: " + elm.getAttribute("bgcolor"));
5362 if (m_HtmlBackground.isEmpty())
5363 setupBackground(&m_HtmlBackground);
5364 else
5365 setupBackground(&m_BodyBackground);
5367 renderPseudoElement(":before", elm);
5370 // ***************************************************************************
5371 void CGroupHTML::htmlBR(const CHtmlElement &elm)
5373 if (!_Paragraph || _Paragraph->getNumChildren() == 0)
5375 addString("\n");
5377 else
5379 endParagraph();
5383 // ***************************************************************************
5384 void CGroupHTML::htmlBUTTON(const CHtmlElement &elm)
5386 std::string name = elm.getAttribute("name");
5387 std::string value = elm.getAttribute("value");
5388 std::string formId = elm.getAttribute("form");
5389 std::string formAction = elm.getAttribute("formaction");
5390 std::string tooltip = elm.getAttribute("tooltip");
5391 bool disabled = elm.hasAttribute("disabled");
5393 if (formId.empty() && _FormOpen)
5395 formId = _Forms.back().id;
5398 if (!formAction.empty())
5400 formAction = getAbsoluteUrl(formAction);
5403 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "text", formAction));
5404 // Action handler parameters
5405 std::string param;
5406 if (!disabled)
5408 if (elm.getAttribute("type") == "submit")
5410 param = "ah:html_submit_form&name=" + getId() + "&button=" + toString(_FormSubmit.size()-1);
5412 else
5414 param = "ah:";
5418 _A.push_back(true);
5419 _Link.push_back(param);
5420 _LinkTitle.push_back(tooltip);
5421 _LinkClass.push_back("ryzom-ui-button");
5423 // TODO: this creates separate button element
5424 //renderPseudoElement(":before", elm);
5426 void CGroupHTML::htmlBUTTONend(const CHtmlElement &elm)
5428 // TODO: this creates separate button element
5429 //renderPseudoElement(":after", elm);
5431 popIfNotEmpty(_A);
5432 popIfNotEmpty(_Link);
5433 popIfNotEmpty(_LinkTitle);
5434 popIfNotEmpty(_LinkClass);
5437 // ***************************************************************************
5438 void CGroupHTML::htmlDD(const CHtmlElement &elm)
5440 if (_DL.empty())
5441 return;
5443 // if there was no closing tag for <dt>, then remove <dt> style
5444 if (_DL.back().DT)
5446 nlwarning("BUG: nested DT in DD");
5447 _DL.back().DT = false;
5450 if (_DL.back().DD)
5452 nlwarning("BUG: nested DD in DD");
5453 _DL.back().DD = false;
5454 popIfNotEmpty(_Indent);
5457 _DL.back().DD = true;
5458 _Indent.push_back(getIndent() + ULIndent);
5460 if (!_LI)
5462 _LI = true;
5463 newParagraph(ULBeginSpace);
5465 else
5467 newParagraph(LIBeginSpace);
5470 renderPseudoElement(":before", elm);
5473 void CGroupHTML::htmlDDend(const CHtmlElement &elm)
5475 if (_DL.empty())
5476 return;
5478 renderPseudoElement(":after", elm);
5480 // parser will process two DD in a row as nested when first DD is not closed
5481 if (_DL.back().DD)
5483 _DL.back().DD = false;
5484 popIfNotEmpty(_Indent);
5488 // ***************************************************************************
5489 void CGroupHTML::htmlDIV(const CHtmlElement &elm)
5491 _DivName = elm.getAttribute("name");
5493 string instClass = elm.getAttribute("class");
5495 // use generic template system
5496 if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
5498 string style = elm.getAttribute("style");
5499 string id = elm.getAttribute("id");
5500 if (id.empty())
5501 id = "DIV" + toString(getNextAutoIdSeq());
5503 typedef pair<string, string> TTmplParam;
5504 vector<TTmplParam> tmplParams;
5506 string templateName;
5507 if (!style.empty())
5509 TStyle styles = parseStyle(style);
5510 TStyle::iterator it;
5511 for (it=styles.begin(); it != styles.end(); it++)
5513 if ((*it).first == "template")
5514 templateName = (*it).second;
5515 else
5516 tmplParams.push_back(TTmplParam((*it).first, (*it).second));
5520 if (!templateName.empty())
5522 string parentId;
5523 bool haveParentDiv = getDiv() != NULL;
5524 if (haveParentDiv)
5525 parentId = getDiv()->getId();
5526 else
5528 if (!_Paragraph)
5529 newParagraph (0);
5531 parentId = _Paragraph->getId();
5534 CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, parentId, tmplParams);
5535 if (inst)
5537 inst->setId(parentId+":"+id);
5538 inst->updateCoords();
5539 if (haveParentDiv)
5541 inst->setParent(getDiv());
5542 inst->setParentSize(getDiv());
5543 inst->setParentPos(getDiv());
5544 inst->setPosRef(Hotspot_TL);
5545 inst->setParentPosRef(Hotspot_TL);
5546 getDiv()->addGroup(inst);
5548 else
5550 getParagraph()->addChild(inst);
5551 paragraphChange();
5553 _Divs.push_back(inst);
5558 renderPseudoElement(":before", elm);
5561 void CGroupHTML::htmlDIVend(const CHtmlElement &elm)
5563 renderPseudoElement(":after", elm);
5564 _DivName.clear();
5565 popIfNotEmpty(_Divs);
5568 // ***************************************************************************
5569 void CGroupHTML::htmlDL(const CHtmlElement &elm)
5571 _DL.push_back(HTMLDListElement());
5572 _LI = _DL.size() > 1 || !_UL.empty();
5574 renderPseudoElement(":before", elm);
5577 void CGroupHTML::htmlDLend(const CHtmlElement &elm)
5579 if (_DL.empty())
5580 return;
5582 renderPseudoElement(":after", elm);
5584 // unclosed DT
5585 if (_DL.back().DT)
5587 nlwarning("BUG: unclosed DT in DL");
5590 // unclosed DD
5591 if (_DL.back().DD)
5593 popIfNotEmpty(_Indent);
5594 nlwarning("BUG: unclosed DD in DL");
5597 popIfNotEmpty (_DL);
5600 // ***************************************************************************
5601 void CGroupHTML::htmlDT(const CHtmlElement &elm)
5603 if (_DL.empty())
5604 return;
5606 // TODO: check if nested tags still happen and fix it in parser
5607 // : remove special handling for nesting and let it happen
5609 // html parser and libxml2 should prevent nested tags like these
5610 if (_DL.back().DD)
5612 nlwarning("BUG: nested DD in DT");
5614 _DL.back().DD = false;
5615 popIfNotEmpty(_Indent);
5618 // html parser and libxml2 should prevent nested tags like these
5619 if (_DL.back().DT)
5621 nlwarning("BUG: nested DT in DT");
5624 _DL.back().DT = true;
5626 if (!_LI)
5628 _LI = true;
5629 newParagraph(ULBeginSpace);
5631 else
5633 newParagraph(LIBeginSpace);
5636 renderPseudoElement(":before", elm);
5639 void CGroupHTML::htmlDTend(const CHtmlElement &elm)
5641 if (_DL.empty())
5642 return;
5644 renderPseudoElement(":after", elm);
5646 _DL.back().DT = false;
5649 // ***************************************************************************
5650 void CGroupHTML::htmlFONT(const CHtmlElement &elm)
5652 if (elm.hasNonEmptyAttribute("color"))
5654 CRGBA color;
5655 if (scanHTMLColor(elm.getAttribute("color").c_str(), color))
5656 _Style.Current.TextColor = color;
5659 if (elm.hasNonEmptyAttribute("size"))
5661 uint fontsize;
5662 fromString(elm.getAttribute("size"), fontsize);
5663 _Style.Current.FontSize = fontsize;
5667 // ***************************************************************************
5668 void CGroupHTML::htmlFORM(const CHtmlElement &elm)
5670 _FormOpen = true;
5672 // Build the form
5673 CGroupHTML::CForm form;
5674 // id check is case sensitive and auto id's are uppercase
5675 form.id = toLowerAscii(trim(elm.getAttribute("id")));
5676 if (form.id.empty())
5678 form.id = toString("FORM%d", _Forms.size());
5681 // Get the action name
5682 if (elm.hasNonEmptyAttribute("action"))
5684 form.Action = getAbsoluteUrl(elm.getAttribute("action"));
5686 else
5688 form.Action = _URL;
5691 _Forms.push_back(form);
5693 renderPseudoElement(":before", elm);
5696 void CGroupHTML::htmlFORMend(const CHtmlElement &elm)
5698 _FormOpen = false;
5699 renderPseudoElement(":after", elm);
5702 // ***************************************************************************
5703 void CGroupHTML::htmlH(const CHtmlElement &elm)
5705 newParagraph(PBeginSpace);
5706 renderPseudoElement(":before", elm);
5709 void CGroupHTML::htmlHend(const CHtmlElement &elm)
5711 renderPseudoElement(":after", elm);
5714 // ***************************************************************************
5715 void CGroupHTML::htmlHEAD(const CHtmlElement &elm)
5717 _ReadingHeadTag = !_IgnoreHeadTag;
5718 _IgnoreHeadTag = true;
5721 void CGroupHTML::htmlHEADend(const CHtmlElement &elm)
5723 _ReadingHeadTag = false;
5726 // ***************************************************************************
5727 void CGroupHTML::htmlHR(const CHtmlElement &elm)
5729 CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0);
5730 if (sep)
5732 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*>(sep->getView("hr"));
5733 if (bitmap)
5735 bitmap->setColor(_Style.Current.TextColor);
5736 if (_Style.Current.Width > 0)
5738 clamp(_Style.Current.Width, 1, 32000);
5739 bitmap->setW(_Style.Current.Width);
5740 bitmap->setSizeRef(CInterfaceElement::none);
5742 if (_Style.Current.Height > 0)
5744 clamp(_Style.Current.Height, 1, 1000);
5745 bitmap->setH(_Style.Current.Height);
5749 renderPseudoElement(":before", elm);
5750 addHtmlGroup(sep, 0);
5751 renderPseudoElement(":after", elm);
5755 // ***************************************************************************
5756 void CGroupHTML::htmlHTML(const CHtmlElement &elm)
5758 if (elm.hasNonEmptyAttribute("style"))
5759 _Style.applyStyle(elm.getAttribute("style"));
5761 _Style.Root = _Style.Current;
5763 setupBackground(&m_HtmlBackground);
5766 // ***************************************************************************
5767 void CGroupHTML::htmlI(const CHtmlElement &elm)
5769 _Localize = true;
5770 renderPseudoElement(":before", elm);
5773 void CGroupHTML::htmlIend(const CHtmlElement &elm)
5775 renderPseudoElement(":after", elm);
5776 _Localize = false;
5779 // ***************************************************************************
5780 void CGroupHTML::htmlIMG(const CHtmlElement &elm)
5782 std::string src = trim(elm.getAttribute("src"));
5783 if (src.empty())
5785 // no 'src' attribute, or empty
5786 return;
5789 float tmpf;
5790 std::string id = elm.getAttribute("id");
5792 if (elm.hasNonEmptyAttribute("width"))
5793 getPercentage(_Style.Current.Width, tmpf, elm.getAttribute("width").c_str());
5794 if (elm.hasNonEmptyAttribute("height"))
5795 getPercentage(_Style.Current.Height, tmpf, elm.getAttribute("height").c_str());
5797 // Get the global color name
5798 if (elm.hasAttribute("global_color"))
5799 _Style.Current.GlobalColor = true;
5801 // Tooltip
5802 // keep "alt" attribute for backward compatibility
5803 std::string tooltip = elm.getAttribute("alt");
5804 // tooltip
5805 if (elm.hasNonEmptyAttribute("title"))
5806 tooltip = elm.getAttribute("title");
5808 // Mouse over image
5809 string overSrc = elm.getAttribute("data-over-src");
5811 // inside a/button with valid url (ie, button is not disabled)
5812 string url = getLink();
5813 if (getA() && !url.empty() && getParent() && getParent()->getParent())
5815 string params = "name=" + getId() + "|url=" + url;
5816 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "browse", params.c_str(), tooltip, _Style.Current);
5818 else
5819 if (!tooltip.empty() || !overSrc.empty())
5821 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "", "", tooltip, _Style.Current);
5823 else
5825 // Get the option to reload (class==reload)
5826 bool reloadImg = false;
5828 if (elm.hasNonEmptyAttribute("style"))
5830 string styleString = elm.getAttribute("style");
5831 TStyle styles = parseStyle(styleString);
5832 TStyle::iterator it;
5834 it = styles.find("reload");
5835 if (it != styles.end() && (*it).second == "1")
5836 reloadImg = true;
5839 addImage(id, elm.getAttribute("src"), reloadImg, _Style.Current);
5843 // ***************************************************************************
5844 void CGroupHTML::htmlINPUT(const CHtmlElement &elm)
5846 if (_Forms.empty())
5847 return;
5849 // read general property
5850 string id = elm.getAttribute("id");
5852 // Widget template name (old)
5853 string templateName = elm.getAttribute("z_btn_tmpl");
5854 // Input name is the new
5855 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
5856 templateName = elm.getAttribute("z_input_tmpl");
5858 // Widget minimal width
5859 uint32 minWidth = 0;
5860 fromString(elm.getAttribute("z_input_width"), minWidth);
5862 // <input type="...">
5863 std::string type = trim(elm.getAttribute("type"));
5864 if (type.empty())
5866 // no 'type' attribute, or empty
5867 return;
5870 // Global color flag
5871 if (elm.hasAttribute("global_color"))
5872 _Style.Current.GlobalColor = true;
5874 // Tooltip
5875 std::string tooltip = elm.getAttribute("alt");
5877 if (type == "image")
5879 string name = elm.getAttribute("name");
5880 string src = elm.getAttribute("src");
5881 string over = elm.getAttribute("data-over-src");
5882 string formId = elm.getAttribute("form");
5883 string formAction = elm.getAttribute("formaction");
5885 if (formId.empty() && _FormOpen) {
5886 formId = _Forms.back().id;
5889 insertFormImageButton(name, tooltip, src, over, formId, formAction, minWidth, templateName);
5891 else if (type == "button" || type == "submit")
5893 string name = elm.getAttribute("name");
5894 string value = elm.getAttribute("value");
5895 string formId = elm.getAttribute("form");
5896 string formAction = elm.getAttribute("formaction");
5898 if (formId.empty() && _FormOpen) {
5899 formId = _Forms.back().id;
5902 insertFormTextButton(name, tooltip, value, formId, formAction, minWidth, templateName);
5904 else if (type == "text")
5906 // Get the string name
5907 string name = elm.getAttribute("name");
5908 string ucValue = elm.getAttribute("value");
5910 uint size = 20;
5911 uint maxlength = 1024;
5912 if (elm.hasNonEmptyAttribute("size"))
5913 fromString(elm.getAttribute("size"), size);
5914 if (elm.hasNonEmptyAttribute("maxlength"))
5915 fromString(elm.getAttribute("maxlength"), maxlength);
5917 // ryzom client used to have 'size' attribute in pixels, (12 == was default font size)
5918 if (_Style.hasStyle("-ryzom-input-size-px") && _Style.getStyle("-ryzom-input-size-px") == "true")
5919 size = size / 12;
5921 string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
5922 // Add the editbox
5923 CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size, false, ucValue, maxlength);
5924 if (textArea)
5926 // Add the text area to the form
5927 CGroupHTML::CForm::CEntry entry;
5928 entry.Name = name;
5929 entry.TextArea = textArea;
5930 _Forms.back().Entries.push_back (entry);
5933 else if (type == "checkbox" || type == "radio")
5935 renderPseudoElement(":before", elm);
5937 CCtrlButton::EType btnType;
5938 string name = elm.getAttribute("name");
5939 string normal = elm.getAttribute("src");
5940 string pushed;
5941 string over;
5942 string ucValue = "on";
5943 bool checked = elm.hasAttribute("checked");
5945 // TODO: unknown if empty attribute should override or not
5946 if (elm.hasNonEmptyAttribute("value"))
5947 ucValue = elm.getAttribute("value");
5949 if (type == "radio")
5951 btnType = CCtrlButton::RadioButton;
5952 normal = DefaultRadioButtonBitmapNormal;
5953 pushed = DefaultRadioButtonBitmapPushed;
5954 over = DefaultRadioButtonBitmapOver;
5956 else
5958 btnType = CCtrlButton::ToggleButton;
5959 normal = DefaultCheckBoxBitmapNormal;
5960 pushed = DefaultCheckBoxBitmapPushed;
5961 over = DefaultCheckBoxBitmapOver;
5964 // Add the ctrl button
5965 CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, _Style.Current);
5966 if (checkbox)
5968 if (btnType == CCtrlButton::RadioButton)
5970 // override with 'id' because radio buttons share same name
5971 if (!id.empty())
5972 checkbox->setId(id);
5974 // group together buttons with same name
5975 CForm &form = _Forms.back();
5976 bool notfound = true;
5977 for (uint i=0; i<form.Entries.size(); i++)
5979 if (form.Entries[i].Name == name && form.Entries[i].Checkbox->getType() == CCtrlButton::RadioButton)
5981 checkbox->initRBRefFromRadioButton(form.Entries[i].Checkbox);
5982 notfound = false;
5983 break;
5986 if (notfound)
5988 // this will start a new group (initRBRef() would take first button in group container otherwise)
5989 checkbox->initRBRefFromRadioButton(checkbox);
5993 checkbox->setPushed (checked);
5995 // Add the button to the form
5996 CGroupHTML::CForm::CEntry entry;
5997 entry.Name = name;
5998 entry.Value = decodeHTMLEntities(ucValue);
5999 entry.Checkbox = checkbox;
6000 _Forms.back().Entries.push_back (entry);
6002 renderPseudoElement(":after", elm);
6004 else if (type == "hidden")
6006 if (elm.hasNonEmptyAttribute("name"))
6008 // Get the name
6009 string name = elm.getAttribute("name");
6011 // Get the value
6012 string ucValue = elm.getAttribute("value");
6014 // Add an entry
6015 CGroupHTML::CForm::CEntry entry;
6016 entry.Name = name;
6017 entry.Value = decodeHTMLEntities(ucValue);
6018 _Forms.back().Entries.push_back (entry);
6023 // ***************************************************************************
6024 void CGroupHTML::htmlLI(const CHtmlElement &elm)
6026 if (_UL.empty())
6027 return;
6029 // UL, OL top margin if this is the first LI
6030 if (!_LI)
6032 _LI = true;
6033 newParagraph(ULBeginSpace);
6035 else
6037 newParagraph(LIBeginSpace);
6040 // OL list index can be overridden by <li value="1"> attribute
6041 if (elm.hasNonEmptyAttribute("value"))
6042 fromString(elm.getAttribute("value"), _UL.back().Value);
6044 string str = _UL.back().getListMarkerText();
6045 addString (str);
6047 // list-style-type: outside
6048 if (_CurrentViewLink)
6050 getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW());
6053 flushString ();
6055 // after marker
6056 renderPseudoElement(":before", elm);
6058 _UL.back().Value++;
6061 void CGroupHTML::htmlLIend(const CHtmlElement &elm)
6063 renderPseudoElement(":after", elm);
6066 // ***************************************************************************
6067 void CGroupHTML::htmlLUA(const CHtmlElement &elm)
6069 // we receive an embeded lua script
6070 _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain
6071 _LuaScript.clear();
6074 void CGroupHTML::htmlLUAend(const CHtmlElement &elm)
6076 if (_ParsingLua && _TrustedDomain)
6078 _ParsingLua = false;
6079 // execute the embeded lua script
6080 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript;
6081 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
6085 // ***************************************************************************
6086 void CGroupHTML::htmlMETA(const CHtmlElement &elm)
6088 if (!_ReadingHeadTag)
6089 return;
6091 std::string httpEquiv = elm.getAttribute("http-equiv");
6092 std::string httpContent = elm.getAttribute("content");
6093 if (httpEquiv.empty() || httpContent.empty())
6095 return;
6098 // only first http-equiv="refresh" should be handled
6099 if (_RefreshUrl.empty() && httpEquiv == "refresh")
6101 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
6102 double timeSec = times.thisFrameMs / 1000.0f;
6104 string::size_type pos = httpContent.find_first_of(";");
6105 if (pos == string::npos)
6107 fromString(httpContent, _NextRefreshTime);
6108 _RefreshUrl = _URL;
6110 else
6112 fromString(httpContent.substr(0, pos), _NextRefreshTime);
6114 pos = toLowerAscii(httpContent).find("url=");
6115 if (pos != string::npos)
6116 _RefreshUrl = getAbsoluteUrl(httpContent.substr(pos + 4));
6119 _NextRefreshTime += timeSec;
6123 // ***************************************************************************
6124 void CGroupHTML::htmlMETER(const CHtmlElement &elm)
6126 HTMLMeterElement meter;
6127 meter.readValues(elm);
6129 std::string id = "meter";
6130 if (elm.hasAttribute("id"))
6131 id = elm.getAttribute("id");
6133 // width: 5em, height: 1em
6134 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 5;
6135 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6136 // FIXME: only using border-top
6137 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6139 uint barw = (uint) (width * meter.getValueRatio());
6140 CRGBA bgColor = meter.getBarColor(elm, _Style);
6141 CRGBA valueColor = meter.getValueColor(elm, _Style);
6143 typedef pair<string, string> TTmplParam;
6144 vector<TTmplParam> tmplParams;
6145 tmplParams.push_back(TTmplParam("id", id));
6146 tmplParams.push_back(TTmplParam("active", "true"));
6147 tmplParams.push_back(TTmplParam("w", toString(width)));
6148 tmplParams.push_back(TTmplParam("h", toString(height)));
6149 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6150 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6151 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6152 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6153 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6154 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6156 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_meter", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6157 if (gr)
6159 renderPseudoElement(":before", elm);
6160 getParagraph()->addChild(gr);
6161 renderPseudoElement(":after", elm);
6163 // ignore any inner elements
6164 _IgnoreChildElements = true;
6168 // ***************************************************************************
6169 void CGroupHTML::htmlOBJECT(const CHtmlElement &elm)
6171 _ObjectType = elm.getAttribute("type");
6172 _ObjectData = elm.getAttribute("data");
6173 _ObjectMD5Sum = elm.getAttribute("id");
6174 _ObjectAction = elm.getAttribute("standby");
6175 _Object = true;
6178 void CGroupHTML::htmlOBJECTend(const CHtmlElement &elm)
6180 if (!_TrustedDomain)
6181 return;
6183 if (_ObjectType=="application/ryzom-data")
6185 if (!_ObjectData.empty())
6187 if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum))
6189 CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true);
6191 _ObjectScript.clear();
6194 _Object = false;
6197 // ***************************************************************************
6198 void CGroupHTML::htmlOL(const CHtmlElement &elm)
6200 sint32 start = 1;
6201 std::string type("1");
6203 if (elm.hasNonEmptyAttribute("start"))
6204 fromString(elm.getAttribute("start"), start);
6205 if (elm.hasNonEmptyAttribute("type"))
6206 type = elm.getAttribute("type");
6208 _UL.push_back(HTMLOListElement(start, type));
6209 // if LI is already present
6210 _LI = _UL.size() > 1 || _DL.size() > 1;
6211 _Indent.push_back(getIndent() + ULIndent);
6213 renderPseudoElement(":before", elm);
6216 void CGroupHTML::htmlOLend(const CHtmlElement &elm)
6218 htmlULend(elm);
6221 // ***************************************************************************
6222 void CGroupHTML::htmlOPTION(const CHtmlElement &elm)
6224 _SelectOption = true;
6225 _SelectOptionStr.clear();
6227 // Got one form ?
6228 if (_Forms.empty() || _Forms.back().Entries.empty())
6229 return;
6231 _Forms.back().Entries.back().SelectValues.push_back(elm.getAttribute("value"));
6233 if (elm.hasAttribute("selected"))
6234 _Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6236 if (elm.hasAttribute("disabled"))
6237 _Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6240 void CGroupHTML::htmlOPTIONend(const CHtmlElement &elm)
6242 if (_Forms.empty() || _Forms.back().Entries.empty())
6243 return;
6245 // use option text as value
6246 if (!elm.hasAttribute("value"))
6248 _Forms.back().Entries.back().SelectValues.back() = _SelectOptionStr;
6251 // insert the parsed text into the select control
6252 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6253 if (cb)
6255 uint lineIndex = cb->getNumTexts();
6256 cb->addText(_SelectOptionStr);
6257 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6259 cb->setGrayed(lineIndex, true);
6262 else
6264 CGroupMenu *sb = _Forms.back().Entries.back().SelectBox;
6265 if (sb)
6267 uint lineIndex = sb->getNumLine();
6268 sb->addLine(_SelectOptionStr, "", "");
6270 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6272 sb->setGrayedLine(lineIndex, true);
6274 else
6276 // create option line checkbox, CGroupMenu is taking ownership of the checbox
6277 CInterfaceGroup *ig = CWidgetManager::getInstance()->getParser()->createGroupInstance("menu_checkbox", "", NULL, 0);
6278 if (ig)
6280 CCtrlButton *cb = dynamic_cast<CCtrlButton *>(ig->getCtrl("b"));
6281 if (cb)
6283 if (_Forms.back().Entries.back().sbMultiple)
6285 cb->setType(CCtrlButton::ToggleButton);
6286 cb->setTexture(DefaultCheckBoxBitmapNormal);
6287 cb->setTexturePushed(DefaultCheckBoxBitmapPushed);
6288 cb->setTextureOver(DefaultCheckBoxBitmapOver);
6290 else
6292 cb->setType(CCtrlButton::RadioButton);
6293 cb->setTexture(DefaultRadioButtonBitmapNormal);
6294 cb->setTexturePushed(DefaultRadioButtonBitmapPushed);
6295 cb->setTextureOver(DefaultRadioButtonBitmapOver);
6297 if (_Forms.back().Entries.back().sbRBRef == NULL)
6298 _Forms.back().Entries.back().sbRBRef = cb;
6300 cb->initRBRefFromRadioButton(_Forms.back().Entries.back().sbRBRef);
6303 cb->setPushed(_Forms.back().Entries.back().InitialSelection == lineIndex);
6304 sb->setUserGroupLeft(lineIndex, ig);
6306 else
6308 nlwarning("Failed to get 'b' element from 'menu_checkbox' template");
6309 delete ig;
6317 // ***************************************************************************
6318 void CGroupHTML::htmlP(const CHtmlElement &elm)
6320 newParagraph(PBeginSpace);
6321 renderPseudoElement(":before", elm);
6324 void CGroupHTML::htmlPend(const CHtmlElement &elm)
6326 renderPseudoElement(":after", elm);
6329 // ***************************************************************************
6330 void CGroupHTML::htmlPRE(const CHtmlElement &elm)
6332 _PRE.push_back(true);
6333 newParagraph(0);
6335 renderPseudoElement(":before", elm);
6338 void CGroupHTML::htmlPREend(const CHtmlElement &elm)
6340 renderPseudoElement(":after", elm);
6342 popIfNotEmpty(_PRE);
6345 // ***************************************************************************
6346 void CGroupHTML::htmlPROGRESS(const CHtmlElement &elm)
6348 HTMLProgressElement progress;
6349 progress.readValues(elm);
6351 std::string id = "progress";
6352 if (elm.hasAttribute("id"))
6353 id = elm.getAttribute("id");
6355 // width: 10em, height: 1em
6356 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 10;
6357 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6358 // FIXME: only using border-top
6359 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6361 uint barw = (uint) (width * progress.getValueRatio());
6362 CRGBA bgColor = progress.getBarColor(elm, _Style);
6363 CRGBA valueColor = progress.getValueColor(elm, _Style);
6365 typedef pair<string, string> TTmplParam;
6366 vector<TTmplParam> tmplParams;
6367 tmplParams.push_back(TTmplParam("id", id));
6368 tmplParams.push_back(TTmplParam("active", "true"));
6369 tmplParams.push_back(TTmplParam("w", toString(width)));
6370 tmplParams.push_back(TTmplParam("h", toString(height)));
6371 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6372 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6373 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6374 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6375 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6376 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6378 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_progress", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6379 if (gr)
6381 renderPseudoElement(":before", elm);
6382 getParagraph()->addChild(gr);
6383 renderPseudoElement(":after", elm);
6385 // ignore any inner elements
6386 _IgnoreChildElements = true;
6390 // ***************************************************************************
6391 void CGroupHTML::htmlSCRIPT(const CHtmlElement &elm)
6393 _IgnoreText = true;
6396 void CGroupHTML::htmlSCRIPTend(const CHtmlElement &elm)
6398 _IgnoreText = false;
6401 // ***************************************************************************
6402 void CGroupHTML::htmlSELECT(const CHtmlElement &elm)
6404 if (_Forms.empty())
6405 return;
6407 // A select box
6408 string name = elm.getAttribute("name");
6409 bool multiple = elm.hasAttribute("multiple");
6410 sint32 size = 0;
6412 if (elm.hasNonEmptyAttribute("size"))
6413 fromString(elm.getAttribute("size"), size);
6415 CGroupHTML::CForm::CEntry entry;
6416 entry.Name = name;
6417 entry.sbMultiple = multiple;
6418 if (size > 1 || multiple)
6420 entry.InitialSelection = -1;
6421 CGroupMenu *sb = addSelectBox(DefaultFormSelectBoxMenuGroup, name.c_str());
6422 if (sb)
6424 if (size < 1)
6425 size = 4;
6427 if (_Style.Current.Width > -1)
6428 sb->setMinW(_Style.Current.Width);
6430 if (_Style.Current.Height > -1)
6431 sb->setMinH(_Style.Current.Height);
6433 sb->setMaxVisibleLine(size);
6434 sb->setFontSize(_Style.Current.FontSize, false);
6437 entry.SelectBox = sb;
6439 else
6441 CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
6442 entry.ComboBox = cb;
6444 if (cb)
6446 // create view text
6447 cb->updateCoords();
6448 setTextStyle(cb->getViewText(), _Style.Current);
6451 _Forms.back().Entries.push_back (entry);
6454 void CGroupHTML::htmlSELECTend(const CHtmlElement &elm)
6456 _SelectOption = false;
6457 if (_Forms.empty() || _Forms.back().Entries.empty())
6458 return;
6460 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6461 if (cb)
6463 cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection);
6464 // TODO: magic padding
6465 cb->setW(cb->evalContentWidth() + 16);
6469 // ***************************************************************************
6470 void CGroupHTML::htmlSTYLE(const CHtmlElement &elm)
6472 _IgnoreText = true;
6475 void CGroupHTML::htmlSTYLEend(const CHtmlElement &elm)
6477 _IgnoreText = false;
6480 // ***************************************************************************
6481 void CGroupHTML::htmlTABLE(const CHtmlElement &elm)
6483 // Get cells parameters
6484 getCellsParameters(elm, false);
6486 CGroupTable *table = new CGroupTable(TCtorParam());
6488 if (elm.hasNonEmptyAttribute("id"))
6489 table->setId(getCurrentGroup()->getId() + ":" + elm.getAttribute("id"));
6490 else
6491 table->setId(getCurrentGroup()->getId() + ":TABLE" + toString(getNextAutoIdSeq()));
6493 // TODO: border-spacing: 2em;
6495 if (elm.hasNonEmptyAttribute("cellspacing"))
6496 fromString(elm.getAttribute("cellspacing"), table->CellSpacing);
6498 // TODO: cssLength, horiz/vert values
6499 if (_Style.hasStyle("border-spacing"))
6500 fromString(_Style.getStyle("border-spacing"), table->CellSpacing);
6502 // overrides border-spacing if set to 'collapse'
6503 if (_Style.checkStyle("border-collapse", "collapse"))
6504 table->CellSpacing = 0;
6507 if (elm.hasNonEmptyAttribute("cellpadding"))
6508 fromString(elm.getAttribute("cellpadding"), table->CellPadding);
6510 if (_Style.hasStyle("width"))
6512 // _Style.Width does not handle '%' unit currently
6513 if (_Style.Current.Width > 0)
6515 table->ForceWidthMin = _Style.Current.Width;
6516 table->TableRatio = 0;
6518 else
6520 getPercentage (table->ForceWidthMin, table->TableRatio, _Style.getStyle("width").c_str());
6523 else if (elm.hasNonEmptyAttribute("width"))
6525 getPercentage (table->ForceWidthMin, table->TableRatio, elm.getAttribute("width").c_str());
6528 // border from css or from attribute
6530 CSSRect<CSSBorder> border;
6531 border.Top.Color = _Style.Current.TextColor;
6532 border.Right.Color = _Style.Current.TextColor;
6533 border.Bottom.Color = _Style.Current.TextColor;
6534 border.Left.Color = _Style.Current.TextColor;
6536 if (elm.hasAttribute("border"))
6538 uint32 borderWidth = 0;
6539 CRGBA borderColor = CRGBA::Transparent;
6541 std::string s = elm.getAttribute("border");
6542 if (s.empty())
6543 borderWidth = 1;
6544 else
6545 fromString(elm.getAttribute("border"), borderWidth);
6547 if (elm.hasNonEmptyAttribute("bordercolor"))
6548 scanHTMLColor(elm.getAttribute("bordercolor").c_str(), borderColor);
6549 else
6550 borderColor = CRGBA(128, 128, 128, 255);
6552 table->CellBorder = (borderWidth > 0);
6554 border.Top.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6555 border.Right.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6556 border.Bottom.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6557 border.Left.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6560 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6561 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6562 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6563 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6565 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6566 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6567 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6568 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6570 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6571 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6572 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6573 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6575 table->Border->setBorder(border);
6576 table->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6577 table->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6580 setupBackground(table->Background);
6581 table->setModulateGlobalColor(_Style.Current.GlobalColor);
6583 table->setMarginLeft(getIndent());
6584 addHtmlGroup (table, 0);
6586 renderPseudoElement(":before", elm);
6588 _Tables.push_back(table);
6590 // Add a cell pointer
6591 _Cells.push_back(NULL);
6592 _TR.push_back(false);
6593 _Indent.push_back(0);
6596 void CGroupHTML::htmlTABLEend(const CHtmlElement &elm)
6598 popIfNotEmpty(_CellParams);
6599 popIfNotEmpty(_TR);
6600 popIfNotEmpty(_Cells);
6601 popIfNotEmpty(_Tables);
6602 popIfNotEmpty(_Indent);
6604 renderPseudoElement(":after", elm);
6607 // ***************************************************************************
6608 void CGroupHTML::htmlTD(const CHtmlElement &elm)
6610 CRGBA rowColor = CRGBA::Transparent;
6611 // remember row color so we can blend it with cell color
6612 if (!_CellParams.empty())
6613 rowColor = _CellParams.back().BgColor;
6615 // Get cells parameters
6616 getCellsParameters(elm, true);
6618 // if cell has own background,then it must be blended with row
6619 if (rowColor.A > 0 && _Style.Current.Background.color.A < 255)
6621 _Style.Current.Background.color.blendFromui(rowColor,
6622 _Style.Current.Background.color, _Style.Current.Background.color.A);
6625 if (elm.ID == HTML_TH)
6627 if (!_Style.hasStyle("font-weight"))
6628 _Style.Current.FontWeight = FONT_WEIGHT_BOLD;
6629 // center if not specified otherwise.
6630 if (!elm.hasNonEmptyAttribute("align") && !_Style.hasStyle("text-align"))
6631 _CellParams.back().Align = CGroupCell::Center;
6634 CGroupTable *table = getTable();
6635 if (!table)
6637 // <td> appears to be outside <table>
6638 return;
6641 if (_Cells.empty())
6643 // <table> not started
6644 return;
6647 _Cells.back() = new CGroupCell(CViewBase::TCtorParam());
6648 if (elm.hasNonEmptyAttribute("id"))
6649 _Cells.back()->setId(table->getId() + ":" + elm.getAttribute("id"));
6650 else
6651 _Cells.back()->setId(table->getId() + ":TD" + toString(getNextAutoIdSeq()));
6652 // inner cell content
6653 _Cells.back()->Group->setId(_Cells.back()->getId() + ":CELL");
6655 setupBackground(_Cells.back()->Background);
6656 _Cells.back()->setModulateGlobalColor(_Style.Current.GlobalColor);
6658 if (elm.hasNonEmptyAttribute("colspan"))
6659 fromString(elm.getAttribute("colspan"), _Cells.back()->ColSpan);
6660 if (elm.hasNonEmptyAttribute("rowspan"))
6661 fromString(elm.getAttribute("rowspan"), _Cells.back()->RowSpan);
6663 _Cells.back()->Align = _CellParams.back().Align;
6664 _Cells.back()->VAlign = _CellParams.back().VAlign;
6665 _Cells.back()->LeftMargin = _CellParams.back().LeftMargin;
6666 _Cells.back()->NoWrap = _CellParams.back().NoWrap;
6667 _Cells.back()->ColSpan = std::max(1, _Cells.back()->ColSpan);
6668 _Cells.back()->RowSpan = std::max(1, _Cells.back()->RowSpan);
6669 _Cells.back()->Height = _CellParams.back().Height;
6671 float temp;
6672 if (_Style.hasStyle("width"))
6674 // _Style.Width does not handle '%' unit currently
6675 if (_Style.Current.Width > 0)
6677 _Cells.back()->WidthWanted = _Style.Current.Width;
6678 _Cells.back()->TableRatio = 0;
6680 else
6682 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, _Style.getStyle("width").c_str());
6685 else if (elm.hasNonEmptyAttribute("width"))
6687 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, elm.getAttribute("width").c_str());
6690 _Cells.back()->NewLine = getTR();
6692 CSSRect<CSSBorder> border;
6693 border.Top.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6694 border.Right.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6695 border.Bottom.set(table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6696 border.Left.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6698 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6699 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6700 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6701 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6703 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6704 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6705 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6706 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6708 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6709 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6710 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6711 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6713 _Cells.back()->Border->setBorder(border);
6714 _Cells.back()->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6715 _Cells.back()->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6717 // padding from <table cellpadding="1">
6718 if (table->CellPadding)
6720 // FIXME: padding is ignored by vertical align
6721 _Cells.back()->PaddingTop = table->CellPadding;
6722 _Cells.back()->PaddingRight = table->CellPadding;
6723 _Cells.back()->PaddingBottom = table->CellPadding;
6724 _Cells.back()->PaddingLeft = table->CellPadding;
6727 if (_Style.hasStyle("padding-top")) _Cells.back()->PaddingTop = _Style.Current.PaddingTop;
6728 if (_Style.hasStyle("padding-right")) _Cells.back()->PaddingRight = _Style.Current.PaddingRight;
6729 if (_Style.hasStyle("padding-bottom")) _Cells.back()->PaddingBottom = _Style.Current.PaddingBottom;
6730 if (_Style.hasStyle("padding-left")) _Cells.back()->PaddingLeft = _Style.Current.PaddingLeft;
6732 table->addChild (_Cells.back());
6734 // reusing indent pushed by table
6735 _Indent.back() = 0;
6737 newParagraph(TDBeginSpace);
6738 // indent is already 0, getParagraph()->setMarginLeft(0); // maybe setIndent(0) if LI is using one
6740 // Reset TR flag
6741 if (!_TR.empty())
6742 _TR.back() = false;
6744 renderPseudoElement(":before", elm);
6747 void CGroupHTML::htmlTDend(const CHtmlElement &elm)
6749 renderPseudoElement(":after", elm);
6751 popIfNotEmpty(_CellParams);
6752 if (!_Cells.empty())
6753 _Cells.back() = NULL;
6756 // ***************************************************************************
6757 void CGroupHTML::htmlTEXTAREA(const CHtmlElement &elm)
6759 _IgnoreChildElements = true;
6761 // TODO: allow textarea without form
6762 if (_Forms.empty())
6763 return;
6765 // read general property
6766 string templateName;
6768 // Widget template name
6769 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
6770 templateName = elm.getAttribute("z_input_tmpl");
6772 // Get the string name
6773 _TextAreaName.clear();
6774 _TextAreaRow = 1;
6775 _TextAreaCols = 10;
6776 _TextAreaMaxLength = 1024;
6777 if (elm.hasNonEmptyAttribute("name"))
6778 _TextAreaName = elm.getAttribute("name");
6779 if (elm.hasNonEmptyAttribute("rows"))
6780 fromString(elm.getAttribute("rows"), _TextAreaRow);
6781 if (elm.hasNonEmptyAttribute("cols"))
6782 fromString(elm.getAttribute("cols"), _TextAreaCols);
6783 if (elm.hasNonEmptyAttribute("maxlength"))
6784 fromString(elm.getAttribute("maxlength"), _TextAreaMaxLength);
6786 _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup;
6788 std::string content = strFindReplaceAll(elm.serializeChilds(), std::string("\r"), std::string(""));
6790 CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, content, _TextAreaMaxLength);
6791 if (textArea)
6793 // Add the text area to the form
6794 CGroupHTML::CForm::CEntry entry;
6795 entry.Name = _TextAreaName;
6796 entry.TextArea = textArea;
6797 _Forms.back().Entries.push_back (entry);
6801 // ***************************************************************************
6802 void CGroupHTML::htmlTH(const CHtmlElement &elm)
6804 htmlTD(elm);
6807 void CGroupHTML::htmlTHend(const CHtmlElement &elm)
6809 htmlTDend(elm);
6812 // ***************************************************************************
6813 void CGroupHTML::htmlTITLE(const CHtmlElement &elm)
6815 _IgnoreChildElements = true;
6817 // TODO: only from <head>
6818 // if (!_ReadingHeadTag) return;
6820 // consume all child elements
6821 _TitleString = strFindReplaceAll(elm.serializeChilds(), std::string("\t"), std::string(" "));
6822 _TitleString = strFindReplaceAll(_TitleString, std::string("\n"), std::string(" "));
6823 setTitle(_TitleString);
6826 // ***************************************************************************
6827 void CGroupHTML::htmlTR(const CHtmlElement &elm)
6829 // prevent inheriting from table
6830 if (!_CellParams.empty())
6832 _CellParams.back().BgColor = CRGBA::Transparent;
6833 _CellParams.back().Height = 0;
6836 // Get cells parameters
6837 getCellsParameters(elm, true);
6839 // TODO: this probably ends up in first cell
6840 renderPseudoElement(":before", elm);
6842 // Set TR flag
6843 if (!_TR.empty())
6844 _TR.back() = true;
6847 void CGroupHTML::htmlTRend(const CHtmlElement &elm)
6849 // TODO: this probably ends up in last cell
6850 renderPseudoElement(":after", elm);
6852 popIfNotEmpty(_CellParams);
6855 // ***************************************************************************
6856 void CGroupHTML::htmlUL(const CHtmlElement &elm)
6858 if (_UL.empty())
6859 _UL.push_back(HTMLOListElement(1, "disc"));
6860 else if (_UL.size() == 1)
6861 _UL.push_back(HTMLOListElement(1, "circle"));
6862 else
6863 _UL.push_back(HTMLOListElement(1, "square"));
6865 // if LI is already present
6866 _LI = _UL.size() > 1 || _DL.size() > 1;
6867 _Indent.push_back(getIndent() + ULIndent);
6869 renderPseudoElement(":before", elm);
6872 void CGroupHTML::htmlULend(const CHtmlElement &elm)
6874 if (_UL.empty())
6875 return;
6877 renderPseudoElement(":after", elm);
6879 popIfNotEmpty(_UL);
6880 popIfNotEmpty(_Indent);