llconsole.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /**
  2. * @file llconsole.cpp
  3. * @brief a scrolling console output device
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewergpl$
  6. *
  7. * Copyright (c) 2001-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "llconsole.h"
  34. #include "llgl.h"
  35. #include "llstring.h"
  36. #include "llstl.h" // For DeletePointer()
  37. // HACK: Defined in newview/llviewerwindow.cpp
  38. extern S32 viewer_window_width();
  39. extern S32 viewer_window_height();
  40. // To be used for the main (chat) console only !
  41. LLConsole* gConsolep = NULL;
  42. constexpr F32 FADE_DURATION = 2.f;
  43. constexpr S32 MIN_CONSOLE_WIDTH = 50;
  44. // Why do not these match ?
  45. constexpr S32 CONSOLE_GUTTER_LEFT = 14;
  46. constexpr S32 CONSOLE_GUTTER_RIGHT = 15;
  47. // Static variables
  48. LLColor4 LLConsole::sConsoleBackground;
  49. LLConsole::LLConsole(const std::string& name, const LLRect& rect,
  50. S32 font_size_index, U32 max_lines, F32 persist_time)
  51. : LLLineBuffer(),
  52. LLView(name, rect, false),
  53. mMaxLines(max_lines),
  54. mLinePersistTime(persist_time),
  55. mFadeTime(persist_time - FADE_DURATION),
  56. mFont(LLFontGL::getFontSansSerif()),
  57. mConsoleWidth(0),
  58. mConsoleHeight(0)
  59. {
  60. mTimer.reset();
  61. setFontSize(font_size_index);
  62. }
  63. LLConsole::~LLConsole()
  64. {
  65. clear();
  66. }
  67. //static
  68. void LLConsole::setBackground(const LLColor4& color, F32 opacity)
  69. {
  70. sConsoleBackground = color;
  71. sConsoleBackground.mV[VW] *= llclamp(opacity, 0.f, 1.f);
  72. }
  73. void LLConsole::setLinePersistTime(F32 seconds)
  74. {
  75. mLinePersistTime = seconds;
  76. mFadeTime = mLinePersistTime - FADE_DURATION;
  77. }
  78. void LLConsole::reshape(S32 width, S32 height, bool called_from_parent)
  79. {
  80. S32 new_width = llmax(MIN_CONSOLE_WIDTH,
  81. llmin(width, viewer_window_width()));
  82. S32 new_height = llclamp((S32)mLineHeight + 15, getRect().getHeight(),
  83. viewer_window_height());
  84. if (mConsoleWidth == new_width)
  85. {
  86. return;
  87. }
  88. mConsoleWidth = new_width;
  89. mConsoleHeight= new_height;
  90. LLView::reshape(new_width, new_height, called_from_parent);
  91. for (paragraph_t::iterator it = mParagraphs.begin(),
  92. end = mParagraphs.end();
  93. it != end; ++it)
  94. {
  95. (*it)->updateLines((F32)getRect().getWidth(), mFont, true);
  96. }
  97. }
  98. void LLConsole::setFontSize(S32 size_index)
  99. {
  100. mFontSize = size_index;
  101. if (size_index == -1)
  102. {
  103. mFont = LLFontGL::getFontMonospace();
  104. }
  105. else if (size_index == 0)
  106. {
  107. mFont = LLFontGL::getFontSansSerif();
  108. }
  109. else if (size_index == 1)
  110. {
  111. mFont = LLFontGL::getFontSansSerifLarge();
  112. }
  113. else
  114. {
  115. mFont = LLFontGL::getFontSansSerifHuge();
  116. }
  117. mLineHeight = mFont->getLineHeight();
  118. for (paragraph_t::iterator it = mParagraphs.begin(),
  119. end = mParagraphs.end();
  120. it != end; ++it)
  121. {
  122. (*it)->updateLines((F32)getRect().getWidth(), mFont, true);
  123. }
  124. }
  125. void LLConsole::draw()
  126. {
  127. mQueueMutex.lock();
  128. for (paragraph_t::iterator it = mNewParagraphs.begin(),
  129. end = mNewParagraphs.end();
  130. it != end; ++it)
  131. {
  132. Paragraph* paragraph = *it;
  133. if (paragraph)
  134. {
  135. mParagraphs.push_back(paragraph);
  136. paragraph->updateLines((F32)getRect().getWidth(), mFont);
  137. }
  138. }
  139. mNewParagraphs.clear();
  140. if (mParagraphs.empty()) // No text to draw.
  141. {
  142. mQueueMutex.unlock();
  143. return;
  144. }
  145. // Skip lines added more than mLinePersistTime ago
  146. F32 cur_time = mTimer.getElapsedTimeF32();
  147. F32 skip_time = cur_time - mLinePersistTime;
  148. F32 fade_time = cur_time - mFadeTime;
  149. U32 num_lines = 0;
  150. paragraph_t::reverse_iterator rit;
  151. rit = mParagraphs.rbegin();
  152. U32 paragraph_num = mParagraphs.size();
  153. while (!mParagraphs.empty() && rit != mParagraphs.rend())
  154. {
  155. Paragraph* para = *rit;
  156. num_lines += para->mLines.size();
  157. if (num_lines > mMaxLines ||
  158. (mLinePersistTime > 0.f &&
  159. (para->mAddTime - skip_time) /
  160. (mLinePersistTime - mFadeTime) <= 0.f))
  161. {
  162. // All lines above here are done. Lose them.
  163. for (U32 i = 0; i < paragraph_num; ++i)
  164. {
  165. if (!mParagraphs.empty())
  166. {
  167. Paragraph* paragraph = mParagraphs.front();
  168. mParagraphs.pop_front();
  169. delete paragraph;
  170. }
  171. }
  172. break;
  173. }
  174. --paragraph_num;
  175. ++rit;
  176. }
  177. if (mParagraphs.empty())
  178. {
  179. mQueueMutex.unlock();
  180. return;
  181. }
  182. // Draw remaining lines
  183. F32 y_pos = 0.f;
  184. S32 message_spacing = 4;
  185. S32 target_height = 0;
  186. S32 target_width = 0;
  187. LLGLSUIDefault gls_ui;
  188. if (!LLUI::sConsoleBoxPerMessage)
  189. {
  190. // This section makes a single huge black box behind all the text.
  191. S32 bkg_height = 4;
  192. if (LLUI::sDisableMessagesSpacing)
  193. {
  194. message_spacing = 0;
  195. bkg_height = 8;
  196. }
  197. S32 bkg_width = 0;
  198. for (paragraph_t::reverse_iterator rit = mParagraphs.rbegin(),
  199. rend = mParagraphs.rend();
  200. rit != rend; ++rit)
  201. {
  202. Paragraph* para = *rit;
  203. target_height = llfloor(para->mLines.size() *
  204. mLineHeight + message_spacing);
  205. target_width = llfloor(para->mMaxWidth + CONSOLE_GUTTER_RIGHT);
  206. bkg_height += target_height;
  207. if (target_width > bkg_width)
  208. {
  209. bkg_width = target_width;
  210. }
  211. // Why is this not using llfloor as above ?
  212. y_pos += para->mLines.size() * mLineHeight;
  213. y_pos += message_spacing; // Extra spacing between messages.
  214. }
  215. LLUIImage::sRoundedSquare->drawSolid(-CONSOLE_GUTTER_LEFT,
  216. (S32)(y_pos + mLineHeight -
  217. bkg_height -
  218. message_spacing),
  219. bkg_width, bkg_height,
  220. sConsoleBackground);
  221. }
  222. y_pos = 0.f;
  223. for (paragraph_t::reverse_iterator rit = mParagraphs.rbegin(),
  224. rend = mParagraphs.rend();
  225. rit != rend; ++rit)
  226. {
  227. Paragraph* para = *rit;
  228. target_width = llfloor(para->mMaxWidth + CONSOLE_GUTTER_RIGHT);
  229. y_pos += para->mLines.size() * mLineHeight;
  230. if (LLUI::sConsoleBoxPerMessage)
  231. {
  232. // Per-message block boxes
  233. target_height = llfloor(para->mLines.size() * mLineHeight + 8);
  234. LLUIImage::sRoundedSquare->drawSolid(-CONSOLE_GUTTER_LEFT,
  235. (S32)(y_pos + mLineHeight -
  236. target_height),
  237. target_width,
  238. target_height,
  239. sConsoleBackground);
  240. }
  241. F32 y_off = 0;
  242. F32 alpha;
  243. if (mLinePersistTime > 0.f && para->mAddTime < fade_time)
  244. {
  245. alpha = (para->mAddTime - skip_time) /
  246. (mLinePersistTime - mFadeTime);
  247. }
  248. else
  249. {
  250. alpha = 1.f;
  251. }
  252. if (alpha > 0.f)
  253. {
  254. for (lines_t::iterator line_it = para->mLines.begin(),
  255. end = para->mLines.end();
  256. line_it != end; ++line_it)
  257. {
  258. for (line_color_segments_t::iterator seg_it = line_it->begin(),
  259. end2 = line_it->end();
  260. seg_it != end2; ++seg_it)
  261. {
  262. const LLColor4& scolor = seg_it->mColor;
  263. LLColor4 color(scolor.mV[VX], scolor.mV[VY], scolor.mV[VZ],
  264. scolor.mV[VW] * alpha);
  265. mFont->render(seg_it->mText, 0,
  266. seg_it->mXPosition - 8, y_pos - y_off,
  267. color, LLFontGL::LEFT, LLFontGL::BASELINE,
  268. LLFontGL::DROP_SHADOW, S32_MAX,
  269. target_width);
  270. }
  271. y_off += mLineHeight;
  272. }
  273. }
  274. y_pos += message_spacing; // Extra spacing between messages.
  275. }
  276. mQueueMutex.unlock();
  277. }
  278. //virtual
  279. void LLConsole::clear()
  280. {
  281. mTimer.reset();
  282. mQueueMutex.lock();
  283. std::for_each(mParagraphs.begin(), mParagraphs.end(), DeletePointer());
  284. mParagraphs.clear();
  285. std::for_each(mNewParagraphs.begin(), mNewParagraphs.end(),
  286. DeletePointer());
  287. mNewParagraphs.clear();
  288. mQueueMutex.unlock();
  289. }
  290. //virtual
  291. void LLConsole::addLine(const std::string& utf8line)
  292. {
  293. addConsoleLine(utf8line, LLColor4::white);
  294. }
  295. void LLConsole::addConsoleLine(const std::string& utf8line,
  296. const LLColor4& color)
  297. {
  298. LLWString wline = utf8str_to_wstring(utf8line);
  299. addConsoleLine(wline, color);
  300. }
  301. void LLConsole::addConsoleLine(const LLWString& wline, const LLColor4& color)
  302. {
  303. Paragraph* paragraph = new Paragraph(wline, color,
  304. mTimer.getElapsedTimeF32());
  305. mQueueMutex.lock();
  306. mNewParagraphs.push_back(paragraph);
  307. mQueueMutex.unlock();
  308. }
  309. void LLConsole::replaceParaText(Paragraph* para, const LLWString& search_text,
  310. const LLWString& replace_text,
  311. bool case_insensitive, bool new_paragraph)
  312. {
  313. if (!para) return;
  314. size_t search_length = search_text.size();
  315. size_t replace_length = replace_text.size();
  316. S32 offset = replace_length - search_length;
  317. LLWString final_text, para_text;
  318. final_text = para_text = para->mParagraphText;
  319. if (case_insensitive)
  320. {
  321. LLWStringUtil::toLower(para_text);
  322. }
  323. bool replaced = false;
  324. size_t pos;
  325. while ((pos = para_text.find(search_text)) != std::string::npos)
  326. {
  327. replaced = true;
  328. if (pos == 0)
  329. {
  330. para_text.clear();
  331. }
  332. else
  333. {
  334. para_text = final_text.substr(0, pos);
  335. }
  336. para_text += replace_text;
  337. if (pos + search_length < final_text.size())
  338. {
  339. para_text += final_text.substr(pos + search_length);
  340. }
  341. final_text = para_text;
  342. if (case_insensitive)
  343. {
  344. LLWStringUtil::toLower(para_text);
  345. }
  346. // Update the corresponding segment length when the search and replace
  347. // texts lengths differ.
  348. if (offset != 0)
  349. {
  350. S32 seg_pos = 0;
  351. for (paragraph_color_segments_t::iterator
  352. seg_it = para->mParagraphColorSegments.begin(),
  353. seg_end = para->mParagraphColorSegments.end();
  354. seg_it != seg_end; ++seg_it)
  355. {
  356. ParagraphColorSegment segment = *seg_it;
  357. if (seg_pos >= (S32)pos)
  358. {
  359. segment.mNumChars += offset;
  360. if (segment.mNumChars <= 0) // Empty replacement text ?
  361. {
  362. para->mParagraphColorSegments.erase(seg_it);
  363. }
  364. break;
  365. }
  366. seg_pos += segment.mNumChars;
  367. }
  368. }
  369. }
  370. if (replaced)
  371. {
  372. para->mParagraphText = final_text;
  373. if (offset != 0 && !new_paragraph)
  374. {
  375. para->updateLines((F32)getRect().getWidth(), mFont, true);
  376. }
  377. }
  378. }
  379. void LLConsole::replaceAllText(const std::string& search_txt,
  380. const std::string& replace_txt,
  381. bool case_insensitive)
  382. {
  383. LLWString search_text = utf8str_to_wstring(search_txt);
  384. if (case_insensitive)
  385. {
  386. LLWStringUtil::toLower(search_text);
  387. }
  388. LLWString replace_text = utf8str_to_wstring(replace_txt);
  389. for (paragraph_t::iterator it = mParagraphs.begin(),
  390. end = mParagraphs.end();
  391. it != end; ++it)
  392. {
  393. replaceParaText(*it, search_text, replace_text, case_insensitive);
  394. }
  395. mQueueMutex.lock();
  396. for (paragraph_t::iterator it = mNewParagraphs.begin(),
  397. end = mNewParagraphs.end();
  398. it != end; ++it)
  399. {
  400. replaceParaText(*it, search_text, replace_text, case_insensitive,
  401. true);
  402. }
  403. mQueueMutex.unlock();
  404. }
  405. // Called when a paragraph is added to the console or window is resized.
  406. void LLConsole::Paragraph::updateLines(F32 screen_width, LLFontGL* font,
  407. bool force_resize)
  408. {
  409. if (!force_resize && mMaxWidth >= 0.f && mMaxWidth < screen_width)
  410. {
  411. return; // No resize required.
  412. }
  413. if (mParagraphText.empty() || mParagraphColorSegments.empty() || !font)
  414. {
  415. return; // Not enough info to complete.
  416. }
  417. screen_width -= 30; // Margin for small windows.
  418. mLines.clear(); // Chuck everything.
  419. mMaxWidth = 0.f;
  420. paragraph_color_segments_t::iterator current_color =
  421. mParagraphColorSegments.begin();
  422. paragraph_color_segments_t::iterator end_segments =
  423. mParagraphColorSegments.end();
  424. U32 current_color_length = current_color->mNumChars;
  425. // Wrap lines that are longer than the view is wide.
  426. S32 paragraph_text_length = mParagraphText.size();
  427. S32 paragraph_offset = 0; // Offset into the paragraph text.
  428. while (paragraph_offset < paragraph_text_length)
  429. {
  430. bool found_newline = false; // skip '\n'
  431. // Figure out if a word-wrapped line fits here.
  432. LLWString::size_type line_end =
  433. mParagraphText.find_first_of(llwchar('\n'), paragraph_offset);
  434. if (line_end != LLWString::npos)
  435. {
  436. found_newline = true; // skip '\n'
  437. }
  438. else
  439. {
  440. line_end = paragraph_text_length;
  441. }
  442. U32 drawable = font->maxDrawableChars(mParagraphText.c_str() +
  443. paragraph_offset,
  444. screen_width,
  445. line_end - paragraph_offset,
  446. true);
  447. if (drawable != 0 || found_newline)
  448. {
  449. F32 x_position = 0; // Screen X position of text.
  450. mMaxWidth =
  451. llmax(mMaxWidth,
  452. (F32)font->getWidth(mParagraphText.substr(paragraph_offset,
  453. drawable).c_str()));
  454. line_color_segments_t line;
  455. U32 left_to_draw = drawable;
  456. U32 drawn = 0;
  457. while (left_to_draw >= current_color_length &&
  458. current_color != end_segments)
  459. {
  460. LLWString color_text =
  461. mParagraphText.substr(paragraph_offset + drawn,
  462. current_color_length);
  463. // Append segment to line.
  464. line.emplace_back(color_text, current_color->mColor,
  465. x_position);
  466. // Set up next screen position.
  467. x_position += font->getWidth(color_text.c_str());
  468. drawn += current_color_length;
  469. left_to_draw -= current_color_length;
  470. // Goto next paragraph color record.
  471. current_color++;
  472. if (current_color != end_segments)
  473. {
  474. current_color_length = current_color->mNumChars;
  475. }
  476. }
  477. if (left_to_draw > 0 && current_color != end_segments)
  478. {
  479. LLWString color_text =
  480. mParagraphText.substr(paragraph_offset + drawn,
  481. left_to_draw);
  482. // Append segment to line.
  483. line.emplace_back(color_text, current_color->mColor,
  484. x_position);
  485. current_color_length -= left_to_draw;
  486. }
  487. // Append line to paragraph line list.
  488. mLines.emplace_back(line);
  489. }
  490. else
  491. {
  492. break; // Nothing more to print
  493. }
  494. paragraph_offset += drawable + (found_newline ? 1 : 0);
  495. }
  496. }
  497. // Pass in the string and the default color for this block of text.
  498. LLConsole::Paragraph::Paragraph(LLWString str, const LLColor4& color,
  499. F32 add_time)
  500. : mParagraphText(str),
  501. mAddTime(add_time),
  502. mMaxWidth(-1)
  503. {
  504. // Generate one highlight color segment for this paragraph.
  505. mParagraphColorSegments.emplace_back(wstring_to_utf8str(str).size(),
  506. color);
  507. }