Merge branch 'main/rendor-staging' into fixes
[ryzomcore.git] / nel / src / gui / group_html.cpp
blob5b43c895e153faa5ebaddc897db23164a1664d10
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 // Image setTexture will remove itself from Images while iterating over it.
303 // Do the swap to keep iterator safe.
304 std::vector<SImageInfo> vec;
305 vec.swap(Images);
307 // tmpdest file does not exist if download skipped (ie cache was used)
308 if (CFile::fileExists(tmpdest) || CFile::getFileSize(tmpdest) == 0)
310 try {
311 // verify that image is not corrupted
312 uint32 w, h;
313 CBitmap::loadSize(tmpdest, w, h);
314 if (w != 0 && h != 0)
316 if (CFile::fileExists(dest))
317 CFile::deleteFile(dest);
320 catch(const NLMISC::Exception &e)
322 // exception message has .tmp file name, so keep it for further analysis
323 nlwarning("Invalid image (%s) from url (%s): %s", tmpdest.c_str(), url.c_str(), e.what());
326 // to reload image on page, the easiest seems to be changing texture
327 // to temp file temporarily. that forces driver to reload texture from disk
328 // ITexture::touch() seem not to do this.
329 // cache was updated, first set texture as temp file
330 for(std::vector<SImageInfo>::iterator it = vec.begin(); it != vec.end(); ++it)
332 SImageInfo &img = *it;
333 Parent->setImage(img.Image, tmpdest, img.Type);
334 Parent->setImageSize(img.Image, img.Style);
337 CFile::moveFile(dest, tmpdest);
340 if (!CFile::fileExists(dest) || CFile::getFileSize(dest) == 0)
342 // placeholder if cached image failed
343 dest = "web_del.tga";
346 // even if image was cached, incase there was 'http://' image set to CViewBitmap
347 for(std::vector<SImageInfo>::iterator it = vec.begin(); it != vec.end(); ++it)
349 SImageInfo &img = *it;
350 Parent->setImage(img.Image, dest, img.Type);
351 Parent->setImageSize(img.Image, img.Style);
355 void CGroupHTML::TextureDownloadCB::finish()
357 // tmpdest file does not exist if download skipped (ie cache was used)
358 if (CFile::fileExists(tmpdest) && CFile::getFileSize(tmpdest) > 0)
360 if (CFile::fileExists(dest))
361 CFile::deleteFile(dest);
363 CFile::moveFile(dest, tmpdest);
366 CViewRenderer &rVR = *CViewRenderer::getInstance();
367 for(uint i = 0; i < TextureIds.size(); i++)
369 rVR.reloadTexture(TextureIds[i].first, dest);
370 TextureIds[i].second->invalidateCoords();
374 void CGroupHTML::BnpDownloadCB::finish()
376 bool verified = false;
377 // no tmpfile if file was already in cache
378 if (CFile::fileExists(tmpdest))
380 verified = m_md5sum.empty() || (m_md5sum != getMD5(tmpdest).toString());
381 if (verified)
383 if (CFile::fileExists(dest))
385 CFile::deleteFile(dest);
387 CFile::moveFile(dest, tmpdest);
389 else
391 CFile::deleteFile(tmpdest);
394 else if (CFile::fileExists(dest))
396 verified = m_md5sum.empty() || (m_md5sum != getMD5(dest).toString());
399 if (!m_lua.empty())
401 std::string script = "\nlocal __CURRENT_WINDOW__ = \""+Parent->getId()+"\"";
402 script += toString("\nlocal __DOWNLOAD_STATUS__ = %s\n", verified ? "true" : "false");
403 script += m_lua;
404 CLuaManager::getInstance().executeLuaScript(script, true );
408 // Check if domain is on TrustedDomain
409 bool CGroupHTML::isTrustedDomain(const string &domain)
411 vector<string>::iterator it;
412 it = find ( options.trustedDomains.begin(), options.trustedDomains.end(), domain);
413 return it != options.trustedDomains.end();
416 // Update view after download has finished
417 void CGroupHTML::setImage(CViewBase * view, const string &file, const TImageType type)
419 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
420 if(btn)
422 if (type == NormalImage)
424 btn->setTexture (file);
425 btn->setTexturePushed(file);
426 btn->invalidateCoords();
427 btn->invalidateContent();
428 paragraphChange();
430 else
432 btn->setTextureOver(file);
435 return;
438 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
439 if(btm)
441 btm->setTexture (file);
442 btm->invalidateCoords();
443 btm->invalidateContent();
444 paragraphChange();
446 return;
449 CGroupCell *btgc = dynamic_cast<CGroupCell*>(view);
450 if(btgc)
452 btgc->setTexture (file);
453 btgc->invalidateCoords();
454 btgc->invalidateContent();
455 paragraphChange();
457 return;
460 CGroupTable *table = dynamic_cast<CGroupTable*>(view);
461 if (table)
463 table->setTexture(file);
465 return;
469 // Force image width, height
470 void CGroupHTML::setImageSize(CViewBase *view, const CStyleParams &style)
472 sint32 width = style.Width;
473 sint32 height = style.Height;
474 sint32 maxw = style.MaxWidth;
475 sint32 maxh = style.MaxHeight;
477 sint32 imageWidth, imageHeight;
478 bool changed = true;
480 // get image texture size
481 // if image is being downloaded, then correct size is set after thats done
482 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
483 if(btn)
485 btn->fitTexture();
486 imageWidth = btn->getW(false);
487 imageHeight = btn->getH(false);
489 else
491 CViewBitmap *btm = dynamic_cast<CViewBitmap*>(view);
492 if(btm)
494 btm->fitTexture();
495 imageWidth = btm->getW(false);
496 imageHeight = btm->getH(false);
498 else
500 // not supported
501 return;
505 // if width/height is not requested, then use image size
506 // else recalculate missing value, keep image ratio
507 if (width == -1 && height == -1)
509 width = imageWidth;
510 height = imageHeight;
512 changed = false;
514 else
515 if (width == -1 || height == -1) {
516 float ratio = (float) imageWidth / std::max(1, imageHeight);
517 if (width == -1)
518 width = height * ratio;
519 else
520 height = width / ratio;
523 // apply max-width, max-height rules if asked
524 if (maxw > -1 || maxh > -1)
526 _Style.applyCssMinMax(width, height, 0, 0, maxw, maxh);
527 changed = true;
530 if (changed)
532 CCtrlButton *btn = dynamic_cast<CCtrlButton*>(view);
533 if(btn)
535 btn->setScale(true);
536 btn->setW(width);
537 btn->setH(height);
539 else
541 CViewBitmap *image = dynamic_cast<CViewBitmap*>(view);
542 if(image)
544 image->setScale(true);
545 image->setW(width);
546 image->setH(height);
552 void CGroupHTML::setTextButtonStyle(CCtrlTextButton *ctrlButton, const CStyleParams &style)
554 // this will also set size for <a class="ryzom-ui-button"> treating it like "display: inline-block;"
555 if (style.Width > 0) ctrlButton->setWMin(style.Width);
556 if (style.Height > 0) ctrlButton->setHMin(style.Height);
558 CViewText *pVT = ctrlButton->getViewText();
559 if (pVT)
561 setTextStyle(pVT, style);
564 if (style.hasStyle("background-color"))
566 ctrlButton->setColor(style.Background.color);
567 if (style.hasStyle("-ryzom-background-color-over"))
569 ctrlButton->setColorOver(style.BackgroundColorOver);
571 else
573 ctrlButton->setColorOver(style.Background.color);
575 ctrlButton->setTexture("", "blank.tga", "", false);
576 ctrlButton->setTextureOver("", "blank.tga", "");
577 ctrlButton->setProperty("force_text_over", "true");
579 else if (style.hasStyle("-ryzom-background-color-over"))
581 ctrlButton->setColorOver(style.BackgroundColorOver);
582 ctrlButton->setProperty("force_text_over", "true");
583 ctrlButton->setTextureOver("blank.tga", "blank.tga", "blank.tga");
587 void CGroupHTML::setTextStyle(CViewText *pVT, const CStyleParams &style)
589 if (pVT)
591 pVT->setColor(style.TextColor);
592 pVT->setFontName(style.FontFamily);
593 pVT->setFontSize(style.FontSize, false);
594 pVT->setEmbolden(style.FontWeight >= FONT_WEIGHT_BOLD);
595 pVT->setOblique(style.FontOblique);
596 pVT->setUnderlined(style.Underlined);
597 pVT->setStrikeThrough(style.StrikeThrough);
598 if (style.TextShadow.Enabled)
600 pVT->setShadow(true);
601 pVT->setShadowColor(style.TextShadow.Color);
602 pVT->setShadowOutline(style.TextShadow.Outline);
603 pVT->setShadowOffset(style.TextShadow.X, style.TextShadow.Y);
608 // Get an url and return the local filename with the path where the url image should be
609 string CGroupHTML::localImageName(const string &url)
611 string dest = "cache/";
612 dest += getMD5((uint8 *)url.c_str(), (uint32)url.size()).toString();
613 dest += ".cache";
614 return dest;
617 void CGroupHTML::pumpCurlQueue()
619 if (RunningCurls < options.curlMaxConnections)
621 std::list<CDataDownload*>::iterator it=Curls.begin();
622 while(it != Curls.end() && RunningCurls < options.curlMaxConnections)
624 if ((*it)->data == NULL)
626 LOG_DL("(%s) starting new download '%s'", _Id.c_str(), it->url.c_str());
627 if (!startCurlDownload(*it))
629 LOG_DL("(%s) failed to start '%s)'", _Id.c_str(), it->url.c_str());
630 finishCurlDownload(*it);
632 it = Curls.erase(it);
633 continue;
637 ++it;
641 if (RunningCurls > 0 || !Curls.empty())
642 LOG_DL("(%s) RunningCurls %d, _Curls %d", _Id.c_str(), RunningCurls, Curls.size());
645 // Add url to MultiCurl queue and return cURL handle
646 bool CGroupHTML::startCurlDownload(CDataDownload *download)
648 if (!MultiCurl)
650 nlwarning("Invalid MultiCurl handle, unable to download '%s'", download->url.c_str());
651 return false;
654 time_t currentTime;
655 time(&currentTime);
657 CHttpCacheObject cache;
658 if (CFile::fileExists(download->dest))
659 cache = CHttpCache::getInstance()->lookup(download->dest);
661 if (cache.Expires > currentTime)
663 LOG_DL("Cache for (%s) is not expired (%s, expires:%d)", download->url.c_str(), download->dest.c_str(), cache.Expires - currentTime);
664 return false;
667 // use browser Id so that two browsers would not use same temp file
668 download->tmpdest = localImageName(_Id + download->dest) + ".tmp";
670 // erase the tmp file if exists
671 if (CFile::fileExists(download->tmpdest))
673 CFile::deleteFile(download->tmpdest);
676 FILE *fp = nlfopen (download->tmpdest, "wb");
677 if (fp == NULL)
679 nlwarning("Can't open file '%s' for writing: code=%d '%s'", download->tmpdest.c_str (), errno, strerror(errno));
680 return false;
683 CURL *curl = curl_easy_init();
684 if (!curl)
686 fclose(fp);
687 CFile::deleteFile(download->tmpdest);
689 nlwarning("Creating cURL handle failed, unable to download '%s'", download->url.c_str());
690 return false;
692 LOG_DL("curl easy handle %p created for '%s'", curl, download->url.c_str());
694 // https://
695 if (toLowerAscii(download->url.substr(0, 8)) == "https://")
697 // if supported, use custom SSL context function to load certificates
698 NLWEB::CCurlCertificates::useCertificates(curl);
701 download->data = new CCurlWWWData(curl, download->url);
702 download->fp = fp;
704 // initial connection timeout, curl default is 300sec
705 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, download->ConnectionTimeout);
707 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, true);
708 curl_easy_setopt(curl, CURLOPT_URL, download->url.c_str());
710 // limit curl to HTTP and HTTPS protocols only
711 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
712 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
714 std::vector<std::string> headers;
715 if (!cache.Etag.empty())
716 headers.push_back("If-None-Match: " + cache.Etag);
718 if (!cache.LastModified.empty())
719 headers.push_back("If-Modified-Since: " + cache.LastModified);
721 if (headers.size() > 0)
722 download->data->sendHeaders(headers);
724 // catch headers
725 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
726 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, download->data);
728 std::string userAgent = options.appName + "/" + options.appVersion;
729 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
731 CUrlParser uri(download->url);
732 if (!uri.host.empty())
733 sendCookies(curl, uri.host, isTrustedDomain(uri.host));
735 curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
736 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
738 CURLMcode ret = curl_multi_add_handle(MultiCurl, curl);
739 if (ret != CURLM_OK)
741 nlwarning("cURL multi handle %p error %d on '%s'", curl, ret, download->url.c_str());
742 return false;
745 RunningCurls++;
746 return true;
749 void CGroupHTML::finishCurlDownload(CDataDownload *download)
751 if (download)
753 download->finish();
754 delete download;
756 else
758 nlwarning("Unknown CURL download (nullptr)");
762 // Add a image download request in the multi_curl
763 // return new textureId and download callback
764 ICurlDownloadCB *CGroupHTML::addTextureDownload(const string &url, sint32 &texId, CViewBase *view)
766 CViewRenderer &rVR = *CViewRenderer::getInstance();
767 // data:image/png;base64,AA...==
768 if (startsWith(url, "data:image/"))
770 texId = rVR.createTextureFromDataURL(url);
771 return NULL;
774 std::string finalUrl;
775 // load the image from local files/bnp
776 if (lookupLocalFile(finalUrl, std::string(CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga").c_str(), false))
778 texId = rVR.createTexture(finalUrl);
779 return NULL;
782 finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
784 // use requested url for local name (cache)
785 string dest = localImageName(url);
786 LOG_DL("add to download '%s' dest '%s'", finalUrl.c_str(), dest.c_str());
788 if (CFile::fileExists(dest) && CFile::getFileSize(dest) > 0)
789 texId = rVR.createTexture(dest);
790 else
791 texId = rVR.newTextureId(dest);
793 // Search if we are not already downloading this url.
794 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
796 if((*it)->url == finalUrl)
798 LOG_DL("already downloading '%s' img %p", finalUrl.c_str(), img);
799 TextureDownloadCB *cb = dynamic_cast<TextureDownloadCB*>(*it);
800 if (cb)
802 cb->addTexture(texId, view);
803 // return pointer to shared ImageDownloadCB
804 return cb;
806 else
808 nlwarning("Found texture download '%s', but casting to TextureDownloadCB failed", finalUrl.c_str());
813 Curls.push_back(new TextureDownloadCB(finalUrl, dest, texId, this));
814 // as we return pointer to callback, skip starting downloads just now
815 //pumpCurlQueue();
816 return Curls.back();
819 // Add a image download request in the multi_curl
820 ICurlDownloadCB *CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type, const std::string &placeholder)
822 std::string finalUrl;
823 img->setModulateGlobalColor(style.GlobalColor);
825 // data:image/png;base64,AA...==
826 if (startsWith(url, "data:image/"))
828 setImage(img, decodeURIComponent(url), type);
829 setImageSize(img, style);
830 return NULL;
833 // load the image from local files/bnp
834 std::string image = CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga";
835 if (lookupLocalFile(finalUrl, image.c_str(), false))
837 setImage(img, image, type);
838 setImageSize(img, style);
839 return NULL;
842 finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
844 // use requested url for local name (cache)
845 string dest = localImageName(url);
846 LOG_DL("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
848 // Display cached image while downloading new
849 if (type != OverImage)
851 std::string temp = dest;
852 if (!CFile::fileExists(temp) || CFile::getFileSize(temp) == 0)
854 temp = placeholder;
856 setImage(img, temp, type);
857 setImageSize(img, style);
860 // Search if we are not already downloading this url.
861 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
863 if((*it)->url == finalUrl)
865 LOG_DL("already downloading '%s' img %p", finalUrl.c_str(), img);
866 ImageDownloadCB *cb = dynamic_cast<ImageDownloadCB*>(*it);
867 if (cb)
869 cb->addImage(img, style, type);
870 // return pointer to shared ImageDownloadCB
871 return cb;
873 else
875 nlwarning("Found image download '%s', but casting to ImageDownloadCB failed", finalUrl.c_str());
880 Curls.push_back(new ImageDownloadCB(finalUrl, dest, img, style, type, this));
881 // as we return pointer to callback, skip starting downloads just now
882 //pumpCurlQueue();
883 return Curls.back();
886 void CGroupHTML::removeImageDownload(ICurlDownloadCB *handle, CViewBase *img)
888 ImageDownloadCB *cb = dynamic_cast<ImageDownloadCB*>(handle);
889 if (!cb) {
890 nlwarning("Trying to remove image from downloads, but ICurlDownloadCB pointer did not cast to ImageDownloadCB");
891 return;
893 // image will be removed from handle, but handle is kept and image will be downloaded
894 cb->removeImage(img);
897 void CGroupHTML::initImageDownload()
899 LOG_DL("Init Image Download");
901 string pathName = "cache";
902 if ( ! CFile::isExists( pathName ) )
903 CFile::createDirectory( pathName );
907 // Get an url and return the local filename with the path where the bnp should be
908 string CGroupHTML::localBnpName(const string &url)
910 size_t lastIndex = url.find_last_of("/");
911 string dest = "user/"+url.substr(lastIndex+1);
912 return dest;
915 // Add a bnp download request in the multi_curl, return true if already downloaded
916 bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum)
918 url = upgradeInsecureUrl(getAbsoluteUrl(url));
920 // Search if we are not already downloading this url.
921 for(std::list<CDataDownload*>::const_iterator it = Curls.begin(); it != Curls.end(); ++it)
923 if((*it)->url == url)
925 LOG_DL("already downloading '%s'", url.c_str());
926 return false;
930 string dest = localBnpName(url);
931 LOG_DL("add to download '%s' dest '%s'", url.c_str(), dest.c_str());
933 // create/delete the local file
934 if (NLMISC::CFile::fileExists(dest))
936 if (action == "override" || action == "delete")
938 CFile::setRWAccess(dest);
939 NLMISC::CFile::deleteFile(dest);
941 else
943 return true;
946 if (action != "delete")
948 Curls.push_back(new BnpDownloadCB(url, dest, md5sum, script, this));
949 pumpCurlQueue();
951 else
952 return true;
954 return false;
957 void CGroupHTML::initBnpDownload()
959 if (!_TrustedDomain)
960 return;
962 LOG_DL("Init Bnp Download");
963 string pathName = "user";
964 if ( ! CFile::isExists( pathName ) )
965 CFile::createDirectory( pathName );
968 void CGroupHTML::addStylesheetDownload(const std::vector<CHtmlParser::StyleLink> links)
970 for(uint i = 0; i < links.size(); ++i)
972 _StylesheetQueue.push_back(links[i]);
973 std::string url = getAbsoluteUrl(links[i].Url);
974 _StylesheetQueue.back().Url = url;
976 // push to the front of the queue
977 Curls.push_front(new StylesheetDownloadCB(url, localImageName(url), this));
979 pumpCurlQueue();
982 // Call this evenly to check if an element is downloaded and then manage it
983 void CGroupHTML::checkDownloads()
985 //nlassert(_CrtCheckMemory());
987 if(Curls.empty() && _CurlWWW == NULL)
989 return;
992 int NewRunningCurls = 0;
993 while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(MultiCurl, &NewRunningCurls))
995 LOG_DL("more to do now %d - %d curls", NewRunningCurls, Curls.size());
998 LOG_DL("NewRunningCurls:%d, RunningCurls:%d", NewRunningCurls, RunningCurls);
1000 // check which downloads are done
1001 CURLMsg *msg;
1002 int msgs_left;
1003 while ((msg = curl_multi_info_read(MultiCurl, &msgs_left)))
1005 LOG_DL("> (%s) msgs_left %d", _Id.c_str(), msgs_left);
1006 if (msg->msg == CURLMSG_DONE)
1008 if (_CurlWWW && _CurlWWW->Request && _CurlWWW->Request == msg->easy_handle)
1010 std::string error;
1011 bool success = msg->data.result == CURLE_OK;
1012 if (!success)
1014 error = curl_easy_strerror(msg->data.result);
1016 LOG_DL("html download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
1017 htmlDownloadFinished(success, error);
1019 else
1021 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
1023 if((*it)->data && (*it)->data->Request == msg->easy_handle)
1025 std::string error;
1026 bool success = msg->data.result == CURLE_OK;
1027 if (!success)
1029 error = curl_easy_strerror(msg->data.result);
1031 LOG_DL("data download finished with curl code %d (%s)", (sint)msg->msg, error.c_str());
1032 dataDownloadFinished(success, error, *it);
1034 Curls.erase(it);
1035 break;
1042 RunningCurls = NewRunningCurls;
1043 pumpCurlQueue();
1047 void CGroupHTML::releaseDownloads()
1049 LOG_DL("Release Downloads");
1051 if (_CurlWWW)
1053 LOG_DL("(%s) stop html url '%s'", _Id.c_str(), _CurlWWW->Url.c_str());
1054 if (MultiCurl)
1055 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
1057 delete _CurlWWW;
1058 _CurlWWW = NULL;
1061 releaseDataDownloads();
1064 void CGroupHTML::releaseDataDownloads()
1066 LOG_DL("Clear pointers to %d curls", Curls.size());
1068 // remove all queued and already started downloads
1069 for(std::list<CDataDownload*>::iterator it = Curls.begin(); it != Curls.end(); ++it)
1071 CDataDownload &dl = *(*it);
1072 if (dl.data)
1074 LOG_DL("(%s) stop data url '%s'", _Id.c_str(), dl.url.c_str());
1075 if (MultiCurl)
1077 curl_multi_remove_handle(MultiCurl, dl.data->Request);
1080 // close and remove temp file
1081 if (dl.fp)
1083 fclose(dl.fp);
1085 if (CFile::fileExists(dl.tmpdest))
1087 CFile::deleteFile(dl.tmpdest);
1091 // release CDataDownload
1092 delete *it;
1094 Curls.clear();
1096 // also clear css queue as it depends on Curls
1097 _StylesheetQueue.clear();
1100 class CGroupListAdaptor : public CInterfaceGroup
1102 public:
1103 CGroupListAdaptor(const TCtorParam &param)
1104 : CInterfaceGroup(param)
1107 private:
1108 void updateCoords()
1110 if (_Parent)
1112 // Get the W max from the parent
1113 _W = std::min(_Parent->getMaxWReal(), _Parent->getWReal());
1114 _WReal = _W;
1116 CInterfaceGroup::updateCoords();
1120 // ***************************************************************************
1122 template<class A> void popIfNotEmpty(A &vect) { if(!vect.empty()) vect.pop_back(); }
1124 // ***************************************************************************
1125 TStyle CGroupHTML::parseStyle (const string &str_styles)
1127 TStyle styles;
1128 vector<string> elements;
1129 NLMISC::splitString(str_styles, ";", elements);
1131 for(uint i = 0; i < elements.size(); ++i)
1133 vector<string> style;
1134 NLMISC::splitString(elements[i], ":", style);
1135 if (style.size() >= 2)
1137 string fullstyle = style[1];
1138 for (uint j=2; j < style.size(); j++)
1139 fullstyle += ":"+style[j];
1140 styles[trim(style[0])] = trimSeparators(fullstyle);
1144 return styles;
1147 // ***************************************************************************
1149 void CGroupHTML::addText (const char *buf, int len)
1151 if (_Browsing)
1153 if (_IgnoreText)
1154 return;
1156 // Build a UTF8 string
1157 if (_ParsingLua && _TrustedDomain)
1159 // we are parsing a lua script
1160 _LuaScript += string(buf, buf + len);
1161 // no more to do
1162 return;
1165 // Build a unicode string
1166 CUtfStringView inputStringView(buf, len);
1168 // Build the final unicode string
1169 string tmp;
1170 tmp.reserve(len);
1171 u32char lastChar = 0;
1172 u32char inputStringView0 = *inputStringView.begin();
1173 for (CUtfStringView::iterator it(inputStringView.begin()), end(inputStringView.end()); it != end; ++it)
1175 u32char output;
1176 bool keep;
1177 // special treatment for 'nbsp' (which is returned as a discreet space)
1178 if (len == 1 && inputStringView0 == 32)
1180 // this is a nbsp entity
1181 output = *it;
1182 keep = true;
1184 else
1186 // not nbsp, use normal white space removal routine
1187 keep = translateChar (output, *it, lastChar);
1190 if (keep)
1192 CUtfStringView::append(tmp, output);
1193 lastChar = output;
1197 if (!tmp.empty())
1198 addString(tmp);
1202 // ***************************************************************************
1204 #define registerAnchorName(prefix) \
1206 if (present[prefix##_ID] && value[prefix##_ID]) \
1207 _AnchorName.push_back(value[prefix##_ID]); \
1210 // ***************************************************************************
1211 void CGroupHTML::beginElement (CHtmlElement &elm)
1213 _Style.pushStyle();
1214 _CurrentHTMLElement = &elm;
1215 _CurrentHTMLNextSibling = elm.nextSibling;
1217 // set element style from css and style attribute
1218 _Style.getStyleFor(elm);
1219 if (!elm.Style.empty())
1221 _Style.applyStyle(elm.Style);
1224 if (elm.hasNonEmptyAttribute("name"))
1226 _AnchorName.push_back(elm.getAttribute("name"));
1228 if (elm.hasNonEmptyAttribute("id"))
1230 _AnchorName.push_back(elm.getAttribute("id"));
1233 if (_Style.Current.DisplayBlock)
1235 endParagraph();
1238 switch(elm.ID)
1240 case HTML_A: htmlA(elm); break;
1241 case HTML_BASE: htmlBASE(elm); break;
1242 case HTML_BODY: htmlBODY(elm); break;
1243 case HTML_BR: htmlBR(elm); break;
1244 case HTML_BUTTON: htmlBUTTON(elm); break;
1245 case HTML_DD: htmlDD(elm); break;
1246 case HTML_DEL: renderPseudoElement(":before", elm); break;
1247 case HTML_DIV: htmlDIV(elm); break;
1248 case HTML_DL: htmlDL(elm); break;
1249 case HTML_DT: htmlDT(elm); break;
1250 case HTML_EM: renderPseudoElement(":before", elm); break;
1251 case HTML_FONT: htmlFONT(elm); break;
1252 case HTML_FORM: htmlFORM(elm); break;
1253 case HTML_H1://no-break
1254 case HTML_H2://no-break
1255 case HTML_H3://no-break
1256 case HTML_H4://no-break
1257 case HTML_H5://no-break
1258 case HTML_H6: htmlH(elm); break;
1259 case HTML_HEAD: htmlHEAD(elm); break;
1260 case HTML_HR: htmlHR(elm); break;
1261 case HTML_HTML: htmlHTML(elm); break;
1262 case HTML_I: htmlI(elm); break;
1263 case HTML_IMG: htmlIMG(elm); break;
1264 case HTML_INPUT: htmlINPUT(elm); break;
1265 case HTML_LI: htmlLI(elm); break;
1266 case HTML_LUA: htmlLUA(elm); break;
1267 case HTML_META: htmlMETA(elm); break;
1268 case HTML_METER: htmlMETER(elm); break;
1269 case HTML_OBJECT: htmlOBJECT(elm); break;
1270 case HTML_OL: htmlOL(elm); break;
1271 case HTML_OPTION: htmlOPTION(elm); break;
1272 case HTML_P: htmlP(elm); break;
1273 case HTML_PRE: htmlPRE(elm); break;
1274 case HTML_PROGRESS: htmlPROGRESS(elm); break;
1275 case HTML_SCRIPT: htmlSCRIPT(elm); break;
1276 case HTML_SELECT: htmlSELECT(elm); break;
1277 case HTML_SMALL: renderPseudoElement(":before", elm); break;
1278 case HTML_SPAN: renderPseudoElement(":before", elm); break;
1279 case HTML_STRONG: renderPseudoElement(":before", elm); break;
1280 case HTML_STYLE: htmlSTYLE(elm); break;
1281 case HTML_TABLE: htmlTABLE(elm); break;
1282 case HTML_TBODY: renderPseudoElement(":before", elm); break;
1283 case HTML_TD: htmlTD(elm); break;
1284 case HTML_TEXTAREA: htmlTEXTAREA(elm); break;
1285 case HTML_TFOOT: renderPseudoElement(":before", elm); break;
1286 case HTML_TH: htmlTH(elm); break;
1287 case HTML_TITLE: htmlTITLE(elm); break;
1288 case HTML_TR: htmlTR(elm); break;
1289 case HTML_U: renderPseudoElement(":before", elm); break;
1290 case HTML_UL: htmlUL(elm); break;
1291 default:
1292 renderPseudoElement(":before", elm);
1293 break;
1297 // ***************************************************************************
1298 void CGroupHTML::endElement(CHtmlElement &elm)
1300 _CurrentHTMLElement = &elm;
1302 switch(elm.ID)
1304 case HTML_A: htmlAend(elm); break;
1305 case HTML_BASE: break;
1306 case HTML_BODY: renderPseudoElement(":after", elm); break;
1307 case HTML_BR: break;
1308 case HTML_BUTTON: htmlBUTTONend(elm); break;
1309 case HTML_DD: htmlDDend(elm); break;
1310 case HTML_DEL: renderPseudoElement(":after", elm); break;
1311 case HTML_DIV: htmlDIVend(elm); break;
1312 case HTML_DL: htmlDLend(elm); break;
1313 case HTML_DT: htmlDTend(elm); break;
1314 case HTML_EM: renderPseudoElement(":after", elm);break;
1315 case HTML_FONT: break;
1316 case HTML_FORM: htmlFORMend(elm); break;
1317 case HTML_H1://no-break
1318 case HTML_H2://no-break
1319 case HTML_H3://no-break
1320 case HTML_H4://no-break
1321 case HTML_H5://no-break
1322 case HTML_H6: htmlHend(elm); break;
1323 case HTML_HEAD: htmlHEADend(elm); break;
1324 case HTML_HR: break;
1325 case HTML_HTML: break;
1326 case HTML_I: htmlIend(elm); break;
1327 case HTML_IMG: break;
1328 case HTML_INPUT: break;
1329 case HTML_LI: htmlLIend(elm); break;
1330 case HTML_LUA: htmlLUAend(elm); break;
1331 case HTML_META: break;
1332 case HTML_METER: break;
1333 case HTML_OBJECT: htmlOBJECTend(elm); break;
1334 case HTML_OL: htmlOLend(elm); break;
1335 case HTML_OPTION: htmlOPTIONend(elm); break;
1336 case HTML_P: htmlPend(elm); break;
1337 case HTML_PRE: htmlPREend(elm); break;
1338 case HTML_SCRIPT: htmlSCRIPTend(elm); break;
1339 case HTML_SELECT: htmlSELECTend(elm); break;
1340 case HTML_SMALL: renderPseudoElement(":after", elm);break;
1341 case HTML_SPAN: renderPseudoElement(":after", elm);break;
1342 case HTML_STRONG: renderPseudoElement(":after", elm);break;
1343 case HTML_STYLE: htmlSTYLEend(elm); break;
1344 case HTML_TABLE: htmlTABLEend(elm); break;
1345 case HTML_TD: htmlTDend(elm); break;
1346 case HTML_TBODY: renderPseudoElement(":after", elm); break;
1347 case HTML_TEXTAREA: break;
1348 case HTML_TFOOT: renderPseudoElement(":after", elm); break;
1349 case HTML_TH: htmlTHend(elm); break;
1350 case HTML_TITLE: break;
1351 case HTML_TR: htmlTRend(elm); break;
1352 case HTML_U: renderPseudoElement(":after", elm); break;
1353 case HTML_UL: htmlULend(elm); break;
1354 default:
1355 renderPseudoElement(":after", elm);
1356 break;
1359 if (_Style.Current.DisplayBlock)
1361 endParagraph();
1364 _Style.popStyle();
1367 // ***************************************************************************
1368 void CGroupHTML::renderPseudoElement(const std::string &pseudo, const CHtmlElement &elm)
1370 if (pseudo != ":before" && pseudo != ":after")
1371 return;
1373 if (!elm.hasPseudo(pseudo))
1374 return;
1376 _Style.pushStyle();
1377 _Style.applyStyle(elm.getPseudo(pseudo));
1379 // TODO: 'content' should already be tokenized in css parser as it has all the functions for that
1380 std::string content = trim(_Style.getStyle("content"));
1381 if (toLowerAscii(content) == "none" || toLowerAscii(content) == "normal")
1383 _Style.popStyle();
1384 return;
1387 std::string::size_type pos = 0;
1388 // TODO: tokenize by whitespace
1389 while(pos < content.size())
1391 std::string::size_type start;
1392 std::string token;
1394 // not supported
1395 // counter, open-quote, close-quote, no-open-quote, no-close-quote
1396 if (content[pos] == '"' || content[pos] == '\'')
1398 char quote = content[pos];
1399 pos++;
1400 start = pos;
1401 while(pos < content.size() && content[pos] != quote)
1403 if (content[pos] == '\\') pos++;
1404 pos++;
1406 token = content.substr(start, pos - start);
1407 addString(token);
1409 // skip closing quote
1410 pos++;
1412 else if (content[pos] == 'u' && pos < content.size() - 6 && toLowerAscii(content.substr(pos, 4)) == "url(")
1414 // url(/path-to/image.jpg) / "Alt!"
1415 // url("/path to/image.jpg") / "Alt!"
1416 std::string tooltip;
1418 start = pos + 4;
1419 // fails if url contains ')'
1420 pos = content.find(")", start);
1421 if (pos == std::string::npos)
1422 break;
1424 token = trim(content.substr(start, pos - start));
1425 // skip ')'
1426 pos++;
1428 // scan for tooltip
1429 start = pos;
1430 while(pos < content.size() && content[pos] == ' ' && content[pos] != '/')
1432 pos++;
1434 if (pos < content.size() && content[pos] == '/')
1436 // skip '/'
1437 pos++;
1439 // skip whitespace
1440 while(pos < content.size() && content[pos] == ' ')
1442 pos++;
1444 if (pos < content.size() && (content[pos] == '\'' || content[pos] == '"'))
1446 char openQuote = content[pos];
1447 pos++;
1448 start = pos;
1449 while(pos < content.size() && content[pos] != openQuote)
1451 if (content[pos] == '\\') pos++;
1452 pos++;
1454 tooltip = content.substr(start, pos - start);
1456 // skip closing quote
1457 pos++;
1459 else
1461 // tooltip should be quoted
1462 pos = start;
1463 tooltip.clear();
1466 else
1468 // no tooltip
1469 pos = start;
1472 if (tooltip.empty())
1474 addImage(getId() + pseudo, token, false, _Style.Current);
1476 else
1478 tooltip = trimQuotes(tooltip);
1479 addButton(CCtrlButton::PushButton, getId() + pseudo, token, token, "", "", "", tooltip.c_str(), _Style.Current);
1482 else if (content[pos] == 'a' && pos < content.size() - 7)
1484 // attr(title)
1485 start = pos + 5;
1486 std::string::size_type end = 0;
1487 end = content.find(")", start);
1488 if (end != std::string::npos)
1490 token = content.substr(start, end - start);
1491 // skip ')'
1492 pos = end + 1;
1493 if (elm.hasAttribute(token))
1495 addString(elm.getAttribute(token));
1498 else
1500 // skip over 'a'
1501 pos++;
1504 else
1506 pos++;
1510 _Style.popStyle();
1513 // ***************************************************************************
1514 void CGroupHTML::renderDOM(CHtmlElement &elm)
1516 if (elm.Type == CHtmlElement::TEXT_NODE)
1518 addText(elm.Value.c_str(), elm.Value.size());
1520 else
1522 beginElement(elm);
1524 if (!_IgnoreChildElements)
1526 std::list<CHtmlElement>::iterator it = elm.Children.begin();
1527 while(it != elm.Children.end())
1529 renderDOM(*it);
1531 ++it;
1534 _IgnoreChildElements = false;
1536 endElement(elm);
1540 // ***************************************************************************
1541 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTML, std::string, "html");
1544 // ***************************************************************************
1545 uint32 CGroupHTML::_GroupHtmlUIDPool= 0;
1546 CGroupHTML::TGroupHtmlByUIDMap CGroupHTML::_GroupHtmlByUID;
1549 // ***************************************************************************
1550 CGroupHTML::CGroupHTML(const TCtorParam &param)
1551 : CGroupScrollText(param),
1552 _TimeoutValue(DEFAULT_RYZOM_CONNECTION_TIMEOUT),
1553 _RedirectsRemaining(DEFAULT_RYZOM_REDIRECT_LIMIT),
1554 _CurrentHTMLElement(NULL)
1556 // add it to map of group html created
1557 _GroupHtmlUID= ++_GroupHtmlUIDPool; // valid assigned Id begin to 1!
1558 _GroupHtmlByUID[_GroupHtmlUID]= this;
1560 // init
1561 _TrustedDomain = false;
1562 _ParsingLua = false;
1563 _LuaHrefHack = false;
1564 _IgnoreText = false;
1565 _IgnoreChildElements = false;
1566 _BrowseNextTime = false;
1567 _PostNextTime = false;
1568 _Browsing = false;
1569 _CurrentViewLink = NULL;
1570 _CurrentViewImage = NULL;
1571 _Indent.clear();
1572 _LI = false;
1573 _SelectOption = false;
1574 _GroupListAdaptor = NULL;
1575 _UrlFragment.clear();
1576 _RefreshUrl.clear();
1577 _NextRefreshTime = 0.0;
1578 _LastRefreshTime = 0.0;
1579 _RenderNextTime = false;
1580 _WaitingForStylesheet = false;
1581 _AutoIdSeq = 0;
1582 _FormOpen = false;
1584 // Register
1585 CWidgetManager::getInstance()->registerClockMsgTarget(this);
1587 // HTML parameters
1588 ErrorColor = CRGBA(255, 0, 0);
1589 LinkColor = CRGBA(0, 0, 255);
1590 ErrorColorGlobalColor = false;
1591 LinkColorGlobalColor = false;
1592 TextColorGlobalColor = false;
1593 LIBeginSpace = 4;
1594 ULBeginSpace = 12;
1595 PBeginSpace = 12;
1596 TDBeginSpace = 0;
1597 ULIndent = 30;
1598 LineSpaceFontFactor = 0.5f;
1599 DefaultButtonGroup = "html_text_button";
1600 DefaultFormTextGroup = "edit_box_widget";
1601 DefaultFormTextAreaGroup = "edit_box_widget_multiline";
1602 DefaultFormSelectGroup = "html_form_select_widget";
1603 DefaultFormSelectBoxMenuGroup = "html_form_select_box_menu_widget";
1604 DefaultCheckBoxBitmapNormal = "checkbox_normal.tga";
1605 DefaultCheckBoxBitmapPushed = "checkbox_pushed.tga";
1606 DefaultCheckBoxBitmapOver = "checkbox_over.tga";
1607 DefaultRadioButtonBitmapNormal = "w_radiobutton.png";
1608 DefaultRadioButtonBitmapPushed = "w_radiobutton_pushed.png";
1609 DefaultBackgroundBitmapView = "bg";
1610 clearContext();
1612 MultiCurl = curl_multi_init();
1613 #ifdef CURLMOPT_MAX_HOST_CONNECTIONS
1614 if (MultiCurl)
1616 // added in libcurl 7.30.0
1617 curl_multi_setopt(MultiCurl, CURLMOPT_MAX_HOST_CONNECTIONS, options.curlMaxConnections);
1618 curl_multi_setopt(MultiCurl, CURLMOPT_PIPELINING, 1);
1620 #endif
1621 RunningCurls = 0;
1622 _CurlWWW = NULL;
1624 initImageDownload();
1625 initBnpDownload();
1627 // setup default browser style
1628 setProperty("browser_css_file", "browser.css");
1631 // ***************************************************************************
1633 CGroupHTML::~CGroupHTML()
1635 //releaseImageDownload();
1637 // TestYoyo
1638 //nlinfo("** CGroupHTML Destroy: %x, %s, uid%d", this, _Id.c_str(), _GroupHtmlUID);
1640 /* Erase from map of Group HTML (thus requestTerminated() callback won't be called)
1641 Do it first, just because don't want requestTerminated() to be called while I'm destroying
1642 (useless and may be dangerous)
1644 _GroupHtmlByUID.erase(_GroupHtmlUID);
1646 clearContext();
1647 releaseDownloads();
1649 if (_CurlWWW)
1650 delete _CurlWWW;
1652 if(MultiCurl)
1653 curl_multi_cleanup(MultiCurl);
1656 std::string CGroupHTML::getProperty( const std::string &name ) const
1658 if( name == "url" )
1660 return _URL;
1662 else
1663 if( name == "title_prefix" )
1665 return _TitlePrefix;
1667 else
1668 if( name == "error_color" )
1670 return toString( ErrorColor );
1672 else
1673 if( name == "link_color" )
1675 return toString( LinkColor );
1677 else
1678 if( name == "error_color_global_color" )
1680 return toString( ErrorColorGlobalColor );
1682 else
1683 if( name == "link_color_global_color" )
1685 return toString( LinkColorGlobalColor );
1687 else
1688 if( name == "text_color_global_color" )
1690 return toString( TextColorGlobalColor );
1692 else
1693 if( name == "td_begin_space" )
1695 return toString( TDBeginSpace );
1697 else
1698 if( name == "paragraph_begin_space" )
1700 return toString( PBeginSpace );
1702 else
1703 if( name == "li_begin_space" )
1705 return toString( LIBeginSpace );
1707 else
1708 if( name == "ul_begin_space" )
1710 return toString( ULBeginSpace );
1712 else
1713 if( name == "ul_indent" )
1715 return toString( ULIndent );
1717 else
1718 if( name == "multi_line_space_factor" )
1720 return toString( LineSpaceFontFactor );
1722 else
1723 if( name == "form_text_area_group" )
1725 return DefaultFormTextGroup;
1727 else
1728 if( name == "form_select_group" )
1730 return DefaultFormSelectGroup;
1732 else
1733 if( name == "checkbox_bitmap_normal" )
1735 return DefaultCheckBoxBitmapNormal;
1737 else
1738 if( name == "checkbox_bitmap_pushed" )
1740 return DefaultCheckBoxBitmapPushed;
1742 else
1743 if( name == "checkbox_bitmap_over" )
1745 return DefaultCheckBoxBitmapOver;
1747 else
1748 if( name == "radiobutton_bitmap_normal" )
1750 return DefaultRadioButtonBitmapNormal;
1752 else
1753 if( name == "radiobutton_bitmap_pushed" )
1755 return DefaultRadioButtonBitmapPushed;
1757 else
1758 if( name == "radiobutton_bitmap_over" )
1760 return DefaultRadioButtonBitmapOver;
1762 else
1763 if( name == "background_bitmap_view" )
1765 return DefaultBackgroundBitmapView;
1767 else
1768 if( name == "home" )
1770 return Home;
1772 else
1773 if( name == "browse_next_time" )
1775 return toString( _BrowseNextTime );
1777 else
1778 if( name == "browse_tree" )
1780 return _BrowseTree;
1782 else
1783 if( name == "browse_undo" )
1785 return _BrowseUndoButton;
1787 else
1788 if( name == "browse_redo" )
1790 return _BrowseRedoButton;
1792 else
1793 if( name == "browse_refresh" )
1795 return _BrowseRefreshButton;
1797 else
1798 if( name == "timeout" )
1800 return toString( _TimeoutValue );
1802 else
1803 if( name == "browser_css_file" )
1805 return _BrowserCssFile;
1807 else
1808 return CGroupScrollText::getProperty( name );
1811 void CGroupHTML::setProperty( const std::string &name, const std::string &value )
1813 if( name == "url" )
1815 _URL = value;
1816 return;
1818 else
1819 if( name == "title_prefix" )
1821 _TitlePrefix = value;
1822 return;
1824 else
1825 if( name == "error_color" )
1827 CRGBA c;
1828 if( fromString( value, c ) )
1829 ErrorColor = c;
1830 return;
1832 else
1833 if( name == "link_color" )
1835 CRGBA c;
1836 if( fromString( value, c ) )
1837 LinkColor = c;
1838 return;
1840 else
1841 if( name == "error_color_global_color" )
1843 bool b;
1844 if( fromString( value, b ) )
1845 ErrorColorGlobalColor = b;
1846 return;
1848 else
1849 if( name == "link_color_global_color" )
1851 bool b;
1852 if( fromString( value, b ) )
1853 LinkColorGlobalColor = b;
1854 return;
1856 else
1857 if( name == "text_color_global_color" )
1859 bool b;
1860 if( fromString( value, b ) )
1861 TextColorGlobalColor = b;
1862 return;
1864 else
1865 if( name == "td_begin_space" )
1867 uint i;
1868 if( fromString( value, i ) )
1869 TDBeginSpace = i;
1870 return;
1872 else
1873 if( name == "paragraph_begin_space" )
1875 uint i;
1876 if( fromString( value, i ) )
1877 PBeginSpace = i;
1878 return;
1880 else
1881 if( name == "li_begin_space" )
1883 uint i;
1884 if( fromString( value, i ) )
1885 LIBeginSpace = i;
1886 return;
1888 else
1889 if( name == "ul_begin_space" )
1891 uint i;
1892 if( fromString( value, i ) )
1893 ULBeginSpace = i;
1894 return;
1896 else
1897 if( name == "ul_indent" )
1899 uint i;
1900 if( fromString( value, i ) )
1901 ULIndent = i;
1902 return;
1904 else
1905 if( name == "multi_line_space_factor" )
1907 float f;
1908 if( fromString( value, f ) )
1909 LineSpaceFontFactor = f;
1910 return;
1912 else
1913 if( name == "form_text_area_group" )
1915 DefaultFormTextGroup = value;
1916 return;
1918 else
1919 if( name == "form_select_group" )
1921 DefaultFormSelectGroup = value;
1922 return;
1924 else
1925 if( name == "checkbox_bitmap_normal" )
1927 DefaultCheckBoxBitmapNormal = value;
1928 return;
1930 else
1931 if( name == "checkbox_bitmap_pushed" )
1933 DefaultCheckBoxBitmapPushed = value;
1934 return;
1936 else
1937 if( name == "checkbox_bitmap_over" )
1939 DefaultCheckBoxBitmapOver = value;
1940 return;
1942 else
1943 if( name == "radiobutton_bitmap_normal" )
1945 DefaultRadioButtonBitmapNormal = value;
1946 return;
1948 else
1949 if( name == "radiobutton_bitmap_pushed" )
1951 DefaultRadioButtonBitmapPushed = value;
1952 return;
1954 else
1955 if( name == "radiobutton_bitmap_over" )
1957 DefaultRadioButtonBitmapOver = value;
1958 return;
1960 else
1961 if( name == "background_bitmap_view" )
1963 DefaultBackgroundBitmapView = value;
1964 return;
1966 else
1967 if( name == "home" )
1969 Home = value;
1970 return;
1972 else
1973 if( name == "browse_next_time" )
1975 bool b;
1976 if( fromString( value, b ) )
1977 _BrowseNextTime = b;
1978 return;
1980 else
1981 if( name == "browse_tree" )
1983 _BrowseTree = value;
1984 return;
1986 else
1987 if( name == "browse_undo" )
1989 _BrowseUndoButton = value;
1990 return;
1992 else
1993 if( name == "browse_redo" )
1995 _BrowseRedoButton = value;
1996 return;
1998 else
1999 if( name == "browse_refresh" )
2001 _BrowseRefreshButton = value;
2002 return;
2004 else
2005 if( name == "timeout" )
2007 double d;
2008 if( fromString( value, d ) )
2009 _TimeoutValue = d;
2010 return;
2012 else
2013 if( name == "browser_css_file")
2015 _BrowserStyle.reset();
2016 _BrowserCssFile = value;
2017 if (!_BrowserCssFile.empty())
2019 std::string filename = CPath::lookup(_BrowserCssFile, false, true, true);
2020 if (!filename.empty())
2022 CIFile in;
2023 if (in.open(filename))
2025 std::string css;
2026 if (in.readAll(css))
2027 _BrowserStyle.parseStylesheet(css);
2028 else
2029 nlwarning("Failed to read browser css from '%s'", filename.c_str());
2031 else
2033 nlwarning("Failed to open browser css file '%s'", filename.c_str());
2036 else
2038 nlwarning("Browser css file '%s' not found", _BrowserCssFile.c_str());
2042 else
2043 CGroupScrollText::setProperty( name, value );
2046 xmlNodePtr CGroupHTML::serialize( xmlNodePtr parentNode, const char *type ) const
2048 xmlNodePtr node = CGroupScrollText::serialize( parentNode, type );
2049 if( node == NULL )
2050 return NULL;
2052 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html" );
2053 xmlSetProp( node, BAD_CAST "url", BAD_CAST _URL.c_str() );
2054 xmlSetProp( node, BAD_CAST "title_prefix", BAD_CAST _TitlePrefix.c_str() );
2055 xmlSetProp( node, BAD_CAST "error_color", BAD_CAST toString( ErrorColor ).c_str() );
2056 xmlSetProp( node, BAD_CAST "link_color", BAD_CAST toString( LinkColor ).c_str() );
2058 xmlSetProp( node, BAD_CAST "error_color_global_color",
2059 BAD_CAST toString( ErrorColorGlobalColor ).c_str() );
2060 xmlSetProp( node, BAD_CAST "link_color_global_color",
2061 BAD_CAST toString( LinkColorGlobalColor ).c_str() );
2062 xmlSetProp( node, BAD_CAST "text_color_global_color",
2063 BAD_CAST toString( TextColorGlobalColor ).c_str() );
2065 xmlSetProp( node, BAD_CAST "td_begin_space", BAD_CAST toString( TDBeginSpace ).c_str() );
2066 xmlSetProp( node, BAD_CAST "paragraph_begin_space", BAD_CAST toString( PBeginSpace ).c_str() );
2067 xmlSetProp( node, BAD_CAST "li_begin_space", BAD_CAST toString( LIBeginSpace ).c_str() );
2068 xmlSetProp( node, BAD_CAST "ul_begin_space", BAD_CAST toString( ULBeginSpace ).c_str() );
2069 xmlSetProp( node, BAD_CAST "ul_indent", BAD_CAST toString( ULIndent ).c_str() );
2070 xmlSetProp( node, BAD_CAST "multi_line_space_factor", BAD_CAST toString( LineSpaceFontFactor ).c_str() );
2071 xmlSetProp( node, BAD_CAST "form_text_area_group", BAD_CAST DefaultFormTextGroup.c_str() );
2072 xmlSetProp( node, BAD_CAST "form_select_group", BAD_CAST DefaultFormSelectGroup.c_str() );
2073 xmlSetProp( node, BAD_CAST "checkbox_bitmap_normal", BAD_CAST DefaultCheckBoxBitmapNormal.c_str() );
2074 xmlSetProp( node, BAD_CAST "checkbox_bitmap_pushed", BAD_CAST DefaultCheckBoxBitmapPushed.c_str() );
2075 xmlSetProp( node, BAD_CAST "checkbox_bitmap_over", BAD_CAST DefaultCheckBoxBitmapOver.c_str() );
2076 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_normal", BAD_CAST DefaultRadioButtonBitmapNormal.c_str() );
2077 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_pushed", BAD_CAST DefaultRadioButtonBitmapPushed.c_str() );
2078 xmlSetProp( node, BAD_CAST "radiobutton_bitmap_over", BAD_CAST DefaultRadioButtonBitmapOver.c_str() );
2079 xmlSetProp( node, BAD_CAST "background_bitmap_view", BAD_CAST DefaultBackgroundBitmapView.c_str() );
2080 xmlSetProp( node, BAD_CAST "home", BAD_CAST Home.c_str() );
2081 xmlSetProp( node, BAD_CAST "browse_next_time", BAD_CAST toString( _BrowseNextTime ).c_str() );
2082 xmlSetProp( node, BAD_CAST "browse_tree", BAD_CAST _BrowseTree.c_str() );
2083 xmlSetProp( node, BAD_CAST "browse_undo", BAD_CAST _BrowseUndoButton.c_str() );
2084 xmlSetProp( node, BAD_CAST "browse_redo", BAD_CAST _BrowseRedoButton.c_str() );
2085 xmlSetProp( node, BAD_CAST "browse_refresh", BAD_CAST _BrowseRefreshButton.c_str() );
2086 xmlSetProp( node, BAD_CAST "timeout", BAD_CAST toString( _TimeoutValue ).c_str() );
2087 xmlSetProp( node, BAD_CAST "browser_css_file", BAD_CAST _BrowserCssFile.c_str() );
2089 return node;
2092 // ***************************************************************************
2094 bool CGroupHTML::parse(xmlNodePtr cur,CInterfaceGroup *parentGroup)
2096 nlassert( CWidgetManager::getInstance()->isClockMsgTarget(this));
2099 if(!CGroupScrollText::parse(cur, parentGroup))
2100 return false;
2102 // TestYoyo
2103 //nlinfo("** CGroupHTML parsed Ok: %x, %s, %s, uid%d", this, _Id.c_str(), typeid(this).name(), _GroupHtmlUID);
2105 CXMLAutoPtr ptr;
2107 // Get the url
2108 ptr = xmlGetProp (cur, (xmlChar*)"url");
2109 if (ptr)
2110 _URL = (const char*)ptr;
2112 // Bkup default for undo/redo
2113 _AskedUrl= _URL;
2115 ptr = xmlGetProp (cur, (xmlChar*)"title_prefix");
2116 if (ptr)
2117 _TitlePrefix = CI18N::get((const char*)ptr);
2119 // Parameters
2120 ptr = xmlGetProp (cur, (xmlChar*)"error_color");
2121 if (ptr)
2122 ErrorColor = convertColor(ptr);
2123 ptr = xmlGetProp (cur, (xmlChar*)"link_color");
2124 if (ptr)
2125 LinkColor = convertColor(ptr);
2126 ptr = xmlGetProp (cur, (xmlChar*)"error_color_global_color");
2127 if (ptr)
2128 ErrorColorGlobalColor = convertBool(ptr);
2129 ptr = xmlGetProp (cur, (xmlChar*)"link_color_global_color");
2130 if (ptr)
2131 LinkColorGlobalColor = convertBool(ptr);
2132 ptr = xmlGetProp (cur, (xmlChar*)"text_color_global_color");
2133 if (ptr)
2134 TextColorGlobalColor = convertBool(ptr);
2135 ptr = xmlGetProp (cur, (xmlChar*)"td_begin_space");
2136 if (ptr)
2137 fromString((const char*)ptr, TDBeginSpace);
2138 ptr = xmlGetProp (cur, (xmlChar*)"paragraph_begin_space");
2139 if (ptr)
2140 fromString((const char*)ptr, PBeginSpace);
2141 ptr = xmlGetProp (cur, (xmlChar*)"li_begin_space");
2142 if (ptr)
2143 fromString((const char*)ptr, LIBeginSpace);
2144 ptr = xmlGetProp (cur, (xmlChar*)"ul_begin_space");
2145 if (ptr)
2146 fromString((const char*)ptr, ULBeginSpace);
2147 ptr = xmlGetProp (cur, (xmlChar*)"ul_indent");
2148 if (ptr)
2149 fromString((const char*)ptr, ULIndent);
2150 ptr = xmlGetProp (cur, (xmlChar*)"multi_line_space_factor");
2151 if (ptr)
2152 fromString((const char*)ptr, LineSpaceFontFactor);
2153 ptr = xmlGetProp (cur, (xmlChar*)"form_text_group");
2154 if (ptr)
2155 DefaultFormTextGroup = (const char*)(ptr);
2156 ptr = xmlGetProp (cur, (xmlChar*)"form_text_area_group");
2157 if (ptr)
2158 DefaultFormTextAreaGroup = (const char*)(ptr);
2159 ptr = xmlGetProp (cur, (xmlChar*)"form_select_group");
2160 if (ptr)
2161 DefaultFormSelectGroup = (const char*)(ptr);
2162 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_normal");
2163 if (ptr)
2164 DefaultCheckBoxBitmapNormal = (const char*)(ptr);
2165 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_pushed");
2166 if (ptr)
2167 DefaultCheckBoxBitmapPushed = (const char*)(ptr);
2168 ptr = xmlGetProp (cur, (xmlChar*)"checkbox_bitmap_over");
2169 if (ptr)
2170 DefaultCheckBoxBitmapOver = (const char*)(ptr);
2171 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_normal");
2172 if (ptr)
2173 DefaultRadioButtonBitmapNormal = (const char*)(ptr);
2174 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_pushed");
2175 if (ptr)
2176 DefaultRadioButtonBitmapPushed = (const char*)(ptr);
2177 ptr = xmlGetProp (cur, (xmlChar*)"radiobutton_bitmap_over");
2178 if (ptr)
2179 DefaultRadioButtonBitmapOver = (const char*)(ptr);
2180 ptr = xmlGetProp (cur, (xmlChar*)"background_bitmap_view");
2181 if (ptr)
2182 DefaultBackgroundBitmapView = (const char*)(ptr);
2183 ptr = xmlGetProp (cur, (xmlChar*)"home");
2184 if (ptr)
2185 Home = (const char*)(ptr);
2186 ptr = xmlGetProp (cur, (xmlChar*)"browse_next_time");
2187 if (ptr)
2188 _BrowseNextTime = convertBool(ptr);
2189 ptr = xmlGetProp (cur, (xmlChar*)"browse_tree");
2190 if(ptr)
2191 _BrowseTree = (const char*)ptr;
2192 ptr = xmlGetProp (cur, (xmlChar*)"browse_undo");
2193 if(ptr)
2194 _BrowseUndoButton= (const char*)ptr;
2195 ptr = xmlGetProp (cur, (xmlChar*)"browse_redo");
2196 if(ptr)
2197 _BrowseRedoButton = (const char*)ptr;
2198 ptr = xmlGetProp (cur, (xmlChar*)"browse_refresh");
2199 if(ptr)
2200 _BrowseRefreshButton = (const char*)ptr;
2201 ptr = xmlGetProp (cur, (xmlChar*)"timeout");
2202 if(ptr)
2203 fromString((const char*)ptr, _TimeoutValue);
2205 ptr = xmlGetProp (cur, (xmlChar*)"browser_css_file");
2206 if (ptr)
2208 setProperty("browser_css_file", (const char *)ptr);
2211 return true;
2214 // ***************************************************************************
2216 bool CGroupHTML::handleEvent (const NLGUI::CEventDescriptor& eventDesc)
2218 bool traited = false;
2220 if (eventDesc.getType() == NLGUI::CEventDescriptor::mouse)
2222 const NLGUI::CEventDescriptorMouse &mouseEvent = (const NLGUI::CEventDescriptorMouse &)eventDesc;
2223 if (mouseEvent.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mousewheel)
2225 // Check if mouse wheel event was on any of multiline select box widgets
2226 // Must do this before CGroupScrollText
2227 for (uint i=0; i<_Forms.size() && !traited; i++)
2229 for (uint j=0; j<_Forms[i].Entries.size() && !traited; j++)
2231 if (_Forms[i].Entries[j].SelectBox)
2233 if (_Forms[i].Entries[j].SelectBox->handleEvent(eventDesc))
2235 traited = true;
2236 break;
2244 if (!traited)
2245 traited = CGroupScrollText::handleEvent (eventDesc);
2247 if (eventDesc.getType() == NLGUI::CEventDescriptor::system)
2249 const NLGUI::CEventDescriptorSystem &systemEvent = (const NLGUI::CEventDescriptorSystem &) eventDesc;
2250 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::clocktick)
2252 // Handle now
2253 handle ();
2255 if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::activecalledonparent)
2257 if (!((NLGUI::CEventDescriptorActiveCalledOnParent &) systemEvent).getActive())
2259 // stop refresh when window gets hidden
2260 _NextRefreshTime = 0;
2264 return traited;
2267 // ***************************************************************************
2269 void CGroupHTML::endParagraph()
2271 _Paragraph = NULL;
2273 paragraphChange ();
2276 // ***************************************************************************
2278 void CGroupHTML::newParagraph(uint beginSpace)
2280 // Add a new paragraph
2281 CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam());
2282 newParagraph->setId(getCurrentGroup()->getId() + ":PARAGRAPH" + toString(getNextAutoIdSeq()));
2283 newParagraph->setResizeFromChildH(true);
2285 newParagraph->setMarginLeft(getIndent());
2286 if (!_Style.Current.TextAlign.empty())
2288 if (_Style.Current.TextAlign == "left")
2289 newParagraph->setTextAlign(CGroupParagraph::AlignLeft);
2290 else if (_Style.Current.TextAlign == "center")
2291 newParagraph->setTextAlign(CGroupParagraph::AlignCenter);
2292 else if (_Style.Current.TextAlign == "right")
2293 newParagraph->setTextAlign(CGroupParagraph::AlignRight);
2294 else if (_Style.Current.TextAlign == "justify")
2295 newParagraph->setTextAlign(CGroupParagraph::AlignJustify);
2298 // Add to the group
2299 addHtmlGroup (newParagraph, beginSpace);
2300 _Paragraph = newParagraph;
2302 paragraphChange ();
2305 // ***************************************************************************
2307 void CGroupHTML::browse(const char *url)
2309 // modify undo/redo
2310 pushUrlUndoRedo(url);
2312 // do the browse, with no undo/redo
2313 doBrowse(url);
2316 // ***************************************************************************
2317 void CGroupHTML::refresh()
2319 if (!_URL.empty())
2320 doBrowse(_URL.c_str(), true);
2323 // ***************************************************************************
2324 void CGroupHTML::doBrowse(const char *url, bool force)
2326 LOG_DL("(%s) Browsing URL : '%s'", _Id.c_str(), url);
2328 CUrlParser uri(url);
2329 if (!uri.hash.empty())
2331 // Anchor to scroll after page has loaded
2332 _UrlFragment = uri.hash;
2334 uri.inherit(_DocumentUrl);
2335 uri.hash.clear();
2337 // compare urls and see if we only navigating to new anchor
2338 if (!force && _DocumentUrl == uri.toString())
2340 // scroll happens in updateCoords()
2341 invalidateCoords();
2342 return;
2345 else
2346 _UrlFragment.clear();
2348 // go
2349 _URL = uri.toString();
2350 _BrowseNextTime = true;
2351 _WaitingForStylesheet = false;
2353 // if a BrowseTree is bound to us, try to select the node that opens this URL (auto-locate)
2354 if(!_BrowseTree.empty())
2356 CGroupTree *groupTree=dynamic_cast<CGroupTree*>(CWidgetManager::getInstance()->getElementFromId(_BrowseTree));
2357 if(groupTree)
2359 string nodeId= selectTreeNodeRecurs(groupTree->getRootNode(), url);
2360 // select the node
2361 if(!nodeId.empty())
2363 groupTree->selectNodeById(nodeId);
2369 // ***************************************************************************
2371 void CGroupHTML::browseError (const char *msg)
2373 releaseDownloads();
2375 // Get the list group from CGroupScrollText
2376 removeContent();
2377 newParagraph(0);
2378 CViewText *viewText = new CViewText ("", (string("Error : ")+msg).c_str());
2379 viewText->setColor (ErrorColor);
2380 viewText->setModulateGlobalColor(ErrorColorGlobalColor);
2381 viewText->setMultiLine (true);
2382 getParagraph()->addChild (viewText);
2383 if(!_TitlePrefix.empty())
2384 setTitle (_TitlePrefix);
2386 updateRefreshButton();
2387 invalidateCoords();
2390 void CGroupHTML::browseErrorHtml(const std::string &html)
2392 releaseDownloads();
2393 removeContent();
2395 renderHtmlString(html);
2397 updateRefreshButton();
2398 invalidateCoords();
2401 // ***************************************************************************
2403 bool CGroupHTML::isBrowsing()
2405 // do not show spinning cursor for image downloads (!Curls.empty())
2406 return _BrowseNextTime || _PostNextTime || _RenderNextTime ||
2407 _Browsing || _WaitingForStylesheet ||
2408 _CurlWWW;
2411 // ***************************************************************************
2413 void CGroupHTML::updateCoords()
2415 CGroupScrollText::updateCoords();
2417 // all elements are in their correct place, tell scrollbar to scroll to anchor
2418 if (!_Browsing && !_UrlFragment.empty())
2420 doBrowseAnchor(_UrlFragment);
2421 _UrlFragment.clear();
2424 if (!m_HtmlBackground.isEmpty() || !m_BodyBackground.isEmpty())
2426 // get scroll offset from list
2427 CGroupList *list = getList();
2428 if (list)
2430 CInterfaceElement* vp = list->getParentPos() ? list->getParentPos() : this;
2431 sint htmlW = std::max(vp->getWReal(), list->getWReal());
2432 sint htmlH = list->getHReal();
2433 sint htmlX = list->getXReal() + list->getOfsX();
2434 sint htmlY = list->getYReal() + list->getOfsY();
2436 if (!m_HtmlBackground.isEmpty())
2438 m_HtmlBackground.setFillViewport(true);
2439 m_HtmlBackground.setBorderArea(htmlX, htmlY, htmlW, htmlH);
2440 m_HtmlBackground.setPaddingArea(htmlX, htmlY, htmlW, htmlH);
2441 m_HtmlBackground.setContentArea(htmlX, htmlY, htmlW, htmlH);
2444 if (!m_BodyBackground.isEmpty())
2446 // TODO: html padding + html border
2447 m_BodyBackground.setBorderArea(htmlX, htmlY, htmlW, htmlH);
2448 // TODO: html padding + html border + body border
2449 m_BodyBackground.setPaddingArea(htmlX, htmlY, htmlW, htmlH);
2450 // TODO: html padding + html_border + body padding
2451 m_BodyBackground.setContentArea(htmlX, htmlY, htmlW, htmlH);
2457 // ***************************************************************************
2459 bool CGroupHTML::translateChar(u32char &output, u32char input, u32char lastCharParam) const
2461 // Keep this char ?
2462 bool keep = true;
2464 // char is between table elements
2465 // TODO: only whitespace is handled, text is added to either TD, or after TABLE (should be before)
2466 bool tableWhitespace = getTable() && (_Cells.empty() || _Cells.back() == NULL);
2468 switch (input)
2470 // Return / tab only in <PRE> mode
2471 case '\t':
2472 case '\n':
2474 if (tableWhitespace)
2476 keep = false;
2478 else
2480 // Get the last char
2481 u32char lastChar = lastCharParam;
2482 if (lastChar == 0)
2483 lastChar = getLastChar();
2484 keep = ((lastChar != (u32char)' ') &&
2485 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2486 if(!getPRE())
2487 input = (u32char)' ';
2490 break;
2491 case ' ':
2493 if (tableWhitespace)
2495 keep = false;
2497 else
2499 // Get the last char
2500 u32char lastChar = lastCharParam;
2501 if (lastChar == 0)
2502 lastChar = getLastChar();
2503 keep = ((lastChar != (u32char)' ') &&
2504 (lastChar != (u32char)'\n') &&
2505 (lastChar != 0)) || getPRE() || (_CurrentViewImage && (lastChar == 0));
2508 break;
2509 case 0xd:
2510 keep = false;
2511 break;
2514 if (keep)
2516 output = input;
2519 return keep;
2522 // ***************************************************************************
2524 void CGroupHTML::registerAnchor(CInterfaceElement* elm)
2526 if (!_AnchorName.empty())
2528 for(uint32 i=0; i < _AnchorName.size(); ++i)
2530 // filter out duplicates and register only first
2531 if (!_AnchorName[i].empty() && _Anchors.count(_AnchorName[i]) == 0)
2533 _Anchors[_AnchorName[i]] = elm;
2537 _AnchorName.clear();
2541 // ***************************************************************************
2542 bool CGroupHTML::isSameStyle(CViewLink *text, const CStyleParams &style) const
2544 if (!text) return false;
2546 bool embolden = style.FontWeight >= FONT_WEIGHT_BOLD;
2547 bool sameShadow = style.TextShadow.Enabled && text->getShadow();
2548 if (sameShadow && style.TextShadow.Enabled)
2550 sint sx, sy;
2551 text->getShadowOffset(sx, sy);
2552 sameShadow = (style.TextShadow.Color == text->getShadowColor());
2553 sameShadow = sameShadow && (style.TextShadow.Outline == text->getShadowOutline());
2554 sameShadow = sameShadow && (style.TextShadow.X == sx) && (style.TextShadow.Y == sy);
2556 // Compatible with current parameters ?
2557 return sameShadow &&
2558 (style.TextColor == text->getColor()) &&
2559 (style.FontFamily == text->getFontName()) &&
2560 (style.FontSize == (uint)text->getFontSize()) &&
2561 (style.Underlined == text->getUnderlined()) &&
2562 (style.StrikeThrough == text->getStrikeThrough()) &&
2563 (embolden == text->getEmbolden()) &&
2564 (style.FontOblique == text->getOblique()) &&
2565 (getLink() == text->Link) &&
2566 (style.GlobalColorText == text->getModulateGlobalColor());
2569 // ***************************************************************************
2570 void CGroupHTML::newTextButton(const std::string &text, const std::string &tpl)
2572 _CurrentViewLink = NULL;
2573 _CurrentViewImage = NULL;
2575 // Action handler parameters : "name=group_html_id|form=id_of_the_form|submit_button=button_name"
2576 string param = "name=" + this->_Id + "|url=" + getLink();
2577 string name;
2578 if (!_AnchorName.empty())
2579 name = _AnchorName.back();
2581 typedef pair<string, string> TTmplParam;
2582 vector<TTmplParam> tmplParams;
2583 tmplParams.push_back(TTmplParam("id", ""));
2584 tmplParams.push_back(TTmplParam("onclick", "browse"));
2585 tmplParams.push_back(TTmplParam("onclick_param", param));
2586 tmplParams.push_back(TTmplParam("active", "true"));
2587 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(tpl, getId()+":"+name, tmplParams);
2588 if (!buttonGroup)
2590 nlinfo("Text button template '%s' not found", tpl.c_str());
2591 return;
2593 buttonGroup->setId(getId()+":"+name);
2595 // Add the ctrl button
2596 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
2597 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
2598 if (!ctrlButton)
2600 nlinfo("Text button template '%s' is missing :button or :b text element", tpl.c_str());
2601 return;
2603 ctrlButton->setModulateGlobalColorAll(_Style.Current.GlobalColor);
2604 ctrlButton->setTextModulateGlobalColorNormal(_Style.Current.GlobalColorText);
2605 ctrlButton->setTextModulateGlobalColorOver(_Style.Current.GlobalColorText);
2606 ctrlButton->setTextModulateGlobalColorPushed(_Style.Current.GlobalColorText);
2608 // Translate the tooltip
2609 ctrlButton->setText(text);
2610 ctrlButton->setDefaultContextHelp(std::string(getLinkTitle()));
2611 // empty url / button disabled
2612 ctrlButton->setFrozen(*getLink() == '\0');
2614 setTextButtonStyle(ctrlButton, _Style.Current);
2616 _Paragraph->addChild(buttonGroup);
2619 // ***************************************************************************
2620 void CGroupHTML::newTextLink(const std::string &text)
2622 CViewLink *newLink = new CViewLink(CViewBase::TCtorParam());
2623 if (getA())
2625 newLink->Link = getLink();
2626 newLink->LinkTitle = getLinkTitle();
2627 if (!newLink->Link.empty())
2629 newLink->setHTMLView (this);
2630 newLink->setActionOnLeftClick("browse");
2631 newLink->setParamsOnLeftClick("name=" + getId() + "|url=" + newLink->Link);
2634 newLink->setText(text);
2635 newLink->setMultiLineSpace((uint)((float)(_Style.Current.FontSize)*LineSpaceFontFactor));
2636 newLink->setMultiLine(true);
2637 newLink->setModulateGlobalColor(_Style.Current.GlobalColorText);
2638 setTextStyle(newLink, _Style.Current);
2640 registerAnchor(newLink);
2642 if (getA() && !newLink->Link.empty())
2643 getParagraph()->addChildLink(newLink);
2644 else
2645 getParagraph()->addChild(newLink);
2647 _CurrentViewLink = newLink;
2648 _CurrentViewImage = NULL;
2651 // ***************************************************************************
2653 void CGroupHTML::addString(const std::string &str)
2655 string tmpStr = str;
2657 if (_Localize)
2659 string _str = tmpStr;
2660 string::size_type p = _str.find('#');
2661 if (p == string::npos)
2663 tmpStr = CI18N::get(_str);
2665 else
2667 string cmd = _str.substr(0, p);
2668 string arg = _str.substr(p+1);
2670 if (cmd == "date")
2672 uint year, month, day;
2673 sscanf(arg.c_str(), "%d/%d/%d", &year, &month, &day);
2674 tmpStr = CI18N::get( "uiMFIDate");
2676 year += (year > 70 ? 1900 : 2000);
2678 strFindReplace(tmpStr, "%year", toString("%d", year) );
2679 strFindReplace(tmpStr, "%month", CI18N::get(toString("uiMonth%02d", month)) );
2680 strFindReplace(tmpStr, "%day", toString("%d", day) );
2682 else
2684 tmpStr = arg;
2689 // In title ?
2690 if (_Object)
2692 _ObjectScript += tmpStr;
2694 else if (_SelectOption)
2696 if (!(_Forms.empty()))
2698 if (!_Forms.back().Entries.empty())
2700 _SelectOptionStr += tmpStr;
2704 else
2706 // In a paragraph ?
2707 if (!_Paragraph)
2709 newParagraph (0);
2710 paragraphChange ();
2713 CStyleParams &style = _Style.Current;
2715 // Text added ?
2716 bool added = false;
2718 if (_CurrentViewLink)
2720 bool skipLine = !_CurrentViewLink->getText().empty() && *(_CurrentViewLink->getText().rbegin()) == '\n';
2721 if (!skipLine && isSameStyle(_CurrentViewLink, style))
2723 // Concat the text
2724 _CurrentViewLink->setText(_CurrentViewLink->getText()+tmpStr);
2725 _CurrentViewLink->invalidateContent();
2726 added = true;
2730 // Not added ?
2731 if (!added)
2733 if (getA() && string(getLinkClass()) == "ryzom-ui-button")
2734 newTextButton(tmpStr, DefaultButtonGroup);
2735 else
2736 newTextLink(tmpStr);
2741 // ***************************************************************************
2743 void CGroupHTML::addImage(const std::string &id, const std::string &img, bool reloadImg, const CStyleParams &style)
2745 // In a paragraph ?
2746 if (!_Paragraph)
2748 newParagraph (0);
2749 paragraphChange ();
2752 // No more text in this text view
2753 _CurrentViewLink = NULL;
2755 // Not added ?
2756 CViewBitmap *newImage = new CViewBitmap (TCtorParam());
2757 newImage->setId(id);
2759 addImageDownload(img, newImage, style, NormalImage);
2760 newImage->setRenderLayer(getRenderLayer()+1);
2762 getParagraph()->addChild(newImage);
2763 paragraphChange ();
2765 setImageSize(newImage, style);
2768 // ***************************************************************************
2770 CInterfaceGroup *CGroupHTML::addTextArea(const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const std::string &content, uint maxlength)
2772 // In a paragraph ?
2773 if (!_Paragraph)
2775 newParagraph (0);
2776 paragraphChange ();
2779 // No more text in this text view
2780 _CurrentViewLink = NULL;
2782 CStyleParams &style = _Style.Current;
2784 // override cols/rows values from style
2785 if (style.Width > 0) cols = style.Width / style.FontSize;
2786 if (style.Height > 0) rows = style.Height / style.FontSize;
2788 // Not added ?
2789 std::vector<std::pair<std::string,std::string> > templateParams;
2790 templateParams.push_back (std::pair<std::string,std::string> ("w", toString (cols*style.FontSize)));
2791 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
2792 templateParams.push_back (std::pair<std::string,std::string> ("prompt", ""));
2793 templateParams.push_back (std::pair<std::string,std::string> ("multiline", multiLine?"true":"false"));
2794 templateParams.push_back (std::pair<std::string,std::string> ("fontsize", toString (style.FontSize)));
2795 templateParams.push_back (std::pair<std::string,std::string> ("color", style.TextColor.toString()));
2796 if (style.FontWeight >= FONT_WEIGHT_BOLD)
2797 templateParams.push_back (std::pair<std::string,std::string> ("fontweight", "bold"));
2798 if (style.FontOblique)
2799 templateParams.push_back (std::pair<std::string,std::string> ("fontstyle", "oblique"));
2800 if (multiLine)
2801 templateParams.push_back (std::pair<std::string,std::string> ("multi_min_line", toString(rows)));
2802 templateParams.push_back (std::pair<std::string,std::string> ("want_return", multiLine?"true":"false"));
2803 templateParams.push_back (std::pair<std::string,std::string> ("onenter", ""));
2804 templateParams.push_back (std::pair<std::string,std::string> ("enter_recover_focus", "false"));
2805 if (maxlength > 0)
2806 templateParams.push_back (std::pair<std::string,std::string> ("max_num_chars", toString(maxlength)));
2807 templateParams.push_back (std::pair<std::string,std::string> ("shadow", toString(style.TextShadow.Enabled)));
2808 if (style.TextShadow.Enabled)
2810 templateParams.push_back (std::pair<std::string,std::string> ("shadow_x", toString(style.TextShadow.X)));
2811 templateParams.push_back (std::pair<std::string,std::string> ("shadow_y", toString(style.TextShadow.Y)));
2812 templateParams.push_back (std::pair<std::string,std::string> ("shadow_color", style.TextShadow.Color.toString()));
2813 templateParams.push_back (std::pair<std::string,std::string> ("shadow_outline", toString(style.TextShadow.Outline)));
2816 CInterfaceGroup *textArea = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
2817 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
2819 // Group created ?
2820 if (textArea)
2822 // Set the content
2823 CGroupEditBox *eb = dynamic_cast<CGroupEditBox*>(textArea->getGroup("eb"));
2824 if (eb)
2826 eb->setInputString(decodeHTMLEntities(content));
2827 if (style.hasStyle("background-color"))
2829 CViewBitmap *bg = dynamic_cast<CViewBitmap*>(eb->getView("bg"));
2830 if (bg)
2832 bg->setTexture("blank.tga");
2833 bg->setColor(style.Background.color);
2838 textArea->invalidateCoords();
2839 getParagraph()->addChild (textArea);
2840 paragraphChange ();
2842 return textArea;
2846 // Not group created
2847 return NULL;
2850 // ***************************************************************************
2851 CDBGroupComboBox *CGroupHTML::addComboBox(const std::string &templateName, const char *name)
2853 // In a paragraph ?
2854 if (!_Paragraph)
2856 newParagraph (0);
2857 paragraphChange ();
2862 // Not added ?
2863 std::vector<std::pair<std::string,std::string> > templateParams;
2864 templateParams.push_back (std::pair<std::string,std::string> ("id", name));
2865 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance (templateName.c_str(),
2866 getParagraph()->getId(), templateParams.empty()?NULL:&(templateParams[0]), (uint)templateParams.size());
2868 // Group created ?
2869 if (group)
2871 // Set the content
2872 CDBGroupComboBox *cb = dynamic_cast<CDBGroupComboBox *>(group);
2873 if (!cb)
2875 nlwarning("'%s' template has bad type, combo box expected", templateName.c_str());
2876 delete cb;
2877 return NULL;
2879 else
2881 getParagraph()->addChild (cb);
2882 paragraphChange ();
2883 return cb;
2888 // Not group created
2889 return NULL;
2892 // ***************************************************************************
2893 CGroupMenu *CGroupHTML::addSelectBox(const std::string &templateName, const char *name)
2895 // In a paragraph ?
2896 if (!_Paragraph)
2898 newParagraph (0);
2899 paragraphChange ();
2902 // Not added ?
2903 std::vector<std::pair<std::string,std::string> > templateParams;
2904 templateParams.push_back(std::pair<std::string,std::string> ("id", name));
2905 CInterfaceGroup *group = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName.c_str(),
2906 getParagraph()->getId(), &(templateParams[0]), (uint)templateParams.size());
2908 // Group created ?
2909 if (group)
2911 // Set the content
2912 CGroupMenu *sb = dynamic_cast<CGroupMenu *>(group);
2913 if (!sb)
2915 nlwarning("'%s' template has bad type, CGroupMenu expected", templateName.c_str());
2916 delete sb;
2917 return NULL;
2919 else
2921 getParagraph()->addChild (sb);
2922 paragraphChange ();
2923 return sb;
2927 // No group created
2928 return NULL;
2931 // ***************************************************************************
2933 CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &name, const std::string &normalBitmap, const std::string &pushedBitmap,
2934 const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams,
2935 const std::string &tooltip, const CStyleParams &style)
2937 // In a paragraph ?
2938 if (!_Paragraph)
2940 newParagraph (0);
2941 paragraphChange ();
2944 // Add the ctrl button
2945 CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam());
2946 if (!name.empty())
2948 ctrlButton->setId(name);
2951 std::string normal;
2952 if (startsWith(normalBitmap, "data:image/"))
2954 normal = decodeURIComponent(normalBitmap);
2956 else
2958 // Load only tga files.. (conversion in dds filename is done in the lookup procedure)
2959 normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga";
2961 // if the image doesn't exist on local, we check in the cache
2962 if(!CPath::exists(normal))
2964 // search in the compressed texture
2965 CViewRenderer &rVR = *CViewRenderer::getInstance();
2966 sint32 id = rVR.getTextureIdFromName(normal);
2967 if(id == -1)
2969 normal = localImageName(normalBitmap);
2970 addImageDownload(normalBitmap, ctrlButton, style);
2975 std::string pushed;
2976 if (startsWith(pushedBitmap, "data:image/"))
2978 pushed = decodeURIComponent(pushedBitmap);
2980 else
2982 pushed = pushedBitmap.empty()?"":CFile::getPath(pushedBitmap) + CFile::getFilenameWithoutExtension(pushedBitmap) + ".tga";
2983 // if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it
2984 if(!CPath::exists(pushed))
2986 // search in the compressed texture
2987 CViewRenderer &rVR = *CViewRenderer::getInstance();
2988 sint32 id = rVR.getTextureIdFromName(pushed);
2989 if(id == -1)
2991 pushed = localImageName(pushedBitmap);
2996 std::string over;
2997 if (startsWith(overBitmap, "data:image/"))
2999 over = decodeURIComponent(overBitmap);
3001 else
3003 over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga";
3004 // schedule mouseover bitmap for download if its different from normal
3005 if (!over.empty() && !CPath::exists(over))
3007 if (overBitmap != normalBitmap)
3009 over = localImageName(overBitmap);
3010 addImageDownload(overBitmap, ctrlButton, style, OverImage);
3015 ctrlButton->setType (type);
3016 if (!normal.empty())
3017 ctrlButton->setTexture (normal);
3018 if (!pushed.empty())
3019 ctrlButton->setTexturePushed (pushed);
3020 if (!over.empty())
3021 ctrlButton->setTextureOver (over);
3022 ctrlButton->setModulateGlobalColorAll (style.GlobalColor);
3023 ctrlButton->setActionOnLeftClick (actionHandler);
3024 ctrlButton->setParamsOnLeftClick (actionHandlerParams);
3026 // Translate the tooltip or display raw text (tooltip from webig)
3027 if (!tooltip.empty())
3029 if (CI18N::hasTranslation(tooltip))
3031 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
3032 //ctrlButton->setOnContextHelp(CI18N::get(tooltip).toString());
3034 else
3036 ctrlButton->setDefaultContextHelp(tooltip);
3037 //ctrlButton->setOnContextHelp(string(tooltip));
3040 ctrlButton->setInstantContextHelp(true);
3041 ctrlButton->setToolTipParent(TTMouse);
3042 ctrlButton->setToolTipParentPosRef(Hotspot_TTAuto);
3043 ctrlButton->setToolTipPosRef(Hotspot_TTAuto);
3046 getParagraph()->addChild (ctrlButton);
3047 paragraphChange ();
3049 setImageSize(ctrlButton, style);
3051 return ctrlButton;
3054 // ***************************************************************************
3056 void CGroupHTML::flushString()
3058 _CurrentViewLink = NULL;
3061 // ***************************************************************************
3063 void CGroupHTML::clearContext()
3065 _Paragraph = NULL;
3066 _PRE.clear();
3067 _Indent.clear();
3068 _LI = false;
3069 _UL.clear();
3070 _DL.clear();
3071 _A.clear();
3072 _Link.clear();
3073 _LinkTitle.clear();
3074 _Tables.clear();
3075 _Cells.clear();
3076 _TR.clear();
3077 _Forms.clear();
3078 _FormOpen = false;
3079 _FormSubmit.clear();
3080 _Groups.clear();
3081 _Divs.clear();
3082 _Anchors.clear();
3083 _AnchorName.clear();
3084 _CellParams.clear();
3085 _Object = false;
3086 _Localize = false;
3087 _ReadingHeadTag = false;
3088 _IgnoreHeadTag = false;
3089 _IgnoreBaseUrlTag = false;
3090 _AutoIdSeq = 0;
3091 m_TableRowBackgroundColor.clear();
3093 paragraphChange ();
3095 releaseDataDownloads();
3098 // ***************************************************************************
3100 u32char CGroupHTML::getLastChar() const
3102 if (_CurrentViewLink)
3104 ::u32string str = CUtfStringView(_CurrentViewLink->getText()).toUtf32(); // FIXME: Optimize reverse UTF iteration
3105 if (!str.empty())
3106 return str[str.length()-1];
3108 return 0;
3111 // ***************************************************************************
3113 void CGroupHTML::paragraphChange ()
3115 _CurrentViewLink = NULL;
3116 _CurrentViewImage = NULL;
3117 CGroupParagraph *paragraph = getParagraph();
3118 if (paragraph)
3120 // Number of child in this paragraph
3121 uint numChild = paragraph->getNumChildren();
3122 if (numChild)
3124 // Get the last child
3125 CViewBase *child = paragraph->getChild(numChild-1);
3127 // Is this a string view ?
3128 _CurrentViewLink = dynamic_cast<CViewLink*>(child);
3129 _CurrentViewImage = dynamic_cast<CViewBitmap*>(child);
3134 // ***************************************************************************
3136 CInterfaceGroup *CGroupHTML::getCurrentGroup()
3138 if (!_Cells.empty() && _Cells.back())
3139 return _Cells.back()->Group;
3140 else
3141 return _GroupListAdaptor;
3144 // ***************************************************************************
3146 void CGroupHTML::addHtmlGroup (CInterfaceGroup *group, uint beginSpace)
3148 if (!group)
3149 return;
3151 registerAnchor(group);
3153 if (!_DivName.empty())
3155 group->setName(_DivName);
3156 _Groups.push_back(group);
3159 group->setSizeRef(CInterfaceElement::width);
3161 // Compute begin space between paragraph and tables
3162 // * If first in group, no begin space
3164 // Pointer on the current paragraph (can be a table too)
3165 CGroupParagraph *p = dynamic_cast<CGroupParagraph*>(group);
3167 CInterfaceGroup *parentGroup = CGroupHTML::getCurrentGroup();
3168 const std::vector<CInterfaceGroup*> &groups = parentGroup->getGroups ();
3169 group->setParent(parentGroup);
3170 group->setParentSize(parentGroup);
3171 if (groups.empty())
3173 group->setParentPos(parentGroup);
3174 group->setPosRef(Hotspot_TL);
3175 group->setParentPosRef(Hotspot_TL);
3176 beginSpace = 0;
3178 else
3180 // Last is a paragraph ?
3181 group->setParentPos(groups.back());
3182 group->setPosRef(Hotspot_TL);
3183 group->setParentPosRef(Hotspot_BL);
3186 // Set the begin space
3187 if (p)
3188 p->setTopSpace(beginSpace);
3189 else
3190 group->setY(-(sint32)beginSpace);
3191 parentGroup->addGroup (group);
3194 // ***************************************************************************
3196 void CGroupHTML::setContainerTitle (const std::string &title)
3198 CInterfaceElement *parent = getParent();
3199 if (parent)
3201 if ((parent = parent->getParent()))
3203 CGroupContainer *container = dynamic_cast<CGroupContainer*>(parent);
3204 if (container)
3206 container->setTitle(title);
3212 void CGroupHTML::setTitle(const std::string &title)
3214 if(_TitlePrefix.empty())
3215 _TitleString = title;
3216 else
3217 _TitleString = _TitlePrefix + " - " + title;
3219 setContainerTitle(_TitleString);
3222 std::string CGroupHTML::getTitle() const {
3223 return _TitleString;
3226 // ***************************************************************************
3228 bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl)
3230 result = url;
3231 string tmp;
3233 if (toLowerAscii(result).find("file:") == 0 && result.size() > 5)
3235 result = result.substr(5, result.size()-5);
3237 else if (result.find("://") != string::npos || result.find("//") == 0)
3239 // http://, https://, etc or protocol-less url "//domain.com/image.png"
3240 return false;
3243 tmp = CPath::lookup (CFile::getFilename(result), false, false, false);
3244 if (tmp.empty())
3246 // try to find in local directory
3247 tmp = CPath::lookup (result, false, false, true);
3250 if (!tmp.empty())
3252 // Normalize the path
3253 if (isUrl)
3254 //result = "file:"+toLowerAscii(CPath::standardizePath (CPath::getFullPath (CFile::getPath(result)))+CFile::getFilename(result));*/
3255 result = "file:/"+tmp;
3256 else
3257 result = tmp;
3258 return true;
3260 else
3262 // Is it a texture in the big texture ?
3263 if (CViewRenderer::getInstance()->getTextureIdFromName (result) >= 0)
3265 return true;
3267 else
3269 // This is not a file in the CPath, let libwww open this URL
3270 result = url;
3271 return false;
3276 // ***************************************************************************
3278 void CGroupHTML::submitForm(uint button, sint32 x, sint32 y)
3280 if (button >= _FormSubmit.size())
3281 return;
3283 for(uint formId = 0; formId < _Forms.size(); formId++)
3285 // case sensitive search (user id is lowecase, auto id is uppercase)
3286 if (_Forms[formId].id == _FormSubmit[button].form)
3288 _PostNextTime = true;
3289 _PostFormId = formId;
3290 _PostFormAction = _FormSubmit[button].formAction;
3291 _PostFormSubmitType = _FormSubmit[button].type;
3292 _PostFormSubmitButton = _FormSubmit[button].name;
3293 _PostFormSubmitValue = _FormSubmit[button].value;
3294 _PostFormSubmitX = x;
3295 _PostFormSubmitY = y;
3297 return;
3301 nlwarning("Unable to find form '%s' to submit (button '%s')", _FormSubmit[button].form.c_str(), _FormSubmit[button].name.c_str());
3304 // ***************************************************************************
3306 void CGroupHTML::setupBackground(CSSBackgroundRenderer *bg)
3308 if (!bg) return;
3310 bg->setModulateGlobalColor(_Style.Current.GlobalColor);
3311 bg->setBackground(_Style.Current.Background);
3312 bg->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
3314 bg->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
3316 if (!_Style.Current.Background.image.empty())
3317 addTextureDownload(_Style.Current.Background.image, bg->TextureId, this);
3320 // ***************************************************************************
3322 void CGroupHTML::setBackgroundColor (const CRGBA &bgcolor)
3324 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3325 CViewBase *view = getView (DefaultBackgroundBitmapView);
3326 if (view)
3327 view->setActive(false);
3329 m_HtmlBackground.setColor(bgcolor);
3332 // ***************************************************************************
3334 void CGroupHTML::setBackground (const string &bgtex, bool scale, bool tile)
3336 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
3337 CViewBase *view = getView (DefaultBackgroundBitmapView);
3338 if (view)
3339 view->setActive(false);
3341 m_HtmlBackground.setImage(bgtex);
3342 m_HtmlBackground.setImageRepeat(tile);
3343 m_HtmlBackground.setImageCover(scale);
3345 if (!bgtex.empty())
3346 addTextureDownload(bgtex, m_HtmlBackground.TextureId, this);
3350 struct CButtonFreezer : public CInterfaceElementVisitor
3352 virtual void visitCtrl(CCtrlBase *ctrl)
3354 CCtrlBaseButton *textButt = dynamic_cast<CCtrlTextButton *>(ctrl);
3355 if (textButt)
3357 textButt->setFrozen(true);
3362 // ***************************************************************************
3364 void CGroupHTML::handle ()
3366 H_AUTO(RZ_Interface_Html_handle)
3368 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
3370 // handle curl downloads
3371 checkDownloads();
3373 // handle refresh timer
3374 if (_NextRefreshTime > 0 && _NextRefreshTime <= (times.thisFrameMs / 1000.0f) )
3376 // there might be valid uses for 0sec refresh, but two in a row is probably a mistake
3377 if (_NextRefreshTime - _LastRefreshTime >= 1.0)
3379 _LastRefreshTime = _NextRefreshTime;
3380 doBrowse(_RefreshUrl.c_str());
3382 else
3383 nlwarning("Ignore second 0sec http-equiv refresh in a row (url '%s')", _URL.c_str());
3385 _NextRefreshTime = 0;
3388 if (_CurlWWW)
3390 // still transfering html page
3391 if (_TimeoutValue != 0 && _ConnectingTimeout <= ( times.thisFrameMs / 1000.0f ) )
3393 browseError(("Connection timeout : "+_URL).c_str());
3396 else
3397 if (_RenderNextTime)
3399 _RenderNextTime = false;
3400 renderHtmlString(_DocumentHtml);
3402 else
3403 if (_WaitingForStylesheet)
3405 renderDocument();
3407 else
3408 if (_BrowseNextTime || _PostNextTime)
3410 // Set timeout
3411 _ConnectingTimeout = ( times.thisFrameMs / 1000.0f ) + _TimeoutValue;
3413 // freeze form buttons
3414 CButtonFreezer freezer;
3415 this->visit(&freezer);
3417 // Home ?
3418 if (_URL == "home")
3419 _URL = home();
3421 string finalUrl;
3422 bool isLocal = lookupLocalFile (finalUrl, _URL.c_str(), true);
3424 _URL = finalUrl;
3426 CUrlParser uri (_URL);
3427 _TrustedDomain = isTrustedDomain(uri.host);
3428 _DocumentDomain = uri.host;
3430 // file is probably from bnp (ingame help)
3431 if (isLocal)
3433 doBrowseLocalFile(finalUrl);
3435 else
3437 SFormFields formfields;
3438 if (_PostNextTime)
3440 buildHTTPPostParams(formfields);
3441 // _URL is set from form.Action
3442 finalUrl = _URL;
3444 else
3446 // Add custom get params from child classes
3447 addHTTPGetParams (finalUrl, _TrustedDomain);
3450 doBrowseRemoteUrl(finalUrl, "", _PostNextTime, formfields);
3453 _BrowseNextTime = false;
3454 _PostNextTime = false;
3458 // ***************************************************************************
3459 void CGroupHTML::buildHTTPPostParams (SFormFields &formfields)
3461 // Add text area text
3462 uint i;
3464 if (_PostFormId >= _Forms.size())
3466 nlwarning("(%s) invalid form index %d, _Forms %d", _Id.c_str(), _PostFormId, _Forms.size());
3467 return;
3469 // Ref the form
3470 CForm &form = _Forms[_PostFormId];
3472 // button can override form action url (and methor, but we only do POST)
3473 _URL = _PostFormAction.empty() ? form.Action : _PostFormAction;
3475 CUrlParser uri(_URL);
3476 _TrustedDomain = isTrustedDomain(uri.host);
3477 _DocumentDomain = uri.host;
3479 for (i=0; i<form.Entries.size(); i++)
3481 // Text area ?
3482 bool addEntry = false;
3483 string entryData;
3484 if (form.Entries[i].TextArea)
3486 // Get the edit box view
3487 CInterfaceGroup *group = form.Entries[i].TextArea->getGroup ("eb");
3488 if (group)
3490 // Should be a CGroupEditBox
3491 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
3492 if (editBox)
3494 entryData = editBox->getViewText()->getText();
3495 addEntry = true;
3499 else if (form.Entries[i].Checkbox)
3501 // todo handle unicode POST here
3502 if (form.Entries[i].Checkbox->getPushed ())
3504 entryData = form.Entries[i].Value;
3505 addEntry = true;
3508 else if (form.Entries[i].ComboBox)
3510 CDBGroupComboBox *cb = form.Entries[i].ComboBox;
3511 entryData = form.Entries[i].SelectValues[cb->getSelection()];
3512 addEntry = true;
3514 else if (form.Entries[i].SelectBox)
3516 CGroupMenu *sb = form.Entries[i].SelectBox;
3517 CGroupSubMenu *rootMenu = sb->getRootMenu();
3518 if (rootMenu)
3520 for(uint j=0; j<rootMenu->getNumLine(); ++j)
3522 CInterfaceGroup *ig = rootMenu->getUserGroupLeft(j);
3523 if (ig)
3525 CCtrlBaseButton *cb = dynamic_cast<CCtrlBaseButton *>(ig->getCtrl("b"));
3526 if (cb && cb->getPushed())
3527 formfields.add(form.Entries[i].Name, form.Entries[i].SelectValues[j]);
3532 // This is a hidden value
3533 else
3535 entryData = form.Entries[i].Value;
3536 addEntry = true;
3539 // Add this entry
3540 if (addEntry)
3542 formfields.add(form.Entries[i].Name, entryData);
3546 if (_PostFormSubmitType == "image")
3548 // Add the button coordinates
3549 if (_PostFormSubmitButton.find_first_of("[") == string::npos)
3551 formfields.add(_PostFormSubmitButton + "_x", NLMISC::toString(_PostFormSubmitX));
3552 formfields.add(_PostFormSubmitButton + "_y", NLMISC::toString(_PostFormSubmitY));
3554 else
3556 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitX));
3557 formfields.add(_PostFormSubmitButton, NLMISC::toString(_PostFormSubmitY));
3560 else
3561 formfields.add(_PostFormSubmitButton, _PostFormSubmitValue);
3563 // Add custom params from child classes
3564 addHTTPPostParams(formfields, _TrustedDomain);
3567 // ***************************************************************************
3568 void CGroupHTML::doBrowseLocalFile(const std::string &uri)
3570 releaseDownloads();
3571 updateRefreshButton();
3573 std::string filename;
3574 if (toLowerAscii(uri).find("file:/") == 0)
3576 filename = uri.substr(6, uri.size() - 6);
3578 else
3580 filename = uri;
3583 LOG_DL("browse local file '%s'", filename.c_str());
3585 _TrustedDomain = true;
3586 _DocumentDomain = "localhost";
3588 CIFile in;
3589 if (in.open(filename))
3591 std::string html;
3592 while(!in.eof())
3594 char buf[1024];
3595 in.getline(buf, 1024);
3596 html += std::string(buf) + "\n";
3598 in.close();
3600 if (!renderHtmlString(html))
3602 browseError((string("Failed to parse html from file : ")+filename).c_str());
3605 else
3607 browseError((string("The page address is malformed : ")+filename).c_str());
3611 // ***************************************************************************
3612 void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
3614 // stop all downloads from previous page
3615 releaseDownloads();
3616 updateRefreshButton();
3618 // Reset the title
3619 if(_TitlePrefix.empty())
3620 setTitle (CI18N::get("uiPleaseWait"));
3621 else
3622 setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
3624 url = upgradeInsecureUrl(url);
3626 LOG_DL("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d",
3627 _Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size());
3629 if (!MultiCurl)
3631 browseError(string("Invalid MultCurl handle, loading url failed : "+url).c_str());
3632 return;
3635 CURL *curl = curl_easy_init();
3636 if (!curl)
3638 nlwarning("(%s) failed to create curl handle", _Id.c_str());
3639 browseError(string("Failed to create cURL handle : " + url).c_str());
3640 return;
3643 // https://
3644 if (toLowerAscii(url.substr(0, 8)) == "https://")
3646 // if supported, use custom SSL context function to load certificates
3647 NLWEB::CCurlCertificates::useCertificates(curl);
3650 // do not follow redirects, we have own handler
3651 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
3652 // after redirect
3653 curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
3655 // tell curl to use compression if possible (gzip, deflate)
3656 // leaving this empty allows all encodings that curl supports
3657 //curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
3659 // limit curl to HTTP and HTTPS protocols only
3660 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3661 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
3663 // Destination
3664 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
3666 // User-Agent:
3667 std::string userAgent = options.appName + "/" + options.appVersion;
3668 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
3670 // Cookies
3671 sendCookies(curl, _DocumentDomain, _TrustedDomain);
3673 // Referer
3674 if (!referer.empty())
3676 curl_easy_setopt(curl, CURLOPT_REFERER, referer.c_str());
3677 LOG_DL("(%s) set referer '%s'", _Id.c_str(), referer.c_str());
3680 if (doPost)
3682 // serialize form data and add it to curl
3683 std::string data;
3684 for(uint i=0; i<formfields.Values.size(); ++i)
3686 char * escapedName = curl_easy_escape(curl, formfields.Values[i].name.c_str(), formfields.Values[i].name.size());
3687 char * escapedValue = curl_easy_escape(curl, formfields.Values[i].value.c_str(), formfields.Values[i].value.size());
3689 if (i>0)
3690 data += "&";
3692 data += std::string(escapedName) + "=" + escapedValue;
3694 curl_free(escapedName);
3695 curl_free(escapedValue);
3697 curl_easy_setopt(curl, CURLOPT_POST, 1);
3698 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
3699 curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data.c_str());
3701 else
3703 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
3706 // transfer handle
3707 _CurlWWW = new CCurlWWWData(curl, url);
3709 // set the language code used by the client
3710 std::vector<std::string> headers;
3711 headers.push_back("Accept-Language: "+options.languageCode);
3712 headers.push_back("Accept-Charset: utf-8");
3713 _CurlWWW->sendHeaders(headers);
3715 // catch headers for redirect
3716 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NLGUI::curlHeaderCallback);
3717 curl_easy_setopt(curl, CURLOPT_WRITEHEADER, _CurlWWW);
3719 // catch body
3720 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NLGUI::curlDataCallback);
3721 curl_easy_setopt(curl, CURLOPT_WRITEDATA, _CurlWWW);
3723 #ifdef LOG_CURL_PROGRESS
3724 // progress callback
3725 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
3726 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, NLGUI::curlProgressCallback);
3727 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, _CurlWWW);
3728 #else
3729 // progress off
3730 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
3731 #endif
3734 curl_multi_add_handle(MultiCurl, curl);
3736 // start the transfer
3737 int NewRunningCurls = 0;
3738 curl_multi_perform(MultiCurl, &NewRunningCurls);
3739 RunningCurls++;
3741 _RedirectsRemaining = DEFAULT_RYZOM_REDIRECT_LIMIT;
3744 // ***************************************************************************
3745 void CGroupHTML::htmlDownloadFinished(bool success, const std::string &error)
3747 if (!success)
3749 CUrlParser uri(_CurlWWW->Url);
3751 // potentially unwanted chars
3752 std::string url = _CurlWWW->Url;
3753 url = strFindReplaceAll(url, string("<"), string("%3C"));
3754 url = strFindReplaceAll(url, string(">"), string("%3E"));
3755 url = strFindReplaceAll(url, string("\""), string("%22"));
3756 url = strFindReplaceAll(url, string("'"), string("%27"));
3758 std::string err;
3759 err = "<html><head><title>cURL error</title></head><body>";
3760 err += "<h1>Connection failed with cURL error</h1>";
3761 err += error;
3762 err += "<hr>(" + uri.scheme + "://" + uri.host + ") <a href=\"" + url + "\">reload</a>";
3763 err += "</body></html>";
3764 browseErrorHtml(err);
3765 return;
3768 // received content from remote
3769 std::string content = trim(_CurlWWW->Content);
3771 // save HSTS header from all requests regardless of HTTP code
3772 if (_CurlWWW->hasHSTSHeader())
3774 CUrlParser uri(_CurlWWW->Url);
3775 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
3778 receiveCookies(_CurlWWW->Request, _DocumentDomain, _TrustedDomain);
3780 long code;
3781 curl_easy_getinfo(_CurlWWW->Request, CURLINFO_RESPONSE_CODE, &code);
3782 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());
3784 if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3786 if (_RedirectsRemaining < 0)
3788 browseError(string("Redirect limit reached : " + _URL).c_str());
3789 return;
3792 // redirect, get the location and try browse again
3793 // we cant use curl redirection because 'addHTTPGetParams()' must be called on new destination
3794 std::string location(_CurlWWW->getLocationHeader());
3795 if (location.empty())
3797 browseError(string("Request was redirected, but location was not set : "+_URL).c_str());
3798 return;
3801 LOG_DL("(%s) request (%d) redirected to (len %d) '%s'", _Id.c_str(), _RedirectsRemaining, location.size(), location.c_str());
3802 location = getAbsoluteUrl(location);
3804 _PostNextTime = false;
3805 _RedirectsRemaining--;
3807 doBrowse(location.c_str());
3809 else if ( (code < 200 || code >= 300) )
3811 // catches 304 not modified, but html is not in cache anyway
3812 // if server did not send any error back
3813 if (content.empty())
3815 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>");
3819 char *ch;
3820 std::string contentType;
3821 CURLcode res = curl_easy_getinfo(_CurlWWW->Request, CURLINFO_CONTENT_TYPE, &ch);
3822 if (res == CURLE_OK && ch != NULL)
3824 contentType = ch;
3827 htmlDownloadFinished(content, contentType, code);
3829 // clear curl handler
3830 if (MultiCurl)
3832 curl_multi_remove_handle(MultiCurl, _CurlWWW->Request);
3835 delete _CurlWWW;
3836 _CurlWWW = NULL;
3838 // refresh button uses _CurlWWW. refresh button may stay disabled if
3839 // there is no css files to download and page is rendered before _CurlWWW is freed
3840 updateRefreshButton();
3843 void CGroupHTML::dataDownloadFinished(bool success, const std::string &error, CDataDownload *data)
3845 fclose(data->fp);
3847 CUrlParser uri(data->url);
3848 if (!uri.host.empty())
3850 receiveCookies(data->data->Request, uri.host, isTrustedDomain(uri.host));
3853 long code = -1;
3854 curl_easy_getinfo(data->data->Request, CURLINFO_RESPONSE_CODE, &code);
3856 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());
3857 curl_multi_remove_handle(MultiCurl, data->data->Request);
3859 // save HSTS header from all requests regardless of HTTP code
3860 if (success)
3862 if (data->data->hasHSTSHeader())
3864 CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, data->data->getHSTSHeader());
3867 // 2XX success, 304 Not Modified
3868 if ((code >= 200 && code <= 204) || code == 304)
3870 CHttpCacheObject obj;
3871 obj.Expires = data->data->getExpires();
3872 obj.Etag = data->data->getEtag();
3873 obj.LastModified = data->data->getLastModified();
3875 CHttpCache::getInstance()->store(data->dest, obj);
3876 if (code == 304 && CFile::fileExists(data->tmpdest))
3878 CFile::deleteFile(data->tmpdest);
3881 else if ((code >= 301 && code <= 303) || code == 307 || code == 308)
3883 if (data->redirects < DEFAULT_RYZOM_REDIRECT_LIMIT)
3885 std::string location(data->data->getLocationHeader());
3886 if (!location.empty())
3888 CUrlParser uri(location);
3889 if (!uri.isAbsolute())
3891 uri.inherit(data->url);
3892 location = uri.toString();
3895 // clear old request state, and curl easy handle
3896 delete data->data;
3897 data->data = NULL;
3898 data->fp = NULL;
3899 data->url = location;
3900 data->redirects++;
3902 // push same request in the front of the queue
3903 // cache filename is based of original url
3904 Curls.push_front(data);
3906 LOG_DL("Redirect '%s'", location.c_str());
3907 // no finished callback called, so cleanup old temp
3908 if (CFile::fileExists(data->tmpdest))
3910 CFile::deleteFile(data->tmpdest);
3912 return;
3915 nlwarning("Redirected to empty url '%s'", data->url.c_str());
3917 else
3919 nlwarning("Redirect limit reached for '%s'", data->url.c_str());
3922 else
3924 nlwarning("HTTP request failed with code [%d] for '%s'\n",code, data->url.c_str());
3925 // 404, 500, etc
3926 if (CFile::fileExists(data->dest))
3928 CFile::deleteFile(data->dest);
3932 else
3934 nlwarning("DATA download failed '%s', error '%s'", data->url.c_str(), error.c_str());
3937 finishCurlDownload(data);
3940 void CGroupHTML::htmlDownloadFinished(const std::string &content, const std::string &type, long code)
3942 LOG_DL("(%s) HTML download finished, content length %d, type '%s', code %d", _Id.c_str(), content.size(), type.c_str(), code);
3944 // create <html> markup for image downloads
3945 if (type.find("image/") == 0 && !content.empty())
3949 std::string dest = localImageName(_URL);
3950 COFile out;
3951 out.open(dest);
3952 out.serialBuffer((uint8 *)(content.c_str()), content.size());
3953 out.close();
3954 LOG_DL("(%s) image saved to '%s', url '%s'", _Id.c_str(), dest.c_str(), _URL.c_str());
3956 catch(...) { }
3958 // create html code with image url inside and do the request again
3959 renderHtmlString("<html><head><title>"+_URL+"</title></head><body><img src=\"" + _URL + "\"></body></html>");
3961 else if (_TrustedDomain && type.find("text/lua") == 0)
3963 setTitle(_TitleString);
3965 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+content;
3966 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
3967 _LuaScript.clear();
3969 // disable refresh button
3970 clearRefresh();
3971 // disable redo into this url
3972 _AskedUrl.clear();
3974 else
3976 // Sanitize downloaded HTML UTF-8 encoding, and render
3977 renderHtmlString(CUtfStringView(content).toUtf8(true));
3981 // ***************************************************************************
3982 void CGroupHTML::cssDownloadFinished(const std::string &url, const std::string &local)
3984 for(std::vector<CHtmlParser::StyleLink>::iterator it = _StylesheetQueue.begin();
3985 it != _StylesheetQueue.end(); ++it)
3987 if (it->Url == url)
3989 // read downloaded file into HtmlStyles
3990 if (CFile::fileExists(local) && it->Index < _HtmlStyles.size())
3992 CIFile in;
3993 if (in.open(local))
3995 if (!in.readAll(_HtmlStyles[it->Index]))
3997 nlwarning("Failed to read downloaded css file(%s), url(%s)", local.c_str(), url.c_str());
4002 _StylesheetQueue.erase(it);
4003 break;
4008 void CGroupHTML::renderDocument()
4010 if (!Curls.empty() && !_StylesheetQueue.empty())
4012 // waiting for stylesheets to finish downloading
4013 return;
4015 _WaitingForStylesheet = false;
4017 //TGameTime renderStart = CTime::getLocalTime();
4019 // clear previous state and page
4020 beginBuild();
4021 removeContent();
4023 // process all <style> and <link rel=stylesheet> elements
4024 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4026 if (!_HtmlStyles[i].empty())
4028 _Style.parseStylesheet(_HtmlStyles[i]);
4031 _HtmlStyles.clear();
4033 std::list<CHtmlElement>::iterator it = _HtmlDOM.Children.begin();
4034 while(it != _HtmlDOM.Children.end())
4036 renderDOM(*it);
4037 ++it;
4040 endBuild();
4042 //TGameTime renderStop = CTime::getLocalTime();
4043 //nlwarning("[%s] render: %.1fms (%s)\n", _Id.c_str(), (renderStop - renderStart), _URL.c_str());
4046 // ***************************************************************************
4048 bool CGroupHTML::renderHtmlString(const std::string &html)
4050 bool success;
4052 // if we are already rendering, then queue up the next page
4053 if (_Browsing)
4055 _DocumentHtml = html;
4056 _RenderNextTime = true;
4058 return true;
4062 _DocumentUrl = _URL;
4063 _DocumentHtml = html;
4064 _NextRefreshTime = 0;
4065 _RefreshUrl.clear();
4067 if (trim(html).empty())
4069 // clear the page
4070 beginBuild();
4072 // clear previous page and state
4073 removeContent();
4075 endBuild();
4077 success = false;
4079 else
4081 // browser.css
4082 resetCssStyle();
4084 // start new rendering
4085 _HtmlDOM = CHtmlElement(CHtmlElement::NONE, "<root>");
4086 _CurrentHTMLElement = NULL;
4087 success = parseHtml(html);
4088 if (success)
4090 _WaitingForStylesheet = !_StylesheetQueue.empty();
4091 renderDocument();
4093 else
4095 std::string error = "ERROR: HTML parse failed.";
4096 error += toString("\nsize %d bytes", html.size());
4097 error += toString("\n---start---\n%s\n---end---\n", html.c_str());
4098 browseError(error.c_str());
4102 return success;
4105 // ***************************************************************************
4106 void CGroupHTML::doBrowseAnchor(const std::string &anchor)
4108 if (_Anchors.count(anchor) == 0)
4110 return;
4113 CInterfaceElement *pIE = _Anchors.find(anchor)->second;
4114 if (pIE)
4116 // hotspot depends on vertical/horizontal scrollbar
4117 CCtrlScroll *pSB = getScrollBar();
4118 if (pSB)
4120 pSB->ensureVisible(pIE, Hotspot_Tx, Hotspot_Tx);
4125 // ***************************************************************************
4127 void CGroupHTML::draw ()
4129 uint8 CurrentAlpha = 255;
4130 // search a parent container
4131 CInterfaceGroup *gr = getParent();
4132 while (gr)
4134 if (gr->isGroupContainer())
4136 CGroupContainer *gc = static_cast<CGroupContainer *>(gr);
4137 CurrentAlpha = gc->getCurrentContainerAlpha();
4138 break;
4140 gr = gr->getParent();
4142 m_HtmlBackground.CurrentAlpha = CurrentAlpha;
4143 m_BodyBackground.CurrentAlpha = CurrentAlpha;
4145 m_HtmlBackground.draw();
4146 m_BodyBackground.draw();
4147 CGroupScrollText::draw ();
4150 // ***************************************************************************
4152 void CGroupHTML::beginBuild ()
4154 _Browsing = true;
4157 void CGroupHTML::endBuild ()
4159 // set the browser as complete
4160 _Browsing = false;
4161 updateRefreshButton();
4163 // check that the title is set, or reset it (in the case the page
4164 // does not provide a title)
4165 if (_TitleString.empty())
4167 setTitle(_TitlePrefix);
4170 invalidateCoords();
4173 // ***************************************************************************
4175 void CGroupHTML::addHTTPGetParams (string &/* url */, bool /*trustedDomain*/)
4179 // ***************************************************************************
4181 void CGroupHTML::addHTTPPostParams (SFormFields &/* formfields */, bool /*trustedDomain*/)
4185 // ***************************************************************************
4187 string CGroupHTML::home () const
4189 return Home;
4192 // ***************************************************************************
4194 void CGroupHTML::removeContent ()
4196 // Remove old document
4197 if (!_GroupListAdaptor)
4199 _GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list
4200 _GroupListAdaptor->setId(getList()->getId() + ":GLA");
4201 _GroupListAdaptor->setResizeFromChildH(true);
4202 getList()->addChild (_GroupListAdaptor, true);
4205 // Group list adaptor not exist ?
4206 _GroupListAdaptor->clearGroups();
4207 _GroupListAdaptor->clearControls();
4208 _GroupListAdaptor->clearViews();
4209 CWidgetManager::getInstance()->clearViewUnders();
4210 CWidgetManager::getInstance()->clearCtrlsUnders();
4212 // Clear all the context
4213 clearContext();
4215 // Reset default background
4216 m_HtmlBackground.clear();
4217 m_BodyBackground.clear();
4219 // TODO: DefaultBackgroundBitmapView should be removed from interface xml
4220 CViewBase *view = getView (DefaultBackgroundBitmapView);
4221 if (view)
4222 view->setActive(false);
4224 paragraphChange ();
4227 // ***************************************************************************
4228 const std::string &CGroupHTML::selectTreeNodeRecurs(CGroupTree::SNode *node, const std::string &url)
4230 static std::string emptyString;
4231 if(!node)
4233 return emptyString;
4236 // if this node match
4237 if(actionLaunchUrlRecurs(node->AHName, node->AHParams, url))
4239 return node->Id;
4241 // fails => look into children
4242 else
4244 for(uint i=0;i<node->Children.size();i++)
4246 const string &childRes= selectTreeNodeRecurs(node->Children[i], url);
4247 if(!childRes.empty())
4248 return childRes;
4251 // none match...
4252 return emptyString;
4256 // ***************************************************************************
4257 bool CGroupHTML::actionLaunchUrlRecurs(const std::string &ah, const std::string &params, const std::string &url)
4259 // check if this action match
4260 if( (ah=="launch_help" || ah=="browse") && IActionHandler::getParam (params, "url") == url)
4262 return true;
4264 // can be a proc that contains launch_help/browse => look recurs
4265 else if(ah=="proc")
4267 const std::string &procName= params;
4268 // look into this proc
4269 uint numActions= CWidgetManager::getInstance()->getParser()->getProcedureNumActions(procName);
4270 for(uint i=0;i<numActions;i++)
4272 string procAh, procParams;
4273 if( CWidgetManager::getInstance()->getParser()->getProcedureAction(procName, i, procAh, procParams))
4275 // recurs proc if needed!
4276 if (actionLaunchUrlRecurs(procAh, procParams, url))
4277 return true;
4282 return false;
4285 // ***************************************************************************
4286 void CGroupHTML::clearRefresh()
4288 _URL.clear();
4289 updateRefreshButton();
4292 // ***************************************************************************
4293 void CGroupHTML::clearUndoRedo()
4295 // erase any undo/redo
4296 _BrowseUndo.clear();
4297 _BrowseRedo.clear();
4299 // update buttons validation
4300 updateUndoRedoButtons();
4303 // ***************************************************************************
4304 void CGroupHTML::pushUrlUndoRedo(const std::string &url)
4306 // if same url, no op
4307 if(url==_AskedUrl)
4308 return;
4310 // erase any redo, push undo, set current
4311 _BrowseRedo.clear();
4312 if(!_AskedUrl.empty())
4313 _BrowseUndo.push_back(_AskedUrl);
4314 _AskedUrl= url;
4316 // limit undo
4317 while(_BrowseUndo.size()>MaxUrlUndoRedo)
4318 _BrowseUndo.pop_front();
4320 // update buttons validation
4321 updateUndoRedoButtons();
4324 // ***************************************************************************
4325 void CGroupHTML::browseUndo()
4327 if(_BrowseUndo.empty())
4328 return;
4330 // push to redo, pop undo, and set current
4331 if (!_AskedUrl.empty())
4332 _BrowseRedo.push_front(_AskedUrl);
4334 _AskedUrl= _BrowseUndo.back();
4335 _BrowseUndo.pop_back();
4337 // update buttons validation
4338 updateUndoRedoButtons();
4340 // and then browse the undoed url, with no undo/redo
4341 doBrowse(_AskedUrl.c_str());
4344 // ***************************************************************************
4345 void CGroupHTML::browseRedo()
4347 if(_BrowseRedo.empty())
4348 return;
4350 // push to undo, pop redo, and set current
4351 _BrowseUndo.push_back(_AskedUrl);
4352 _AskedUrl= _BrowseRedo.front();
4353 _BrowseRedo.pop_front();
4355 // update buttons validation
4356 updateUndoRedoButtons();
4358 // and then browse the redoed url, with no undo/redo
4359 doBrowse(_AskedUrl.c_str());
4362 // ***************************************************************************
4363 void CGroupHTML::updateUndoRedoButtons()
4365 CCtrlBaseButton *butUndo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseUndoButton));
4366 CCtrlBaseButton *butRedo= dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRedoButton));
4368 // gray according to list size
4369 if(butUndo)
4370 butUndo->setFrozen(_BrowseUndo.empty());
4371 if(butRedo)
4372 butRedo->setFrozen(_BrowseRedo.empty());
4375 // ***************************************************************************
4376 void CGroupHTML::updateRefreshButton()
4378 CCtrlBaseButton *butRefresh = dynamic_cast<CCtrlBaseButton *>(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton));
4379 if(butRefresh)
4381 // connecting, rendering, or is missing url
4382 bool frozen = _CurlWWW || _Browsing || _URL.empty();
4383 butRefresh->setFrozen(frozen);
4387 // ***************************************************************************
4389 NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLInputOffset, std::string, "html_input_offset");
4391 CGroupHTMLInputOffset::CGroupHTMLInputOffset(const TCtorParam &param)
4392 : CInterfaceGroup(param),
4393 Offset(0)
4397 xmlNodePtr CGroupHTMLInputOffset::serialize( xmlNodePtr parentNode, const char *type ) const
4399 xmlNodePtr node = CInterfaceGroup::serialize( parentNode, type );
4400 if( node == NULL )
4401 return NULL;
4403 xmlSetProp( node, BAD_CAST "type", BAD_CAST "html_input_offset" );
4404 xmlSetProp( node, BAD_CAST "y_offset", BAD_CAST toString( Offset ).c_str() );
4406 return node;
4409 // ***************************************************************************
4410 bool CGroupHTMLInputOffset::parse(xmlNodePtr cur, CInterfaceGroup *parentGroup)
4412 if (!CInterfaceGroup::parse(cur, parentGroup)) return false;
4413 CXMLAutoPtr ptr;
4414 // Get the url
4415 ptr = xmlGetProp (cur, (xmlChar*)"y_offset");
4416 if (ptr)
4417 fromString((const char*)ptr, Offset);
4418 return true;
4421 // ***************************************************************************
4422 int CGroupHTML::luaParseHtml(CLuaState &ls)
4424 const char *funcName = "parseHtml";
4425 CLuaIHM::checkArgCount(ls, funcName, 1);
4426 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4427 std::string html = ls.toString(1);
4429 parseHtml(html);
4431 return 0;
4434 int CGroupHTML::luaClearRefresh(CLuaState &ls)
4436 const char *funcName = "clearRefresh";
4437 CLuaIHM::checkArgCount(ls, funcName, 0);
4439 clearRefresh();
4441 return 0;
4444 int CGroupHTML::luaClearUndoRedo(CLuaState &ls)
4446 const char *funcName = "clearUndoRedo";
4447 CLuaIHM::checkArgCount(ls, funcName, 0);
4449 clearUndoRedo();
4450 return 0;
4453 // ***************************************************************************
4454 int CGroupHTML::luaBrowse(CLuaState &ls)
4456 const char *funcName = "browse";
4457 CLuaIHM::checkArgCount(ls, funcName, 1);
4458 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4459 browse(ls.toString(1));
4460 return 0;
4463 // ***************************************************************************
4464 int CGroupHTML::luaRefresh(CLuaState &ls)
4466 const char *funcName = "refresh";
4467 CLuaIHM::checkArgCount(ls, funcName, 0);
4468 refresh();
4469 return 0;
4472 // ***************************************************************************
4473 int CGroupHTML::luaRemoveContent(CLuaState &ls)
4475 const char *funcName = "removeContent";
4476 CLuaIHM::checkArgCount(ls, funcName, 0);
4477 removeContent();
4478 return 0;
4481 // ***************************************************************************
4482 int CGroupHTML::luaRenderHtml(CLuaState &ls)
4484 const char *funcName = "renderHtml";
4485 CLuaIHM::checkArgCount(ls, funcName, 1);
4486 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4487 std::string html = ls.toString(1);
4489 // Always trust domain if rendered from lua
4490 _TrustedDomain = true;
4491 renderHtmlString(html);
4493 return 0;
4496 // ***************************************************************************
4497 int CGroupHTML::luaSetBackground(CLuaState &ls)
4499 const char *funcName = "setBackground";
4500 CLuaIHM::checkArgCount(ls, funcName, 3);
4501 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4502 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4503 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4504 std::string image = ls.toString(1);
4505 bool scale = ls.toBoolean(2);
4506 bool repeat = ls.toBoolean(3);
4508 setBackground(image, scale, repeat);
4510 return 0;
4513 // ***************************************************************************
4514 int CGroupHTML::luaInsertText(CLuaState &ls)
4516 const char *funcName = "insertText";
4517 CLuaIHM::checkArgCount(ls, funcName, 3);
4518 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4519 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TSTRING);
4520 CLuaIHM::checkArgType(ls, funcName, 3, LUA_TBOOLEAN);
4522 string name = ls.toString(1);
4523 string text = ls.toString(2);
4525 if (!_Forms.empty())
4527 for (uint i=0; i<_Forms.back().Entries.size(); i++)
4529 if (_Forms.back().Entries[i].TextArea && _Forms.back().Entries[i].Name == name)
4531 // Get the edit box view
4532 CInterfaceGroup *group = _Forms.back().Entries[i].TextArea->getGroup ("eb");
4533 if (group)
4535 // Should be a CGroupEditBox
4536 CGroupEditBox *editBox = dynamic_cast<CGroupEditBox*>(group);
4537 if (editBox)
4538 editBox->writeString(text, false, ls.toBoolean(3));
4544 return 0;
4547 // ***************************************************************************
4548 int CGroupHTML::luaAddString(CLuaState &ls)
4550 const char *funcName = "addString";
4551 CLuaIHM::checkArgCount(ls, funcName, 1);
4552 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4553 addString(ls.toString(1));
4554 return 0;
4557 // ***************************************************************************
4558 int CGroupHTML::luaAddImage(CLuaState &ls)
4560 const char *funcName = "addImage";
4561 CLuaIHM::checkArgCount(ls, funcName, 2);
4562 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4563 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4564 if (!_Paragraph)
4566 newParagraph(0);
4567 paragraphChange();
4570 CStyleParams style;
4571 style.GlobalColor = ls.toBoolean(2);
4573 string url = getLink();
4574 if (!url.empty())
4576 string params = "name=" + getId() + "|url=" + getLink ();
4577 addButton(CCtrlButton::PushButton, "", ls.toString(1), ls.toString(1),
4578 "", "browse", params.c_str(), "", style);
4580 else
4582 addImage("", ls.toString(1), false, style);
4586 return 0;
4589 // ***************************************************************************
4590 int CGroupHTML::luaShowDiv(CLuaState &ls)
4592 const char *funcName = "showDiv";
4593 CLuaIHM::checkArgCount(ls, funcName, 2);
4594 CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
4595 CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
4597 if (!_Groups.empty())
4599 for (uint i=0; i<_Groups.size(); i++)
4601 CInterfaceGroup *group = _Groups[i];
4602 if (group->getName() == ls.toString(1))
4604 group->setActive(ls.toBoolean(2));
4608 return 0;
4611 // ***************************************************************************
4612 void CGroupHTML::setURL(const std::string &url)
4614 browse(url.c_str());
4617 void CGroupHTML::setHTML(const std::string &html)
4619 renderHtmlString(html);
4622 void CGroupHTML::setHome(const std::string &home)
4624 Home = home;
4627 // ***************************************************************************
4628 void CGroupHTML::parseStylesheetFile(const std::string &fname)
4630 CIFile css;
4631 if (css.open(fname))
4633 uint32 remaining = css.getFileSize();
4634 std::string content;
4635 try {
4636 while(!css.eof() && remaining > 0)
4638 const uint BUF_SIZE = 4096;
4639 char buf[BUF_SIZE];
4641 uint32 readJustNow = std::min(remaining, BUF_SIZE);
4642 css.serialBuffer((uint8 *)&buf, readJustNow);
4643 content.append(buf, readJustNow);
4644 remaining -= readJustNow;
4647 _Style.parseStylesheet(content);
4649 catch(const Exception &e)
4651 nlwarning("exception while reading css file '%s'", e.what());
4654 else
4656 nlwarning("Stylesheet file '%s' not found (%s)", fname.c_str(), _URL.c_str());
4660 // ***************************************************************************
4661 bool CGroupHTML::parseHtml(const std::string &htmlString)
4663 CHtmlElement *parsedDOM;
4664 if (_CurrentHTMLElement == NULL)
4666 // parse under <root> element (clean dom)
4667 parsedDOM = &_HtmlDOM;
4669 else
4671 // parse under currently rendered <lua> element
4672 parsedDOM = _CurrentHTMLElement;
4675 std::vector<CHtmlParser::StyleLink> links;
4677 CHtmlParser parser;
4678 parser.getDOM(htmlString, *parsedDOM, _HtmlStyles, links);
4680 // <link> elements inserted from lua::parseHtml are ignored
4681 if (_CurrentHTMLElement == NULL && !links.empty())
4683 addStylesheetDownload(links);
4685 else if (_CurrentHTMLElement != NULL)
4687 // Called from active element (lua)
4688 // <style> order is not preserved as document is already being rendered
4689 for(uint i = 0; i < _HtmlStyles.size(); ++i)
4691 if (!_HtmlStyles[i].empty())
4693 _Style.parseStylesheet(_HtmlStyles[i]);
4696 _HtmlStyles.clear();
4699 // this should rarely fail as first element should be <html>
4700 bool success = parsedDOM->Children.size() > 0;
4702 std::list<CHtmlElement>::iterator it = parsedDOM->Children.begin();
4703 while(it != parsedDOM->Children.end())
4705 if (it->Type == CHtmlElement::ELEMENT_NODE && it->Value == "html")
4707 // move newly parsed childs from <body> into siblings
4708 if (_CurrentHTMLElement) {
4709 std::list<CHtmlElement>::iterator it2 = it->Children.begin();
4710 while(it2 != it->Children.end())
4712 if (it2->Type == CHtmlElement::ELEMENT_NODE && it2->Value == "body")
4714 spliceFragment(it2);
4715 break;
4717 ++it2;
4719 // remove <html> fragment from current element child
4720 it = parsedDOM->Children.erase(it);
4722 else
4724 // remove link to <root> (html->parent == '<root>') or css selector matching will break
4725 it->parent = NULL;
4726 ++it;
4728 continue;
4731 // skip over other non-handled element
4732 ++it;
4735 return success;
4738 void CGroupHTML::spliceFragment(std::list<CHtmlElement>::iterator src)
4740 if(!_CurrentHTMLElement->parent)
4742 nlwarning("BUG: Current node is missing parent element. unable to splice fragment");
4743 return;
4746 // get the iterators for current element (<lua>) and next sibling
4747 std::list<CHtmlElement>::iterator currentElement;
4748 currentElement = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLElement);
4749 if (currentElement == _CurrentHTMLElement->parent->Children.end())
4751 nlwarning("BUG: unable to find current element iterator from parent");
4752 return;
4755 // where fragment should be moved
4756 std::list<CHtmlElement>::iterator insertBefore;
4757 if (_CurrentHTMLNextSibling == NULL)
4759 insertBefore = _CurrentHTMLElement->parent->Children.end();
4760 } else {
4761 // get iterator for nextSibling
4762 insertBefore = std::find(_CurrentHTMLElement->parent->Children.begin(), _CurrentHTMLElement->parent->Children.end(), *_CurrentHTMLNextSibling);
4765 _CurrentHTMLElement->parent->Children.splice(insertBefore, src->Children);
4767 // reindex moved elements
4768 CHtmlElement *prev = NULL;
4769 uint childIndex = _CurrentHTMLElement->childIndex;
4770 while(currentElement != _CurrentHTMLElement->parent->Children.end())
4772 if (currentElement->Type == CHtmlElement::ELEMENT_NODE)
4774 if (prev != NULL)
4776 currentElement->parent = _CurrentHTMLElement->parent;
4777 currentElement->childIndex = childIndex;
4778 currentElement->previousSibling = prev;
4779 prev->nextSibling = &(*currentElement);
4782 childIndex++;
4783 prev = &(*currentElement);
4785 ++currentElement;
4789 // ***************************************************************************
4790 inline bool isDigit(char c, uint base = 16)
4792 if (c>='0' && c<='9') return true;
4793 if (base != 16) return false;
4794 if (c>='A' && c<='F') return true;
4795 if (c>='a' && c<='f') return true;
4796 return false;
4799 // ***************************************************************************
4800 inline char convertHexDigit(char c)
4802 if (c>='0' && c<='9') return c-'0';
4803 if (c>='A' && c<='F') return c-'A'+10;
4804 if (c>='a' && c<='f') return c-'a'+10;
4805 return 0;
4808 // ***************************************************************************
4809 std::string CGroupHTML::decodeHTMLEntities(const std::string &str)
4811 std::string result;
4812 result.reserve(str.size() + (str.size() >> 2));
4813 uint last, pos;
4815 for (uint i=0; i<str.length(); ++i)
4817 // HTML entity
4818 if (str[i] == '&' && (str.length()-i) >= 4)
4820 pos = i+1;
4822 // unicode character
4823 if (str[pos] == '#')
4825 ++pos;
4827 // using decimal by default
4828 uint base = 10;
4830 // using hexadecimal if &#x
4831 if (str[pos] == 'x')
4833 base = 16;
4834 ++pos;
4837 // setup "last" to point at the first character following "&#x?[0-9a-f]+"
4838 for (last = pos; last < str.length(); ++last) if (!isDigit(str[last], base)) break;
4840 // make sure that at least 1 digit was found
4841 // and have the terminating ';' to complete the token: "&#x?[0-9a-f]+;"
4842 if (last == pos || str[last] != ';')
4844 result += str[i];
4845 continue;
4848 u32char c = 0;
4850 // convert digits to unicode character
4851 while (pos < last) c = convertHexDigit(str[pos++]) + (c * u32char(base));
4853 // append our new character to the result string
4854 CUtfStringView::append(result, c);
4856 // move 'i' forward to point at the ';' .. the for(...) will increment i to point to next char
4857 i = last;
4859 continue;
4862 // special xml characters
4863 if (str.substr(i+1,5)=="quot;") { i+=5; result+='\"'; continue; }
4864 if (str.substr(i+1,4)=="amp;") { i+=4; result+='&'; continue; }
4865 if (str.substr(i+1,3)=="lt;") { i+=3; result+='<'; continue; }
4866 if (str.substr(i+1,3)=="gt;") { i+=3; result+='>'; continue; }
4869 // all the special cases are catered for... treat this as a normal character
4870 result += str[i];
4873 return result;
4876 // ***************************************************************************
4877 std::string CGroupHTML::getAbsoluteUrl(const std::string &url)
4879 CUrlParser uri(url);
4880 if (uri.isAbsolute())
4881 return url;
4883 uri.inherit(_URL);
4885 return uri.toString();
4888 // ***************************************************************************
4889 void CGroupHTML::resetCssStyle()
4891 _WaitingForStylesheet = false;
4892 _StylesheetQueue.clear();
4893 _Style.reset();
4894 _Style = _BrowserStyle;
4897 // ***************************************************************************
4898 std::string CGroupHTML::HTMLOListElement::getListMarkerText() const
4900 std::string ret;
4901 sint32 number = Value;
4903 if (Type == "disc")
4905 // (ucchar)0x2219;
4906 ret = "\xe2\x88\x99 ";
4908 else if (Type == "circle")
4910 // (uchar)0x26AA;
4911 ret = "\xe2\x9a\xaa ";
4913 else if (Type == "square")
4915 // (ucchar)0x25AA;
4916 ret = "\xe2\x96\xaa ";
4918 else if (Type == "a" || Type == "A")
4920 // @see toAlphabeticOrNumeric in WebKit
4921 static const char lower[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
4922 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
4923 static const char upper[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
4924 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
4925 uint size = 26;
4926 if (number < 1)
4928 ret = toString(number);
4930 else
4932 const char* digits = (Type == "A" ? upper : lower);
4933 while(number > 0)
4935 --number;
4936 ret.insert(ret.begin(), digits[number % size]);
4937 number /= size;
4940 ret += ". ";
4942 else if (Type == "i" || Type == "I")
4944 // @see toRoman in WebKit
4945 static const char lower[7] = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
4946 static const char upper[7] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'};
4948 if (number < 1 || number > 3999)
4950 ret = toString(number);
4952 else
4954 const char* digits = (Type == "I" ? upper : lower);
4955 uint8 i, d=0;
4958 uint32 num = number % 10;
4959 if (num % 5 < 4)
4961 for (i = num % 5; i > 0; i--)
4963 ret.insert(ret.begin(), digits[d]);
4966 if (num >= 4 && num <= 8)
4968 ret.insert(ret.begin(), digits[d + 1]);
4970 if (num == 9)
4972 ret.insert(ret.begin(), digits[d + 2]);
4974 if (num % 5 == 4)
4976 ret.insert(ret.begin(), digits[d]);
4978 number /= 10;
4979 d += 2;
4981 while (number > 0);
4983 if (Type == "I")
4985 ret = toUpper(ret);
4988 ret += ". ";
4990 else
4992 ret = toString(Value) + ". ";
4995 return ret;
4998 void CGroupHTML::HTMLMeterElement::readValues(const CHtmlElement &elm)
5000 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5001 value = 0.f;
5002 if (!elm.hasAttribute("min") || !fromString(elm.getAttribute("min"), min))
5003 min = 0.f;
5004 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5005 max = 1.f;
5007 // ensure min < max
5008 if (max < min)
5009 std::swap(min, max);
5011 if (!elm.hasAttribute("low") || !fromString(elm.getAttribute("low"), low))
5012 low = min;
5013 if (!elm.hasAttribute("high") || !fromString(elm.getAttribute("high"), high))
5014 high = max;
5016 if (!elm.hasAttribute("optimum") || !fromString(elm.getAttribute("optimum"), optimum))
5017 optimum = (max - min) / 2.f;
5019 // ensure low < high
5020 if (high < low)
5021 std::swap(low, high);
5022 if (low < min)
5023 low = min;
5024 if (high > max)
5025 high = max;
5028 float CGroupHTML::HTMLMeterElement::getValueRatio() const
5030 if (max <= min)
5031 return 0.f;
5033 return (value - min) / (max - min);
5036 CGroupHTML::HTMLMeterElement::EValueRegion CGroupHTML::HTMLMeterElement::getValueRegion() const
5038 if (optimum <= low)
5040 // low region is optimum
5041 if (value <= low)
5042 return VALUE_OPTIMUM;
5043 else if (value <= high)
5044 return VALUE_SUB_OPTIMAL;
5046 return VALUE_EVEN_LESS_GOOD;
5048 else if (optimum >= high)
5050 // high region is optimum
5051 if (value >= high)
5052 return VALUE_OPTIMUM;
5053 else if (value >= low)
5054 return VALUE_SUB_OPTIMAL;
5056 return VALUE_EVEN_LESS_GOOD;
5059 // middle region is optimum
5060 if (value >= low && value <= high)
5061 return VALUE_OPTIMUM;
5063 return VALUE_SUB_OPTIMAL;
5066 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5068 // color meter (inactive) bar segment
5069 // firefox:: meter { background:none; background-color: #555; },
5070 // webkit:: meter::-webkit-meter-bar { background:none; background-color: #555; }
5071 // webkit makes background color visible when padding is added
5072 CRGBA color(150, 150, 150, 255);
5074 // use webkit pseudo elements as thats easier than firefox pseudo classes
5075 // background-color is expected to be set from browser.css
5076 style.pushStyle();
5077 style.applyStyle(elm.getPseudo(":-webkit-meter-bar"));
5078 if(style.hasStyle("background-color"))
5079 color = style.Current.Background.color;
5080 style.popStyle();
5082 return color;
5085 NLMISC::CRGBA CGroupHTML::HTMLMeterElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5087 // background-color is expected to be set from browser.css
5088 CRGBA color;
5089 style.pushStyle();
5090 switch(getValueRegion())
5092 case VALUE_OPTIMUM:
5094 style.applyStyle(elm.getPseudo(":-webkit-meter-optimum-value"));
5095 if (style.hasStyle("background-color"))
5096 color = style.Current.Background.color;
5097 break;
5099 case VALUE_SUB_OPTIMAL:
5101 style.applyStyle(elm.getPseudo(":-webkit-meter-suboptimum-value"));
5102 if (style.hasStyle("background-color"))
5103 color = style.Current.Background.color;
5104 break;
5106 case VALUE_EVEN_LESS_GOOD: // fall through
5107 default:
5109 style.applyStyle(elm.getPseudo(":-webkit-meter-even-less-good-value"));
5110 if (style.hasStyle("background-color"))
5111 color = style.Current.Background.color;
5112 break;
5114 }//switch
5115 style.popStyle();
5117 return color;
5120 // ****************************************************************************
5121 void CGroupHTML::HTMLProgressElement::readValues(const CHtmlElement &elm)
5123 if (!elm.hasAttribute("value") || !fromString(elm.getAttribute("value"), value))
5124 value = 0.f;
5125 if (!elm.hasAttribute("max") || !fromString(elm.getAttribute("max"), max))
5126 max = 1.f;
5128 if (value > max)
5129 value = max;
5132 // ****************************************************************************
5133 float CGroupHTML::HTMLProgressElement::getValueRatio() const
5135 if (max > 0.f)
5136 return value / max;
5137 return 0.f;
5140 // ****************************************************************************
5141 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getBarColor(const CHtmlElement &elm, CCssStyle &style) const
5143 CRGBA color;
5145 style.pushStyle();
5146 style.applyStyle(elm.getPseudo(":-webkit-progress-bar"));
5147 if (style.hasStyle("background-color"))
5148 color = style.Current.Background.color;
5149 style.popStyle();
5151 return color;
5154 // ****************************************************************************
5155 NLMISC::CRGBA CGroupHTML::HTMLProgressElement::getValueColor(const CHtmlElement &elm, CCssStyle &style) const
5157 CRGBA color;
5159 style.pushStyle();
5160 style.applyStyle(elm.getPseudo(":-webkit-progress-value"));
5161 if (style.hasStyle("background-color"))
5162 color = style.Current.Background.color;
5163 style.popStyle();
5165 return color;
5168 // ****************************************************************************
5169 void CGroupHTML::getCellsParameters(const CHtmlElement &elm, bool inherit)
5171 CGroupHTML::CCellParams cellParams;
5172 if (!_CellParams.empty() && inherit)
5173 cellParams = _CellParams.back();
5175 if (!_Style.hasStyle("background-color") && elm.hasNonEmptyAttribute("bgcolor"))
5177 CRGBA c;
5178 if (scanHTMLColor(elm.getAttribute("bgcolor").c_str(), c))
5179 _Style.Current.Background.color = c;
5181 cellParams.BgColor = _Style.Current.Background.color;
5183 if (elm.hasAttribute("nowrap") || _Style.Current.WhiteSpace == "nowrap")
5184 cellParams.NoWrap = true;
5186 if (elm.hasNonEmptyAttribute("l_margin"))
5187 fromString(elm.getAttribute("l_margin"), cellParams.LeftMargin);
5189 if (_Style.hasStyle("height"))
5190 cellParams.Height = _Style.Current.Height;
5191 else if (elm.hasNonEmptyAttribute("height"))
5192 fromString(elm.getAttribute("height"), cellParams.Height);
5195 std::string align;
5196 // having text-align on table/tr should not override td align attribute
5197 if (_Style.hasStyle("text-align"))
5198 align = _Style.Current.TextAlign;
5199 else if (elm.hasNonEmptyAttribute("align"))
5200 align = toLowerAscii(elm.getAttribute("align"));
5202 if (align == "left")
5203 cellParams.Align = CGroupCell::Left;
5204 else if (align == "center")
5205 cellParams.Align = CGroupCell::Center;
5206 else if (align == "right")
5207 cellParams.Align = CGroupCell::Right;
5208 else if (align != "justify")
5209 align.clear();
5211 // copy td align (can be empty) attribute back into css
5212 _Style.Current.TextAlign = align;
5216 std::string valign;
5217 if (_Style.hasStyle("vertical-align"))
5218 valign = _Style.Current.VerticalAlign;
5219 else if (elm.hasNonEmptyAttribute("valign"))
5220 valign = toLowerAscii(elm.getAttribute("valign"));
5222 if (valign == "top")
5223 cellParams.VAlign = CGroupCell::Top;
5224 else if (valign == "middle")
5225 cellParams.VAlign = CGroupCell::Middle;
5226 else if (valign == "bottom")
5227 cellParams.VAlign = CGroupCell::Bottom;
5230 _CellParams.push_back (cellParams);
5233 // ***************************************************************************
5234 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)
5236 _FormSubmit.push_back(SFormSubmitButton(formId, name, "", "image", action));
5237 // Action handler parameters
5238 std::string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5240 // Add the ctrl button
5241 addButton (CCtrlButton::PushButton, name, src, src, over, "html_submit_form", param.c_str(), tooltip.c_str(), _Style.Current);
5244 // ***************************************************************************
5245 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)
5247 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "submit", formAction));
5248 // Action handler parameters
5249 string param = "name=" + getId() + "|button=" + toString(_FormSubmit.size()-1);
5251 // Add the ctrl button
5252 if (!_Paragraph)
5254 newParagraph (0);
5255 paragraphChange ();
5258 string buttonTemplate(!templateName.empty() ? templateName : DefaultButtonGroup);
5259 typedef pair<string, string> TTmplParam;
5260 vector<TTmplParam> tmplParams;
5261 tmplParams.push_back(TTmplParam("id", name));
5262 tmplParams.push_back(TTmplParam("onclick", "html_submit_form"));
5263 tmplParams.push_back(TTmplParam("onclick_param", param));
5264 tmplParams.push_back(TTmplParam("active", "true"));
5265 if (minWidth > 0) tmplParams.push_back(TTmplParam("wmin", toString(minWidth)));
5266 CInterfaceGroup *buttonGroup = CWidgetManager::getInstance()->getParser()->createGroupInstance(buttonTemplate, _Paragraph->getId(), tmplParams);
5267 if (buttonGroup)
5269 // Add the ctrl button
5270 CCtrlTextButton *ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("button"));
5271 if (!ctrlButton) ctrlButton = dynamic_cast<CCtrlTextButton*>(buttonGroup->getCtrl("b"));
5272 if (ctrlButton)
5274 ctrlButton->setModulateGlobalColorAll (_Style.Current.GlobalColor);
5275 ctrlButton->setTextModulateGlobalColorNormal(_Style.Current.GlobalColorText);
5276 ctrlButton->setTextModulateGlobalColorOver(_Style.Current.GlobalColorText);
5277 ctrlButton->setTextModulateGlobalColorPushed(_Style.Current.GlobalColorText);
5279 // Translate the tooltip
5280 if (!tooltip.empty())
5282 if (CI18N::hasTranslation(tooltip))
5284 ctrlButton->setDefaultContextHelp(CI18N::get(tooltip));
5286 else
5288 ctrlButton->setDefaultContextHelp(tooltip);
5292 ctrlButton->setText(value);
5294 setTextButtonStyle(ctrlButton, _Style.Current);
5296 getParagraph()->addChild (buttonGroup);
5297 paragraphChange ();
5301 // ***************************************************************************
5302 void CGroupHTML::htmlA(const CHtmlElement &elm)
5304 _A.push_back(true);
5305 _Link.push_back ("");
5306 _LinkTitle.push_back("");
5307 _LinkClass.push_back("");
5308 if (elm.hasClass("ryzom-ui-button"))
5309 _LinkClass.back() = "ryzom-ui-button";
5311 // #fragment works with both ID and NAME so register both
5312 if (elm.hasNonEmptyAttribute("name"))
5313 _AnchorName.push_back(elm.getAttribute("name"));
5314 if (elm.hasNonEmptyAttribute("title"))
5315 _LinkTitle.back() = elm.getAttribute("title");
5316 if (elm.hasNonEmptyAttribute("href"))
5318 string suri = elm.getAttribute("href");
5319 if(suri.find("ah:") == 0)
5321 if (_TrustedDomain || suri.find("ah:script:") == 0)
5322 _Link.back() = suri;
5324 else
5326 // convert href from "?key=val" into "http://domain.com/?key=val"
5327 _Link.back() = getAbsoluteUrl(suri);
5331 renderPseudoElement(":before", elm);
5334 void CGroupHTML::htmlAend(const CHtmlElement &elm)
5336 renderPseudoElement(":after", elm);
5338 popIfNotEmpty(_A);
5339 popIfNotEmpty(_Link);
5340 popIfNotEmpty(_LinkTitle);
5341 popIfNotEmpty(_LinkClass);
5344 // ***************************************************************************
5345 void CGroupHTML::htmlBASE(const CHtmlElement &elm)
5347 if (!_ReadingHeadTag || _IgnoreBaseUrlTag)
5348 return;
5350 if (elm.hasNonEmptyAttribute("href"))
5352 CUrlParser uri(elm.getAttribute("href"));
5353 if (uri.isAbsolute())
5355 _URL = uri.toString();
5356 _IgnoreBaseUrlTag = true;
5361 // ***************************************************************************
5362 void CGroupHTML::htmlBODY(const CHtmlElement &elm)
5364 // override <body> (or <html>) css style attribute
5365 if (elm.hasNonEmptyAttribute("bgcolor"))
5366 _Style.applyStyle("background-color: " + elm.getAttribute("bgcolor"));
5368 if (m_HtmlBackground.isEmpty())
5369 setupBackground(&m_HtmlBackground);
5370 else
5371 setupBackground(&m_BodyBackground);
5373 renderPseudoElement(":before", elm);
5376 // ***************************************************************************
5377 void CGroupHTML::htmlBR(const CHtmlElement &elm)
5379 if (!_Paragraph || _Paragraph->getNumChildren() == 0)
5381 addString("\n");
5383 else
5385 endParagraph();
5389 // ***************************************************************************
5390 void CGroupHTML::htmlBUTTON(const CHtmlElement &elm)
5392 std::string name = elm.getAttribute("name");
5393 std::string value = elm.getAttribute("value");
5394 std::string formId = elm.getAttribute("form");
5395 std::string formAction = elm.getAttribute("formaction");
5396 std::string tooltip = elm.getAttribute("tooltip");
5397 bool disabled = elm.hasAttribute("disabled");
5399 if (formId.empty() && _FormOpen)
5401 formId = _Forms.back().id;
5404 if (!formAction.empty())
5406 formAction = getAbsoluteUrl(formAction);
5409 _FormSubmit.push_back(SFormSubmitButton(formId, name, value, "text", formAction));
5410 // Action handler parameters
5411 std::string param;
5412 if (!disabled)
5414 if (elm.getAttribute("type") == "submit")
5416 param = "ah:html_submit_form&name=" + getId() + "&button=" + toString(_FormSubmit.size()-1);
5418 else
5420 param = "ah:";
5424 _A.push_back(true);
5425 _Link.push_back(param);
5426 _LinkTitle.push_back(tooltip);
5427 _LinkClass.push_back("ryzom-ui-button");
5429 // TODO: this creates separate button element
5430 //renderPseudoElement(":before", elm);
5432 void CGroupHTML::htmlBUTTONend(const CHtmlElement &elm)
5434 // TODO: this creates separate button element
5435 //renderPseudoElement(":after", elm);
5437 popIfNotEmpty(_A);
5438 popIfNotEmpty(_Link);
5439 popIfNotEmpty(_LinkTitle);
5440 popIfNotEmpty(_LinkClass);
5443 // ***************************************************************************
5444 void CGroupHTML::htmlDD(const CHtmlElement &elm)
5446 if (_DL.empty())
5447 return;
5449 // if there was no closing tag for <dt>, then remove <dt> style
5450 if (_DL.back().DT)
5452 nlwarning("BUG: nested DT in DD");
5453 _DL.back().DT = false;
5456 if (_DL.back().DD)
5458 nlwarning("BUG: nested DD in DD");
5459 _DL.back().DD = false;
5460 popIfNotEmpty(_Indent);
5463 _DL.back().DD = true;
5464 _Indent.push_back(getIndent() + ULIndent);
5466 if (!_LI)
5468 _LI = true;
5469 newParagraph(ULBeginSpace);
5471 else
5473 newParagraph(LIBeginSpace);
5476 renderPseudoElement(":before", elm);
5479 void CGroupHTML::htmlDDend(const CHtmlElement &elm)
5481 if (_DL.empty())
5482 return;
5484 renderPseudoElement(":after", elm);
5486 // parser will process two DD in a row as nested when first DD is not closed
5487 if (_DL.back().DD)
5489 _DL.back().DD = false;
5490 popIfNotEmpty(_Indent);
5494 // ***************************************************************************
5495 void CGroupHTML::htmlDIV(const CHtmlElement &elm)
5497 _DivName = elm.getAttribute("name");
5499 string instClass = elm.getAttribute("class");
5501 // use generic template system
5502 if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate")
5504 string style = elm.getAttribute("style");
5505 string id = elm.getAttribute("id");
5506 if (id.empty())
5507 id = "DIV" + toString(getNextAutoIdSeq());
5509 typedef pair<string, string> TTmplParam;
5510 vector<TTmplParam> tmplParams;
5512 string templateName;
5513 if (!style.empty())
5515 TStyle styles = parseStyle(style);
5516 TStyle::iterator it;
5517 for (it=styles.begin(); it != styles.end(); it++)
5519 if ((*it).first == "template")
5520 templateName = (*it).second;
5521 else
5522 tmplParams.push_back(TTmplParam((*it).first, (*it).second));
5526 if (!templateName.empty())
5528 string parentId;
5529 bool haveParentDiv = getDiv() != NULL;
5530 if (haveParentDiv)
5531 parentId = getDiv()->getId();
5532 else
5534 if (!_Paragraph)
5535 newParagraph (0);
5537 parentId = _Paragraph->getId();
5540 CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, parentId, tmplParams);
5541 if (inst)
5543 inst->setId(parentId+":"+id);
5544 inst->updateCoords();
5545 if (haveParentDiv)
5547 inst->setParent(getDiv());
5548 inst->setParentSize(getDiv());
5549 inst->setParentPos(getDiv());
5550 inst->setPosRef(Hotspot_TL);
5551 inst->setParentPosRef(Hotspot_TL);
5552 getDiv()->addGroup(inst);
5554 else
5556 getParagraph()->addChild(inst);
5557 paragraphChange();
5559 _Divs.push_back(inst);
5564 renderPseudoElement(":before", elm);
5567 void CGroupHTML::htmlDIVend(const CHtmlElement &elm)
5569 renderPseudoElement(":after", elm);
5570 _DivName.clear();
5571 popIfNotEmpty(_Divs);
5574 // ***************************************************************************
5575 void CGroupHTML::htmlDL(const CHtmlElement &elm)
5577 _DL.push_back(HTMLDListElement());
5578 _LI = _DL.size() > 1 || !_UL.empty();
5580 renderPseudoElement(":before", elm);
5583 void CGroupHTML::htmlDLend(const CHtmlElement &elm)
5585 if (_DL.empty())
5586 return;
5588 renderPseudoElement(":after", elm);
5590 // unclosed DT
5591 if (_DL.back().DT)
5593 nlwarning("BUG: unclosed DT in DL");
5596 // unclosed DD
5597 if (_DL.back().DD)
5599 popIfNotEmpty(_Indent);
5600 nlwarning("BUG: unclosed DD in DL");
5603 popIfNotEmpty (_DL);
5606 // ***************************************************************************
5607 void CGroupHTML::htmlDT(const CHtmlElement &elm)
5609 if (_DL.empty())
5610 return;
5612 // TODO: check if nested tags still happen and fix it in parser
5613 // : remove special handling for nesting and let it happen
5615 // html parser and libxml2 should prevent nested tags like these
5616 if (_DL.back().DD)
5618 nlwarning("BUG: nested DD in DT");
5620 _DL.back().DD = false;
5621 popIfNotEmpty(_Indent);
5624 // html parser and libxml2 should prevent nested tags like these
5625 if (_DL.back().DT)
5627 nlwarning("BUG: nested DT in DT");
5630 _DL.back().DT = true;
5632 if (!_LI)
5634 _LI = true;
5635 newParagraph(ULBeginSpace);
5637 else
5639 newParagraph(LIBeginSpace);
5642 renderPseudoElement(":before", elm);
5645 void CGroupHTML::htmlDTend(const CHtmlElement &elm)
5647 if (_DL.empty())
5648 return;
5650 renderPseudoElement(":after", elm);
5652 _DL.back().DT = false;
5655 // ***************************************************************************
5656 void CGroupHTML::htmlFONT(const CHtmlElement &elm)
5658 if (elm.hasNonEmptyAttribute("color"))
5660 CRGBA color;
5661 if (scanHTMLColor(elm.getAttribute("color").c_str(), color))
5662 _Style.Current.TextColor = color;
5665 if (elm.hasNonEmptyAttribute("size"))
5667 uint fontsize;
5668 fromString(elm.getAttribute("size"), fontsize);
5669 _Style.Current.FontSize = fontsize;
5673 // ***************************************************************************
5674 void CGroupHTML::htmlFORM(const CHtmlElement &elm)
5676 _FormOpen = true;
5678 // Build the form
5679 CGroupHTML::CForm form;
5680 // id check is case sensitive and auto id's are uppercase
5681 form.id = toLowerAscii(trim(elm.getAttribute("id")));
5682 if (form.id.empty())
5684 form.id = toString("FORM%d", _Forms.size());
5687 // Get the action name
5688 if (elm.hasNonEmptyAttribute("action"))
5690 form.Action = getAbsoluteUrl(elm.getAttribute("action"));
5692 else
5694 form.Action = _URL;
5697 _Forms.push_back(form);
5699 renderPseudoElement(":before", elm);
5702 void CGroupHTML::htmlFORMend(const CHtmlElement &elm)
5704 _FormOpen = false;
5705 renderPseudoElement(":after", elm);
5708 // ***************************************************************************
5709 void CGroupHTML::htmlH(const CHtmlElement &elm)
5711 newParagraph(PBeginSpace);
5712 renderPseudoElement(":before", elm);
5715 void CGroupHTML::htmlHend(const CHtmlElement &elm)
5717 renderPseudoElement(":after", elm);
5720 // ***************************************************************************
5721 void CGroupHTML::htmlHEAD(const CHtmlElement &elm)
5723 _ReadingHeadTag = !_IgnoreHeadTag;
5724 _IgnoreHeadTag = true;
5727 void CGroupHTML::htmlHEADend(const CHtmlElement &elm)
5729 _ReadingHeadTag = false;
5732 // ***************************************************************************
5733 void CGroupHTML::htmlHR(const CHtmlElement &elm)
5735 CInterfaceGroup *sep = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_hr", "", NULL, 0);
5736 if (sep)
5738 CViewBitmap *bitmap = dynamic_cast<CViewBitmap*>(sep->getView("hr"));
5739 if (bitmap)
5741 bitmap->setColor(_Style.Current.TextColor);
5742 if (_Style.Current.Width > 0)
5744 clamp(_Style.Current.Width, 1, 32000);
5745 bitmap->setW(_Style.Current.Width);
5746 bitmap->setSizeRef(CInterfaceElement::none);
5748 if (_Style.Current.Height > 0)
5750 clamp(_Style.Current.Height, 1, 1000);
5751 bitmap->setH(_Style.Current.Height);
5755 renderPseudoElement(":before", elm);
5756 addHtmlGroup(sep, 0);
5757 renderPseudoElement(":after", elm);
5761 // ***************************************************************************
5762 void CGroupHTML::htmlHTML(const CHtmlElement &elm)
5764 if (elm.hasNonEmptyAttribute("style"))
5765 _Style.applyStyle(elm.getAttribute("style"));
5767 _Style.Root = _Style.Current;
5769 setupBackground(&m_HtmlBackground);
5772 // ***************************************************************************
5773 void CGroupHTML::htmlI(const CHtmlElement &elm)
5775 _Localize = true;
5776 renderPseudoElement(":before", elm);
5779 void CGroupHTML::htmlIend(const CHtmlElement &elm)
5781 renderPseudoElement(":after", elm);
5782 _Localize = false;
5785 // ***************************************************************************
5786 void CGroupHTML::htmlIMG(const CHtmlElement &elm)
5788 std::string src = trim(elm.getAttribute("src"));
5789 if (src.empty())
5791 // no 'src' attribute, or empty
5792 return;
5795 float tmpf;
5796 std::string id = elm.getAttribute("id");
5798 if (elm.hasNonEmptyAttribute("width"))
5799 getPercentage(_Style.Current.Width, tmpf, elm.getAttribute("width").c_str());
5800 if (elm.hasNonEmptyAttribute("height"))
5801 getPercentage(_Style.Current.Height, tmpf, elm.getAttribute("height").c_str());
5803 // Get the global color name
5804 if (elm.hasAttribute("global_color"))
5805 _Style.Current.GlobalColor = true;
5807 // Tooltip
5808 // keep "alt" attribute for backward compatibility
5809 std::string tooltip = elm.getAttribute("alt");
5810 // tooltip
5811 if (elm.hasNonEmptyAttribute("title"))
5812 tooltip = elm.getAttribute("title");
5814 // Mouse over image
5815 string overSrc = elm.getAttribute("data-over-src");
5817 // inside a/button with valid url (ie, button is not disabled)
5818 string url = getLink();
5819 if (getA() && !url.empty() && getParent() && getParent()->getParent())
5821 string params = "name=" + getId() + "|url=" + url;
5822 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "browse", params.c_str(), tooltip, _Style.Current);
5824 else
5825 if (!tooltip.empty() || !overSrc.empty())
5827 addButton(CCtrlButton::PushButton, id, src, src, overSrc, "", "", tooltip, _Style.Current);
5829 else
5831 // Get the option to reload (class==reload)
5832 bool reloadImg = false;
5834 if (elm.hasNonEmptyAttribute("style"))
5836 string styleString = elm.getAttribute("style");
5837 TStyle styles = parseStyle(styleString);
5838 TStyle::iterator it;
5840 it = styles.find("reload");
5841 if (it != styles.end() && (*it).second == "1")
5842 reloadImg = true;
5845 addImage(id, elm.getAttribute("src"), reloadImg, _Style.Current);
5849 // ***************************************************************************
5850 void CGroupHTML::htmlINPUT(const CHtmlElement &elm)
5852 if (_Forms.empty())
5853 return;
5855 // read general property
5856 string id = elm.getAttribute("id");
5858 // Widget template name (old)
5859 string templateName = elm.getAttribute("z_btn_tmpl");
5860 // Input name is the new
5861 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
5862 templateName = elm.getAttribute("z_input_tmpl");
5864 // Widget minimal width
5865 uint32 minWidth = 0;
5866 fromString(elm.getAttribute("z_input_width"), minWidth);
5868 // <input type="...">
5869 std::string type = trim(elm.getAttribute("type"));
5870 if (type.empty())
5872 // no 'type' attribute, or empty
5873 return;
5876 // Global color flag
5877 if (elm.hasAttribute("global_color"))
5878 _Style.Current.GlobalColor = true;
5880 // Tooltip
5881 std::string tooltip = elm.getAttribute("alt");
5883 if (type == "image")
5885 string name = elm.getAttribute("name");
5886 string src = elm.getAttribute("src");
5887 string over = elm.getAttribute("data-over-src");
5888 string formId = elm.getAttribute("form");
5889 string formAction = elm.getAttribute("formaction");
5891 if (formId.empty() && _FormOpen) {
5892 formId = _Forms.back().id;
5895 insertFormImageButton(name, tooltip, src, over, formId, formAction, minWidth, templateName);
5897 else if (type == "button" || type == "submit")
5899 string name = elm.getAttribute("name");
5900 string value = elm.getAttribute("value");
5901 string formId = elm.getAttribute("form");
5902 string formAction = elm.getAttribute("formaction");
5904 if (formId.empty() && _FormOpen) {
5905 formId = _Forms.back().id;
5908 insertFormTextButton(name, tooltip, value, formId, formAction, minWidth, templateName);
5910 else if (type == "text")
5912 // Get the string name
5913 string name = elm.getAttribute("name");
5914 string ucValue = elm.getAttribute("value");
5916 uint size = 20;
5917 uint maxlength = 1024;
5918 if (elm.hasNonEmptyAttribute("size"))
5919 fromString(elm.getAttribute("size"), size);
5920 if (elm.hasNonEmptyAttribute("maxlength"))
5921 fromString(elm.getAttribute("maxlength"), maxlength);
5923 // ryzom client used to have 'size' attribute in pixels, (12 == was default font size)
5924 if (_Style.hasStyle("-ryzom-input-size-px") && _Style.getStyle("-ryzom-input-size-px") == "true")
5925 size = size / 12;
5927 string textTemplate(!templateName.empty() ? templateName : DefaultFormTextGroup);
5928 // Add the editbox
5929 CInterfaceGroup *textArea = addTextArea (textTemplate, name.c_str (), 1, size, false, ucValue, maxlength);
5930 if (textArea)
5932 // Add the text area to the form
5933 CGroupHTML::CForm::CEntry entry;
5934 entry.Name = name;
5935 entry.TextArea = textArea;
5936 _Forms.back().Entries.push_back (entry);
5939 else if (type == "checkbox" || type == "radio")
5941 renderPseudoElement(":before", elm);
5943 CCtrlButton::EType btnType;
5944 string name = elm.getAttribute("name");
5945 string normal = elm.getAttribute("src");
5946 string pushed;
5947 string over;
5948 string ucValue = "on";
5949 bool checked = elm.hasAttribute("checked");
5951 // TODO: unknown if empty attribute should override or not
5952 if (elm.hasNonEmptyAttribute("value"))
5953 ucValue = elm.getAttribute("value");
5955 if (type == "radio")
5957 btnType = CCtrlButton::RadioButton;
5958 normal = DefaultRadioButtonBitmapNormal;
5959 pushed = DefaultRadioButtonBitmapPushed;
5960 over = DefaultRadioButtonBitmapOver;
5962 else
5964 btnType = CCtrlButton::ToggleButton;
5965 normal = DefaultCheckBoxBitmapNormal;
5966 pushed = DefaultCheckBoxBitmapPushed;
5967 over = DefaultCheckBoxBitmapOver;
5970 // Add the ctrl button
5971 CCtrlButton *checkbox = addButton (btnType, name, normal, pushed, over, "", "", tooltip, _Style.Current);
5972 if (checkbox)
5974 if (btnType == CCtrlButton::RadioButton)
5976 // override with 'id' because radio buttons share same name
5977 if (!id.empty())
5978 checkbox->setId(id);
5980 // group together buttons with same name
5981 CForm &form = _Forms.back();
5982 bool notfound = true;
5983 for (uint i=0; i<form.Entries.size(); i++)
5985 if (form.Entries[i].Name == name && form.Entries[i].Checkbox != NULL && form.Entries[i].Checkbox->getType() == CCtrlButton::RadioButton)
5987 checkbox->initRBRefFromRadioButton(form.Entries[i].Checkbox);
5988 notfound = false;
5989 break;
5992 if (notfound)
5994 // this will start a new group (initRBRef() would take first button in group container otherwise)
5995 checkbox->initRBRefFromRadioButton(checkbox);
5999 checkbox->setPushed (checked);
6001 // Add the button to the form
6002 CGroupHTML::CForm::CEntry entry;
6003 entry.Name = name;
6004 entry.Value = decodeHTMLEntities(ucValue);
6005 entry.Checkbox = checkbox;
6006 _Forms.back().Entries.push_back (entry);
6008 renderPseudoElement(":after", elm);
6010 else if (type == "hidden")
6012 if (elm.hasNonEmptyAttribute("name"))
6014 // Get the name
6015 string name = elm.getAttribute("name");
6017 // Get the value
6018 string ucValue = elm.getAttribute("value");
6020 // Add an entry
6021 CGroupHTML::CForm::CEntry entry;
6022 entry.Name = name;
6023 entry.Value = decodeHTMLEntities(ucValue);
6024 _Forms.back().Entries.push_back (entry);
6029 // ***************************************************************************
6030 void CGroupHTML::htmlLI(const CHtmlElement &elm)
6032 if (_UL.empty())
6033 return;
6035 // UL, OL top margin if this is the first LI
6036 if (!_LI)
6038 _LI = true;
6039 newParagraph(ULBeginSpace);
6041 else
6043 newParagraph(LIBeginSpace);
6046 // OL list index can be overridden by <li value="1"> attribute
6047 if (elm.hasNonEmptyAttribute("value"))
6048 fromString(elm.getAttribute("value"), _UL.back().Value);
6050 string str = _UL.back().getListMarkerText();
6051 addString (str);
6053 // list-style-type: outside
6054 if (_CurrentViewLink)
6056 getParagraph()->setFirstViewIndent(-_CurrentViewLink->getMaxUsedW());
6059 flushString ();
6061 // after marker
6062 renderPseudoElement(":before", elm);
6064 _UL.back().Value++;
6067 void CGroupHTML::htmlLIend(const CHtmlElement &elm)
6069 renderPseudoElement(":after", elm);
6072 // ***************************************************************************
6073 void CGroupHTML::htmlLUA(const CHtmlElement &elm)
6075 // we receive an embeded lua script
6076 _ParsingLua = _TrustedDomain; // Only parse lua if TrustedDomain
6077 _LuaScript.clear();
6080 void CGroupHTML::htmlLUAend(const CHtmlElement &elm)
6082 if (_ParsingLua && _TrustedDomain)
6084 _ParsingLua = false;
6085 // execute the embeded lua script
6086 _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+_LuaScript;
6087 CLuaManager::getInstance().executeLuaScript(_LuaScript, true);
6091 // ***************************************************************************
6092 void CGroupHTML::htmlMETA(const CHtmlElement &elm)
6094 if (!_ReadingHeadTag)
6095 return;
6097 std::string httpEquiv = elm.getAttribute("http-equiv");
6098 std::string httpContent = elm.getAttribute("content");
6099 if (httpEquiv.empty() || httpContent.empty())
6101 return;
6104 // only first http-equiv="refresh" should be handled
6105 if (_RefreshUrl.empty() && httpEquiv == "refresh")
6107 const CWidgetManager::SInterfaceTimes &times = CWidgetManager::getInstance()->getInterfaceTimes();
6108 double timeSec = times.thisFrameMs / 1000.0f;
6110 string::size_type pos = httpContent.find_first_of(";");
6111 if (pos == string::npos)
6113 fromString(httpContent, _NextRefreshTime);
6114 _RefreshUrl = _URL;
6116 else
6118 fromString(httpContent.substr(0, pos), _NextRefreshTime);
6120 pos = toLowerAscii(httpContent).find("url=");
6121 if (pos != string::npos)
6122 _RefreshUrl = getAbsoluteUrl(httpContent.substr(pos + 4));
6125 _NextRefreshTime += timeSec;
6129 // ***************************************************************************
6130 void CGroupHTML::htmlMETER(const CHtmlElement &elm)
6132 HTMLMeterElement meter;
6133 meter.readValues(elm);
6135 std::string id = "meter";
6136 if (elm.hasAttribute("id"))
6137 id = elm.getAttribute("id");
6139 // width: 5em, height: 1em
6140 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 5;
6141 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6142 // FIXME: only using border-top
6143 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6145 uint barw = (uint) (width * meter.getValueRatio());
6146 CRGBA bgColor = meter.getBarColor(elm, _Style);
6147 CRGBA valueColor = meter.getValueColor(elm, _Style);
6149 typedef pair<string, string> TTmplParam;
6150 vector<TTmplParam> tmplParams;
6151 tmplParams.push_back(TTmplParam("id", id));
6152 tmplParams.push_back(TTmplParam("active", "true"));
6153 tmplParams.push_back(TTmplParam("w", toString(width)));
6154 tmplParams.push_back(TTmplParam("h", toString(height)));
6155 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6156 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6157 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6158 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6159 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6160 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6162 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_meter", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6163 if (gr)
6165 renderPseudoElement(":before", elm);
6166 getParagraph()->addChild(gr);
6167 renderPseudoElement(":after", elm);
6169 // ignore any inner elements
6170 _IgnoreChildElements = true;
6174 // ***************************************************************************
6175 void CGroupHTML::htmlOBJECT(const CHtmlElement &elm)
6177 _ObjectType = elm.getAttribute("type");
6178 _ObjectData = elm.getAttribute("data");
6179 _ObjectMD5Sum = elm.getAttribute("id");
6180 _ObjectAction = elm.getAttribute("standby");
6181 _Object = true;
6184 void CGroupHTML::htmlOBJECTend(const CHtmlElement &elm)
6188 if (_ObjectType=="application/ryzom-data")
6190 if (!_TrustedDomain)
6191 return;
6193 if (!_ObjectData.empty())
6195 if (addBnpDownload(_ObjectData, _ObjectAction, _ObjectScript, _ObjectMD5Sum))
6197 CLuaManager::getInstance().executeLuaScript("\nlocal __ALLREADYDL__=true\n"+_ObjectScript, true);
6199 _ObjectScript.clear();
6202 else if (_ObjectType=="application/ryzom-tutorial")
6204 while(strFindReplace(_ObjectScript, "[", "〈"));
6205 while(strFindReplace(_ObjectScript, "]", "〉"));
6206 CLuaManager::getInstance().executeLuaScript("\ngame:executeTutorial([["+_ObjectScript+"]])\n", true);
6207 _ObjectScript.clear();
6209 else if (_ObjectType=="application/ryzom-script")
6211 while(strFindReplace(_ObjectScript, "[", "〈"));
6212 while(strFindReplace(_ObjectScript, "]", "〉"));
6213 CLuaManager::getInstance().executeLuaScript("\ngame:executeRyzomScript([["+_ObjectScript+"]])\n", true);
6214 _ObjectScript.clear();
6216 _Object = false;
6219 // ***************************************************************************
6220 void CGroupHTML::htmlOL(const CHtmlElement &elm)
6222 sint32 start = 1;
6223 std::string type("1");
6225 if (elm.hasNonEmptyAttribute("start"))
6226 fromString(elm.getAttribute("start"), start);
6227 if (elm.hasNonEmptyAttribute("type"))
6228 type = elm.getAttribute("type");
6230 _UL.push_back(HTMLOListElement(start, type));
6231 // if LI is already present
6232 _LI = _UL.size() > 1 || _DL.size() > 1;
6233 _Indent.push_back(getIndent() + ULIndent);
6235 renderPseudoElement(":before", elm);
6238 void CGroupHTML::htmlOLend(const CHtmlElement &elm)
6240 htmlULend(elm);
6243 // ***************************************************************************
6244 void CGroupHTML::htmlOPTION(const CHtmlElement &elm)
6246 _SelectOption = true;
6247 _SelectOptionStr.clear();
6249 // Got one form ?
6250 if (_Forms.empty() || _Forms.back().Entries.empty())
6251 return;
6253 _Forms.back().Entries.back().SelectValues.push_back(elm.getAttribute("value"));
6255 if (elm.hasAttribute("selected"))
6256 _Forms.back().Entries.back().InitialSelection = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6258 if (elm.hasAttribute("disabled"))
6259 _Forms.back().Entries.back().sbOptionDisabled = (sint)_Forms.back().Entries.back().SelectValues.size() - 1;
6262 void CGroupHTML::htmlOPTIONend(const CHtmlElement &elm)
6264 if (_Forms.empty() || _Forms.back().Entries.empty())
6265 return;
6267 // use option text as value
6268 if (!elm.hasAttribute("value"))
6270 _Forms.back().Entries.back().SelectValues.back() = _SelectOptionStr;
6273 // insert the parsed text into the select control
6274 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6275 if (cb)
6277 uint lineIndex = cb->getNumTexts();
6278 cb->addText(_SelectOptionStr);
6279 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6281 cb->setGrayed(lineIndex, true);
6284 else
6286 CGroupMenu *sb = _Forms.back().Entries.back().SelectBox;
6287 if (sb)
6289 uint lineIndex = sb->getNumLine();
6290 sb->addLine(_SelectOptionStr, "", "");
6292 if (_Forms.back().Entries.back().sbOptionDisabled == lineIndex)
6294 sb->setGrayedLine(lineIndex, true);
6296 else
6298 // create option line checkbox, CGroupMenu is taking ownership of the checbox
6299 CInterfaceGroup *ig = CWidgetManager::getInstance()->getParser()->createGroupInstance("menu_checkbox", "", NULL, 0);
6300 if (ig)
6302 CCtrlButton *cb = dynamic_cast<CCtrlButton *>(ig->getCtrl("b"));
6303 if (cb)
6305 if (_Forms.back().Entries.back().sbMultiple)
6307 cb->setType(CCtrlButton::ToggleButton);
6308 cb->setTexture(DefaultCheckBoxBitmapNormal);
6309 cb->setTexturePushed(DefaultCheckBoxBitmapPushed);
6310 cb->setTextureOver(DefaultCheckBoxBitmapOver);
6312 else
6314 cb->setType(CCtrlButton::RadioButton);
6315 cb->setTexture(DefaultRadioButtonBitmapNormal);
6316 cb->setTexturePushed(DefaultRadioButtonBitmapPushed);
6317 cb->setTextureOver(DefaultRadioButtonBitmapOver);
6319 if (_Forms.back().Entries.back().sbRBRef == NULL)
6320 _Forms.back().Entries.back().sbRBRef = cb;
6322 cb->initRBRefFromRadioButton(_Forms.back().Entries.back().sbRBRef);
6325 cb->setPushed(_Forms.back().Entries.back().InitialSelection == lineIndex);
6326 sb->setUserGroupLeft(lineIndex, ig);
6328 else
6330 nlwarning("Failed to get 'b' element from 'menu_checkbox' template");
6331 delete ig;
6339 // ***************************************************************************
6340 void CGroupHTML::htmlP(const CHtmlElement &elm)
6342 newParagraph(PBeginSpace);
6343 renderPseudoElement(":before", elm);
6346 void CGroupHTML::htmlPend(const CHtmlElement &elm)
6348 renderPseudoElement(":after", elm);
6351 // ***************************************************************************
6352 void CGroupHTML::htmlPRE(const CHtmlElement &elm)
6354 _PRE.push_back(true);
6355 newParagraph(0);
6357 renderPseudoElement(":before", elm);
6360 void CGroupHTML::htmlPREend(const CHtmlElement &elm)
6362 renderPseudoElement(":after", elm);
6364 popIfNotEmpty(_PRE);
6367 // ***************************************************************************
6368 void CGroupHTML::htmlPROGRESS(const CHtmlElement &elm)
6370 HTMLProgressElement progress;
6371 progress.readValues(elm);
6373 std::string id = "progress";
6374 if (elm.hasAttribute("id"))
6375 id = elm.getAttribute("id");
6377 // width: 10em, height: 1em
6378 uint32 width = _Style.Current.Width > -1 ? _Style.Current.Width : _Style.Current.FontSize * 10;
6379 uint32 height = _Style.Current.Height > -1 ? _Style.Current.Height : _Style.Current.FontSize;
6380 // FIXME: only using border-top
6381 uint32 border = _Style.Current.Border.Top.Width.getValue() > -1 ? _Style.Current.Border.Top.Width.getValue() : 0;
6383 uint barw = (uint) (width * progress.getValueRatio());
6384 CRGBA bgColor = progress.getBarColor(elm, _Style);
6385 CRGBA valueColor = progress.getValueColor(elm, _Style);
6387 typedef pair<string, string> TTmplParam;
6388 vector<TTmplParam> tmplParams;
6389 tmplParams.push_back(TTmplParam("id", id));
6390 tmplParams.push_back(TTmplParam("active", "true"));
6391 tmplParams.push_back(TTmplParam("w", toString(width)));
6392 tmplParams.push_back(TTmplParam("h", toString(height)));
6393 tmplParams.push_back(TTmplParam("border_x2", toString(border*2)));
6394 tmplParams.push_back(TTmplParam("bgtexture", "blank.tga"));
6395 tmplParams.push_back(TTmplParam("bgcolor", bgColor.toString()));
6396 tmplParams.push_back(TTmplParam("value_w", toString(barw)));
6397 tmplParams.push_back(TTmplParam("value_texture", "blank.tga"));
6398 tmplParams.push_back(TTmplParam("value_color", valueColor.toString()));
6400 CInterfaceGroup *gr = CWidgetManager::getInstance()->getParser()->createGroupInstance("html_progress", getParagraph()->getId(), &tmplParams[0], (uint)tmplParams.size());
6401 if (gr)
6403 renderPseudoElement(":before", elm);
6404 getParagraph()->addChild(gr);
6405 renderPseudoElement(":after", elm);
6407 // ignore any inner elements
6408 _IgnoreChildElements = true;
6412 // ***************************************************************************
6413 void CGroupHTML::htmlSCRIPT(const CHtmlElement &elm)
6415 _IgnoreText = true;
6418 void CGroupHTML::htmlSCRIPTend(const CHtmlElement &elm)
6420 _IgnoreText = false;
6423 // ***************************************************************************
6424 void CGroupHTML::htmlSELECT(const CHtmlElement &elm)
6426 if (_Forms.empty())
6427 return;
6429 // A select box
6430 string name = elm.getAttribute("name");
6431 bool multiple = elm.hasAttribute("multiple");
6432 sint32 size = 0;
6434 if (elm.hasNonEmptyAttribute("size"))
6435 fromString(elm.getAttribute("size"), size);
6437 CGroupHTML::CForm::CEntry entry;
6438 entry.Name = name;
6439 entry.sbMultiple = multiple;
6440 if (size > 1 || multiple)
6442 entry.InitialSelection = -1;
6443 CGroupMenu *sb = addSelectBox(DefaultFormSelectBoxMenuGroup, name.c_str());
6444 if (sb)
6446 if (size < 1)
6447 size = 4;
6449 if (_Style.Current.Width > -1)
6450 sb->setMinW(_Style.Current.Width);
6452 if (_Style.Current.Height > -1)
6453 sb->setMinH(_Style.Current.Height);
6455 sb->setMaxVisibleLine(size);
6456 sb->setFontSize(_Style.Current.FontSize, false);
6459 entry.SelectBox = sb;
6461 else
6463 CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str());
6464 entry.ComboBox = cb;
6466 if (cb)
6468 // create view text
6469 cb->updateCoords();
6470 setTextStyle(cb->getViewText(), _Style.Current);
6473 _Forms.back().Entries.push_back (entry);
6476 void CGroupHTML::htmlSELECTend(const CHtmlElement &elm)
6478 _SelectOption = false;
6479 if (_Forms.empty() || _Forms.back().Entries.empty())
6480 return;
6482 CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox;
6483 if (cb)
6485 cb->setSelectionNoTrigger(_Forms.back().Entries.back().InitialSelection);
6486 // TODO: magic padding
6487 cb->setW(cb->evalContentWidth() + 16);
6491 // ***************************************************************************
6492 void CGroupHTML::htmlSTYLE(const CHtmlElement &elm)
6494 _IgnoreText = true;
6497 void CGroupHTML::htmlSTYLEend(const CHtmlElement &elm)
6499 _IgnoreText = false;
6502 // ***************************************************************************
6503 void CGroupHTML::htmlTABLE(const CHtmlElement &elm)
6505 // Get cells parameters
6506 getCellsParameters(elm, false);
6508 CGroupTable *table = new CGroupTable(TCtorParam());
6510 if (elm.hasNonEmptyAttribute("id"))
6511 table->setId(getCurrentGroup()->getId() + ":" + elm.getAttribute("id"));
6512 else
6513 table->setId(getCurrentGroup()->getId() + ":TABLE" + toString(getNextAutoIdSeq()));
6515 // TODO: border-spacing: 2em;
6517 if (elm.hasNonEmptyAttribute("cellspacing"))
6518 fromString(elm.getAttribute("cellspacing"), table->CellSpacing);
6520 // TODO: cssLength, horiz/vert values
6521 if (_Style.hasStyle("border-spacing"))
6522 fromString(_Style.getStyle("border-spacing"), table->CellSpacing);
6524 // overrides border-spacing if set to 'collapse'
6525 if (_Style.checkStyle("border-collapse", "collapse"))
6526 table->CellSpacing = 0;
6529 if (elm.hasNonEmptyAttribute("cellpadding"))
6530 fromString(elm.getAttribute("cellpadding"), table->CellPadding);
6532 if (_Style.hasStyle("width"))
6534 // _Style.Width does not handle '%' unit currently
6535 if (_Style.Current.Width > 0)
6537 table->ForceWidthMin = _Style.Current.Width;
6538 table->TableRatio = 0;
6540 else
6542 getPercentage (table->ForceWidthMin, table->TableRatio, _Style.getStyle("width").c_str());
6545 else if (elm.hasNonEmptyAttribute("width"))
6547 getPercentage (table->ForceWidthMin, table->TableRatio, elm.getAttribute("width").c_str());
6550 // border from css or from attribute
6552 CSSRect<CSSBorder> border;
6553 border.Top.Color = _Style.Current.TextColor;
6554 border.Right.Color = _Style.Current.TextColor;
6555 border.Bottom.Color = _Style.Current.TextColor;
6556 border.Left.Color = _Style.Current.TextColor;
6558 if (elm.hasAttribute("border"))
6560 uint32 borderWidth = 0;
6561 CRGBA borderColor = CRGBA::Transparent;
6563 std::string s = elm.getAttribute("border");
6564 if (s.empty())
6565 borderWidth = 1;
6566 else
6567 fromString(elm.getAttribute("border"), borderWidth);
6569 if (elm.hasNonEmptyAttribute("bordercolor"))
6570 scanHTMLColor(elm.getAttribute("bordercolor").c_str(), borderColor);
6571 else
6572 borderColor = CRGBA(128, 128, 128, 255);
6574 table->CellBorder = (borderWidth > 0);
6576 border.Top.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6577 border.Right.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6578 border.Bottom.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6579 border.Left.set(borderWidth, CSS_LINE_STYLE_OUTSET, borderColor);
6582 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6583 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6584 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6585 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6587 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6588 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6589 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6590 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6592 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6593 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6594 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6595 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6597 table->Border->setBorder(border);
6598 table->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6599 table->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6602 setupBackground(table->Background);
6603 table->setModulateGlobalColor(_Style.Current.GlobalColor);
6605 table->setMarginLeft(getIndent());
6606 addHtmlGroup (table, 0);
6608 renderPseudoElement(":before", elm);
6610 _Tables.push_back(table);
6612 // Add a cell pointer
6613 _Cells.push_back(NULL);
6614 _TR.push_back(false);
6615 _Indent.push_back(0);
6618 void CGroupHTML::htmlTABLEend(const CHtmlElement &elm)
6620 popIfNotEmpty(_CellParams);
6621 popIfNotEmpty(_TR);
6622 popIfNotEmpty(_Cells);
6623 popIfNotEmpty(_Tables);
6624 popIfNotEmpty(_Indent);
6626 renderPseudoElement(":after", elm);
6629 // ***************************************************************************
6630 void CGroupHTML::htmlTD(const CHtmlElement &elm)
6632 // Get cells parameters
6633 getCellsParameters(elm, true);
6635 if (!m_TableRowBackgroundColor.empty() && m_TableRowBackgroundColor.back().A > 0)
6636 _Style.Current.Background.color.blendFromui(m_TableRowBackgroundColor.back(), _Style.Current.Background.color, _Style.Current.Background.color.A);
6638 if (elm.ID == HTML_TH)
6640 if (!_Style.hasStyle("font-weight"))
6641 _Style.Current.FontWeight = FONT_WEIGHT_BOLD;
6642 // center if not specified otherwise.
6643 if (!elm.hasNonEmptyAttribute("align") && !_Style.hasStyle("text-align"))
6644 _CellParams.back().Align = CGroupCell::Center;
6647 CGroupTable *table = getTable();
6648 if (!table)
6650 // <td> appears to be outside <table>
6651 return;
6654 if (_Cells.empty())
6656 // <table> not started
6657 return;
6660 _Cells.back() = new CGroupCell(CViewBase::TCtorParam());
6661 if (elm.hasNonEmptyAttribute("id"))
6662 _Cells.back()->setId(table->getId() + ":" + elm.getAttribute("id"));
6663 else
6664 _Cells.back()->setId(table->getId() + ":TD" + toString(getNextAutoIdSeq()));
6665 // inner cell content
6666 _Cells.back()->Group->setId(_Cells.back()->getId() + ":CELL");
6668 setupBackground(_Cells.back()->Background);
6669 _Cells.back()->setModulateGlobalColor(_Style.Current.GlobalColor);
6671 if (elm.hasNonEmptyAttribute("colspan"))
6672 fromString(elm.getAttribute("colspan"), _Cells.back()->ColSpan);
6673 if (elm.hasNonEmptyAttribute("rowspan"))
6674 fromString(elm.getAttribute("rowspan"), _Cells.back()->RowSpan);
6676 _Cells.back()->Align = _CellParams.back().Align;
6677 _Cells.back()->VAlign = _CellParams.back().VAlign;
6678 _Cells.back()->LeftMargin = _CellParams.back().LeftMargin;
6679 _Cells.back()->NoWrap = _CellParams.back().NoWrap;
6680 _Cells.back()->ColSpan = std::max(1, _Cells.back()->ColSpan);
6681 _Cells.back()->RowSpan = std::max(1, _Cells.back()->RowSpan);
6682 _Cells.back()->Height = _CellParams.back().Height;
6684 float temp;
6685 if (_Style.hasStyle("width"))
6687 // _Style.Width does not handle '%' unit currently
6688 if (_Style.Current.Width > 0)
6690 _Cells.back()->WidthWanted = _Style.Current.Width;
6691 _Cells.back()->TableRatio = 0;
6693 else
6695 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, _Style.getStyle("width").c_str());
6698 else if (elm.hasNonEmptyAttribute("width"))
6700 getPercentage (_Cells.back()->WidthWanted, _Cells.back()->TableRatio, elm.getAttribute("width").c_str());
6703 _Cells.back()->NewLine = getTR();
6705 CSSRect<CSSBorder> border;
6706 border.Top.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6707 border.Right.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6708 border.Bottom.set(table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6709 border.Left.set( table->CellBorder ? 1 : 0, CSS_LINE_STYLE_INSET, _Style.Current.TextColor);
6711 if (_Style.hasStyle("border-top-width")) border.Top.Width = _Style.Current.Border.Top.Width;
6712 if (_Style.hasStyle("border-right-width")) border.Right.Width = _Style.Current.Border.Right.Width;
6713 if (_Style.hasStyle("border-bottom-width")) border.Bottom.Width = _Style.Current.Border.Bottom.Width;
6714 if (_Style.hasStyle("border-left-width")) border.Left.Width = _Style.Current.Border.Left.Width;
6716 if (_Style.hasStyle("border-top-color")) border.Top.Color = _Style.Current.Border.Top.Color;
6717 if (_Style.hasStyle("border-right-color")) border.Right.Color = _Style.Current.Border.Right.Color;
6718 if (_Style.hasStyle("border-bottom-color")) border.Bottom.Color = _Style.Current.Border.Bottom.Color;
6719 if (_Style.hasStyle("border-left-color")) border.Left.Color = _Style.Current.Border.Left.Color;
6721 if (_Style.hasStyle("border-top-style")) border.Top.Style = _Style.Current.Border.Top.Style;
6722 if (_Style.hasStyle("border-right-style")) border.Right.Style = _Style.Current.Border.Right.Style;
6723 if (_Style.hasStyle("border-bottom-style")) border.Bottom.Style = _Style.Current.Border.Bottom.Style;
6724 if (_Style.hasStyle("border-left-style")) border.Left.Style = _Style.Current.Border.Left.Style;
6726 _Cells.back()->Border->setBorder(border);
6727 _Cells.back()->Border->setFontSize(_Style.Root.FontSize, _Style.Current.FontSize);
6728 _Cells.back()->Border->setViewport(getList()->getParentPos() ? getList()->getParentPos() : this);
6730 // padding from <table cellpadding="1">
6731 if (table->CellPadding)
6733 // FIXME: padding is ignored by vertical align
6734 _Cells.back()->PaddingTop = table->CellPadding;
6735 _Cells.back()->PaddingRight = table->CellPadding;
6736 _Cells.back()->PaddingBottom = table->CellPadding;
6737 _Cells.back()->PaddingLeft = table->CellPadding;
6740 if (_Style.hasStyle("padding-top")) _Cells.back()->PaddingTop = _Style.Current.PaddingTop;
6741 if (_Style.hasStyle("padding-right")) _Cells.back()->PaddingRight = _Style.Current.PaddingRight;
6742 if (_Style.hasStyle("padding-bottom")) _Cells.back()->PaddingBottom = _Style.Current.PaddingBottom;
6743 if (_Style.hasStyle("padding-left")) _Cells.back()->PaddingLeft = _Style.Current.PaddingLeft;
6745 table->addChild (_Cells.back());
6747 // reusing indent pushed by table
6748 _Indent.back() = 0;
6750 newParagraph(TDBeginSpace);
6751 // indent is already 0, getParagraph()->setMarginLeft(0); // maybe setIndent(0) if LI is using one
6753 // Reset TR flag
6754 if (!_TR.empty())
6755 _TR.back() = false;
6757 renderPseudoElement(":before", elm);
6760 void CGroupHTML::htmlTDend(const CHtmlElement &elm)
6762 renderPseudoElement(":after", elm);
6764 popIfNotEmpty(_CellParams);
6765 if (!_Cells.empty())
6766 _Cells.back() = NULL;
6769 // ***************************************************************************
6770 void CGroupHTML::htmlTEXTAREA(const CHtmlElement &elm)
6772 _IgnoreChildElements = true;
6774 // TODO: allow textarea without form
6775 if (_Forms.empty())
6776 return;
6778 // read general property
6779 string templateName;
6781 // Widget template name
6782 if (elm.hasNonEmptyAttribute("z_input_tmpl"))
6783 templateName = elm.getAttribute("z_input_tmpl");
6785 // Get the string name
6786 _TextAreaName.clear();
6787 _TextAreaRow = 1;
6788 _TextAreaCols = 10;
6789 _TextAreaMaxLength = 1024;
6790 if (elm.hasNonEmptyAttribute("name"))
6791 _TextAreaName = elm.getAttribute("name");
6792 if (elm.hasNonEmptyAttribute("rows"))
6793 fromString(elm.getAttribute("rows"), _TextAreaRow);
6794 if (elm.hasNonEmptyAttribute("cols"))
6795 fromString(elm.getAttribute("cols"), _TextAreaCols);
6796 if (elm.hasNonEmptyAttribute("maxlength"))
6797 fromString(elm.getAttribute("maxlength"), _TextAreaMaxLength);
6799 _TextAreaTemplate = !templateName.empty() ? templateName : DefaultFormTextAreaGroup;
6801 std::string content = strFindReplaceAll(elm.serializeChilds(false), std::string("\r"), std::string(""));
6803 CInterfaceGroup *textArea = addTextArea (_TextAreaTemplate, _TextAreaName.c_str (), _TextAreaRow, _TextAreaCols, true, content, _TextAreaMaxLength);
6804 if (textArea)
6806 // Add the text area to the form
6807 CGroupHTML::CForm::CEntry entry;
6808 entry.Name = _TextAreaName;
6809 entry.TextArea = textArea;
6810 _Forms.back().Entries.push_back (entry);
6814 // ***************************************************************************
6815 void CGroupHTML::htmlTH(const CHtmlElement &elm)
6817 htmlTD(elm);
6820 void CGroupHTML::htmlTHend(const CHtmlElement &elm)
6822 htmlTDend(elm);
6825 // ***************************************************************************
6826 void CGroupHTML::htmlTITLE(const CHtmlElement &elm)
6828 _IgnoreChildElements = true;
6830 // TODO: only from <head>
6831 // if (!_ReadingHeadTag) return;
6833 // consume all child elements
6834 _TitleString = strFindReplaceAll(elm.serializeChilds(false), std::string("\t"), std::string(" "));
6835 _TitleString = strFindReplaceAll(_TitleString, std::string("\n"), std::string(" "));
6836 setTitle(_TitleString);
6839 // ***************************************************************************
6840 void CGroupHTML::htmlTR(const CHtmlElement &elm)
6842 // prevent inheriting from table
6843 if (!_CellParams.empty())
6845 _CellParams.back().BgColor = CRGBA::Transparent;
6846 _CellParams.back().Height = 0;
6849 // Get cells parameters
6850 getCellsParameters(elm, true);
6852 m_TableRowBackgroundColor.push_back(_CellParams.back().BgColor);
6853 _CellParams.back().BgColor = CRGBA::Transparent;
6855 // TODO: this probably ends up in first cell
6856 renderPseudoElement(":before", elm);
6858 // Set TR flag
6859 if (!_TR.empty())
6860 _TR.back() = true;
6863 void CGroupHTML::htmlTRend(const CHtmlElement &elm)
6865 // TODO: this probably ends up in last cell
6866 renderPseudoElement(":after", elm);
6868 popIfNotEmpty(_CellParams);
6869 popIfNotEmpty(m_TableRowBackgroundColor);
6872 // ***************************************************************************
6873 void CGroupHTML::htmlUL(const CHtmlElement &elm)
6875 if (_UL.empty())
6876 _UL.push_back(HTMLOListElement(1, "disc"));
6877 else if (_UL.size() == 1)
6878 _UL.push_back(HTMLOListElement(1, "circle"));
6879 else
6880 _UL.push_back(HTMLOListElement(1, "square"));
6882 // if LI is already present
6883 _LI = _UL.size() > 1 || _DL.size() > 1;
6884 _Indent.push_back(getIndent() + ULIndent);
6886 renderPseudoElement(":before", elm);
6889 void CGroupHTML::htmlULend(const CHtmlElement &elm)
6891 if (_UL.empty())
6892 return;
6894 renderPseudoElement(":after", elm);
6896 popIfNotEmpty(_UL);
6897 popIfNotEmpty(_Indent);