Added: Clickable urls in chat text (issue 282)

This commit is contained in:
Nimetu 2017-01-21 13:29:34 +02:00
parent b9ab826feb
commit 2e5bcdf248
4 changed files with 335 additions and 27 deletions

View file

@ -537,3 +537,17 @@ function game:openWebIGBrowserHeader()
ui.w = ui_webig_browser_w; ui.w = ui_webig_browser_w;
end end
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

View file

@ -3127,4 +3127,19 @@
<action handler="set" <action handler="set"
params="dblink=UI:SAVE:ISDETACHED:DYNAMIC_CHAT@0|value=0" /> params="dblink=UI:SAVE:ISDETACHED:DYNAMIC_CHAT@0|value=0" />
</proc> </proc>
<!-- there seems to be no way to pass url to menu, use lua script for middleman -->
<group type="menu"
id="chat_uri_action_menu"
extends="base_menu">
<action id="copy"
name="uiCopy"
handler="lua"
params="game:chatUrlCopy()" />
<action id="openig"
name="uiBrowse"
handler="lua"
params="game:chatUrlBrowse()" />
</group>
</interface_config> </interface_config>

View file

@ -20,6 +20,7 @@
// client // client
#include "chat_text_manager.h" #include "chat_text_manager.h"
#include "nel/gui/view_text.h" #include "nel/gui/view_text.h"
#include "nel/gui/group_paragraph.h"
#include "interface_manager.h" #include "interface_manager.h"
using namespace std; using namespace std;
@ -149,34 +150,165 @@ static CInterfaceGroup *buildLineWithCommand(CInterfaceGroup *commandGroup, CVie
return group; return group;
} }
//================================================================================= static inline bool isUrlTag(const ucstring &s, ucstring::size_type index, ucstring::size_type textSize)
CViewBase *CChatTextManager::createMsgText(const ucstring &cstMsg, NLMISC::CRGBA col, bool justified /*=false*/)
{ {
ucstring msg = cstMsg; // Format http://, https://
CInterfaceGroup *commandGroup = parseCommandTag(msg); // or markdown style (title)[http://..]
CViewText *vt = new CViewText(CViewText::TCtorParam()); if(textSize > index+7)
// 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())
{ {
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;
}
}
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); CCDBNodeLeaf *node = NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:SHOW_CLOCK_12H", false);
if (node && node->getValueBool()) if (node && node->getValueBool())
cur_time = CInterfaceManager::getTimestampHuman("[%I:%M:%S %p] "); cur_time = CInterfaceManager::getTimestampHuman("[%I:%M:%S %p] ");
else else
cur_time = CInterfaceManager::getTimestampHuman(); cur_time = CInterfaceManager::getTimestampHuman();
}
// if text contain any color code, set the text formated and white, ucstring::size_type codePos = msg.find(ucstring("@{"));
// otherwise, set text normal and apply global color
size_t codePos = msg.find(ucstring("@{"));
if (codePos != ucstring::npos) if (codePos != ucstring::npos)
{ {
// Prepend the current time (do it after the color if the color at first position. // 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; 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->setTextFormatTaged(msg);
vt->setColor(NLMISC::CRGBA::White); vt->setColor(NLMISC::CRGBA::White);
} }
else else
{ {
msg = cur_time + msg;
vt->setText(msg); vt->setText(msg);
vt->setColor(col); 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() CChatTextManager &CChatTextManager::getInstance()
{ {

View file

@ -24,6 +24,7 @@
namespace NLGUI namespace NLGUI
{ {
class CViewBase; class CViewBase;
class CInterfaceGroup;
} }
class ucstring; class ucstring;
@ -49,8 +50,9 @@ public:
* \param msg the actual text * \param msg the actual text
* \param col the color of the 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 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 // Singleton access
static CChatTextManager &getInstance(); static CChatTextManager &getInstance();
@ -73,6 +75,9 @@ private:
~CChatTextManager(); ~CChatTextManager();
bool showTimestamps() const; 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 // shortcut to get text manager instance