Use configured resolution for login/outgame/ingame
[ryzomcore.git] / nel / src / gui / group_html.cpp
blobc6a06b2aa3d81dd9ed0b98b2388ce51dbffb6d86
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;
3079 m_TableRowBackgroundColor.clear();
3081 paragraphChange ();
3083 // clear the pointer to the current image download since all the button are deleted
3084 LOG_DL("Clear pointers to %d curls", Curls.size());
3086 // remove image refs from downloads
3087 /*for(std::list<CDataDownload>::iterator it = Curls.begin(); it != Curls.end(); ++it)
3089 it->imgs.clear();
3093 // ***************************************************************************
3095 u32char CGroupHTML::getLastChar() const
3097 if (_CurrentViewLink)
3099 ::u32string str = CUtfStringView(_CurrentViewLink->getText()).toUtf32(); // FIXME: Optimize reverse UTF iteration
3100 if (!str.empty())
3101 return str[str.length()-1];
3103 return 0;
3106 // ***************************************************************************
3108 void CGroupHTML::paragraphChange ()
3110 _CurrentViewLink = NULL;
3111 _CurrentViewImage = NULL;
3112 CGroupParagraph *paragraph = getParagraph();
3113 if (paragraph)
3115 // Number of child in this paragraph
3116 uint numChild = paragraph->getNumChildren();
3117 if (numChild)
3119 // Get the last child
3120 CViewBase *child = paragraph->getChild(numChild-1);
3122 // Is this a string view ?
3123 _CurrentViewLink = dynamic_cast<CViewLink*>(child);
3124 _CurrentViewImage = dynamic_cast<CViewBitmap*>(child);
3129 // ***************************************************************************
3131 CInterfaceGroup *CGroupHTML::getCurrentGroup()
3133 if (!_Cells.empty() && _Cells.back())
3134 return _Cells.back()->Group;
3135 else
3136 return _GroupListAdaptor;
3139 // ***************************************************************************
3141 void CGroupHTML::addHtmlGroup (CInterfaceGroup *group, uint beginSpace)
3143 if (!group)
3144 return;
3146 registerAnchor(group);
3148 if (!_DivName.empty())
3150 group->setName(_DivName);
3151 _Groups.push_back(group);
3154 group->setSizeRef(CInterfaceElement::width);
3156 // Compute begin space between paragraph and tables
3157 // * If first in group, no begin space
3159 // Pointer on the current paragraph (can be a table too)
3160 CGroupParagraph *p = dynamic_cast<CGroupParagraph*>(group);
3162 CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
3163 const std::vector<CInterfaceGroup*> &groups = parentGroup->getGroups ();
3164 group->setParent(parentGroup);
3165 group->setParentSize(parentGroup);
3166 if (groups.empty())
3168 group->setParentPos(parentGroup);
3169 group->setPosRef(Hotspot_TL);
3170 group->setParentPosRef(Hotspot_TL);
3171 beginSpace = 0;
3173 else
3175 // Last is a paragraph ?
3176 group->setParentPos(groups.back());
3177 group->setPosRef(Hotspot_TL);
3178 group->setParentPosRef(Hotspot_BL);
3181 // Set the begin space
3182 if (p)
3183 p->setTopSpace(beginSpace);
3184 else
3185 group->setY(-(sint32)beginSpace);
3186 parentGroup->addGroup (group);
3189 // ***************************************************************************
3191 void CGroupHTML::setContainerTitle (const std::string &title)
3193 CInterfaceElement *parent = getParent();
3194 if (parent)
3196 if ((parent = parent->getParent()))
3198 CGroupContainer *container = dynamic_cast<CGroupContainer*>(parent);
3199 if (container)
3201 container->setTitle(title);
3207 void CGroupHTML::setTitle(const std::string &title)
3209 if(_TitlePrefix.empty())
3210 _TitleString = title;
3211 else
3212 _TitleString = _TitlePrefix + " - " + title;
3214 setContainerTitle(_TitleString);
3217 std::string CGroupHTML::getTitle() const {
3218 return _TitleString;
3221 // ***************************************************************************
3223 bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
3225 result = url;
3226 string tmp;
3228 if (toLowerAscii(result).find("file:") == 0 && result.size() > 5)
3230 result = result.substr(5, result.size()-5);
3232 else if (result.find("://") != string::npos || result.find("//") == 0)
3234 // http://, https://, etc or protocol-less url "//domain.com/image.png"
3235 return false;
3238 tmp = CPath::lookup (CFile::getFilename(result), false, false, false);
3239 if (tmp.empty())
3241 // try to find in local directory
3242 tmp = CPath::lookup (result, false, false, true);
3245 if (!tmp.empty())
3247 // Normalize the path
3248 if (isUrl)
3249 //result = "file:"+toLowerAscii(CPath::standardizePath (CPath::getFullPath (CFile::getPath(result)))+CFile::getFilename(result));*/
3250 result = "file:/"+tmp;
3251 else
3252 result = tmp;
3253 return true;
3255 else
3257 // Is it a texture in the big texture ?
3258 if (CViewRenderer::getInstance()->getTextureIdFromName (result) >= 0)
3260 return true;
3262 else
3264 // This is not a file in the CPath, let libwww open this URL
3265 result = url;
3266 return false;
3271 // ***************************************************************************
3273 void CGroupHTML::submitForm(uint button, sint32 x, sint32 y)
3275 if (button >= _FormSubmit.size())
3276 return;
3278 for(uint formId = 0; formId < _Forms.size(); formId++)
3280 // case sensitive search (user id is lowecase, auto id is uppercase)
3281 if (_Forms[formId].id == _FormSubmit[button].form)
3283 _PostNextTime = true;
3284 _PostFormId = formId;
3285 _PostFormAction = _FormSubmit[button].formAction;
3286 _PostFormSubmitType = _FormSubmit[button].type;
3287 _PostFormSubmitButton = _FormSubmit[button].name;
3288 _PostFormSubmitValue = _FormSubmit[button].value;
3289 _PostFormSubmitX = x;
3290 _PostFormSubmitY = y;
3292 return;
3296 nlwarning("Unable to find form '%s' to submit (button '%s')", _FormSubmit[button].form.c_str(), _FormSubmit[button].name.c_str());
3299 // ***************************************************************************
3301 void CGroupHTML::setupBackground(CSSBackgroundRenderer *bg)
3303 if (!bg) return;
3305 bg->setModulateGlobalColor(_Style.Current.GlobalColor);
3306 bg->setBackground(_Style.Current.Background);
3307 bg->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
3309 bg->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
3311 if (!_Style.Current.Background.image.empty())
3312 addTextureDownload(_Style.Current.Background.image, bg->TextureId, this);
3315 // ***************************************************************************
3317 void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
3319 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3320 CViewBase *view = getView (DefaultBackgroundBitmapView);
3321 if (view)
3322 view->setActive(false);
3324 m_HtmlBackground.setColor(bgcolor);
3327 // ***************************************************************************
3329 void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
3331 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3332 CViewBase *view = getView (DefaultBackgroundBitmapView);
3333 if (view)
3334 view->setActive(false);
3336 m_HtmlBackground.setImage(bgtex);
3337 m_HtmlBackground.setImageRepeat(tile);
3338 m_HtmlBackground.setImageCover(scale);
3340 if (!bgtex.empty())
3341 addTextureDownload(bgtex, m_HtmlBackground.TextureId, this);
3345 struct CButtonFreezer : public CInterfaceElementVisitor
3347 virtual void visitCtrl(CCtrlBase *ctrl)
3349 CCtrlBaseButton *textButt = dynamic_cast<CCtrlTextButton *>(ctrl);
3350 if (textButt)
3352 textButt->setFrozen(true);
3357 // ***************************************************************************
3359 void CGroupHTML::handle ()
3361 H_AUTO(RZ_Interface_Html_handle)
3363 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
3365 // handle curl downloads
3366 checkDownloads();
3368 // handle refresh timer
3369 if (_NextRefreshTime > 0 && _NextRefreshTime <= (times.thisFrameMs / 1000.0f) )
3371 // there might be valid uses for 0sec refresh, but two in a row is probably a mistake
3372 if (_NextRefreshTime - _LastRefreshTime >= 1.0)
3374 _LastRefreshTime = _NextRefreshTime;
3375 doBrowse(_RefreshUrl.c_str());
3377 else
3378 nlwarning("Ignore second 0sec http-equiv refresh in a row (url '%s')", _URL.c_str());
3380 _NextRefreshTime = 0;
3383 if (_CurlWWW)
3385 // still transfering html page
3386 if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
3388 browseError(("Connection timeout : "+_URL).c_str());
3391 else
3392 if (_RenderNextTime)
3394 _RenderNextTime = false;
3395 renderHtmlString(_DocumentHtml);
3397 else
3398 if (_WaitingForStylesheet)
3400 renderDocument();
3402 else
3403 if (_BrowseNextTime || _PostNextTime)
3405 // Set timeout
3406 _ConnectingTimeout = ( times.thisFrameMs / 1000.0f ) + _TimeoutValue;
3408 // freeze form buttons
3409 CButtonFreezer freezer;
3410 this->visit(&freezer);
3412 // Home ?
3413 if (_URL == "home")
3414 _URL = home();
3416 string finalUrl;
3417 bool isLocal = lookupLocalFile (finalUrl, _URL.c_str(), true);
3419 _URL = finalUrl;
3421 CUrlParser uri (_URL);
3422 _TrustedDomain = isTrustedDomain(uri.host);
3423 _DocumentDomain = uri.host;
3425 // file is probably from bnp (ingame help)
3426 if (isLocal)
3428 doBrowseLocalFile(finalUrl);
3430 else
3432 SFormFields formfields;
3433 if (_PostNextTime)
3435 buildHTTPPostParams(formfields);
3436 // _URL is set from form.Action
3437 finalUrl = _URL;
3439 else
3441 // Add custom get params from child classes
3442 addHTTPGetParams (finalUrl, _TrustedDomain);
3445 doBrowseRemoteUrl(finalUrl, "", _PostNextTime, formfields);
3448 _BrowseNextTime = false;
3449 _PostNextTime = false;
3453 // ***************************************************************************
3454 void CGroupHTML::buildHTTPPostParams (SFormFields &formfields)
3456 // Add text area text
3457 uint i;
3459 if (_PostFormId >= _Forms.size())
3461 nlwarning("(%s) invalid form index %d, _Forms %d", _Id.c_str(), _PostFormId, _Forms.size());
3462 return;
3464 // Ref the form
3465 CForm &form = _Forms[_PostFormId];
3467 // button can override form action url (and methor, but we only do POST)
3468 _URL = _PostFormAction.empty() ? form.Action : _PostFormAction;
3470 CUrlParser uri(_URL);
3471 _TrustedDomain = isTrustedDomain(uri.host);
3472 _DocumentDomain = uri.host;
3474 for (i=0; i<form.Entries.size(); i++)
3476 // Text area ?
3477 bool addEntry = false;
3478 string entryData;
3479 if (form.Entries[i].TextArea)
3481 // Get the edit box view
3482 CInterfaceGroup *group = form.Entries[i].TextArea->getGroup ("eb");
3483 if (group)
3485 // Should be a CGroupEditBox
3486 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
3487 if (editBox)
3489 entryData = editBox->getViewText()->getText();
3490 addEntry = true;
3494 else if (form.Entries[i].Checkbox)
3496 // todo handle unicode POST here
3497 if (form.Entries[i].Checkbox->getPushed ())
3499 entryData = form.Entries[i].Value;
3500 addEntry = true;
3503 else if (form.Entries[i].ComboBox)
3505 CDBGroupComboBox *cb = form.Entries[i].ComboBox;
3506 entryData = form.Entries[i].SelectValues[cb->getSelection()];
3507 addEntry = true;
3509 else if (form.Entries[i].SelectBox)
3511 CGroupMenu *sb = form.Entries[i].SelectBox;
3512 CGroupSubMenu *rootMenu = sb->getRootMenu();
3513 if (rootMenu)
3515 for(uint j=0; j<rootMenu->getNumLine(); ++j)
3517 CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
3518 if (ig)
3520 CCtrlBaseButton *cb = dynamic_cast<CCtrlBaseButton *>(ig->getCtrl("b"));
3521 if (cb && cb->getPushed())
3522 formfields.add(form.Entries[i].Name, form.Entries[i].SelectValues[j]);
3527 // This is a hidden value
3528 else
3530 entryData = form.Entries[i].Value;
3531 addEntry = true;
3534 // Add this entry
3535 if (addEntry)
3537 formfields.add(form.Entries[i].Name, entryData);
3541 if (_PostFormSubmitType == "image")
3543 // Add the button coordinates
3544 if (_PostFormSubmitButton.find_first_of("[") == string::npos)
3546 formfields.add(_PostFormSubmitButton + "_x", NLMISC::toString(_PostFormSubmitX));
3547 formfields.add(_PostFormSubmitButton + "_y", NLMISC::toString(_PostFormSubmitY));
3549 else
3551 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitX));
3552 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitY));
3555 else
3556 formfields.add(_PostFormSubmitButton, _PostFormSubmitValue);
3558 // Add custom params from child classes
3559 addHTTPPostParams(formfields, _TrustedDomain);
3562 // ***************************************************************************
3563 void CGroupHTML::doBrowseLocalFile(const std::string &uri)
3565 releaseDownloads();
3566 updateRefreshButton();
3568 std::string filename;
3569 if (toLowerAscii(uri).find("file:/") == 0)
3571 filename = uri.substr(6, uri.size() - 6);
3573 else
3575 filename = uri;
3578 LOG_DL("browse local file '%s'", filename.c_str());
3580 _TrustedDomain = true;
3581 _DocumentDomain = "localhost";
3583 CIFile in;
3584 if (in.open(filename))
3586 std::string html;
3587 while(!in.eof())
3589 char buf[1024];
3590 in.getline(buf, 1024);
3591 html += std::string(buf) + "\n";
3593 in.close();
3595 if (!renderHtmlString(html))
3597 browseError((string("Failed to parse html from file : ")+filename).c_str());
3600 else
3602 browseError((string("The page address is malformed : ")+filename).c_str());
3606 // ***************************************************************************
3607 void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
3609 // stop all downloads from previous page
3610 releaseDownloads();
3611 updateRefreshButton();
3613 // Reset the title
3614 if(_TitlePrefix.empty())
3615 setTitle (CI18N::get("uiPleaseWait"));
3616 else
3617 setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
3619 url = upgradeInsecureUrl(url);
3621 LOG_DL("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d",
3622 _Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size());
3624 if (!MultiCurl)
3626 browseError(string("Invalid MultCurl handle, loading url failed : "+url).c_str());
3627 return;
3630 CURL *curl = curl_easy_init();
3631 if (!curl)
3633 nlwarning("(%s) failed to create curl handle", _Id.c_str());
3634 browseError(string("Failed to create cURL handle : " + url).c_str());
3635 return;
3638 // https://
3639 if (toLowerAscii(url.substr(0, 8)) == "https://")
3641 // if supported, use custom SSL context function to load certificates
3642 NLWEB::CCurlCertificates::useCertificates(curl);
3645 // do not follow redirects, we have own handler
3646 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
3647 // after redirect
3648 curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
3650 // tell curl to use compression if possible (gzip, deflate)
3651 // leaving this empty allows all encodings that curl supports
3652 //curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
3654 // limit curl to HTTP and HTTPS protocols only
3655 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3656 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3658 // Destination
3659 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
3661 // User-Agent:
3662 std::string userAgent = options.appName + "/" + options.appVersion;
3663 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
3665 // Cookies
3666 sendCookies(curl, _DocumentDomain, _TrustedDomain);
3668 // Referer
3669 if (!referer.empty())
3671 curl_easy_setopt(curl, CURLOPT_REFERER, referer.c_str());
3672 LOG_DL("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
3675 if (doPost)
3677 // serialize form data and add it to curl
3678 std::string data;
3679 for(uint i=0; i<formfields.Values.size(); ++i)
3681 char * escapedName = curl_easy_escape(curl, formfields.Values[i].name.c_str(), formfields.Values[i].name.size());
3682 char * escapedValue = curl_easy_escape(curl, formfields.Values[i].value.c_str(), formfields.Values[i].value.size());
3684 if (i>0)
3685 data += "&";
3687 data += std::string(escapedName) + "=" + escapedValue;
3689 curl_free(escapedName);
3690 curl_free(escapedValue);
3692 curl_easy_setopt(curl, CURLOPT_POST, 1);
3693 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
3694 curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data.c_str());
3696 else
3698 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
3701 // transfer handle
3702 _CurlWWW = new CCurlWWWData(curl, url);
3704 // set the language code used by the client
3705 std::vector<std::string> headers;
3706 headers.push_back("Accept-Language: "+options.languageCode);
3707 headers.push_back("Accept-Charset: utf-8");
3708 _CurlWWW->sendHeaders(headers);
3710 // catch headers for redirect
3711 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
3712 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
3714 // catch body
3715 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NLGUI::curlDataCallback);
3716 curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
3718 #ifdef LOG_CURL_PROGRESS
3719 // progress callback
3720 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
3721 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, NLGUI::curlProgressCallback);
3722 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, _CurlWWW);
3723 #else
3724 // progress off
3725 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
3726 #endif
3729 curl_multi_add_handle(MultiCurl, curl);
3731 // start the transfer
3732 int NewRunningCurls = 0;
3733 curl_multi_perform(MultiCurl, &NewRunningCurls);
3734 RunningCurls++;
3736 _RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
3739 // ***************************************************************************
3740 void CGroupHTML::htmlDownloadFinished(bool success, const std::string &error)
3742 if (!success)
3744 CUrlParser uri(_CurlWWW->Url);
3746 // potentially unwanted chars
3747 std::string url = _CurlWWW->Url;
3748 url = strFindReplaceAll(url, string("<"), string("%3C"));
3749 url = strFindReplaceAll(url, string(">"), string("%3E"));
3750 url = strFindReplaceAll(url, string("\""), string("%22"));
3751 url = strFindReplaceAll(url, string("'"), string("%27"));
3753 std::string err;
3754 err = "<html><head><title>cURL error</title></head><body>";
3755 err += "<h1>Connection failed with cURL error</h1>";
3756 err += error;
3757 err += "<hr>(" + uri.scheme + "://" + uri.host + ") <a href=\"" + url + "\">reload</a>";
3758 err += "</body></html>";
3759 browseErrorHtml(err);
3760 return;
3763 // received content from remote
3764 std::string content = trim(_CurlWWW->Content);
3766 // save HSTS header from all requests regardless of HTTP code
3767 if (_CurlWWW->hasHSTSHeader())
3769 CUrlParser uri(_CurlWWW->Url);
3770 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
3773 receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
3775 long code;
3776 curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
3777 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());
3779 if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3781 if (_RedirectsRemaining < 0)
3783 browseError(string("Redirect limit reached : " + _URL).c_str());
3784 return;
3787 // redirect, get the location and try browse again
3788 // we cant use curl redirection because 'addHTTPGetParams()' must be called on new destination
3789 std::string location(_CurlWWW->getLocationHeader());
3790 if (location.empty())
3792 browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
3793 return;
3796 LOG_DL("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
3797 location = getAbsoluteUrl(location);
3799 _PostNextTime = false;
3800 _RedirectsRemaining--;
3802 doBrowse(location.c_str());
3804 else if ( (code < 200 || code >= 300) )
3806 // catches 304 not modified, but html is not in cache anyway
3807 // if server did not send any error back
3808 if (content.empty())
3810 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>");
3814 char *ch;
3815 std::string contentType;
3816 CURLcode res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
3817 if (res == CURLE_OK && ch != NULL)
3819 contentType = ch;
3822 htmlDownloadFinished(content, contentType, code);
3824 // clear curl handler
3825 if (MultiCurl)
3827 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
3830 delete _CurlWWW;
3831 _CurlWWW = NULL;
3833 // refresh button uses _CurlWWW. refresh button may stay disabled if
3834 // there is no css files to download and page is rendered before _CurlWWW is freed
3835 updateRefreshButton();
3838 void CGroupHTML::dataDownloadFinished(bool success, const std::string &error, CDataDownload *data)
3840 fclose(data->fp);
3842 CUrlParser uri(data->url);
3843 if (!uri.host.empty())
3845 receiveCookies(data->data->Request, uri.host, isTrustedDomain(uri.host));
3848 long code = -1;
3849 curl_easy_getinfo(data->data->Request, CURLINFO_RESPONSE_CODE, &code);
3851 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());
3852 curl_multi_remove_handle(MultiCurl, data->data->Request);
3854 // save HSTS header from all requests regardless of HTTP code
3855 if (success)
3857 if (data->data->hasHSTSHeader())
3859 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, data->data->getHSTSHeader());
3862 // 2XX success, 304 Not Modified
3863 if ((code >= 200 && code <= 204) || code == 304)
3865 CHttpCacheObject obj;
3866 obj.Expires = data->data->getExpires();
3867 obj.Etag = data->data->getEtag();
3868 obj.LastModified = data->data->getLastModified();
3870 CHttpCache::getInstance()->store(data->dest, obj);
3871 if (code == 304 && CFile::fileExists(data->tmpdest))
3873 CFile::deleteFile(data->tmpdest);
3876 else if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3878 if (data->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT)
3880 std::string location(data->data->getLocationHeader());
3881 if (!location.empty())
3883 CUrlParser uri(location);
3884 if (!uri.isAbsolute())
3886 uri.inherit(data->url);
3887 location = uri.toString();
3890 // clear old request state, and curl easy handle
3891 delete data->data;
3892 data->data = NULL;
3893 data->fp = NULL;
3894 data->url = location;
3895 data->redirects++;
3897 // push same request in the front of the queue
3898 // cache filename is based of original url
3899 Curls.push_front(data);
3901 LOG_DL("Redirect '%s'", location.c_str());
3902 // no finished callback called, so cleanup old temp
3903 if (CFile::fileExists(data->tmpdest))
3905 CFile::deleteFile(data->tmpdest);
3907 return;
3910 nlwarning("Redirected to empty url '%s'", data->url.c_str());
3912 else
3914 nlwarning("Redirect limit reached for '%s'", data->url.c_str());
3917 else
3919 nlwarning("HTTP request failed with code [%d] for '%s'\n",code, data->url.c_str());
3920 // 404, 500, etc
3921 if (CFile::fileExists(data->dest))
3923 CFile::deleteFile(data->dest);
3927 else
3929 nlwarning("DATA download failed '%s', error '%s'", data->url.c_str(), error.c_str());
3932 finishCurlDownload(data);
3935 void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
3937 LOG_DL("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
3939 // create <html> markup for image downloads
3940 if (type.find("image/") == 0 && !content.empty())
3944 std::string dest = localImageName(_URL);
3945 COFile out;
3946 out.open(dest);
3947 out.serialBuffer((uint8 *)(content.c_str()), content.size());
3948 out.close();
3949 LOG_DL("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
3951 catch(...) { }
3953 // create html code with image url inside and do the request again
3954 renderHtmlString("<html><head><title>"+_URL+"</title></head><body><img src=\"" + _URL + "\"></body></html>");
3956 else if (_TrustedDomain && type.find("text/lua") == 0)
3958 setTitle(_TitleString);
3960 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+content;
3961 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
3962 _LuaScript.clear();
3964 // disable refresh button
3965 clearRefresh();
3966 // disable redo into this url
3967 _AskedUrl.clear();
3969 else
3971 // Sanitize downloaded HTML UTF-8 encoding, and render
3972 renderHtmlString(CUtfStringView(content).toUtf8(true));
3976 // ***************************************************************************
3977 void CGroupHTML::cssDownloadFinished(const std::string &url, const std::string &local)
3979 for(std::vector<CHtmlParser::StyleLink>::iterator it = _StylesheetQueue.begin();
3980 it != _StylesheetQueue.end(); ++it)
3982 if (it->Url == url)
3984 // read downloaded file into HtmlStyles
3985 if (CFile::fileExists(local) && it->Index < _HtmlStyles.size())
3987 CIFile in;
3988 if (in.open(local))
3990 if (!in.readAll(_HtmlStyles[it->Index]))
3992 nlwarning("Failed to read downloaded css file(%s), url(%s)", local.c_str(), url.c_str());
3997 _StylesheetQueue.erase(it);
3998 break;
4003 void CGroupHTML::renderDocument()
4005 if (!Curls.empty() && !_StylesheetQueue.empty())
4007 // waiting for stylesheets to finish downloading
4008 return;
4010 _WaitingForStylesheet = false;
4012 //TGameTime renderStart = CTime::getLocalTime();
4014 // clear previous state and page
4015 beginBuild();
4016 removeContent();
4018 // process all <style> and <link rel=stylesheet> elements
4019 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4021 if (!_HtmlStyles[i].empty())
4023 _Style.parseStylesheet(_HtmlStyles[i]);
4026 _HtmlStyles.clear();
4028 std::list<CHtmlElement>::iterator it = _HtmlDOM.Children.begin();
4029 while(it != _HtmlDOM.Children.end())
4031 renderDOM(*it);
4032 ++it;
4035 endBuild();
4037 //TGameTime renderStop = CTime::getLocalTime();
4038 //nlwarning("[%s] render: %.1fms (%s)\n", _Id.c_str(), (renderStop - renderStart), _URL.c_str());
4041 // ***************************************************************************
4043 bool CGroupHTML::renderHtmlString(const std::string &html)
4045 bool success;
4047 // if we are already rendering, then queue up the next page
4048 if (_Browsing)
4050 _DocumentHtml = html;
4051 _RenderNextTime = true;
4053 return true;
4057 _DocumentUrl = _URL;
4058 _DocumentHtml = html;
4059 _NextRefreshTime = 0;
4060 _RefreshUrl.clear();
4062 if (trim(html).empty())
4064 // clear the page
4065 beginBuild();
4067 // clear previous page and state
4068 removeContent();
4070 endBuild();
4072 success = false;
4074 else
4076 // browser.css
4077 resetCssStyle();
4079 // start new rendering
4080 _HtmlDOM = CHtmlElement(CHtmlElement::NONE, "<root>");
4081 _CurrentHTMLElement = NULL;
4082 success = parseHtml(html);
4083 if (success)
4085 _WaitingForStylesheet = !_StylesheetQueue.empty();
4086 renderDocument();
4088 else
4090 std::string error = "ERROR: HTML parse failed.";
4091 error += toString("\nsize %d bytes", html.size());
4092 error += toString("\n---start---\n%s\n---end---\n", html.c_str());
4093 browseError(error.c_str());
4097 return success;
4100 // ***************************************************************************
4101 void CGroupHTML::doBrowseAnchor(const std::string &anchor)
4103 if (_Anchors.count(anchor) == 0)
4105 return;
4108 CInterfaceElement *pIE = _Anchors.find(anchor)->second;
4109 if (pIE)
4111 // hotspot depends on vertical/horizontal scrollbar
4112 CCtrlScroll *pSB = getScrollBar();
4113 if (pSB)
4115 pSB->ensureVisible(pIE, Hotspot_Tx, Hotspot_Tx);
4120 // ***************************************************************************
4122 void CGroupHTML::draw ()
4124 uint8 CurrentAlpha = 255;
4125 // search a parent container
4126 CInterfaceGroup *gr = getParent();
4127 while (gr)
4129 if (gr->isGroupContainer())
4131 CGroupContainer *gc = static_cast<CGroupContainer *>(gr);
4132 CurrentAlpha = gc->getCurrentContainerAlpha();
4133 break;
4135 gr = gr->getParent();
4137 m_HtmlBackground.CurrentAlpha = CurrentAlpha;
4138 m_BodyBackground.CurrentAlpha = CurrentAlpha;
4140 m_HtmlBackground.draw();
4141 m_BodyBackground.draw();
4142 CGroupScrollText::draw ();
4145 // ***************************************************************************
4147 void CGroupHTML::beginBuild ()
4149 _Browsing = true;
4152 void CGroupHTML::endBuild ()
4154 // set the browser as complete
4155 _Browsing = false;
4156 updateRefreshButton();
4158 // check that the title is set, or reset it (in the case the page
4159 // does not provide a title)
4160 if (_TitleString.empty())
4162 setTitle(_TitlePrefix);
4165 invalidateCoords();
4168 // ***************************************************************************
4170 void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
4174 // ***************************************************************************
4176 void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
4180 // ***************************************************************************
4182 string CGroupHTML::home () const
4184 return Home;
4187 // ***************************************************************************
4189 void CGroupHTML::removeContent ()
4191 // Remove old document
4192 if (!_GroupListAdaptor)
4194 _GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
4195 _GroupListAdaptor->setId(getList()->getId() + ":GLA");
4196 _GroupListAdaptor->setResizeFromChildH(true);
4197 getList()->addChild (_GroupListAdaptor, true);
4200 // Group list adaptor not exist ?
4201 _GroupListAdaptor->clearGroups();
4202 _GroupListAdaptor->clearControls();
4203 _GroupListAdaptor->clearViews();
4204 CWidgetManager::getInstance()->clearViewUnders();
4205 CWidgetManager::getInstance()->clearCtrlsUnders();
4207 // Clear all the context
4208 clearContext();
4210 // Reset default background
4211 m_HtmlBackground.clear();
4212 m_BodyBackground.clear();
4214 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
4215 CViewBase *view = getView (DefaultBackgroundBitmapView);
4216 if (view)
4217 view->setActive(false);
4219 paragraphChange ();
4222 // ***************************************************************************
4223 const std::string &CGroupHTML::selectTreeNodeRecurs(CGroupTree::SNode *node, const std::string &url)
4225 static std::string emptyString;
4226 if(!node)
4228 return emptyString;
4231 // if this node match
4232 if(actionLaunchUrlRecurs(node->AHName, node->AHParams, url))
4234 return node->Id;
4236 // fails => look into children
4237 else
4239 for(uint i=0;i<node->Children.size();i++)
4241 const string &childRes= selectTreeNodeRecurs(node->Children[i], url);
4242 if(!childRes.empty())
4243 return childRes;
4246 // none match...
4247 return emptyString;
4251 // ***************************************************************************
4252 bool CGroupHTML::actionLaunchUrlRecurs(const std::string &ah, const std::string &params, const std::string &url)
4254 // check if this action match
4255 if( (ah=="launch_help" || ah=="browse") && IActionHandler::getParam (params, "url") == url)
4257 return true;
4259 // can be a proc that contains launch_help/browse => look recurs
4260 else if(ah=="proc")
4262 const std::string &procName= params;
4263 // look into this proc
4264 uint numActions= CWidgetManager::getInstance()->getParser()->getProcedureNumActions(procName);
4265 for(uint i=0;i<numActions;i++)
4267 string procAh, procParams;
4268 if( CWidgetManager::getInstance()->getParser()->getProcedureAction(procName, i, procAh, procParams))
4270 // recurs proc if needed!
4271 if (actionLaunchUrlRecurs(procAh, procParams, url))
4272 return true;
4277 return false;
4280 // ***************************************************************************
4281 void CGroupHTML::clearRefresh()
4283 _URL.clear();
4284 updateRefreshButton();
4287 // ***************************************************************************
4288 void CGroupHTML::clearUndoRedo()
4290 // erase any undo/redo
4291 _BrowseUndo.clear();
4292 _BrowseRedo.clear();
4294 // update buttons validation
4295 updateUndoRedoButtons();
4298 // ***************************************************************************
4299 void CGroupHTML::pushUrlUndoRedo(const std::string &url)
4301 // if same url, no op
4302 if(url==_AskedUrl)
4303 return;
4305 // erase any redo, push undo, set current
4306 _BrowseRedo.clear();
4307 if(!_AskedUrl.empty())
4308 _BrowseUndo.push_back(_AskedUrl);
4309 _AskedUrl= url;
4311 // limit undo
4312 while(_BrowseUndo.size()>MaxUrlUndoRedo)
4313 _BrowseUndo.pop_front();
4315 // update buttons validation
4316 updateUndoRedoButtons();
4319 // ***************************************************************************
4320 void CGroupHTML::browseUndo()
4322 if(_BrowseUndo.empty())
4323 return;
4325 // push to redo, pop undo, and set current
4326 if (!_AskedUrl.empty())
4327 _BrowseRedo.push_front(_AskedUrl);
4329 _AskedUrl= _BrowseUndo.back();
4330 _BrowseUndo.pop_back();
4332 // update buttons validation
4333 updateUndoRedoButtons();
4335 // and then browse the undoed url, with no undo/redo
4336 doBrowse(_AskedUrl.c_str());
4339 // ***************************************************************************
4340 void CGroupHTML::browseRedo()
4342 if(_BrowseRedo.empty())
4343 return;
4345 // push to undo, pop redo, and set current
4346 _BrowseUndo.push_back(_AskedUrl);
4347 _AskedUrl= _BrowseRedo.front();
4348 _BrowseRedo.pop_front();
4350 // update buttons validation
4351 updateUndoRedoButtons();
4353 // and then browse the redoed url, with no undo/redo
4354 doBrowse(_AskedUrl.c_str());
4357 // ***************************************************************************
4358 void CGroupHTML::updateUndoRedoButtons()
4360 CCtrlBaseButton *butUndo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
4361 CCtrlBaseButton *butRedo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRedoButton));
4363 // gray according to list size
4364 if(butUndo)
4365 butUndo->setFrozen(_BrowseUndo.empty());
4366 if(butRedo)
4367 butRedo->setFrozen(_BrowseRedo.empty());
4370 // ***************************************************************************
4371 void CGroupHTML::updateRefreshButton()
4373 CCtrlBaseButton *butRefresh = dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
4374 if(butRefresh)
4376 // connecting, rendering, or is missing url
4377 bool frozen = _CurlWWW || _Browsing || _URL.empty();
4378 butRefresh->setFrozen(frozen);
4382 // ***************************************************************************
4384 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
4386 CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam &param)
4387 : CInterfaceGroup(param),
4388 Offset(0)
4392 xmlNodePtr CGroupHTMLInputOffset::serialize( xmlNodePtr parentNode, const char *type ) const
4394 xmlNodePtr node = CInterfaceGroup::serialize( parentNode, type );
4395 if( node == NULL )
4396 return NULL;
4398 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html_input_offset" );
4399 xmlSetProp( node, BAD_CAST "y_offset", BAD_CAST toString( Offset ).c_str() );
4401 return node;
4404 // ***************************************************************************
4405 bool CGroupHTMLInputOffset::parse(xmlNodePtr cur, CInterfaceGroup *parentGroup)
4407 if (!CInterfaceGroup::parse(cur, parentGroup)) return false;
4408 CXMLAutoPtr ptr;
4409 // Get the url
4410 ptr = xmlGetProp (cur, (xmlChar*)"y_offset");
4411 if (ptr)
4412 fromString((const char*)ptr, Offset);
4413 return true;
4416 // ***************************************************************************
4417 int CGroupHTML::luaParseHtml(CLuaState &ls)
4419 const char *funcName = "parseHtml";
4420 CLuaIHM::checkArgCount(ls, funcName, 1);
4421 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4422 std::string html = ls.toString(1);
4424 parseHtml(html);
4426 return 0;
4429 int CGroupHTML::luaClearRefresh(CLuaState &ls)
4431 const char *funcName = "clearRefresh";
4432 CLuaIHM::checkArgCount(ls, funcName, 0);
4434 clearRefresh();
4436 return 0;
4439 int CGroupHTML::luaClearUndoRedo(CLuaState &ls)
4441 const char *funcName = "clearUndoRedo";
4442 CLuaIHM::checkArgCount(ls, funcName, 0);
4444 clearUndoRedo();
4445 return 0;
4448 // ***************************************************************************
4449 int CGroupHTML::luaBrowse(CLuaState &ls)
4451 const char *funcName = "browse";
4452 CLuaIHM::checkArgCount(ls, funcName, 1);
4453 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4454 browse(ls.toString(1));
4455 return 0;
4458 // ***************************************************************************
4459 int CGroupHTML::luaRefresh(CLuaState &ls)
4461 const char *funcName = "refresh";
4462 CLuaIHM::checkArgCount(ls, funcName, 0);
4463 refresh();
4464 return 0;
4467 // ***************************************************************************
4468 int CGroupHTML::luaRemoveContent(CLuaState &ls)
4470 const char *funcName = "removeContent";
4471 CLuaIHM::checkArgCount(ls, funcName, 0);
4472 removeContent();
4473 return 0;
4476 // ***************************************************************************
4477 int CGroupHTML::luaRenderHtml(CLuaState &ls)
4479 const char *funcName = "renderHtml";
4480 CLuaIHM::checkArgCount(ls, funcName, 1);
4481 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4482 std::string html = ls.toString(1);
4484 // Always trust domain if rendered from lua
4485 _TrustedDomain = true;
4486 renderHtmlString(html);
4488 return 0;
4491 // ***************************************************************************
4492 int CGroupHTML::luaSetBackground(CLuaState &ls)
4494 const char *funcName = "setBackground";
4495 CLuaIHM::checkArgCount(ls, funcName, 3);
4496 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4497 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4498 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4499 std::string image = ls.toString(1);
4500 bool scale = ls.toBoolean(2);
4501 bool repeat = ls.toBoolean(3);
4503 setBackground(image, scale, repeat);
4505 return 0;
4508 // ***************************************************************************
4509 int CGroupHTML::luaInsertText(CLuaState &ls)
4511 const char *funcName = "insertText";
4512 CLuaIHM::checkArgCount(ls, funcName, 3);
4513 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4514 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TSTRING);
4515 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4517 string name = ls.toString(1);
4518 string text = ls.toString(2);
4520 if (!_Forms.empty())
4522 for (uint i=0; i<_Forms.back().Entries.size(); i++)
4524 if (_Forms.back().Entries[i].TextArea && _Forms.back().Entries[i].Name == name)
4526 // Get the edit box view
4527 CInterfaceGroup *group = _Forms.back().Entries[i].TextArea->getGroup ("eb");
4528 if (group)
4530 // Should be a CGroupEditBox
4531 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
4532 if (editBox)
4533 editBox->writeString(text, false, ls.toBoolean(3));
4539 return 0;
4542 // ***************************************************************************
4543 int CGroupHTML::luaAddString(CLuaState &ls)
4545 const char *funcName = "addString";
4546 CLuaIHM::checkArgCount(ls, funcName, 1);
4547 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4548 addString(ls.toString(1));
4549 return 0;
4552 // ***************************************************************************
4553 int CGroupHTML::luaAddImage(CLuaState &ls)
4555 const char *funcName = "addImage";
4556 CLuaIHM::checkArgCount(ls, funcName, 2);
4557 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4558 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4559 if (!_Paragraph)
4561 newParagraph(0);
4562 paragraphChange();
4565 CStyleParams style;
4566 style.GlobalColor = ls.toBoolean(2);
4568 string url = getLink();
4569 if (!url.empty())
4571 string params = "name=" + getId() + "|url=" + getLink ();
4572 addButton(CCtrlButton::PushButton, "", ls.toString(1), ls.toString(1),
4573 "", "browse", params.c_str(), "", style);
4575 else
4577 addImage("", ls.toString(1), false, style);
4581 return 0;
4584 // ***************************************************************************
4585 int CGroupHTML::luaShowDiv(CLuaState &ls)
4587 const char *funcName = "showDiv";
4588 CLuaIHM::checkArgCount(ls, funcName, 2);
4589 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4590 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4592 if (!_Groups.empty())
4594 for (uint i=0; i<_Groups.size(); i++)
4596 CInterfaceGroup *group = _Groups[i];
4597 if (group->getName() == ls.toString(1))
4599 group->setActive(ls.toBoolean(2));
4603 return 0;
4606 // ***************************************************************************
4607 void CGroupHTML::setURL(const std::string &url)
4609 browse(url.c_str());
4612 void CGroupHTML::setHTML(const std::string &html)
4614 renderHtmlString(html);
4617 void CGroupHTML::setHome(const std::string &home)
4619 Home = home;
4622 // ***************************************************************************
4623 void CGroupHTML::parseStylesheetFile(const std::string &fname)
4625 CIFile css;
4626 if (css.open(fname))
4628 uint32 remaining = css.getFileSize();
4629 std::string content;
4630 try {
4631 while(!css.eof() && remaining > 0)
4633 const uint BUF_SIZE = 4096;
4634 char buf[BUF_SIZE];
4636 uint32 readJustNow = std::min(remaining, BUF_SIZE);
4637 css.serialBuffer((uint8 *)&buf, readJustNow);
4638 content.append(buf, readJustNow);
4639 remaining -= readJustNow;
4642 _Style.parseStylesheet(content);
4644 catch(const Exception &e)
4646 nlwarning("exception while reading css file '%s'", e.what());
4649 else
4651 nlwarning("Stylesheet file '%s' not found (%s)", fname.c_str(), _URL.c_str());
4655 // ***************************************************************************
4656 bool CGroupHTML::parseHtml(const std::string &htmlString)
4658 CHtmlElement *parsedDOM;
4659 if (_CurrentHTMLElement == NULL)
4661 // parse under <root> element (clean dom)
4662 parsedDOM = &_HtmlDOM;
4664 else
4666 // parse under currently rendered <lua> element
4667 parsedDOM = _CurrentHTMLElement;
4670 std::vector<CHtmlParser::StyleLink> links;
4672 CHtmlParser parser;
4673 parser.getDOM(htmlString, *parsedDOM, _HtmlStyles, links);
4675 // <link> elements inserted from lua::parseHtml are ignored
4676 if (_CurrentHTMLElement == NULL && !links.empty())
4678 addStylesheetDownload(links);
4680 else if (_CurrentHTMLElement != NULL)
4682 // Called from active element (lua)
4683 // <style> order is not preserved as document is already being rendered
4684 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4686 if (!_HtmlStyles[i].empty())
4688 _Style.parseStylesheet(_HtmlStyles[i]);
4691 _HtmlStyles.clear();
4694 // this should rarely fail as first element should be <html>
4695 bool success = parsedDOM->Children.size() > 0;
4697 std::list<CHtmlElement>::iterator it = parsedDOM->Children.begin();
4698 while(it != parsedDOM->Children.end())
4700 if (it->Type == CHtmlElement::ELEMENT_NODE && it->Value == "html")
4702 // move newly parsed childs from <body> into siblings
4703 if (_CurrentHTMLElement) {
4704 std::list<CHtmlElement>::iterator it2 = it->Children.begin();
4705 while(it2 != it->Children.end())
4707 if (it2->Type == CHtmlElement::ELEMENT_NODE && it2->Value == "body")
4709 spliceFragment(it2);
4710 break;
4712 ++it2;
4714 // remove <html> fragment from current element child
4715 it = parsedDOM->Children.erase(it);
4717 else
4719 // remove link to <root> (html->parent == '<root>') or css selector matching will break
4720 it->parent = NULL;
4721 ++it;
4723 continue;
4726 // skip over other non-handled element
4727 ++it;
4730 return success;
4733 void CGroupHTML::spliceFragment(std::list<CHtmlElement>::iterator src)
4735 if(!_CurrentHTMLElement->parent)
4737 nlwarning("BUG: Current node is missing parent element. unable to splice fragment");
4738 return;
4741 // get the iterators for current element (<lua>) and next sibling
4742 std::list<CHtmlElement>::iterator currentElement;
4743 currentElement = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLElement);
4744 if (currentElement == _CurrentHTMLElement->parent->Children.end())
4746 nlwarning("BUG: unable to find current element iterator from parent");
4747 return;
4750 // where fragment should be moved
4751 std::list<CHtmlElement>::iterator insertBefore;
4752 if (_CurrentHTMLNextSibling == NULL)
4754 insertBefore = _CurrentHTMLElement->parent->Children.end();
4755 } else {
4756 // get iterator for nextSibling
4757 insertBefore = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLNextSibling);
4760 _CurrentHTMLElement->parent->Children.splice(insertBefore, src->Children);
4762 // reindex moved elements
4763 CHtmlElement *prev = NULL;
4764 uint childIndex = _CurrentHTMLElement->childIndex;
4765 while(currentElement != _CurrentHTMLElement->parent->Children.end())
4767 if (currentElement->Type == CHtmlElement::ELEMENT_NODE)
4769 if (prev != NULL)
4771 currentElement->parent = _CurrentHTMLElement->parent;
4772 currentElement->childIndex = childIndex;
4773 currentElement->previousSibling = prev;
4774 prev->nextSibling = &(*currentElement);
4777 childIndex++;
4778 prev = &(*currentElement);
4780 ++currentElement;
4784 // ***************************************************************************
4785 inline bool isDigit(char c, uint base = 16)
4787 if (c>='0' && c<='9') return true;
4788 if (base != 16) return false;
4789 if (c>='A' && c<='F') return true;
4790 if (c>='a' && c<='f') return true;
4791 return false;
4794 // ***************************************************************************
4795 inline char convertHexDigit(char c)
4797 if (c>='0' && c<='9') return c-'0';
4798 if (c>='A' && c<='F') return c-'A'+10;
4799 if (c>='a' && c<='f') return c-'a'+10;
4800 return 0;
4803 // ***************************************************************************
4804 std::string CGroupHTML::decodeHTMLEntities(const std::string &str)
4806 std::string result;
4807 result.reserve(str.size() + (str.size() >> 2));
4808 uint last, pos;
4810 for (uint i=0; i<str.length(); ++i)
4812 // HTML entity
4813 if (str[i] == '&' && (str.length()-i) >= 4)
4815 pos = i+1;
4817 // unicode character
4818 if (str[pos] == '#')
4820 ++pos;
4822 // using decimal by default
4823 uint base = 10;
4825 // using hexadecimal if &#x
4826 if (str[pos] == 'x')
4828 base = 16;
4829 ++pos;
4832 // setup "last" to point at the first character following "&#x?[0-9a-f]+"
4833 for (last = pos; last < str.length(); ++last) if (!isDigit(str[last], base)) break;
4835 // make sure that at least 1 digit was found
4836 // and have the terminating ';' to complete the token: "&#x?[0-9a-f]+;"
4837 if (last == pos || str[last] != ';')
4839 result += str[i];
4840 continue;
4843 u32char c = 0;
4845 // convert digits to unicode character
4846 while (pos < last) c = convertHexDigit(str[pos++]) + (c * u32char(base));
4848 // append our new character to the result string
4849 CUtfStringView::append(result, c);
4851 // move 'i' forward to point at the ';' .. the for(...) will increment i to point to next char
4852 i = last;
4854 continue;
4857 // special xml characters
4858 if (str.substr(i+1,5)=="quot;") { i+=5; result+='\"'; continue; }
4859 if (str.substr(i+1,4)=="amp;") { i+=4; result+='&'; continue; }
4860 if (str.substr(i+1,3)=="lt;") { i+=3; result+='<'; continue; }
4861 if (str.substr(i+1,3)=="gt;") { i+=3; result+='>'; continue; }
4864 // all the special cases are catered for... treat this as a normal character
4865 result += str[i];
4868 return result;
4871 // ***************************************************************************
4872 std::string CGroupHTML::getAbsoluteUrl(const std::string &url)
4874 CUrlParser uri(url);
4875 if (uri.isAbsolute())
4876 return url;
4878 uri.inherit(_URL);
4880 return uri.toString();
4883 // ***************************************************************************
4884 void CGroupHTML::resetCssStyle()
4886 _WaitingForStylesheet = false;
4887 _StylesheetQueue.clear();
4888 _Style.reset();
4889 _Style = _BrowserStyle;
4892 // ***************************************************************************
4893 std::string CGroupHTML::HTMLOListElement::getListMarkerText() const
4895 std::string ret;
4896 sint32 number = Value;
4898 if (Type == "disc")
4900 // (ucchar)0x2219;
4901 ret = "\xe2\x88\x99 ";
4903 else if (Type == "circle")
4905 // (uchar)0x26AA;
4906 ret = "\xe2\x9a\xaa ";
4908 else if (Type == "square")
4910 // (ucchar)0x25AA;
4911 ret = "\xe2\x96\xaa ";
4913 else if (Type == "a" || Type == "A")
4915 // @see toAlphabeticOrNumeric in WebKit
4916 static const char lower[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
4917 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
4918 static const char upper[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
4919 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
4920 uint size = 26;
4921 if (number < 1)
4923 ret = toString(number);
4925 else
4927 const char* digits = (Type == "A" ? upper : lower);
4928 while(number > 0)
4930 --number;
4931 ret.insert(ret.begin(), digits[number % size]);
4932 number /= size;
4935 ret += ". ";
4937 else if (Type == "i" || Type == "I")
4939 // @see toRoman in WebKit
4940 static const char lower[7] = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
4941 static const char upper[7] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'};
4943 if (number < 1 || number > 3999)
4945 ret = toString(number);
4947 else
4949 const char* digits = (Type == "I" ? upper : lower);
4950 uint8 i, d=0;
4953 uint32 num = number % 10;
4954 if (num % 5 < 4)
4956 for (i = num % 5; i > 0; i--)
4958 ret.insert(ret.begin(), digits[d]);
4961 if (num >= 4 && num <= 8)
4963 ret.insert(ret.begin(), digits[d + 1]);
4965 if (num == 9)
4967 ret.insert(ret.begin(), digits[d + 2]);
4969 if (num % 5 == 4)
4971 ret.insert(ret.begin(), digits[d]);
4973 number /= 10;
4974 d += 2;
4976 while (number > 0);
4978 if (Type == "I")
4980 ret = toUpper(ret);
4983 ret += ". ";
4985 else
4987 ret = toString(Value) + ". ";
4990 return ret;
4993 void CGroupHTML::HTMLMeterElement::readValues(const CHtmlElement &elm)
4995 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
4996 value = 0.f;
4997 if (!elm.hasAttribute("min") || !fromString(elm.getAttribute("min"), min))
4998 min = 0.f;
4999 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5000 max = 1.f;
5002 // ensure min < max
5003 if (max < min)
5004 std::swap(min, max);
5006 if (!elm.hasAttribute("low") || !fromString(elm.getAttribute("low"), low))
5007 low = min;
5008 if (!elm.hasAttribute("high") || !fromString(elm.getAttribute("high"), high))
5009 high = max;
5011 if (!elm.hasAttribute("optimum") || !fromString(elm.getAttribute("optimum"), optimum))
5012 optimum = (max - min) / 2.f;
5014 // ensure low < high
5015 if (high < low)
5016 std::swap(low, high);
5017 if (low < min)
5018 low = min;
5019 if (high > max)
5020 high = max;
5023 float CGroupHTML::HTMLMeterElement::getValueRatio() const
5025 if (max <= min)
5026 return 0.f;
5028 return (value - min) / (max - min);
5031 CGroupHTML::HTMLMeterElement::EValueRegion CGroupHTML::HTMLMeterElement::getValueRegion() const
5033 if (optimum <= low)
5035 // low region is optimum
5036 if (value <= low)
5037 return VALUE_OPTIMUM;
5038 else if (value <= high)
5039 return VALUE_SUB_OPTIMAL;
5041 return VALUE_EVEN_LESS_GOOD;
5043 else if (optimum >= high)
5045 // high region is optimum
5046 if (value >= high)
5047 return VALUE_OPTIMUM;
5048 else if (value >= low)
5049 return VALUE_SUB_OPTIMAL;
5051 return VALUE_EVEN_LESS_GOOD;
5054 // middle region is optimum
5055 if (value >= low && value <= high)
5056 return VALUE_OPTIMUM;
5058 return VALUE_SUB_OPTIMAL;
5061 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5063 // color meter (inactive) bar segment
5064 // firefox:: meter { background:none; background-color: #555; },
5065 // webkit:: meter::-webkit-meter-bar { background:none; background-color: #555; }
5066 // webkit makes background color visible when padding is added
5067 CRGBA color(150, 150, 150, 255);
5069 // use webkit pseudo elements as thats easier than firefox pseudo classes
5070 // background-color is expected to be set from browser.css
5071 style.pushStyle();
5072 style.applyStyle(elm.getPseudo(":-webkit-meter-bar"));
5073 if(style.hasStyle("background-color"))
5074 color = style.Current.Background.color;
5075 style.popStyle();
5077 return color;
5080 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5082 // background-color is expected to be set from browser.css
5083 CRGBA color;
5084 style.pushStyle();
5085 switch(getValueRegion())
5087 case VALUE_OPTIMUM:
5089 style.applyStyle(elm.getPseudo(":-webkit-meter-optimum-value"));
5090 if (style.hasStyle("background-color"))
5091 color = style.Current.Background.color;
5092 break;
5094 case VALUE_SUB_OPTIMAL:
5096 style.applyStyle(elm.getPseudo(":-webkit-meter-suboptimum-value"));
5097 if (style.hasStyle("background-color"))
5098 color = style.Current.Background.color;
5099 break;
5101 case VALUE_EVEN_LESS_GOOD: // fall through
5102 default:
5104 style.applyStyle(elm.getPseudo(":-webkit-meter-even-less-good-value"));
5105 if (style.hasStyle("background-color"))
5106 color = style.Current.Background.color;
5107 break;
5109 }//switch
5110 style.popStyle();
5112 return color;
5115 // ****************************************************************************
5116 void CGroupHTML::HTMLProgressElement::readValues(const CHtmlElement &elm)
5118 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5119 value = 0.f;
5120 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5121 max = 1.f;
5123 if (value > max)
5124 value = max;
5127 // ****************************************************************************
5128 float CGroupHTML::HTMLProgressElement::getValueRatio() const
5130 if (max > 0.f)
5131 return value / max;
5132 return 0.f;
5135 // ****************************************************************************
5136 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5138 CRGBA color;
5140 style.pushStyle();
5141 style.applyStyle(elm.getPseudo(":-webkit-progress-bar"));
5142 if (style.hasStyle("background-color"))
5143 color = style.Current.Background.color;
5144 style.popStyle();
5146 return color;
5149 // ****************************************************************************
5150 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5152 CRGBA color;
5154 style.pushStyle();
5155 style.applyStyle(elm.getPseudo(":-webkit-progress-value"));
5156 if (style.hasStyle("background-color"))
5157 color = style.Current.Background.color;
5158 style.popStyle();
5160 return color;
5163 // ****************************************************************************
5164 void CGroupHTML::getCellsParameters(const CHtmlElement &elm, bool inherit)
5166 CGroupHTML::CCellParams cellParams;
5167 if (!_CellParams.empty() && inherit)
5168 cellParams = _CellParams.back();
5170 if (!_Style.hasStyle("background-color") && elm.hasNonEmptyAttribute("bgcolor"))
5172 CRGBA c;
5173 if (scanHTMLColor(elm.getAttribute("bgcolor").c_str(), c))
5174 _Style.Current.Background.color = c;
5176 cellParams.BgColor = _Style.Current.Background.color;
5178 if (elm.hasAttribute("nowrap") || _Style.Current.WhiteSpace == "nowrap")
5179 cellParams.NoWrap = true;
5181 if (elm.hasNonEmptyAttribute("l_margin"))
5182 fromString(elm.getAttribute("l_margin"), cellParams.LeftMargin);
5184 if (_Style.hasStyle("height"))
5185 cellParams.Height = _Style.Current.Height;
5186 else if (elm.hasNonEmptyAttribute("height"))
5187 fromString(elm.getAttribute("height"), cellParams.Height);
5190 std::string align;
5191 // having text-align on table/tr should not override td align attribute
5192 if (_Style.hasStyle("text-align"))
5193 align = _Style.Current.TextAlign;
5194 else if (elm.hasNonEmptyAttribute("align"))
5195 align = toLowerAscii(elm.getAttribute("align"));
5197 if (align == "left")
5198 cellParams.Align = CGroupCell::Left;
5199 else if (align == "center")
5200 cellParams.Align = CGroupCell::Center;
5201 else if (align == "right")
5202 cellParams.Align = CGroupCell::Right;
5203 else if (align != "justify")
5204 align.clear();
5206 // copy td align (can be empty) attribute back into css
5207 _Style.Current.TextAlign = align;
5211 std::string valign;
5212 if (_Style.hasStyle("vertical-align"))
5213 valign = _Style.Current.VerticalAlign;
5214 else if (elm.hasNonEmptyAttribute("valign"))
5215 valign = toLowerAscii(elm.getAttribute("valign"));
5217 if (valign == "top")
5218 cellParams.VAlign = CGroupCell::Top;
5219 else if (valign == "middle")
5220 cellParams.VAlign = CGroupCell::Middle;
5221 else if (valign == "bottom")
5222 cellParams.VAlign = CGroupCell::Bottom;
5225 _CellParams.push_back (cellParams);
5228 // ***************************************************************************
5229 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)
5231 _FormSubmit.push_back(SFormSubmitButton(formId, name, "", "image", action));
5232 // Action handler parameters
5233 std::string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5235 // Add the ctrl button
5236 addButton (CCtrlButton::PushButton, name, src, src, over, "html_submit_form", param.c_str(), tooltip.c_str(), _Style.Current);
5239 // ***************************************************************************
5240 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)
5242 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "submit", formAction));
5243 // Action handler parameters
5244 string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5246 // Add the ctrl button
5247 if (!_Paragraph)
5249 newParagraph (0);
5250 paragraphChange ();
5253 string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup);
5254 typedef pair<string, string> TTmplParam;
5255 vector<TTmplParam> tmplParams;
5256 tmplParams.push_back(TTmplParam("id", name));
5257 tmplParams.push_back(TTmplParam("onclick", "html_submit_form"));
5258 tmplParams.push_back(TTmplParam("onclick_param", param));
5259 tmplParams.push_back(TTmplParam("active", "true"));
5260 if (minWidth > 0) tmplParams.push_back(TTmplParam("wmin", toString(minWidth)));
5261 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
5262 if (buttonGroup)
5264 // Add the ctrl button
5265 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
5266 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
5267 if (ctrlButton)
5269 ctrlButton->setModulateGlobalColorAll (_Style.Current.GlobalColor);
5270 ctrlButton->setTextModulateGlobalColorNormal(_Style.Current.GlobalColorText);
5271 ctrlButton->setTextModulateGlobalColorOver(_Style.Current.GlobalColorText);
5272 ctrlButton->setTextModulateGlobalColorPushed(_Style.Current.GlobalColorText);
5274 // Translate the tooltip
5275 if (!tooltip.empty())
5277 if (CI18N::hasTranslation(tooltip))
5279 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
5281 else
5283 ctrlButton->setDefaultContextHelp(tooltip);
5287 ctrlButton->setText(value);
5289 setTextButtonStyle(ctrlButton, _Style.Current);
5291 getParagraph()->addChild (buttonGroup);
5292 paragraphChange ();
5296 // ***************************************************************************
5297 void CGroupHTML::htmlA(const CHtmlElement &elm)
5299 _A.push_back(true);
5300 _Link.push_back ("");
5301 _LinkTitle.push_back("");
5302 _LinkClass.push_back("");
5303 if (elm.hasClass("ryzom-ui-button"))
5304 _LinkClass.back() = "ryzom-ui-button";
5306 // #fragment works with both ID and NAME so register both
5307 if (elm.hasNonEmptyAttribute("name"))
5308 _AnchorName.push_back(elm.getAttribute("name"));
5309 if (elm.hasNonEmptyAttribute("title"))
5310 _LinkTitle.back() = elm.getAttribute("title");
5311 if (elm.hasNonEmptyAttribute("href"))
5313 string suri = elm.getAttribute("href");
5314 if(suri.find("ah:") == 0)
5316 if (_TrustedDomain)
5317 _Link.back() = suri;
5319 else
5321 // convert href from "?key=val" into "http://domain.com/?key=val"
5322 _Link.back() = getAbsoluteUrl(suri);
5326 renderPseudoElement(":before", elm);
5329 void CGroupHTML::htmlAend(const CHtmlElement &elm)
5331 renderPseudoElement(":after", elm);
5333 popIfNotEmpty(_A);
5334 popIfNotEmpty(_Link);
5335 popIfNotEmpty(_LinkTitle);
5336 popIfNotEmpty(_LinkClass);
5339 // ***************************************************************************
5340 void CGroupHTML::htmlBASE(const CHtmlElement &elm)
5342 if (!_ReadingHeadTag || _IgnoreBaseUrlTag)
5343 return;
5345 if (elm.hasNonEmptyAttribute("href"))
5347 CUrlParser uri(elm.getAttribute("href"));
5348 if (uri.isAbsolute())
5350 _URL = uri.toString();
5351 _IgnoreBaseUrlTag = true;
5356 // ***************************************************************************
5357 void CGroupHTML::htmlBODY(const CHtmlElement &elm)
5359 // override <body> (or <html>) css style attribute
5360 if (elm.hasNonEmptyAttribute("bgcolor"))
5361 _Style.applyStyle("background-color: " + elm.getAttribute("bgcolor"));
5363 if (m_HtmlBackground.isEmpty())
5364 setupBackground(&m_HtmlBackground);
5365 else
5366 setupBackground(&m_BodyBackground);
5368 renderPseudoElement(":before", elm);
5371 // ***************************************************************************
5372 void CGroupHTML::htmlBR(const CHtmlElement &elm)
5374 if (!_Paragraph || _Paragraph->getNumChildren() == 0)
5376 addString("\n");
5378 else
5380 endParagraph();
5384 // ***************************************************************************
5385 void CGroupHTML::htmlBUTTON(const CHtmlElement &elm)
5387 std::string name = elm.getAttribute("name");
5388 std::string value = elm.getAttribute("value");
5389 std::string formId = elm.getAttribute("form");
5390 std::string formAction = elm.getAttribute("formaction");
5391 std::string tooltip = elm.getAttribute("tooltip");
5392 bool disabled = elm.hasAttribute("disabled");
5394 if (formId.empty() && _FormOpen)
5396 formId = _Forms.back().id;
5399 if (!formAction.empty())
5401 formAction = getAbsoluteUrl(formAction);
5404 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "text", formAction));
5405 // Action handler parameters
5406 std::string param;
5407 if (!disabled)
5409 if (elm.getAttribute("type") == "submit")
5411 param = "ah:html_submit_form&name=" + getId() + "&button=" + toString(_FormSubmit.size()-1);
5413 else
5415 param = "ah:";
5419 _A.push_back(true);
5420 _Link.push_back(param);
5421 _LinkTitle.push_back(tooltip);
5422 _LinkClass.push_back("ryzom-ui-button");
5424 // TODO: this creates separate button element
5425 //renderPseudoElement(":before", elm);
5427 void CGroupHTML::htmlBUTTONend(const CHtmlElement &elm)
5429 // TODO: this creates separate button element
5430 //renderPseudoElement(":after", elm);
5432 popIfNotEmpty(_A);
5433 popIfNotEmpty(_Link);
5434 popIfNotEmpty(_LinkTitle);
5435 popIfNotEmpty(_LinkClass);
5438 // ***************************************************************************
5439 void CGroupHTML::htmlDD(const CHtmlElement &elm)
5441 if (_DL.empty())
5442 return;
5444 // if there was no closing tag for <dt>, then remove <dt> style
5445 if (_DL.back().DT)
5447 nlwarning("BUG: nested DT in DD");
5448 _DL.back().DT = false;
5451 if (_DL.back().DD)
5453 nlwarning("BUG: nested DD in DD");
5454 _DL.back().DD = false;
5455 popIfNotEmpty(_Indent);
5458 _DL.back().DD = true;
5459 _Indent.push_back(getIndent() + ULIndent);
5461 if (!_LI)
5463 _LI = true;
5464 newParagraph(ULBeginSpace);
5466 else
5468 newParagraph(LIBeginSpace);
5471 renderPseudoElement(":before", elm);
5474 void CGroupHTML::htmlDDend(const CHtmlElement &elm)
5476 if (_DL.empty())
5477 return;
5479 renderPseudoElement(":after", elm);
5481 // parser will process two DD in a row as nested when first DD is not closed
5482 if (_DL.back().DD)
5484 _DL.back().DD = false;
5485 popIfNotEmpty(_Indent);
5489 // ***************************************************************************
5490 void CGroupHTML::htmlDIV(const CHtmlElement &elm)
5492 _DivName = elm.getAttribute("name");
5494 string instClass = elm.getAttribute("class");
5496 // use generic template system
5497 if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
5499 string style = elm.getAttribute("style");
5500 string id = elm.getAttribute("id");
5501 if (id.empty())
5502 id = "DIV" + toString(getNextAutoIdSeq());
5504 typedef pair<string, string> TTmplParam;
5505 vector<TTmplParam> tmplParams;
5507 string templateName;
5508 if (!style.empty())
5510 TStyle styles = parseStyle(style);
5511 TStyle::iterator it;
5512 for (it=styles.begin(); it != styles.end(); it++)
5514 if ((*it).first == "template")
5515 templateName = (*it).second;
5516 else
5517 tmplParams.push_back(TTmplParam((*it).first, (*it).second));
5521 if (!templateName.empty())
5523 string parentId;
5524 bool haveParentDiv = getDiv() != NULL;
5525 if (haveParentDiv)
5526 parentId = getDiv()->getId();
5527 else
5529 if (!_Paragraph)
5530 newParagraph (0);
5532 parentId = _Paragraph->getId();
5535 CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, parentId, tmplParams);
5536 if (inst)
5538 inst->setId(parentId+":"+id);
5539 inst->updateCoords();
5540 if (haveParentDiv)
5542 inst->setParent(getDiv());
5543 inst->setParentSize(getDiv());
5544 inst->setParentPos(getDiv());
5545 inst->setPosRef(Hotspot_TL);
5546 inst->setParentPosRef(Hotspot_TL);
5547 getDiv()->addGroup(inst);
5549 else
5551 getParagraph()->addChild(inst);
5552 paragraphChange();
5554 _Divs.push_back(inst);
5559 renderPseudoElement(":before", elm);
5562 void CGroupHTML::htmlDIVend(const CHtmlElement &elm)
5564 renderPseudoElement(":after", elm);
5565 _DivName.clear();
5566 popIfNotEmpty(_Divs);
5569 // ***************************************************************************
5570 void CGroupHTML::htmlDL(const CHtmlElement &elm)
5572 _DL.push_back(HTMLDListElement());
5573 _LI = _DL.size() > 1 || !_UL.empty();
5575 renderPseudoElement(":before", elm);
5578 void CGroupHTML::htmlDLend(const CHtmlElement &elm)
5580 if (_DL.empty())
5581 return;
5583 renderPseudoElement(":after", elm);
5585 // unclosed DT
5586 if (_DL.back().DT)
5588 nlwarning("BUG: unclosed DT in DL");
5591 // unclosed DD
5592 if (_DL.back().DD)
5594 popIfNotEmpty(_Indent);
5595 nlwarning("BUG: unclosed DD in DL");
5598 popIfNotEmpty (_DL);
5601 // ***************************************************************************
5602 void CGroupHTML::htmlDT(const CHtmlElement &elm)
5604 if (_DL.empty())
5605 return;
5607 // TODO: check if nested tags still happen and fix it in parser
5608 // : remove special handling for nesting and let it happen
5610 // html parser and libxml2 should prevent nested tags like these
5611 if (_DL.back().DD)
5613 nlwarning("BUG: nested DD in DT");
5615 _DL.back().DD = false;
5616 popIfNotEmpty(_Indent);
5619 // html parser and libxml2 should prevent nested tags like these
5620 if (_DL.back().DT)
5622 nlwarning("BUG: nested DT in DT");
5625 _DL.back().DT = true;
5627 if (!_LI)
5629 _LI = true;
5630 newParagraph(ULBeginSpace);
5632 else
5634 newParagraph(LIBeginSpace);
5637 renderPseudoElement(":before", elm);
5640 void CGroupHTML::htmlDTend(const CHtmlElement &elm)
5642 if (_DL.empty())
5643 return;
5645 renderPseudoElement(":after", elm);
5647 _DL.back().DT = false;
5650 // ***************************************************************************
5651 void CGroupHTML::htmlFONT(const CHtmlElement &elm)
5653 if (elm.hasNonEmptyAttribute("color"))
5655 CRGBA color;
5656 if (scanHTMLColor(elm.getAttribute("color").c_str(), color))
5657 _Style.Current.TextColor = color;
5660 if (elm.hasNonEmptyAttribute("size"))
5662 uint fontsize;
5663 fromString(elm.getAttribute("size"), fontsize);
5664 _Style.Current.FontSize = fontsize;
5668 // ***************************************************************************
5669 void CGroupHTML::htmlFORM(const CHtmlElement &elm)
5671 _FormOpen = true;
5673 // Build the form
5674 CGroupHTML::CForm form;
5675 // id check is case sensitive and auto id's are uppercase
5676 form.id = toLowerAscii(trim(elm.getAttribute("id")));
5677 if (form.id.empty())
5679 form.id = toString("FORM%d", _Forms.size());
5682 // Get the action name
5683 if (elm.hasNonEmptyAttribute("action"))
5685 form.Action = getAbsoluteUrl(elm.getAttribute("action"));
5687 else
5689 form.Action = _URL;
5692 _Forms.push_back(form);
5694 renderPseudoElement(":before", elm);
5697 void CGroupHTML::htmlFORMend(const CHtmlElement &elm)
5699 _FormOpen = false;
5700 renderPseudoElement(":after", elm);
5703 // ***************************************************************************
5704 void CGroupHTML::htmlH(const CHtmlElement &elm)
5706 newParagraph(PBeginSpace);
5707 renderPseudoElement(":before", elm);
5710 void CGroupHTML::htmlHend(const CHtmlElement &elm)
5712 renderPseudoElement(":after", elm);
5715 // ***************************************************************************
5716 void CGroupHTML::htmlHEAD(const CHtmlElement &elm)
5718 _ReadingHeadTag = !_IgnoreHeadTag;
5719 _IgnoreHeadTag = true;
5722 void CGroupHTML::htmlHEADend(const CHtmlElement &elm)
5724 _ReadingHeadTag = false;
5727 // ***************************************************************************
5728 void CGroupHTML::htmlHR(const CHtmlElement &elm)
5730 CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0);
5731 if (sep)
5733 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*>(sep->getView("hr"));
5734 if (bitmap)
5736 bitmap->setColor(_Style.Current.TextColor);
5737 if (_Style.Current.Width > 0)
5739 clamp(_Style.Current.Width, 1, 32000);
5740 bitmap->setW(_Style.Current.Width);
5741 bitmap->setSizeRef(CInterfaceElement::none);
5743 if (_Style.Current.Height > 0)
5745 clamp(_Style.Current.Height, 1, 1000);
5746 bitmap->setH(_Style.Current.Height);
5750 renderPseudoElement(":before", elm);
5751 addHtmlGroup(sep, 0);
5752 renderPseudoElement(":after", elm);
5756 // ***************************************************************************
5757 void CGroupHTML::htmlHTML(const CHtmlElement &elm)
5759 if (elm.hasNonEmptyAttribute("style"))
5760 _Style.applyStyle(elm.getAttribute("style"));
5762 _Style.Root = _Style.Current;
5764 setupBackground(&m_HtmlBackground);
5767 // ***************************************************************************
5768 void CGroupHTML::htmlI(const CHtmlElement &elm)
5770 _Localize = true;
5771 renderPseudoElement(":before", elm);
5774 void CGroupHTML::htmlIend(const CHtmlElement &elm)
5776 renderPseudoElement(":after", elm);
5777 _Localize = false;
5780 // ***************************************************************************
5781 void CGroupHTML::htmlIMG(const CHtmlElement &elm)
5783 std::string src = trim(elm.getAttribute("src"));
5784 if (src.empty())
5786 // no 'src' attribute, or empty
5787 return;
5790 float tmpf;
5791 std::string id = elm.getAttribute("id");
5793 if (elm.hasNonEmptyAttribute("width"))
5794 getPercentage(_Style.Current.Width, tmpf, elm.getAttribute("width").c_str());
5795 if (elm.hasNonEmptyAttribute("height"))
5796 getPercentage(_Style.Current.Height, tmpf, elm.getAttribute("height").c_str());
5798 // Get the global color name
5799 if (elm.hasAttribute("global_color"))
5800 _Style.Current.GlobalColor = true;
5802 // Tooltip
5803 // keep "alt" attribute for backward compatibility
5804 std::string tooltip = elm.getAttribute("alt");
5805 // tooltip
5806 if (elm.hasNonEmptyAttribute("title"))
5807 tooltip = elm.getAttribute("title");
5809 // Mouse over image
5810 string overSrc = elm.getAttribute("data-over-src");
5812 // inside a/button with valid url (ie, button is not disabled)
5813 string url = getLink();
5814 if (getA() && !url.empty() && getParent() && getParent()->getParent())
5816 string params = "name=" + getId() + "|url=" + url;
5817 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "browse", params.c_str(), tooltip, _Style.Current);
5819 else
5820 if (!tooltip.empty() || !overSrc.empty())
5822 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "", "", tooltip, _Style.Current);
5824 else
5826 // Get the option to reload (class==reload)
5827 bool reloadImg = false;
5829 if (elm.hasNonEmptyAttribute("style"))
5831 string styleString = elm.getAttribute("style");
5832 TStyle styles = parseStyle(styleString);
5833 TStyle::iterator it;
5835 it = styles.find("reload");
5836 if (it != styles.end() && (*it).second == "1")
5837 reloadImg = true;
5840 addImage(id, elm.getAttribute("src"), reloadImg, _Style.Current);
5844 // ***************************************************************************
5845 void CGroupHTML::htmlINPUT(const CHtmlElement &elm)
5847 if (_Forms.empty())
5848 return;
5850 // read general property
5851 string id = elm.getAttribute("id");
5853 // Widget template name (old)
5854 string templateName = elm.getAttribute("z_btn_tmpl");
5855 // Input name is the new
5856 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
5857 templateName = elm.getAttribute("z_input_tmpl");
5859 // Widget minimal width
5860 uint32 minWidth = 0;
5861 fromString(elm.getAttribute("z_input_width"), minWidth);
5863 // <input type="...">
5864 std::string type = trim(elm.getAttribute("type"));
5865 if (type.empty())
5867 // no 'type' attribute, or empty
5868 return;
5871 // Global color flag
5872 if (elm.hasAttribute("global_color"))
5873 _Style.Current.GlobalColor = true;
5875 // Tooltip
5876 std::string tooltip = elm.getAttribute("alt");
5878 if (type == "image")
5880 string name = elm.getAttribute("name");
5881 string src = elm.getAttribute("src");
5882 string over = elm.getAttribute("data-over-src");
5883 string formId = elm.getAttribute("form");
5884 string formAction = elm.getAttribute("formaction");
5886 if (formId.empty() && _FormOpen) {
5887 formId = _Forms.back().id;
5890 insertFormImageButton(name, tooltip, src, over, formId, formAction, minWidth, templateName);
5892 else if (type == "button" || type == "submit")
5894 string name = elm.getAttribute("name");
5895 string value = elm.getAttribute("value");
5896 string formId = elm.getAttribute("form");
5897 string formAction = elm.getAttribute("formaction");
5899 if (formId.empty() && _FormOpen) {
5900 formId = _Forms.back().id;
5903 insertFormTextButton(name, tooltip, value, formId, formAction, minWidth, templateName);
5905 else if (type == "text")
5907 // Get the string name
5908 string name = elm.getAttribute("name");
5909 string ucValue = elm.getAttribute("value");
5911 uint size = 20;
5912 uint maxlength = 1024;
5913 if (elm.hasNonEmptyAttribute("size"))
5914 fromString(elm.getAttribute("size"), size);
5915 if (elm.hasNonEmptyAttribute("maxlength"))
5916 fromString(elm.getAttribute("maxlength"), maxlength);
5918 // ryzom client used to have 'size' attribute in pixels, (12 == was default font size)
5919 if (_Style.hasStyle("-ryzom-input-size-px") && _Style.getStyle("-ryzom-input-size-px") == "true")
5920 size = size / 12;
5922 string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
5923 // Add the editbox
5924 CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size, false, ucValue, maxlength);
5925 if (textArea)
5927 // Add the text area to the form
5928 CGroupHTML::CForm::CEntry entry;
5929 entry.Name = name;
5930 entry.TextArea = textArea;
5931 _Forms.back().Entries.push_back (entry);
5934 else if (type == "checkbox" || type == "radio")
5936 renderPseudoElement(":before", elm);
5938 CCtrlButton::EType btnType;
5939 string name = elm.getAttribute("name");
5940 string normal = elm.getAttribute("src");
5941 string pushed;
5942 string over;
5943 string ucValue = "on";
5944 bool checked = elm.hasAttribute("checked");
5946 // TODO: unknown if empty attribute should override or not
5947 if (elm.hasNonEmptyAttribute("value"))
5948 ucValue = elm.getAttribute("value");
5950 if (type == "radio")
5952 btnType = CCtrlButton::RadioButton;
5953 normal = DefaultRadioButtonBitmapNormal;
5954 pushed = DefaultRadioButtonBitmapPushed;
5955 over = DefaultRadioButtonBitmapOver;
5957 else
5959 btnType = CCtrlButton::ToggleButton;
5960 normal = DefaultCheckBoxBitmapNormal;
5961 pushed = DefaultCheckBoxBitmapPushed;
5962 over = DefaultCheckBoxBitmapOver;
5965 // Add the ctrl button
5966 CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, _Style.Current);
5967 if (checkbox)
5969 if (btnType == CCtrlButton::RadioButton)
5971 // override with 'id' because radio buttons share same name
5972 if (!id.empty())
5973 checkbox->setId(id);
5975 // group together buttons with same name
5976 CForm &form = _Forms.back();
5977 bool notfound = true;
5978 for (uint i=0; i<form.Entries.size(); i++)
5980 if (form.Entries[i].Name == name && form.Entries[i].Checkbox->getType() == CCtrlButton::RadioButton)
5982 checkbox->initRBRefFromRadioButton(form.Entries[i].Checkbox);
5983 notfound = false;
5984 break;
5987 if (notfound)
5989 // this will start a new group (initRBRef() would take first button in group container otherwise)
5990 checkbox->initRBRefFromRadioButton(checkbox);
5994 checkbox->setPushed (checked);
5996 // Add the button to the form
5997 CGroupHTML::CForm::CEntry entry;
5998 entry.Name = name;
5999 entry.Value = decodeHTMLEntities(ucValue);
6000 entry.Checkbox = checkbox;
6001 _Forms.back().Entries.push_back (entry);
6003 renderPseudoElement(":after", elm);
6005 else if (type == "hidden")
6007 if (elm.hasNonEmptyAttribute("name"))
6009 // Get the name
6010 string name = elm.getAttribute("name");
6012 // Get the value
6013 string ucValue = elm.getAttribute("value");
6015 // Add an entry
6016 CGroupHTML::CForm::CEntry entry;
6017 entry.Name = name;
6018 entry.Value = decodeHTMLEntities(ucValue);
6019 _Forms.back().Entries.push_back (entry);
6024 // ***************************************************************************
6025 void CGroupHTML::htmlLI(const CHtmlElement &elm)
6027 if (_UL.empty())
6028 return;
6030 // UL, OL top margin if this is the first LI
6031 if (!_LI)
6033 _LI = true;
6034 newParagraph(ULBeginSpace);
6036 else
6038 newParagraph(LIBeginSpace);
6041 // OL list index can be overridden by <li value="1"> attribute
6042 if (elm.hasNonEmptyAttribute("value"))
6043 fromString(elm.getAttribute("value"), _UL.back().Value);
6045 string str = _UL.back().getListMarkerText();
6046 addString (str);
6048 // list-style-type: outside
6049 if (_CurrentViewLink)
6051 getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW());
6054 flushString ();
6056 // after marker
6057 renderPseudoElement(":before", elm);
6059 _UL.back().Value++;
6062 void CGroupHTML::htmlLIend(const CHtmlElement &elm)
6064 renderPseudoElement(":after", elm);
6067 // ***************************************************************************
6068 void CGroupHTML::htmlLUA(const CHtmlElement &elm)
6070 // we receive an embeded lua script
6071 _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain
6072 _LuaScript.clear();
6075 void CGroupHTML::htmlLUAend(const CHtmlElement &elm)
6077 if (_ParsingLua && _TrustedDomain)
6079 _ParsingLua = false;
6080 // execute the embeded lua script
6081 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript;
6082 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
6086 // ***************************************************************************
6087 void CGroupHTML::htmlMETA(const CHtmlElement &elm)
6089 if (!_ReadingHeadTag)
6090 return;
6092 std::string httpEquiv = elm.getAttribute("http-equiv");
6093 std::string httpContent = elm.getAttribute("content");
6094 if (httpEquiv.empty() || httpContent.empty())
6096 return;
6099 // only first http-equiv="refresh" should be handled
6100 if (_RefreshUrl.empty() && httpEquiv == "refresh")
6102 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
6103 double timeSec = times.thisFrameMs / 1000.0f;
6105 string::size_type pos = httpContent.find_first_of(";");
6106 if (pos == string::npos)
6108 fromString(httpContent, _NextRefreshTime);
6109 _RefreshUrl = _URL;
6111 else
6113 fromString(httpContent.substr(0, pos), _NextRefreshTime);
6115 pos = toLowerAscii(httpContent).find("url=");
6116 if (pos != string::npos)
6117 _RefreshUrl = getAbsoluteUrl(httpContent.substr(pos + 4));
6120 _NextRefreshTime += timeSec;
6124 // ***************************************************************************
6125 void CGroupHTML::htmlMETER(const CHtmlElement &elm)
6127 HTMLMeterElement meter;
6128 meter.readValues(elm);
6130 std::string id = "meter";
6131 if (elm.hasAttribute("id"))
6132 id = elm.getAttribute("id");
6134 // width: 5em, height: 1em
6135 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 5;
6136 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6137 // FIXME: only using border-top
6138 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6140 uint barw = (uint) (width * meter.getValueRatio());
6141 CRGBA bgColor = meter.getBarColor(elm, _Style);
6142 CRGBA valueColor = meter.getValueColor(elm, _Style);
6144 typedef pair<string, string> TTmplParam;
6145 vector<TTmplParam> tmplParams;
6146 tmplParams.push_back(TTmplParam("id", id));
6147 tmplParams.push_back(TTmplParam("active", "true"));
6148 tmplParams.push_back(TTmplParam("w", toString(width)));
6149 tmplParams.push_back(TTmplParam("h", toString(height)));
6150 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6151 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6152 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6153 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6154 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6155 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6157 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_meter", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6158 if (gr)
6160 renderPseudoElement(":before", elm);
6161 getParagraph()->addChild(gr);
6162 renderPseudoElement(":after", elm);
6164 // ignore any inner elements
6165 _IgnoreChildElements = true;
6169 // ***************************************************************************
6170 void CGroupHTML::htmlOBJECT(const CHtmlElement &elm)
6172 _ObjectType = elm.getAttribute("type");
6173 _ObjectData = elm.getAttribute("data");
6174 _ObjectMD5Sum = elm.getAttribute("id");
6175 _ObjectAction = elm.getAttribute("standby");
6176 _Object = true;
6179 void CGroupHTML::htmlOBJECTend(const CHtmlElement &elm)
6181 if (!_TrustedDomain)
6182 return;
6184 if (_ObjectType=="application/ryzom-data")
6186 if (!_ObjectData.empty())
6188 if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum))
6190 CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true);
6192 _ObjectScript.clear();
6195 _Object = false;
6198 // ***************************************************************************
6199 void CGroupHTML::htmlOL(const CHtmlElement &elm)
6201 sint32 start = 1;
6202 std::string type("1");
6204 if (elm.hasNonEmptyAttribute("start"))
6205 fromString(elm.getAttribute("start"), start);
6206 if (elm.hasNonEmptyAttribute("type"))
6207 type = elm.getAttribute("type");
6209 _UL.push_back(HTMLOListElement(start, type));
6210 // if LI is already present
6211 _LI = _UL.size() > 1 || _DL.size() > 1;
6212 _Indent.push_back(getIndent() + ULIndent);
6214 renderPseudoElement(":before", elm);
6217 void CGroupHTML::htmlOLend(const CHtmlElement &elm)
6219 htmlULend(elm);
6222 // ***************************************************************************
6223 void CGroupHTML::htmlOPTION(const CHtmlElement &elm)
6225 _SelectOption = true;
6226 _SelectOptionStr.clear();
6228 // Got one form ?
6229 if (_Forms.empty() || _Forms.back().Entries.empty())
6230 return;
6232 _Forms.back().Entries.back().SelectValues.push_back(elm.getAttribute("value"));
6234 if (elm.hasAttribute("selected"))
6235 _Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6237 if (elm.hasAttribute("disabled"))
6238 _Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6241 void CGroupHTML::htmlOPTIONend(const CHtmlElement &elm)
6243 if (_Forms.empty() || _Forms.back().Entries.empty())
6244 return;
6246 // use option text as value
6247 if (!elm.hasAttribute("value"))
6249 _Forms.back().Entries.back().SelectValues.back() = _SelectOptionStr;
6252 // insert the parsed text into the select control
6253 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6254 if (cb)
6256 uint lineIndex = cb->getNumTexts();
6257 cb->addText(_SelectOptionStr);
6258 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6260 cb->setGrayed(lineIndex, true);
6263 else
6265 CGroupMenu *sb = _Forms.back().Entries.back().SelectBox;
6266 if (sb)
6268 uint lineIndex = sb->getNumLine();
6269 sb->addLine(_SelectOptionStr, "", "");
6271 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6273 sb->setGrayedLine(lineIndex, true);
6275 else
6277 // create option line checkbox, CGroupMenu is taking ownership of the checbox
6278 CInterfaceGroup *ig = CWidgetManager::getInstance()->getParser()->createGroupInstance("menu_checkbox", "", NULL, 0);
6279 if (ig)
6281 CCtrlButton *cb = dynamic_cast<CCtrlButton *>(ig->getCtrl("b"));
6282 if (cb)
6284 if (_Forms.back().Entries.back().sbMultiple)
6286 cb->setType(CCtrlButton::ToggleButton);
6287 cb->setTexture(DefaultCheckBoxBitmapNormal);
6288 cb->setTexturePushed(DefaultCheckBoxBitmapPushed);
6289 cb->setTextureOver(DefaultCheckBoxBitmapOver);
6291 else
6293 cb->setType(CCtrlButton::RadioButton);
6294 cb->setTexture(DefaultRadioButtonBitmapNormal);
6295 cb->setTexturePushed(DefaultRadioButtonBitmapPushed);
6296 cb->setTextureOver(DefaultRadioButtonBitmapOver);
6298 if (_Forms.back().Entries.back().sbRBRef == NULL)
6299 _Forms.back().Entries.back().sbRBRef = cb;
6301 cb->initRBRefFromRadioButton(_Forms.back().Entries.back().sbRBRef);
6304 cb->setPushed(_Forms.back().Entries.back().InitialSelection == lineIndex);
6305 sb->setUserGroupLeft(lineIndex, ig);
6307 else
6309 nlwarning("Failed to get 'b' element from 'menu_checkbox' template");
6310 delete ig;
6318 // ***************************************************************************
6319 void CGroupHTML::htmlP(const CHtmlElement &elm)
6321 newParagraph(PBeginSpace);
6322 renderPseudoElement(":before", elm);
6325 void CGroupHTML::htmlPend(const CHtmlElement &elm)
6327 renderPseudoElement(":after", elm);
6330 // ***************************************************************************
6331 void CGroupHTML::htmlPRE(const CHtmlElement &elm)
6333 _PRE.push_back(true);
6334 newParagraph(0);
6336 renderPseudoElement(":before", elm);
6339 void CGroupHTML::htmlPREend(const CHtmlElement &elm)
6341 renderPseudoElement(":after", elm);
6343 popIfNotEmpty(_PRE);
6346 // ***************************************************************************
6347 void CGroupHTML::htmlPROGRESS(const CHtmlElement &elm)
6349 HTMLProgressElement progress;
6350 progress.readValues(elm);
6352 std::string id = "progress";
6353 if (elm.hasAttribute("id"))
6354 id = elm.getAttribute("id");
6356 // width: 10em, height: 1em
6357 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 10;
6358 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6359 // FIXME: only using border-top
6360 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6362 uint barw = (uint) (width * progress.getValueRatio());
6363 CRGBA bgColor = progress.getBarColor(elm, _Style);
6364 CRGBA valueColor = progress.getValueColor(elm, _Style);
6366 typedef pair<string, string> TTmplParam;
6367 vector<TTmplParam> tmplParams;
6368 tmplParams.push_back(TTmplParam("id", id));
6369 tmplParams.push_back(TTmplParam("active", "true"));
6370 tmplParams.push_back(TTmplParam("w", toString(width)));
6371 tmplParams.push_back(TTmplParam("h", toString(height)));
6372 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6373 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6374 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6375 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6376 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6377 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6379 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_progress", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6380 if (gr)
6382 renderPseudoElement(":before", elm);
6383 getParagraph()->addChild(gr);
6384 renderPseudoElement(":after", elm);
6386 // ignore any inner elements
6387 _IgnoreChildElements = true;
6391 // ***************************************************************************
6392 void CGroupHTML::htmlSCRIPT(const CHtmlElement &elm)
6394 _IgnoreText = true;
6397 void CGroupHTML::htmlSCRIPTend(const CHtmlElement &elm)
6399 _IgnoreText = false;
6402 // ***************************************************************************
6403 void CGroupHTML::htmlSELECT(const CHtmlElement &elm)
6405 if (_Forms.empty())
6406 return;
6408 // A select box
6409 string name = elm.getAttribute("name");
6410 bool multiple = elm.hasAttribute("multiple");
6411 sint32 size = 0;
6413 if (elm.hasNonEmptyAttribute("size"))
6414 fromString(elm.getAttribute("size"), size);
6416 CGroupHTML::CForm::CEntry entry;
6417 entry.Name = name;
6418 entry.sbMultiple = multiple;
6419 if (size > 1 || multiple)
6421 entry.InitialSelection = -1;
6422 CGroupMenu *sb = addSelectBox(DefaultFormSelectBoxMenuGroup, name.c_str());
6423 if (sb)
6425 if (size < 1)
6426 size = 4;
6428 if (_Style.Current.Width > -1)
6429 sb->setMinW(_Style.Current.Width);
6431 if (_Style.Current.Height > -1)
6432 sb->setMinH(_Style.Current.Height);
6434 sb->setMaxVisibleLine(size);
6435 sb->setFontSize(_Style.Current.FontSize, false);
6438 entry.SelectBox = sb;
6440 else
6442 CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
6443 entry.ComboBox = cb;
6445 if (cb)
6447 // create view text
6448 cb->updateCoords();
6449 setTextStyle(cb->getViewText(), _Style.Current);
6452 _Forms.back().Entries.push_back (entry);
6455 void CGroupHTML::htmlSELECTend(const CHtmlElement &elm)
6457 _SelectOption = false;
6458 if (_Forms.empty() || _Forms.back().Entries.empty())
6459 return;
6461 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6462 if (cb)
6464 cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection);
6465 // TODO: magic padding
6466 cb->setW(cb->evalContentWidth() + 16);
6470 // ***************************************************************************
6471 void CGroupHTML::htmlSTYLE(const CHtmlElement &elm)
6473 _IgnoreText = true;
6476 void CGroupHTML::htmlSTYLEend(const CHtmlElement &elm)
6478 _IgnoreText = false;
6481 // ***************************************************************************
6482 void CGroupHTML::htmlTABLE(const CHtmlElement &elm)
6484 // Get cells parameters
6485 getCellsParameters(elm, false);
6487 CGroupTable *table = new CGroupTable(TCtorParam());
6489 if (elm.hasNonEmptyAttribute("id"))
6490 table->setId(getCurrentGroup()->getId() + ":" + elm.getAttribute("id"));
6491 else
6492 table->setId(getCurrentGroup()->getId() + ":TABLE" + toString(getNextAutoIdSeq()));
6494 // TODO: border-spacing: 2em;
6496 if (elm.hasNonEmptyAttribute("cellspacing"))
6497 fromString(elm.getAttribute("cellspacing"), table->CellSpacing);
6499 // TODO: cssLength, horiz/vert values
6500 if (_Style.hasStyle("border-spacing"))
6501 fromString(_Style.getStyle("border-spacing"), table->CellSpacing);
6503 // overrides border-spacing if set to 'collapse'
6504 if (_Style.checkStyle("border-collapse", "collapse"))
6505 table->CellSpacing = 0;
6508 if (elm.hasNonEmptyAttribute("cellpadding"))
6509 fromString(elm.getAttribute("cellpadding"), table->CellPadding);
6511 if (_Style.hasStyle("width"))
6513 // _Style.Width does not handle '%' unit currently
6514 if (_Style.Current.Width > 0)
6516 table->ForceWidthMin = _Style.Current.Width;
6517 table->TableRatio = 0;
6519 else
6521 getPercentage (table->ForceWidthMin, table->TableRatio, _Style.getStyle("width").c_str());
6524 else if (elm.hasNonEmptyAttribute("width"))
6526 getPercentage (table->ForceWidthMin, table->TableRatio, elm.getAttribute("width").c_str());
6529 // border from css or from attribute
6531 CSSRect<CSSBorder> border;
6532 border.Top.Color = _Style.Current.TextColor;
6533 border.Right.Color = _Style.Current.TextColor;
6534 border.Bottom.Color = _Style.Current.TextColor;
6535 border.Left.Color = _Style.Current.TextColor;
6537 if (elm.hasAttribute("border"))
6539 uint32 borderWidth = 0;
6540 CRGBA borderColor = CRGBA::Transparent;
6542 std::string s = elm.getAttribute("border");
6543 if (s.empty())
6544 borderWidth = 1;
6545 else
6546 fromString(elm.getAttribute("border"), borderWidth);
6548 if (elm.hasNonEmptyAttribute("bordercolor"))
6549 scanHTMLColor(elm.getAttribute("bordercolor").c_str(), borderColor);
6550 else
6551 borderColor = CRGBA(128, 128, 128, 255);
6553 table->CellBorder = (borderWidth > 0);
6555 border.Top.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6556 border.Right.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6557 border.Bottom.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6558 border.Left.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6561 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6562 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6563 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6564 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6566 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6567 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6568 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6569 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6571 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6572 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6573 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6574 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6576 table->Border->setBorder(border);
6577 table->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6578 table->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6581 setupBackground(table->Background);
6582 table->setModulateGlobalColor(_Style.Current.GlobalColor);
6584 table->setMarginLeft(getIndent());
6585 addHtmlGroup (table, 0);
6587 renderPseudoElement(":before", elm);
6589 _Tables.push_back(table);
6591 // Add a cell pointer
6592 _Cells.push_back(NULL);
6593 _TR.push_back(false);
6594 _Indent.push_back(0);
6597 void CGroupHTML::htmlTABLEend(const CHtmlElement &elm)
6599 popIfNotEmpty(_CellParams);
6600 popIfNotEmpty(_TR);
6601 popIfNotEmpty(_Cells);
6602 popIfNotEmpty(_Tables);
6603 popIfNotEmpty(_Indent);
6605 renderPseudoElement(":after", elm);
6608 // ***************************************************************************
6609 void CGroupHTML::htmlTD(const CHtmlElement &elm)
6611 // Get cells parameters
6612 getCellsParameters(elm, true);
6614 if (!m_TableRowBackgroundColor.empty() && m_TableRowBackgroundColor.back().A > 0)
6615 _Style.Current.Background.color.blendFromui(m_TableRowBackgroundColor.back(), _Style.Current.Background.color, _Style.Current.Background.color.A);
6617 if (elm.ID == HTML_TH)
6619 if (!_Style.hasStyle("font-weight"))
6620 _Style.Current.FontWeight = FONT_WEIGHT_BOLD;
6621 // center if not specified otherwise.
6622 if (!elm.hasNonEmptyAttribute("align") && !_Style.hasStyle("text-align"))
6623 _CellParams.back().Align = CGroupCell::Center;
6626 CGroupTable *table = getTable();
6627 if (!table)
6629 // <td> appears to be outside <table>
6630 return;
6633 if (_Cells.empty())
6635 // <table> not started
6636 return;
6639 _Cells.back() = new CGroupCell(CViewBase::TCtorParam());
6640 if (elm.hasNonEmptyAttribute("id"))
6641 _Cells.back()->setId(table->getId() + ":" + elm.getAttribute("id"));
6642 else
6643 _Cells.back()->setId(table->getId() + ":TD" + toString(getNextAutoIdSeq()));
6644 // inner cell content
6645 _Cells.back()->Group->setId(_Cells.back()->getId() + ":CELL");
6647 setupBackground(_Cells.back()->Background);
6648 _Cells.back()->setModulateGlobalColor(_Style.Current.GlobalColor);
6650 if (elm.hasNonEmptyAttribute("colspan"))
6651 fromString(elm.getAttribute("colspan"), _Cells.back()->ColSpan);
6652 if (elm.hasNonEmptyAttribute("rowspan"))
6653 fromString(elm.getAttribute("rowspan"), _Cells.back()->RowSpan);
6655 _Cells.back()->Align = _CellParams.back().Align;
6656 _Cells.back()->VAlign = _CellParams.back().VAlign;
6657 _Cells.back()->LeftMargin = _CellParams.back().LeftMargin;
6658 _Cells.back()->NoWrap = _CellParams.back().NoWrap;
6659 _Cells.back()->ColSpan = std::max(1, _Cells.back()->ColSpan);
6660 _Cells.back()->RowSpan = std::max(1, _Cells.back()->RowSpan);
6661 _Cells.back()->Height = _CellParams.back().Height;
6663 float temp;
6664 if (_Style.hasStyle("width"))
6666 // _Style.Width does not handle '%' unit currently
6667 if (_Style.Current.Width > 0)
6669 _Cells.back()->WidthWanted = _Style.Current.Width;
6670 _Cells.back()->TableRatio = 0;
6672 else
6674 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, _Style.getStyle("width").c_str());
6677 else if (elm.hasNonEmptyAttribute("width"))
6679 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, elm.getAttribute("width").c_str());
6682 _Cells.back()->NewLine = getTR();
6684 CSSRect<CSSBorder> border;
6685 border.Top.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6686 border.Right.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6687 border.Bottom.set(table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6688 border.Left.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6690 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6691 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6692 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6693 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6695 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6696 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6697 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6698 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6700 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6701 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6702 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6703 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6705 _Cells.back()->Border->setBorder(border);
6706 _Cells.back()->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6707 _Cells.back()->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6709 // padding from <table cellpadding="1">
6710 if (table->CellPadding)
6712 // FIXME: padding is ignored by vertical align
6713 _Cells.back()->PaddingTop = table->CellPadding;
6714 _Cells.back()->PaddingRight = table->CellPadding;
6715 _Cells.back()->PaddingBottom = table->CellPadding;
6716 _Cells.back()->PaddingLeft = table->CellPadding;
6719 if (_Style.hasStyle("padding-top")) _Cells.back()->PaddingTop = _Style.Current.PaddingTop;
6720 if (_Style.hasStyle("padding-right")) _Cells.back()->PaddingRight = _Style.Current.PaddingRight;
6721 if (_Style.hasStyle("padding-bottom")) _Cells.back()->PaddingBottom = _Style.Current.PaddingBottom;
6722 if (_Style.hasStyle("padding-left")) _Cells.back()->PaddingLeft = _Style.Current.PaddingLeft;
6724 table->addChild (_Cells.back());
6726 // reusing indent pushed by table
6727 _Indent.back() = 0;
6729 newParagraph(TDBeginSpace);
6730 // indent is already 0, getParagraph()->setMarginLeft(0); // maybe setIndent(0) if LI is using one
6732 // Reset TR flag
6733 if (!_TR.empty())
6734 _TR.back() = false;
6736 renderPseudoElement(":before", elm);
6739 void CGroupHTML::htmlTDend(const CHtmlElement &elm)
6741 renderPseudoElement(":after", elm);
6743 popIfNotEmpty(_CellParams);
6744 if (!_Cells.empty())
6745 _Cells.back() = NULL;
6748 // ***************************************************************************
6749 void CGroupHTML::htmlTEXTAREA(const CHtmlElement &elm)
6751 _IgnoreChildElements = true;
6753 // TODO: allow textarea without form
6754 if (_Forms.empty())
6755 return;
6757 // read general property
6758 string templateName;
6760 // Widget template name
6761 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
6762 templateName = elm.getAttribute("z_input_tmpl");
6764 // Get the string name
6765 _TextAreaName.clear();
6766 _TextAreaRow = 1;
6767 _TextAreaCols = 10;
6768 _TextAreaMaxLength = 1024;
6769 if (elm.hasNonEmptyAttribute("name"))
6770 _TextAreaName = elm.getAttribute("name");
6771 if (elm.hasNonEmptyAttribute("rows"))
6772 fromString(elm.getAttribute("rows"), _TextAreaRow);
6773 if (elm.hasNonEmptyAttribute("cols"))
6774 fromString(elm.getAttribute("cols"), _TextAreaCols);
6775 if (elm.hasNonEmptyAttribute("maxlength"))
6776 fromString(elm.getAttribute("maxlength"), _TextAreaMaxLength);
6778 _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup;
6780 std::string content = strFindReplaceAll(elm.serializeChilds(false), std::string("\r"), std::string(""));
6782 CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, content, _TextAreaMaxLength);
6783 if (textArea)
6785 // Add the text area to the form
6786 CGroupHTML::CForm::CEntry entry;
6787 entry.Name = _TextAreaName;
6788 entry.TextArea = textArea;
6789 _Forms.back().Entries.push_back (entry);
6793 // ***************************************************************************
6794 void CGroupHTML::htmlTH(const CHtmlElement &elm)
6796 htmlTD(elm);
6799 void CGroupHTML::htmlTHend(const CHtmlElement &elm)
6801 htmlTDend(elm);
6804 // ***************************************************************************
6805 void CGroupHTML::htmlTITLE(const CHtmlElement &elm)
6807 _IgnoreChildElements = true;
6809 // TODO: only from <head>
6810 // if (!_ReadingHeadTag) return;
6812 // consume all child elements
6813 _TitleString = strFindReplaceAll(elm.serializeChilds(false), std::string("\t"), std::string(" "));
6814 _TitleString = strFindReplaceAll(_TitleString, std::string("\n"), std::string(" "));
6815 setTitle(_TitleString);
6818 // ***************************************************************************
6819 void CGroupHTML::htmlTR(const CHtmlElement &elm)
6821 // prevent inheriting from table
6822 if (!_CellParams.empty())
6824 _CellParams.back().BgColor = CRGBA::Transparent;
6825 _CellParams.back().Height = 0;
6828 // Get cells parameters
6829 getCellsParameters(elm, true);
6831 m_TableRowBackgroundColor.push_back(_CellParams.back().BgColor);
6832 _CellParams.back().BgColor = CRGBA::Transparent;
6834 // TODO: this probably ends up in first cell
6835 renderPseudoElement(":before", elm);
6837 // Set TR flag
6838 if (!_TR.empty())
6839 _TR.back() = true;
6842 void CGroupHTML::htmlTRend(const CHtmlElement &elm)
6844 // TODO: this probably ends up in last cell
6845 renderPseudoElement(":after", elm);
6847 popIfNotEmpty(_CellParams);
6848 popIfNotEmpty(m_TableRowBackgroundColor);
6851 // ***************************************************************************
6852 void CGroupHTML::htmlUL(const CHtmlElement &elm)
6854 if (_UL.empty())
6855 _UL.push_back(HTMLOListElement(1, "disc"));
6856 else if (_UL.size() == 1)
6857 _UL.push_back(HTMLOListElement(1, "circle"));
6858 else
6859 _UL.push_back(HTMLOListElement(1, "square"));
6861 // if LI is already present
6862 _LI = _UL.size() > 1 || _DL.size() > 1;
6863 _Indent.push_back(getIndent() + ULIndent);
6865 renderPseudoElement(":before", elm);
6868 void CGroupHTML::htmlULend(const CHtmlElement &elm)
6870 if (_UL.empty())
6871 return;
6873 renderPseudoElement(":after", elm);
6875 popIfNotEmpty(_UL);
6876 popIfNotEmpty(_Indent);