Line data Source code
1 : /*
2 : Minetest
3 : Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 :
5 : This program is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Lesser General Public License as published by
7 : the Free Software Foundation; either version 2.1 of the License, or
8 : (at your option) any later version.
9 :
10 : This program is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU Lesser General Public License for more details.
14 :
15 : You should have received a copy of the GNU Lesser General Public License along
16 : with this program; if not, write to the Free Software Foundation, Inc.,
17 : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 : */
19 :
20 : #include "chat.h"
21 : #include "debug.h"
22 : #include "strfnd.h"
23 : #include <cctype>
24 : #include <sstream>
25 : #include "util/string.h"
26 : #include "util/numeric.h"
27 :
28 2 : ChatBuffer::ChatBuffer(u32 scrollback):
29 : m_scrollback(scrollback),
30 : m_unformatted(),
31 : m_cols(0),
32 : m_rows(0),
33 : m_scroll(0),
34 : m_formatted(),
35 2 : m_empty_formatted_line()
36 : {
37 2 : if (m_scrollback == 0)
38 0 : m_scrollback = 1;
39 2 : m_empty_formatted_line.first = true;
40 2 : }
41 :
42 2 : ChatBuffer::~ChatBuffer()
43 : {
44 2 : }
45 :
46 8 : void ChatBuffer::addLine(std::wstring name, std::wstring text)
47 : {
48 16 : ChatLine line(name, text);
49 8 : m_unformatted.push_back(line);
50 :
51 8 : if (m_rows > 0)
52 : {
53 : // m_formatted is valid and must be kept valid
54 0 : bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
55 0 : u32 num_added = formatChatLine(line, m_cols, m_formatted);
56 0 : if (scrolled_at_bottom)
57 0 : m_scroll += num_added;
58 : }
59 :
60 : // Limit number of lines by m_scrollback
61 8 : if (m_unformatted.size() > m_scrollback)
62 : {
63 0 : deleteOldest(m_unformatted.size() - m_scrollback);
64 : }
65 8 : }
66 :
67 1 : void ChatBuffer::clear()
68 : {
69 1 : m_unformatted.clear();
70 1 : m_formatted.clear();
71 1 : m_scroll = 0;
72 1 : }
73 :
74 5830 : u32 ChatBuffer::getLineCount() const
75 : {
76 5830 : return m_unformatted.size();
77 : }
78 :
79 0 : u32 ChatBuffer::getScrollback() const
80 : {
81 0 : return m_scrollback;
82 : }
83 :
84 3498 : const ChatLine& ChatBuffer::getLine(u32 index) const
85 : {
86 : assert(index < getLineCount()); // pre-condition
87 3498 : return m_unformatted[index];
88 : }
89 :
90 1166 : void ChatBuffer::step(f32 dtime)
91 : {
92 4664 : for (u32 i = 0; i < m_unformatted.size(); ++i)
93 : {
94 3498 : m_unformatted[i].age += dtime;
95 : }
96 1166 : }
97 :
98 1166 : void ChatBuffer::deleteOldest(u32 count)
99 : {
100 1166 : u32 del_unformatted = 0;
101 1166 : u32 del_formatted = 0;
102 :
103 1166 : while (count > 0 && del_unformatted < m_unformatted.size())
104 : {
105 0 : ++del_unformatted;
106 :
107 : // keep m_formatted in sync
108 0 : if (del_formatted < m_formatted.size())
109 : {
110 :
111 0 : sanity_check(m_formatted[del_formatted].first);
112 0 : ++del_formatted;
113 0 : while (del_formatted < m_formatted.size() &&
114 0 : !m_formatted[del_formatted].first)
115 0 : ++del_formatted;
116 : }
117 :
118 0 : --count;
119 : }
120 :
121 1166 : m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted);
122 1166 : m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted);
123 1166 : }
124 :
125 1166 : void ChatBuffer::deleteByAge(f32 maxAge)
126 : {
127 1166 : u32 count = 0;
128 1166 : while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
129 0 : ++count;
130 1166 : deleteOldest(count);
131 1166 : }
132 :
133 0 : u32 ChatBuffer::getColumns() const
134 : {
135 0 : return m_cols;
136 : }
137 :
138 0 : u32 ChatBuffer::getRows() const
139 : {
140 0 : return m_rows;
141 : }
142 :
143 1 : void ChatBuffer::reformat(u32 cols, u32 rows)
144 : {
145 1 : if (cols == 0 || rows == 0)
146 : {
147 : // Clear formatted buffer
148 1 : m_cols = 0;
149 1 : m_rows = 0;
150 1 : m_scroll = 0;
151 1 : m_formatted.clear();
152 : }
153 0 : else if (cols != m_cols || rows != m_rows)
154 : {
155 : // TODO: Avoid reformatting ALL lines (even invisible ones)
156 : // each time the console size changes.
157 :
158 : // Find out the scroll position in *unformatted* lines
159 0 : u32 restore_scroll_unformatted = 0;
160 0 : u32 restore_scroll_formatted = 0;
161 0 : bool at_bottom = (m_scroll == getBottomScrollPos());
162 0 : if (!at_bottom)
163 : {
164 0 : for (s32 i = 0; i < m_scroll; ++i)
165 : {
166 0 : if (m_formatted[i].first)
167 0 : ++restore_scroll_unformatted;
168 : }
169 : }
170 :
171 : // If number of columns change, reformat everything
172 0 : if (cols != m_cols)
173 : {
174 0 : m_formatted.clear();
175 0 : for (u32 i = 0; i < m_unformatted.size(); ++i)
176 : {
177 0 : if (i == restore_scroll_unformatted)
178 0 : restore_scroll_formatted = m_formatted.size();
179 0 : formatChatLine(m_unformatted[i], cols, m_formatted);
180 : }
181 : }
182 :
183 : // Update the console size
184 0 : m_cols = cols;
185 0 : m_rows = rows;
186 :
187 : // Restore the scroll position
188 0 : if (at_bottom)
189 : {
190 0 : scrollBottom();
191 : }
192 : else
193 : {
194 0 : scrollAbsolute(restore_scroll_formatted);
195 : }
196 : }
197 1 : }
198 :
199 0 : const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
200 : {
201 0 : s32 index = m_scroll + (s32) row;
202 0 : if (index >= 0 && index < (s32) m_formatted.size())
203 0 : return m_formatted[index];
204 : else
205 0 : return m_empty_formatted_line;
206 : }
207 :
208 0 : void ChatBuffer::scroll(s32 rows)
209 : {
210 0 : scrollAbsolute(m_scroll + rows);
211 0 : }
212 :
213 0 : void ChatBuffer::scrollAbsolute(s32 scroll)
214 : {
215 0 : s32 top = getTopScrollPos();
216 0 : s32 bottom = getBottomScrollPos();
217 :
218 0 : m_scroll = scroll;
219 0 : if (m_scroll < top)
220 0 : m_scroll = top;
221 0 : if (m_scroll > bottom)
222 0 : m_scroll = bottom;
223 0 : }
224 :
225 0 : void ChatBuffer::scrollBottom()
226 : {
227 0 : m_scroll = getBottomScrollPos();
228 0 : }
229 :
230 0 : void ChatBuffer::scrollTop()
231 : {
232 0 : m_scroll = getTopScrollPos();
233 0 : }
234 :
235 0 : u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
236 : std::vector<ChatFormattedLine>& destination) const
237 : {
238 0 : u32 num_added = 0;
239 0 : std::vector<ChatFormattedFragment> next_frags;
240 0 : ChatFormattedLine next_line;
241 0 : ChatFormattedFragment temp_frag;
242 0 : u32 out_column = 0;
243 0 : u32 in_pos = 0;
244 0 : u32 hanging_indentation = 0;
245 :
246 : // Format the sender name and produce fragments
247 0 : if (!line.name.empty())
248 : {
249 0 : temp_frag.text = L"<";
250 0 : temp_frag.column = 0;
251 : //temp_frag.bold = 0;
252 0 : next_frags.push_back(temp_frag);
253 0 : temp_frag.text = line.name;
254 0 : temp_frag.column = 0;
255 : //temp_frag.bold = 1;
256 0 : next_frags.push_back(temp_frag);
257 0 : temp_frag.text = L"> ";
258 0 : temp_frag.column = 0;
259 : //temp_frag.bold = 0;
260 0 : next_frags.push_back(temp_frag);
261 : }
262 :
263 : // Choose an indentation level
264 0 : if (line.name.empty())
265 : {
266 : // Server messages
267 0 : hanging_indentation = 0;
268 : }
269 0 : else if (line.name.size() + 3 <= cols/2)
270 : {
271 : // Names shorter than about half the console width
272 0 : hanging_indentation = line.name.size() + 3;
273 : }
274 : else
275 : {
276 : // Very long names
277 0 : hanging_indentation = 2;
278 : }
279 :
280 0 : next_line.first = true;
281 0 : bool text_processing = false;
282 :
283 : // Produce fragments and layout them into lines
284 0 : while (!next_frags.empty() || in_pos < line.text.size())
285 : {
286 : // Layout fragments into lines
287 0 : while (!next_frags.empty())
288 : {
289 0 : ChatFormattedFragment& frag = next_frags[0];
290 0 : if (frag.text.size() <= cols - out_column)
291 : {
292 : // Fragment fits into current line
293 0 : frag.column = out_column;
294 0 : next_line.fragments.push_back(frag);
295 0 : out_column += frag.text.size();
296 0 : next_frags.erase(next_frags.begin());
297 : }
298 : else
299 : {
300 : // Fragment does not fit into current line
301 : // So split it up
302 0 : temp_frag.text = frag.text.substr(0, cols - out_column);
303 0 : temp_frag.column = out_column;
304 : //temp_frag.bold = frag.bold;
305 0 : next_line.fragments.push_back(temp_frag);
306 0 : frag.text = frag.text.substr(cols - out_column);
307 0 : out_column = cols;
308 : }
309 0 : if (out_column == cols || text_processing)
310 : {
311 : // End the current line
312 0 : destination.push_back(next_line);
313 0 : num_added++;
314 0 : next_line.fragments.clear();
315 0 : next_line.first = false;
316 :
317 0 : out_column = text_processing ? hanging_indentation : 0;
318 : }
319 : }
320 :
321 : // Produce fragment
322 0 : if (in_pos < line.text.size())
323 : {
324 0 : u32 remaining_in_input = line.text.size() - in_pos;
325 0 : u32 remaining_in_output = cols - out_column;
326 :
327 : // Determine a fragment length <= the minimum of
328 : // remaining_in_{in,out}put. Try to end the fragment
329 : // on a word boundary.
330 0 : u32 frag_length = 1, space_pos = 0;
331 0 : while (frag_length < remaining_in_input &&
332 : frag_length < remaining_in_output)
333 : {
334 0 : if (isspace(line.text[in_pos + frag_length]))
335 0 : space_pos = frag_length;
336 0 : ++frag_length;
337 : }
338 0 : if (space_pos != 0 && frag_length < remaining_in_input)
339 0 : frag_length = space_pos + 1;
340 :
341 0 : temp_frag.text = line.text.substr(in_pos, frag_length);
342 0 : temp_frag.column = 0;
343 : //temp_frag.bold = 0;
344 0 : next_frags.push_back(temp_frag);
345 0 : in_pos += frag_length;
346 0 : text_processing = true;
347 : }
348 : }
349 :
350 : // End the last line
351 0 : if (num_added == 0 || !next_line.fragments.empty())
352 : {
353 0 : destination.push_back(next_line);
354 0 : num_added++;
355 : }
356 :
357 0 : return num_added;
358 : }
359 :
360 0 : s32 ChatBuffer::getTopScrollPos() const
361 : {
362 0 : s32 formatted_count = (s32) m_formatted.size();
363 0 : s32 rows = (s32) m_rows;
364 0 : if (rows == 0)
365 0 : return 0;
366 0 : else if (formatted_count <= rows)
367 0 : return formatted_count - rows;
368 : else
369 0 : return 0;
370 : }
371 :
372 0 : s32 ChatBuffer::getBottomScrollPos() const
373 : {
374 0 : s32 formatted_count = (s32) m_formatted.size();
375 0 : s32 rows = (s32) m_rows;
376 0 : if (rows == 0)
377 0 : return 0;
378 : else
379 0 : return formatted_count - rows;
380 : }
381 :
382 :
383 :
384 1 : ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit):
385 : m_prompt(prompt),
386 : m_line(L""),
387 : m_history(),
388 : m_history_index(0),
389 : m_history_limit(history_limit),
390 : m_cols(0),
391 : m_view(0),
392 : m_cursor(0),
393 : m_nick_completion_start(0),
394 1 : m_nick_completion_end(0)
395 : {
396 1 : }
397 :
398 1 : ChatPrompt::~ChatPrompt()
399 : {
400 1 : }
401 :
402 0 : void ChatPrompt::input(wchar_t ch)
403 : {
404 0 : m_line.insert(m_cursor, 1, ch);
405 0 : m_cursor++;
406 0 : clampView();
407 0 : m_nick_completion_start = 0;
408 0 : m_nick_completion_end = 0;
409 0 : }
410 :
411 0 : void ChatPrompt::input(const std::wstring &str)
412 : {
413 0 : m_line.insert(m_cursor, str);
414 0 : m_cursor += str.size();
415 0 : clampView();
416 0 : m_nick_completion_start = 0;
417 0 : m_nick_completion_end = 0;
418 0 : }
419 :
420 0 : std::wstring ChatPrompt::submit()
421 : {
422 0 : std::wstring line = m_line;
423 0 : m_line.clear();
424 0 : if (!line.empty())
425 0 : m_history.push_back(line);
426 0 : if (m_history.size() > m_history_limit)
427 0 : m_history.erase(m_history.begin());
428 0 : m_history_index = m_history.size();
429 0 : m_view = 0;
430 0 : m_cursor = 0;
431 0 : m_nick_completion_start = 0;
432 0 : m_nick_completion_end = 0;
433 0 : return line;
434 : }
435 :
436 0 : void ChatPrompt::clear()
437 : {
438 0 : m_line.clear();
439 0 : m_view = 0;
440 0 : m_cursor = 0;
441 0 : m_nick_completion_start = 0;
442 0 : m_nick_completion_end = 0;
443 0 : }
444 :
445 0 : void ChatPrompt::replace(std::wstring line)
446 : {
447 0 : m_line = line;
448 0 : m_view = m_cursor = line.size();
449 0 : clampView();
450 0 : m_nick_completion_start = 0;
451 0 : m_nick_completion_end = 0;
452 0 : }
453 :
454 0 : void ChatPrompt::historyPrev()
455 : {
456 0 : if (m_history_index != 0)
457 : {
458 0 : --m_history_index;
459 0 : replace(m_history[m_history_index]);
460 : }
461 0 : }
462 :
463 0 : void ChatPrompt::historyNext()
464 : {
465 0 : if (m_history_index + 1 >= m_history.size())
466 : {
467 0 : m_history_index = m_history.size();
468 0 : replace(L"");
469 : }
470 : else
471 : {
472 0 : ++m_history_index;
473 0 : replace(m_history[m_history_index]);
474 : }
475 0 : }
476 :
477 0 : void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwards)
478 : {
479 : // Two cases:
480 : // (a) m_nick_completion_start == m_nick_completion_end == 0
481 : // Then no previous nick completion is active.
482 : // Get the word around the cursor and replace with any nick
483 : // that has that word as a prefix.
484 : // (b) else, continue a previous nick completion.
485 : // m_nick_completion_start..m_nick_completion_end are the
486 : // interval where the originally used prefix was. Cycle
487 : // through the list of completions of that prefix.
488 0 : u32 prefix_start = m_nick_completion_start;
489 0 : u32 prefix_end = m_nick_completion_end;
490 0 : bool initial = (prefix_end == 0);
491 0 : if (initial)
492 : {
493 : // no previous nick completion is active
494 0 : prefix_start = prefix_end = m_cursor;
495 0 : while (prefix_start > 0 && !isspace(m_line[prefix_start-1]))
496 0 : --prefix_start;
497 0 : while (prefix_end < m_line.size() && !isspace(m_line[prefix_end]))
498 0 : ++prefix_end;
499 0 : if (prefix_start == prefix_end)
500 0 : return;
501 : }
502 0 : std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
503 :
504 : // find all names that start with the selected prefix
505 0 : std::vector<std::wstring> completions;
506 0 : for (std::list<std::string>::const_iterator
507 0 : i = names.begin();
508 0 : i != names.end(); ++i)
509 : {
510 0 : if (str_starts_with(narrow_to_wide(*i), prefix, true))
511 : {
512 0 : std::wstring completion = narrow_to_wide(*i);
513 0 : if (prefix_start == 0)
514 0 : completion += L": ";
515 0 : completions.push_back(completion);
516 : }
517 : }
518 0 : if (completions.empty())
519 0 : return;
520 :
521 : // find a replacement string and the word that will be replaced
522 0 : u32 word_end = prefix_end;
523 0 : u32 replacement_index = 0;
524 0 : if (!initial)
525 : {
526 0 : while (word_end < m_line.size() && !isspace(m_line[word_end]))
527 0 : ++word_end;
528 0 : std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
529 :
530 : // cycle through completions
531 0 : for (u32 i = 0; i < completions.size(); ++i)
532 : {
533 0 : if (str_equal(word, completions[i], true))
534 : {
535 0 : if (backwards)
536 0 : replacement_index = i + completions.size() - 1;
537 : else
538 0 : replacement_index = i + 1;
539 0 : replacement_index %= completions.size();
540 0 : break;
541 : }
542 : }
543 : }
544 0 : std::wstring replacement = completions[replacement_index];
545 0 : if (word_end < m_line.size() && isspace(word_end))
546 0 : ++word_end;
547 :
548 : // replace existing word with replacement word,
549 : // place the cursor at the end and record the completion prefix
550 0 : m_line.replace(prefix_start, word_end - prefix_start, replacement);
551 0 : m_cursor = prefix_start + replacement.size();
552 0 : clampView();
553 0 : m_nick_completion_start = prefix_start;
554 0 : m_nick_completion_end = prefix_end;
555 : }
556 :
557 1 : void ChatPrompt::reformat(u32 cols)
558 : {
559 1 : if (cols <= m_prompt.size())
560 : {
561 1 : m_cols = 0;
562 1 : m_view = m_cursor;
563 : }
564 : else
565 : {
566 0 : s32 length = m_line.size();
567 0 : bool was_at_end = (m_view + m_cols >= length + 1);
568 0 : m_cols = cols - m_prompt.size();
569 0 : if (was_at_end)
570 0 : m_view = length;
571 0 : clampView();
572 : }
573 1 : }
574 :
575 0 : std::wstring ChatPrompt::getVisiblePortion() const
576 : {
577 0 : return m_prompt + m_line.substr(m_view, m_cols);
578 : }
579 :
580 0 : s32 ChatPrompt::getVisibleCursorPosition() const
581 : {
582 0 : return m_cursor - m_view + m_prompt.size();
583 : }
584 :
585 0 : void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
586 : {
587 0 : s32 old_cursor = m_cursor;
588 0 : s32 new_cursor = m_cursor;
589 :
590 0 : s32 length = m_line.size();
591 0 : s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
592 :
593 0 : if (scope == CURSOROP_SCOPE_CHARACTER)
594 : {
595 0 : new_cursor += increment;
596 : }
597 0 : else if (scope == CURSOROP_SCOPE_WORD)
598 : {
599 0 : if (increment > 0)
600 : {
601 : // skip one word to the right
602 0 : while (new_cursor < length && isspace(m_line[new_cursor]))
603 0 : new_cursor++;
604 0 : while (new_cursor < length && !isspace(m_line[new_cursor]))
605 0 : new_cursor++;
606 0 : while (new_cursor < length && isspace(m_line[new_cursor]))
607 0 : new_cursor++;
608 : }
609 : else
610 : {
611 : // skip one word to the left
612 0 : while (new_cursor >= 1 && isspace(m_line[new_cursor - 1]))
613 0 : new_cursor--;
614 0 : while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1]))
615 0 : new_cursor--;
616 : }
617 : }
618 0 : else if (scope == CURSOROP_SCOPE_LINE)
619 : {
620 0 : new_cursor += increment * length;
621 : }
622 :
623 0 : new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
624 :
625 0 : if (op == CURSOROP_MOVE)
626 : {
627 0 : m_cursor = new_cursor;
628 : }
629 0 : else if (op == CURSOROP_DELETE)
630 : {
631 0 : if (new_cursor < old_cursor)
632 : {
633 0 : m_line.erase(new_cursor, old_cursor - new_cursor);
634 0 : m_cursor = new_cursor;
635 : }
636 0 : else if (new_cursor > old_cursor)
637 : {
638 0 : m_line.erase(old_cursor, new_cursor - old_cursor);
639 0 : m_cursor = old_cursor;
640 : }
641 : }
642 :
643 0 : clampView();
644 :
645 0 : m_nick_completion_start = 0;
646 0 : m_nick_completion_end = 0;
647 0 : }
648 :
649 0 : void ChatPrompt::clampView()
650 : {
651 0 : s32 length = m_line.size();
652 0 : if (length + 1 <= m_cols)
653 : {
654 0 : m_view = 0;
655 : }
656 : else
657 : {
658 0 : m_view = MYMIN(m_view, length + 1 - m_cols);
659 0 : m_view = MYMIN(m_view, m_cursor);
660 0 : m_view = MYMAX(m_view, m_cursor - m_cols + 1);
661 0 : m_view = MYMAX(m_view, 0);
662 : }
663 0 : }
664 :
665 :
666 :
667 1 : ChatBackend::ChatBackend():
668 : m_console_buffer(500),
669 : m_recent_buffer(6),
670 1 : m_prompt(L"]", 500)
671 : {
672 1 : }
673 :
674 1 : ChatBackend::~ChatBackend()
675 : {
676 1 : }
677 :
678 4 : void ChatBackend::addMessage(std::wstring name, std::wstring text)
679 : {
680 : // Note: A message may consist of multiple lines, for example the MOTD.
681 8 : WStrfnd fnd(text);
682 12 : while (!fnd.atend())
683 : {
684 8 : std::wstring line = fnd.next(L"\n");
685 4 : m_console_buffer.addLine(name, line);
686 4 : m_recent_buffer.addLine(name, line);
687 : }
688 4 : }
689 :
690 2 : void ChatBackend::addUnparsedMessage(std::wstring message)
691 : {
692 : // TODO: Remove the need to parse chat messages client-side, by sending
693 : // separate name and text fields in TOCLIENT_CHAT_MESSAGE.
694 :
695 2 : if (message.size() >= 2 && message[0] == L'<')
696 : {
697 0 : std::size_t closing = message.find_first_of(L'>', 1);
698 0 : if (closing != std::wstring::npos &&
699 0 : closing + 2 <= message.size() &&
700 0 : message[closing+1] == L' ')
701 : {
702 0 : std::wstring name = message.substr(1, closing - 1);
703 0 : std::wstring text = message.substr(closing + 2);
704 0 : addMessage(name, text);
705 0 : return;
706 : }
707 : }
708 :
709 : // Unable to parse, probably a server message.
710 2 : addMessage(L"", message);
711 : }
712 :
713 0 : ChatBuffer& ChatBackend::getConsoleBuffer()
714 : {
715 0 : return m_console_buffer;
716 : }
717 :
718 1166 : ChatBuffer& ChatBackend::getRecentBuffer()
719 : {
720 1166 : return m_recent_buffer;
721 : }
722 :
723 1166 : std::wstring ChatBackend::getRecentChat()
724 : {
725 2332 : std::wostringstream stream;
726 4664 : for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i)
727 : {
728 3498 : const ChatLine& line = m_recent_buffer.getLine(i);
729 3498 : if (i != 0)
730 2332 : stream << L"\n";
731 3498 : if (!line.name.empty())
732 0 : stream << L"<" << line.name << L"> ";
733 3498 : stream << line.text;
734 : }
735 2332 : return stream.str();
736 : }
737 :
738 0 : ChatPrompt& ChatBackend::getPrompt()
739 : {
740 0 : return m_prompt;
741 : }
742 :
743 1 : void ChatBackend::reformat(u32 cols, u32 rows)
744 : {
745 1 : m_console_buffer.reformat(cols, rows);
746 :
747 : // no need to reformat m_recent_buffer, its formatted lines
748 : // are not used
749 :
750 1 : m_prompt.reformat(cols);
751 1 : }
752 :
753 1 : void ChatBackend::clearRecentChat()
754 : {
755 1 : m_recent_buffer.clear();
756 1 : }
757 :
758 1166 : void ChatBackend::step(float dtime)
759 : {
760 1166 : m_recent_buffer.step(dtime);
761 1166 : m_recent_buffer.deleteByAge(60.0);
762 :
763 : // no need to age messages in anything but m_recent_buffer
764 1166 : }
765 :
766 0 : void ChatBackend::scroll(s32 rows)
767 : {
768 0 : m_console_buffer.scroll(rows);
769 0 : }
770 :
771 0 : void ChatBackend::scrollPageDown()
772 : {
773 0 : m_console_buffer.scroll(m_console_buffer.getRows());
774 0 : }
775 :
776 0 : void ChatBackend::scrollPageUp()
777 : {
778 0 : m_console_buffer.scroll(-(s32)m_console_buffer.getRows());
779 3 : }
|