diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
index 489484ee8..9f672e961 100644
--- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
+++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.lua
@@ -537,3 +537,17 @@ function game:openWebIGBrowserHeader()
ui.w = ui_webig_browser_w;
end
end
+
+------------------------------------------------------------------------------------------------------------
+local SavedUrl = "";
+function game:chatUrl(url)
+ SavedUrl = url
+ runAH(nil, "active_menu", "menu=ui:interface:chat_uri_action_menu");
+end
+function game:chatUrlCopy()
+ runAH(nil, "copy_to_clipboard", SavedUrl)
+end
+function game:chatUrlBrowse()
+ runAH(nil, "browse", "name=ui:interface:webig:content:html|url=" .. SavedUrl)
+end
+
diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
index d6d4a4cba..cbf4b15ce 100644
--- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
+++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml
@@ -3127,4 +3127,19 @@
+
+
+
+
diff --git a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp
index 750630a17..5195d0b51 100644
--- a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp
+++ b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp
@@ -20,6 +20,7 @@
// client
#include "chat_text_manager.h"
#include "nel/gui/view_text.h"
+#include "nel/gui/group_paragraph.h"
#include "interface_manager.h"
using namespace std;
@@ -149,34 +150,165 @@ static CInterfaceGroup *buildLineWithCommand(CInterfaceGroup *commandGroup, CVie
return group;
}
-//=================================================================================
-CViewBase *CChatTextManager::createMsgText(const ucstring &cstMsg, NLMISC::CRGBA col, bool justified /*=false*/)
+static inline bool isUrlTag(const ucstring &s, ucstring::size_type index, ucstring::size_type textSize)
{
- ucstring msg = cstMsg;
- CInterfaceGroup *commandGroup = parseCommandTag(msg);
- CViewText *vt = new CViewText(CViewText::TCtorParam());
- // get parameters from config.xml
- vt->setShadow(isTextShadowed());
- vt->setShadowOutline(false);
- vt->setFontSize(getTextFontSize());
- vt->setMultiLine(true);
- vt->setTextMode(justified ? CViewText::Justified : CViewText::DontClipWord);
- vt->setMultiLineSpace(getTextMultiLineSpace());
- vt->setModulateGlobalColor(false);
-
- ucstring cur_time;
- if (showTimestamps())
+ // Format http://, https://
+ // or markdown style (title)[http://..]
+ if(textSize > index+7)
{
- CCDBNodeLeaf *node = NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:SHOW_CLOCK_12H", false);
- if (node && node->getValueBool())
- cur_time = CInterfaceManager::getTimestampHuman("[%I:%M:%S %p] ");
- else
- cur_time = CInterfaceManager::getTimestampHuman();
+ bool markdown = false;
+ ucstring::size_type i = index;
+ // advance index to url section if markdown style link is detected
+ if (s[i] == '(')
+ {
+ // scan for ')[http://'
+ while(i < textSize-9)
+ {
+ if (s[i] == ')' && s[i+1] == '[')
+ {
+ i += 2;
+ markdown = true;
+ break;
+ }
+ else
+ if (s[i] == ')')
+ {
+ i += 1;
+ break;
+ }
+ i++;
+ }
+ }
+
+ if (textSize > i + 7)
+ {
+ bool isUrl = (toLower(s.substr(i, 7)) == ucstring("http://") || toLower(s.substr(i, 8)) == ucstring("https://"));
+ // match "text http://" and not "texthttp://"
+ if (isUrl && i > 0 && !markdown)
+ {
+ // '}' is in the list because of color tags, ie "@{FFFF}http://..."
+ const ucchar chars[] = {' ', '"', '\'', '(', '[', '}'};
+ isUrl = std::find(std::begin(chars), std::end(chars), s[i - 1]) != std::end(chars);
+ }
+ return isUrl;
+ }
}
- // if text contain any color code, set the text formated and white,
- // otherwise, set text normal and apply global color
- size_t codePos = msg.find(ucstring("@{"));
+ return false;
+}
+
+// ***************************************************************************
+// isUrlTag must match
+static inline void getUrlTag(const ucstring &s, ucstring::size_type &index, ucstring &url, ucstring &title)
+{
+ bool isMarkdown = false;
+ ucstring::size_type textSize = s.size();
+ ucstring::size_type pos;
+
+ // see if we have markdown format
+ if (s[index] == '(')
+ {
+ index++;
+ pos = index;
+ while(pos < textSize-9)
+ {
+ if (s[pos] == ')' && s[pos + 1] == '[')
+ {
+ isMarkdown = true;
+ title = s.substr(index, pos - index);
+ index = pos + 2;
+ break;
+ }
+ else if (s[pos] == ')')
+ {
+ break;
+ }
+
+ pos++;
+ }
+ }
+
+ ucchar chOpen = ' ';
+ ucchar chClose = ' ';
+ if (isMarkdown)
+ {
+ chOpen = '[';
+ chClose = ']';
+ }
+ else if (index > 0)
+ {
+ chOpen = s[index - 1];
+ if (chOpen == '\'') chClose = '\'';
+ else if (chOpen == '"') chClose = '"';
+ else if (chOpen == '(') chClose = ')';
+ else if (chOpen == '[') chClose = ']';
+ else chClose = ' ';
+ }
+
+ if (chOpen == chClose)
+ {
+ pos = s.find_first_of(chClose, index);
+
+ // handle common special case: 'text http://.../, text'
+ if (pos != ucstring::npos && index > 0)
+ {
+ if (s[index-1] == ' ' && (s[pos-1] == ',' || s[pos-1] == '.'))
+ {
+ pos--;
+ }
+ }
+ }
+ else
+ {
+ // scan for nested open/close tags
+ pos = index;
+ sint nested = 0;
+ while(pos < textSize)
+ {
+ if (s[pos] == chOpen)
+ {
+ nested++;
+ }
+ else if (s[pos] == chClose)
+ {
+ if (nested == 0)
+ {
+ break;
+ }
+ else
+ {
+ nested--;
+ }
+ }
+
+ pos++;
+ }
+ }
+
+ // fallback to full string length as we did match http:// already and url spans to the end probably
+ if (pos == ucstring::npos)
+ {
+ pos = textSize;
+ }
+
+ url = s.substr(index, pos - index);
+ index = pos;
+
+ // skip ']' closing char
+ if (isMarkdown) index++;
+}
+
+//=================================================================================
+static void prependTimestamp(ucstring &msg)
+{
+ ucstring cur_time;
+ CCDBNodeLeaf *node = NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:SHOW_CLOCK_12H", false);
+ if (node && node->getValueBool())
+ cur_time = CInterfaceManager::getTimestampHuman("[%I:%M:%S %p] ");
+ else
+ cur_time = CInterfaceManager::getTimestampHuman();
+
+ ucstring::size_type codePos = msg.find(ucstring("@{"));
if (codePos != ucstring::npos)
{
// Prepend the current time (do it after the color if the color at first position.
@@ -189,13 +321,49 @@ CViewBase *CChatTextManager::createMsgText(const ucstring &cstMsg, NLMISC::CRGBA
{
msg = cur_time + msg;
}
-
+ }
+ else
+ {
+ msg = cur_time + msg;
+ }
+}
+
+//=================================================================================
+CViewBase *CChatTextManager::createMsgText(const ucstring &cstMsg, NLMISC::CRGBA col, bool justified /*=false*/, bool plaintext /*=false*/)
+{
+ ucstring msg = cstMsg;
+ CInterfaceGroup *commandGroup = parseCommandTag(msg);
+
+ if (showTimestamps())
+ prependTimestamp(msg);
+
+ // must wrap all lines to CGroupParagraph because CGroupList will calculate
+ // width from previous line which ends up as CViewText otherwise
+ return createMsgTextComplex(msg, col, justified, plaintext, commandGroup);
+}
+
+//=================================================================================
+CViewBase *CChatTextManager::createMsgTextSimple(const ucstring &msg, NLMISC::CRGBA col, bool justified, CInterfaceGroup *commandGroup)
+{
+ CViewText *vt = new CViewText(CViewText::TCtorParam());
+ // get parameters from config.xml
+ vt->setShadow(isTextShadowed());
+ vt->setShadowOutline(false);
+ vt->setFontSize(getTextFontSize());
+ vt->setMultiLine(true);
+ vt->setTextMode(justified ? CViewText::Justified : CViewText::DontClipWord);
+ vt->setMultiLineSpace(getTextMultiLineSpace());
+ vt->setModulateGlobalColor(false);
+
+ // if text contain any color code, set the text formated and white,
+ // otherwise, set text normal and apply global color
+ if (msg.find(ucstring("@{")) != ucstring::npos)
+ {
vt->setTextFormatTaged(msg);
vt->setColor(NLMISC::CRGBA::White);
}
else
{
- msg = cur_time + msg;
vt->setText(msg);
vt->setColor(col);
}
@@ -210,6 +378,112 @@ CViewBase *CChatTextManager::createMsgText(const ucstring &cstMsg, NLMISC::CRGBA
}
}
+//=================================================================================
+CViewBase *CChatTextManager::createMsgTextComplex(const ucstring &msg, NLMISC::CRGBA col, bool justified, bool plaintext, CInterfaceGroup *commandGroup)
+{
+ ucstring::size_type textSize = msg.size();
+
+ CGroupParagraph *para = new CGroupParagraph(CViewBase::TCtorParam());
+ para->setId("line");
+ para->setSizeRef("w");
+ para->setResizeFromChildH(true);
+
+ if (plaintext)
+ {
+ CViewBase *vt = createMsgTextSimple(msg, col, justified, NULL);
+ vt->setId("text");
+ para->addChild(vt);
+
+ return para;
+ }
+
+
+ // quickly check if text has links or not
+ bool hasUrl;
+ {
+ ucstring s = toLower(msg);
+ hasUrl = (s.find(ucstring("http://")) || s.find(ucstring("https://")));
+ }
+
+ ucstring::size_type pos = 0;
+ for (ucstring::size_type i = 0; i< textSize;)
+ {
+ if (hasUrl && isUrlTag(msg, i, textSize))
+ {
+ if (pos != i)
+ {
+ CViewBase *vt = createMsgTextSimple(msg.substr(pos, i - pos), col, justified, NULL);
+ para->addChild(vt);
+ }
+
+ ucstring url;
+ ucstring title;
+ getUrlTag(msg, i, url, title);
+ if (url.size() > 0)
+ {
+ CViewLink *vt = new CViewLink(CViewBase::TCtorParam());
+ vt->setId("link");
+ vt->setUnderlined(true);
+ vt->setShadow(isTextShadowed());
+ vt->setShadowOutline(false);
+ vt->setFontSize(getTextFontSize());
+ vt->setMultiLine(true);
+ vt->setTextMode(justified ? CViewText::Justified : CViewText::DontClipWord);
+ vt->setMultiLineSpace(getTextMultiLineSpace());
+ vt->setModulateGlobalColor(false);
+
+ //NLMISC::CRGBA color;
+ //color.blendFromui(col, CRGBA(255, 153, 0, 255), 100);
+ //vt->setColor(color);
+ vt->setColor(col);
+
+ if (title.size() > 0)
+ {
+ vt->LinkTitle = title.toUtf8();
+ vt->setText(title);
+ }
+ else
+ {
+ vt->LinkTitle = url.toUtf8();
+ vt->setText(url);
+ }
+
+ if (url.find_first_of('\'') != string::npos)
+ {
+ ucstring clean;
+ for(string::size_type i = 0; i< url.size(); ++i)
+ {
+ if (url[i] == '\'')
+ clean += ucstring("%27");
+ else
+ clean += url[i];
+ }
+ url = clean;
+ }
+ vt->setActionOnLeftClick("lua");
+ vt->setParamsOnLeftClick("game:chatUrl('" + url.toUtf8() + "')");
+
+ para->addChildLink(vt);
+
+ pos = i;
+ }
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ if (pos < textSize)
+ {
+ CViewBase *vt = createMsgTextSimple(msg.substr(pos, textSize - pos), col, justified, NULL);
+ vt->setId("text");
+ para->addChild(vt);
+ }
+
+ return para;
+}
+
//=================================================================================
CChatTextManager &CChatTextManager::getInstance()
{
diff --git a/code/ryzom/client/src/interface_v3/chat_text_manager.h b/code/ryzom/client/src/interface_v3/chat_text_manager.h
index 0dfe05673..41326f067 100644
--- a/code/ryzom/client/src/interface_v3/chat_text_manager.h
+++ b/code/ryzom/client/src/interface_v3/chat_text_manager.h
@@ -24,6 +24,7 @@
namespace NLGUI
{
class CViewBase;
+ class CInterfaceGroup;
}
class ucstring;
@@ -49,8 +50,9 @@ public:
* \param msg the actual text
* \param col the color of the text
* \param justified Should be true for justified text (stretch spaces of line to fill the full width)
+ * \param plaintext Text will not be parsed for uri markup links
*/
- NLGUI::CViewBase *createMsgText(const ucstring &msg, NLMISC::CRGBA col, bool justified = false);
+ NLGUI::CViewBase *createMsgText(const ucstring &msg, NLMISC::CRGBA col, bool justified = false, bool plaintext = false);
// Singleton access
static CChatTextManager &getInstance();
@@ -73,6 +75,9 @@ private:
~CChatTextManager();
bool showTimestamps() const;
+
+ NLGUI::CViewBase *createMsgTextSimple(const ucstring &msg, NLMISC::CRGBA col, bool justified, NLGUI::CInterfaceGroup *commandGroup);
+ NLGUI::CViewBase *createMsgTextComplex(const ucstring &msg, NLMISC::CRGBA col, bool justified, bool plaintext, NLGUI::CInterfaceGroup *commandGroup);
};
// shortcut to get text manager instance