Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / client / src / interface_v3 / chat_text_manager.cpp
blob4895463b0eedb7314e070d9c1b913241ca059b7a
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
3 //
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>
8 //
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/>.
24 #include "stdpch.h"
25 // client
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"
31 using namespace std;
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() :
41 _TextFontSize(NULL),
42 _TextMultilineSpace(NULL),
43 _TextShadowed(NULL),
44 _ShowTimestamps(NULL)
48 //=================================================================================
49 CChatTextManager::~CChatTextManager()
51 delete _TextFontSize;
52 _TextFontSize = NULL;
53 delete _TextMultilineSpace;
54 _TextMultilineSpace = NULL;
55 delete _TextShadowed;
56 _TextShadowed = NULL;
57 delete _ShowTimestamps;
58 _ShowTimestamps = NULL;
60 //=================================================================================
61 uint CChatTextManager::getTextFontSize() const
63 if (!_TextFontSize)
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
87 if (!_TextShadowed)
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
99 if (!_ShowTimestamps)
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");
122 return NULL;
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);
139 nlassert(text);
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);
146 group->setW(w);
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);
156 return group;
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
168 if (s[i] == '(')
170 // scan for ')[http://'
171 while(i < textSize-9)
173 if (s[i] == ')' && s[i+1] == '[')
175 i += 2;
176 markdown = true;
177 break;
179 else
180 if (s[i] == ')')
182 i += 1;
183 break;
185 i++;
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{ ' ', '"', '\'', '(', '[', '}' };
198 #else
199 static std::vector<ucchar> chars;
201 if (chars.empty())
203 chars.push_back(' ');
204 chars.push_back('"');
205 chars.push_back('\'');
206 chars.push_back('(');
207 chars.push_back('[');
208 chars.push_back('}');
210 #endif
211 isUrl = std::find(chars.begin(), chars.end(), s[i - 1]) != chars.end();
213 return isUrl;
217 return false;
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
229 if (s[index] == '(')
231 index++;
232 pos = index;
233 while(pos < textSize-9)
235 if (s[pos] == ')' && s[pos + 1] == '[')
237 isMarkdown = true;
238 title = s.substr(index, pos - index);
239 index = pos + 2;
240 break;
242 else if (s[pos] == ')')
244 break;
247 pos++;
251 char chOpen = ' ';
252 char chClose = ' ';
253 if (isMarkdown)
255 chOpen = '[';
256 chClose = ']';
258 else if (index > 0)
260 chOpen = s[index - 1];
261 if (chOpen == '\'') chClose = '\'';
262 else if (chOpen == '"') chClose = '"';
263 else if (chOpen == '(') chClose = ')';
264 else if (chOpen == '[') chClose = ']';
265 else 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] == '.'))
277 pos--;
281 else
283 // scan for nested open/close tags
284 pos = index;
285 sint nested = 0;
286 while(pos < textSize)
288 if (s[pos] == chOpen)
290 nested++;
292 else if (s[pos] == chClose)
294 if (nested == 0)
296 break;
298 else
300 nested--;
304 pos++;
308 // fallback to full string length as we did match http:// already and url spans to the end probably
309 if (pos == string::npos)
311 pos = textSize;
314 url = s.substr(index, pos - index);
315 index = pos;
317 // skip ']' closing char
318 if (isMarkdown) index++;
321 //=================================================================================
322 static void prependTimestamp(string &msg)
324 string cur_time;
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] ");
328 else
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.
335 if (codePos == 0)
337 codePos = msg.find(string("}"));
338 msg = msg.substr(0, codePos + 1) + cur_time + msg.substr(codePos + 1, msg.length() - codePos);
340 else
342 msg = cur_time + msg;
345 else
347 msg = cur_time + msg;
351 //=================================================================================
352 CViewBase *CChatTextManager::createMsgText(const string &cstMsg, NLMISC::CRGBA col, bool justified /*=false*/, bool plaintext /*=false*/)
354 string msg = cstMsg;
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);
385 else
387 vt->setText(msg);
388 vt->setColor(col);
391 if (!commandGroup)
393 return vt;
395 else
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());
407 para->setId("line");
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);
415 if (plaintext)
417 CViewBase *vt = createMsgTextSimple(msg, col, justified, NULL);
418 vt->setId("text");
419 para->addChild(vt);
421 return para;
425 // quickly check if text has links or not
426 bool hasUrl;
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))
437 if (pos != i)
439 CViewBase *vt = createMsgTextSimple(msg.substr(pos, i - pos), col, justified, NULL);
440 para->addChild(vt);
443 string url;
444 string title;
445 getUrlTag(msg, i, url, title);
446 if (url.size() > 0)
448 CViewLink *vt = new CViewLink(CViewBase::TCtorParam());
449 vt->setId("link");
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);
462 vt->setColor(col);
464 if (title.size() > 0)
466 vt->LinkTitle = title;
467 vt->setText(vt->LinkTitle);
469 else
471 vt->LinkTitle = url;
472 vt->setText(vt->LinkTitle);
475 if (url.find_first_of('\'') != string::npos)
477 string clean;
478 for(string::size_type i = 0; i< url.size(); ++i)
480 if (url[i] == '\'')
481 clean += "%27";
482 else
483 clean += url[i];
485 url = clean;
487 vt->setActionOnLeftClick("lua");
488 vt->setParamsOnLeftClick("game:chatUrl('" + url + "')");
490 para->addChildLink(vt);
492 pos = i;
495 else
497 ++i;
501 if (pos < textSize)
503 CViewBase *vt = createMsgTextSimple(msg.substr(pos, textSize - pos), col, justified, NULL);
504 vt->setId("text");
505 para->addChild(vt);
508 return para;
511 //=================================================================================
512 CChatTextManager &CChatTextManager::getInstance()
514 if( !_Instance )
515 _Instance = new CChatTextManager();
516 return *_Instance;
519 //=================================================================================
520 void CChatTextManager::releaseInstance()
522 if( _Instance )
523 delete _Instance;
524 _Instance = NULL;
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
540 public:
541 virtual void execute(CCtrlBase *pCaller, const string &params )
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
559 public:
560 virtual void execute(CCtrlBase *pCaller, const string &params )
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");