1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
7 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
9 // This program is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU Affero General Public License as
11 // published by the Free Software Foundation, either version 3 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU Affero General Public License for more details.
19 // You should have received a copy of the GNU Affero General Public License
20 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #include "chat_text_manager.h"
27 #include "nel/gui/view_text.h"
28 #include "nel/gui/group_paragraph.h"
29 #include "interface_manager.h"
32 using namespace NLMISC
;
34 CChatTextManager
* CChatTextManager::_Instance
= NULL
;
36 // last selected chat from 'copy_chat_popup' action handler
37 static std::string LastSelectedChat
;
39 //=================================================================================
40 CChatTextManager::CChatTextManager() :
42 _TextMultilineSpace(NULL
),
48 //=================================================================================
49 CChatTextManager::~CChatTextManager()
53 delete _TextMultilineSpace
;
54 _TextMultilineSpace
= NULL
;
57 delete _ShowTimestamps
;
58 _ShowTimestamps
= NULL
;
60 //=================================================================================
61 uint
CChatTextManager::getTextFontSize() const
65 CInterfaceManager
*im
= CInterfaceManager::getInstance();
66 _TextFontSize
= NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:CHAT:FONT_SIZE", false);
67 if (!_TextFontSize
) return 12;
69 return (uint
) _TextFontSize
->getValue32();
72 //=================================================================================
73 uint
CChatTextManager::getTextMultiLineSpace() const
75 if (!_TextMultilineSpace
)
77 CInterfaceManager
*im
= CInterfaceManager::getInstance();
78 _TextMultilineSpace
= NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:CHAT:MULTI_LINE_SPACE", false);
79 if (!_TextMultilineSpace
) return 1;
81 return (uint
) _TextMultilineSpace
->getValue32();
84 //=================================================================================
85 bool CChatTextManager::isTextShadowed() const
89 CInterfaceManager
*im
= CInterfaceManager::getInstance();
90 _TextShadowed
= NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:CHAT:SHADOWED_TEXT", false);
91 if (!_TextShadowed
) return false;
93 return _TextShadowed
->getValueBool();
96 //=================================================================================
97 bool CChatTextManager::showTimestamps() const
101 CInterfaceManager
*im
= CInterfaceManager::getInstance();
102 _ShowTimestamps
= CDBManager::getInstance()->getDbProp("UI:SAVE:CHAT:SHOW_TIMES_IN_CHAT_CB", false);
103 if (!_ShowTimestamps
) return false;
105 return _ShowTimestamps
->getValueBool();
108 //=================================================================================
109 static CInterfaceGroup
*parseCommandTag(string
&line
)
111 string::size_type start
= line
.find("/$$");
112 if (start
== string::npos
) return NULL
;
113 string::size_type end
= line
.find("$$/", start
+ 3);
114 if (end
== string::npos
) return NULL
;
115 std::string commandLine
= line
.substr(start
+ 3, end
- start
- 3);
116 line
= line
.substr(0, start
) + line
.substr(end
+3);
117 vector
<string
> params
;
118 explode(commandLine
, std::string("|"), params
);
119 if (params
.size() != 4)
121 nlwarning("4 parameters wanted for command tag : template|caption|ah|ah_params");
125 static int commandId
= 0;
126 pair
<string
, string
> uiTemplateParams
[4] =
128 make_pair(string("id"), NLMISC::toString("command%d", commandId
++)),
129 make_pair(string("caption"), params
[1]),
130 make_pair(string("ah"), params
[2]),
131 make_pair(string("ah_params"), params
[3])
133 return CWidgetManager::getInstance()->getParser()->createGroupInstance(params
[0], "", uiTemplateParams
, 4);
136 static CInterfaceGroup
*buildLineWithCommand(CInterfaceGroup
*commandGroup
, CViewText
*text
)
138 nlassert(commandGroup
);
140 CInterfaceGroup
*group
= new CInterfaceGroup(CViewBase::TCtorParam());
141 static int groupId
= 0;
142 group
->setId(NLMISC::toString("%d", groupId
++));
143 static volatile bool sizeref
= 1;
144 static volatile sint32 w
= 0;
145 group
->setSizeRef(sizeref
);
147 group
->setResizeFromChildH(true);
149 group
->addGroup(commandGroup
);
150 commandGroup
->setParent(group
);
151 text
->setParent(group
);
152 text
->setParentPos(commandGroup
);
153 text
->setPosRef(Hotspot_TL
);
154 text
->setParentPosRef(Hotspot_TR
);
155 group
->addView(text
);
159 static inline bool isUrlTag(const string
&s
, string::size_type index
, string::size_type textSize
)
161 // Format http://, https://
162 // or markdown style (title)[http://..]
163 if(textSize
> index
+7)
165 bool markdown
= false;
166 string::size_type i
= index
;
167 // advance index to url section if markdown style link is detected
170 // scan for ')[http://'
171 while(i
< textSize
-9)
173 if (s
[i
] == ')' && s
[i
+1] == '[')
189 if (textSize
> i
+ 7)
191 bool isUrl
= (toLowerAscii(s
.substr(i
, 7)) == "http://" || toLowerAscii(s
.substr(i
, 8)) == "https://");
192 // match "text http://" and not "texthttp://"
193 if (isUrl
&& i
> 0 && !markdown
)
195 // '}' is in the list because of color tags, ie "@{FFFF}http://..."
196 #ifdef NL_ISO_CPP0X_AVAILABLE
197 const vector
<ucchar
> chars
{ ' ', '"', '\'', '(', '[', '}' };
199 static std::vector
<ucchar
> chars
;
203 chars
.push_back(' ');
204 chars
.push_back('"');
205 chars
.push_back('\'');
206 chars
.push_back('(');
207 chars
.push_back('[');
208 chars
.push_back('}');
211 isUrl
= std::find(chars
.begin(), chars
.end(), s
[i
- 1]) != chars
.end();
220 // ***************************************************************************
221 // isUrlTag must match
222 static inline void getUrlTag(const string
&s
, string::size_type
&index
, string
&url
, string
&title
)
224 bool isMarkdown
= false;
225 string::size_type textSize
= s
.size();
226 string::size_type pos
;
228 // see if we have markdown format
233 while(pos
< textSize
-9)
235 if (s
[pos
] == ')' && s
[pos
+ 1] == '[')
238 title
= s
.substr(index
, pos
- index
);
242 else if (s
[pos
] == ')')
260 chOpen
= s
[index
- 1];
261 if (chOpen
== '\'') chClose
= '\'';
262 else if (chOpen
== '"') chClose
= '"';
263 else if (chOpen
== '(') chClose
= ')';
264 else if (chOpen
== '[') chClose
= ']';
268 if (chOpen
== chClose
)
270 pos
= s
.find_first_of(chClose
, index
);
272 // handle common special case: 'text http://.../, text'
273 if (pos
!= string::npos
&& index
> 0)
275 if (s
[index
-1] == ' ' && (s
[pos
-1] == ',' || s
[pos
-1] == '.'))
283 // scan for nested open/close tags
286 while(pos
< textSize
)
288 if (s
[pos
] == chOpen
)
292 else if (s
[pos
] == chClose
)
308 // fallback to full string length as we did match http:// already and url spans to the end probably
309 if (pos
== string::npos
)
314 url
= s
.substr(index
, pos
- index
);
317 // skip ']' closing char
318 if (isMarkdown
) index
++;
321 //=================================================================================
322 static void prependTimestamp(string
&msg
)
325 CCDBNodeLeaf
*node
= NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:SHOW_CLOCK_12H", false);
326 if (node
&& node
->getValueBool())
327 cur_time
= CInterfaceManager::getTimestampHuman("[%I:%M:%S %p] ");
329 cur_time
= CInterfaceManager::getTimestampHuman();
331 string::size_type codePos
= msg
.find("@{");
332 if (codePos
!= string::npos
)
334 // Prepend the current time (do it after the color if the color at first position.
337 codePos
= msg
.find(string("}"));
338 msg
= msg
.substr(0, codePos
+ 1) + cur_time
+ msg
.substr(codePos
+ 1, msg
.length() - codePos
);
342 msg
= cur_time
+ msg
;
347 msg
= cur_time
+ msg
;
351 //=================================================================================
352 CViewBase
*CChatTextManager::createMsgText(const string
&cstMsg
, NLMISC::CRGBA col
, bool justified
/*=false*/, bool plaintext
/*=false*/)
355 CInterfaceGroup
*commandGroup
= parseCommandTag(msg
);
357 if (showTimestamps())
358 prependTimestamp(msg
);
360 // must wrap all lines to CGroupParagraph because CGroupList will calculate
361 // width from previous line which ends up as CViewText otherwise
362 return createMsgTextComplex(msg
, col
, justified
, plaintext
, commandGroup
);
365 //=================================================================================
366 CViewBase
*CChatTextManager::createMsgTextSimple(const string
&msg
, NLMISC::CRGBA col
, bool justified
, CInterfaceGroup
*commandGroup
)
368 CViewText
*vt
= new CViewText(CViewText::TCtorParam());
369 // get parameters from config.xml
370 vt
->setShadow(isTextShadowed());
371 vt
->setShadowOutline(false);
372 vt
->setFontSize(getTextFontSize());
373 vt
->setMultiLine(true);
374 vt
->setTextMode(justified
? CViewText::Justified
: CViewText::DontClipWord
);
375 vt
->setMultiLineSpace(getTextMultiLineSpace());
376 vt
->setModulateGlobalColor(false);
378 // if text contain any color code, set the text formated and white,
379 // otherwise, set text normal and apply global color
380 if (msg
.find("@{") != string::npos
)
382 vt
->setTextFormatTaged(msg
);
383 vt
->setColor(NLMISC::CRGBA::White
);
397 return buildLineWithCommand(commandGroup
, vt
);
401 //=================================================================================
402 CViewBase
*CChatTextManager::createMsgTextComplex(const string
&msg
, NLMISC::CRGBA col
, bool justified
, bool plaintext
, CInterfaceGroup
*commandGroup
)
404 string::size_type textSize
= msg
.size();
406 CGroupParagraph
*para
= new CGroupParagraph(CViewBase::TCtorParam());
408 para
->setSizeRef("w");
409 para
->setResizeFromChildH(true);
411 // use right click because left click might be used to activate chat window
412 para
->setRightClickHandler("copy_chat_popup");
413 para
->setRightClickHandlerParams(msg
);
417 CViewBase
*vt
= createMsgTextSimple(msg
, col
, justified
, NULL
);
425 // quickly check if text has links or not
428 string s
= toLowerAscii(msg
);
429 hasUrl
= (s
.find("http://") || s
.find("https://"));
432 string::size_type pos
= 0;
433 for (string::size_type i
= 0; i
< textSize
;)
435 if (hasUrl
&& isUrlTag(msg
, i
, textSize
))
439 CViewBase
*vt
= createMsgTextSimple(msg
.substr(pos
, i
- pos
), col
, justified
, NULL
);
445 getUrlTag(msg
, i
, url
, title
);
448 CViewLink
*vt
= new CViewLink(CViewBase::TCtorParam());
450 vt
->setUnderlined(true);
451 vt
->setShadow(isTextShadowed());
452 vt
->setShadowOutline(false);
453 vt
->setFontSize(getTextFontSize());
454 vt
->setMultiLine(true);
455 vt
->setTextMode(justified
? CViewText::Justified
: CViewText::DontClipWord
);
456 vt
->setMultiLineSpace(getTextMultiLineSpace());
457 vt
->setModulateGlobalColor(false);
459 //NLMISC::CRGBA color;
460 //color.blendFromui(col, CRGBA(255, 153, 0, 255), 100);
461 //vt->setColor(color);
464 if (title
.size() > 0)
466 vt
->LinkTitle
= title
;
467 vt
->setText(vt
->LinkTitle
);
472 vt
->setText(vt
->LinkTitle
);
475 if (url
.find_first_of('\'') != string::npos
)
478 for(string::size_type i
= 0; i
< url
.size(); ++i
)
487 vt
->setActionOnLeftClick("lua");
488 vt
->setParamsOnLeftClick("game:chatUrl('" + url
+ "')");
490 para
->addChildLink(vt
);
503 CViewBase
*vt
= createMsgTextSimple(msg
.substr(pos
, textSize
- pos
), col
, justified
, NULL
);
511 //=================================================================================
512 CChatTextManager
&CChatTextManager::getInstance()
515 _Instance
= new CChatTextManager();
519 //=================================================================================
520 void CChatTextManager::releaseInstance()
527 //=================================================================================
528 void CChatTextManager::reset ()
530 _TextFontSize
= NULL
;
531 _TextMultilineSpace
= NULL
;
532 _TextShadowed
= NULL
;
533 _ShowTimestamps
= NULL
;
536 // ***************************************************************************
537 // Called when we right click on a chat line
538 class CHandlerCopyChatPopup
: public IActionHandler
541 virtual void execute(CCtrlBase
*pCaller
, const string
¶ms
)
543 if (pCaller
== NULL
) return;
545 LastSelectedChat
= params
;
547 CGroupParagraph
*pGP
= dynamic_cast<CGroupParagraph
*>(pCaller
);
548 if (pGP
) pGP
->enableTempOver();
550 CWidgetManager::getInstance()->enableModalWindow (pCaller
, "ui:interface:chat_copy_action_menu");
553 REGISTER_ACTION_HANDLER( CHandlerCopyChatPopup
, "copy_chat_popup");
555 // ***************************************************************************
556 // Called when we right click on a chat line and choose 'copy' from context menu
557 class CHandlerCopyChat
: public IActionHandler
560 virtual void execute(CCtrlBase
*pCaller
, const string
¶ms
)
562 if (pCaller
== NULL
) return;
564 CGroupParagraph
*pGP
= dynamic_cast<CGroupParagraph
*>(pCaller
);
565 if (pGP
) pGP
->disableTempOver();
567 CAHManager::getInstance()->runActionHandler("copy_to_clipboard", NULL
, LastSelectedChat
);
568 CWidgetManager::getInstance()->disableModalWindow();
571 REGISTER_ACTION_HANDLER( CHandlerCopyChat
, "copy_chat");