]> git.lizzy.rs Git - minetest.git/blob - src/gui/guiChatConsole.cpp
Drop dependency on IrrCompileConfig
[minetest.git] / src / gui / guiChatConsole.cpp
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 "guiChatConsole.h"
21 #include "chat.h"
22 #include "client/client.h"
23 #include "debug.h"
24 #include "gettime.h"
25 #include "client/keycode.h"
26 #include "settings.h"
27 #include "porting.h"
28 #include "client/tile.h"
29 #include "client/fontengine.h"
30 #include "log.h"
31 #include "gettext.h"
32 #include "irrlicht_changes/CGUITTFont.h"
33 #include "util/string.h"
34 #include <string>
35
36 inline u32 clamp_u8(s32 value)
37 {
38         return (u32) MYMIN(MYMAX(value, 0), 255);
39 }
40
41 inline bool isInCtrlKeys(const irr::EKEY_CODE& kc)
42 {
43         return kc == KEY_LCONTROL || kc == KEY_RCONTROL || kc == KEY_CONTROL;
44 }
45
46 GUIChatConsole::GUIChatConsole(
47                 gui::IGUIEnvironment* env,
48                 gui::IGUIElement* parent,
49                 s32 id,
50                 ChatBackend* backend,
51                 Client* client,
52                 IMenuManager* menumgr
53 ):
54         IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
55                         core::rect<s32>(0,0,100,100)),
56         m_chat_backend(backend),
57         m_client(client),
58         m_menumgr(menumgr),
59         m_animate_time_old(porting::getTimeMs())
60 {
61         // load background settings
62         s32 console_alpha = g_settings->getS32("console_alpha");
63         m_background_color.setAlpha(clamp_u8(console_alpha));
64
65         // load the background texture depending on settings
66         ITextureSource *tsrc = client->getTextureSource();
67         if (tsrc->isKnownSourceImage("background_chat.jpg")) {
68                 m_background = tsrc->getTexture("background_chat.jpg");
69                 m_background_color.setRed(255);
70                 m_background_color.setGreen(255);
71                 m_background_color.setBlue(255);
72         } else {
73                 v3f console_color = g_settings->getV3F("console_color");
74                 m_background_color.setRed(clamp_u8(myround(console_color.X)));
75                 m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
76                 m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
77         }
78
79         const u16 chat_font_size = g_settings->getU16("chat_font_size");
80         m_font = g_fontengine->getFont(chat_font_size != 0 ?
81                 rangelim(chat_font_size, 5, 72) : FONT_SIZE_UNSPECIFIED, FM_Mono);
82
83         if (!m_font) {
84                 errorstream << "GUIChatConsole: Unable to load mono font" << std::endl;
85         } else {
86                 core::dimension2d<u32> dim = m_font->getDimension(L"M");
87                 m_fontsize = v2u32(dim.Width, dim.Height);
88                 m_font->grab();
89         }
90         m_fontsize.X = MYMAX(m_fontsize.X, 1);
91         m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
92
93         // set default cursor options
94         setCursor(true, true, 2.0, 0.1);
95
96         // track ctrl keys for mouse event
97         m_is_ctrl_down = false;
98         m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks");
99 }
100
101 GUIChatConsole::~GUIChatConsole()
102 {
103         if (m_font)
104                 m_font->drop();
105 }
106
107 void GUIChatConsole::openConsole(f32 scale)
108 {
109         assert(scale > 0.0f && scale <= 1.0f);
110
111         m_open = true;
112         m_desired_height_fraction = scale;
113         m_desired_height = scale * m_screensize.Y;
114         reformatConsole();
115         m_animate_time_old = porting::getTimeMs();
116         IGUIElement::setVisible(true);
117         Environment->setFocus(this);
118         m_menumgr->createdMenu(this);
119 }
120
121 bool GUIChatConsole::isOpen() const
122 {
123         return m_open;
124 }
125
126 bool GUIChatConsole::isOpenInhibited() const
127 {
128         return m_open_inhibited > 0;
129 }
130
131 void GUIChatConsole::closeConsole()
132 {
133         m_open = false;
134         Environment->removeFocus(this);
135         m_menumgr->deletingMenu(this);
136 }
137
138 void GUIChatConsole::closeConsoleAtOnce()
139 {
140         closeConsole();
141         m_height = 0;
142         recalculateConsolePosition();
143 }
144
145 void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line)
146 {
147         ChatPrompt& prompt = m_chat_backend->getPrompt();
148         prompt.addToHistory(prompt.getLine());
149         prompt.replace(line);
150 }
151
152
153 void GUIChatConsole::setCursor(
154         bool visible, bool blinking, f32 blink_speed, f32 relative_height)
155 {
156         if (visible)
157         {
158                 if (blinking)
159                 {
160                         // leave m_cursor_blink unchanged
161                         m_cursor_blink_speed = blink_speed;
162                 }
163                 else
164                 {
165                         m_cursor_blink = 0x8000;  // on
166                         m_cursor_blink_speed = 0.0;
167                 }
168         }
169         else
170         {
171                 m_cursor_blink = 0;  // off
172                 m_cursor_blink_speed = 0.0;
173         }
174         m_cursor_height = relative_height;
175 }
176
177 void GUIChatConsole::draw()
178 {
179         if(!IsVisible)
180                 return;
181
182         video::IVideoDriver* driver = Environment->getVideoDriver();
183
184         // Check screen size
185         v2u32 screensize = driver->getScreenSize();
186         if (screensize != m_screensize)
187         {
188                 // screen size has changed
189                 // scale current console height to new window size
190                 if (m_screensize.Y != 0)
191                         m_height = m_height * screensize.Y / m_screensize.Y;
192                 m_screensize = screensize;
193                 m_desired_height = m_desired_height_fraction * m_screensize.Y;
194                 reformatConsole();
195         }
196
197         // Animation
198         u64 now = porting::getTimeMs();
199         animate(now - m_animate_time_old);
200         m_animate_time_old = now;
201
202         // Draw console elements if visible
203         if (m_height > 0)
204         {
205                 drawBackground();
206                 drawText();
207                 drawPrompt();
208         }
209
210         gui::IGUIElement::draw();
211 }
212
213 void GUIChatConsole::reformatConsole()
214 {
215         s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
216         s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
217         if (cols <= 0 || rows <= 0)
218                 cols = rows = 0;
219         recalculateConsolePosition();
220         m_chat_backend->reformat(cols, rows);
221 }
222
223 void GUIChatConsole::recalculateConsolePosition()
224 {
225         core::rect<s32> rect(0, 0, m_screensize.X, m_height);
226         DesiredRect = rect;
227         recalculateAbsolutePosition(false);
228 }
229
230 void GUIChatConsole::animate(u32 msec)
231 {
232         // animate the console height
233         s32 goal = m_open ? m_desired_height : 0;
234
235         // Set invisible if close animation finished (reset by openConsole)
236         // This function (animate()) is never called once its visibility becomes false so do not
237         //              actually set visible to false before the inhibited period is over
238         if (!m_open && m_height == 0 && m_open_inhibited == 0)
239                 IGUIElement::setVisible(false);
240
241         if (m_height != goal)
242         {
243                 s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
244                 if (max_change == 0)
245                         max_change = 1;
246
247                 if (m_height < goal)
248                 {
249                         // increase height
250                         if (m_height + max_change < goal)
251                                 m_height += max_change;
252                         else
253                                 m_height = goal;
254                 }
255                 else
256                 {
257                         // decrease height
258                         if (m_height > goal + max_change)
259                                 m_height -= max_change;
260                         else
261                                 m_height = goal;
262                 }
263
264                 recalculateConsolePosition();
265         }
266
267         // blink the cursor
268         if (m_cursor_blink_speed != 0.0)
269         {
270                 u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
271                 if (blink_increase == 0)
272                         blink_increase = 1;
273                 m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
274         }
275
276         // decrease open inhibit counter
277         if (m_open_inhibited > msec)
278                 m_open_inhibited -= msec;
279         else
280                 m_open_inhibited = 0;
281 }
282
283 void GUIChatConsole::drawBackground()
284 {
285         video::IVideoDriver* driver = Environment->getVideoDriver();
286         if (m_background != NULL)
287         {
288                 core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
289                 driver->draw2DImage(
290                         m_background,
291                         v2s32(0, 0),
292                         sourcerect,
293                         &AbsoluteClippingRect,
294                         m_background_color,
295                         false);
296         }
297         else
298         {
299                 driver->draw2DRectangle(
300                         m_background_color,
301                         core::rect<s32>(0, 0, m_screensize.X, m_height),
302                         &AbsoluteClippingRect);
303         }
304 }
305
306 void GUIChatConsole::drawText()
307 {
308         if (m_font == NULL)
309                 return;
310
311         ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
312         for (u32 row = 0; row < buf.getRows(); ++row)
313         {
314                 const ChatFormattedLine& line = buf.getFormattedLine(row);
315                 if (line.fragments.empty())
316                         continue;
317
318                 s32 line_height = m_fontsize.Y;
319                 s32 y = row * line_height + m_height - m_desired_height;
320                 if (y + line_height < 0)
321                         continue;
322
323                 for (const ChatFormattedFragment &fragment : line.fragments) {
324                         s32 x = (fragment.column + 1) * m_fontsize.X;
325                         core::rect<s32> destrect(
326                                 x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
327
328                         if (m_font->getType() == irr::gui::EGFT_CUSTOM) {
329                                 // Draw colored text if possible
330                                 gui::CGUITTFont *tmp = static_cast<gui::CGUITTFont*>(m_font);
331                                 tmp->draw(
332                                         fragment.text,
333                                         destrect,
334                                         false,
335                                         false,
336                                         &AbsoluteClippingRect);
337                         } else {
338                                 // Otherwise use standard text
339                                 m_font->draw(
340                                         fragment.text.c_str(),
341                                         destrect,
342                                         video::SColor(255, 255, 255, 255),
343                                         false,
344                                         false,
345                                         &AbsoluteClippingRect);
346                         }
347                 }
348         }
349 }
350
351 void GUIChatConsole::drawPrompt()
352 {
353         if (!m_font)
354                 return;
355
356         ChatPrompt& prompt = m_chat_backend->getPrompt();
357         std::wstring prompt_text = prompt.getVisiblePortion();
358
359         u32 font_width  = m_fontsize.X;
360         u32 font_height = m_fontsize.Y;
361
362         core::dimension2d<u32> size = m_font->getDimension(prompt_text.c_str());
363         u32 text_width = size.Width;
364         if (size.Height > font_height)
365                 font_height = size.Height;
366
367         u32 row = m_chat_backend->getConsoleBuffer().getRows();
368         s32 y = row * font_height + m_height - m_desired_height;
369
370         core::rect<s32> destrect(
371                 font_width, y, font_width + text_width, y + font_height);
372         m_font->draw(
373                 prompt_text.c_str(),
374                 destrect,
375                 video::SColor(255, 255, 255, 255),
376                 false,
377                 false,
378                 &AbsoluteClippingRect);
379
380         // Draw the cursor during on periods
381         if ((m_cursor_blink & 0x8000) != 0)
382         {
383                 s32 cursor_pos = prompt.getVisibleCursorPosition();
384
385                 if (cursor_pos >= 0)
386                 {
387
388                         u32 text_to_cursor_pos_width = m_font->getDimension(prompt_text.substr(0, cursor_pos).c_str()).Width;
389
390                         s32 cursor_len = prompt.getCursorLength();
391                         video::IVideoDriver* driver = Environment->getVideoDriver();
392                         s32 x = font_width + text_to_cursor_pos_width;
393                         core::rect<s32> destrect(
394                                 x,
395                                 y + font_height * (1.0 - m_cursor_height),
396                                 x + font_width * MYMAX(cursor_len, 1),
397                                 y + font_height * (cursor_len ? m_cursor_height+1 : 1)
398                         );
399                         video::SColor cursor_color(255,255,255,255);
400                         driver->draw2DRectangle(
401                                 cursor_color,
402                                 destrect,
403                                 &AbsoluteClippingRect);
404                 }
405         }
406
407 }
408
409 bool GUIChatConsole::OnEvent(const SEvent& event)
410 {
411
412         ChatPrompt &prompt = m_chat_backend->getPrompt();
413
414         if (event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
415         {
416                 // CTRL up
417                 if (isInCtrlKeys(event.KeyInput.Key))
418                 {
419                         m_is_ctrl_down = false;
420                 }
421         }
422         else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
423         {
424                 // CTRL down
425                 if (isInCtrlKeys(event.KeyInput.Key)) {
426                         m_is_ctrl_down = true;
427                 }
428
429                 // Key input
430                 if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
431                         closeConsole();
432
433                         // inhibit open so the_game doesn't reopen immediately
434                         m_open_inhibited = 50;
435                         m_close_on_enter = false;
436                         return true;
437                 }
438
439                 // Mac OS sends private use characters along with some keys.
440                 bool has_char = event.KeyInput.Char && !event.KeyInput.Control &&
441                                 !iswcntrl(event.KeyInput.Char) && !IS_PRIVATE_USE_CHAR(event.KeyInput.Char);
442
443                 if (event.KeyInput.Key == KEY_ESCAPE) {
444                         closeConsoleAtOnce();
445                         m_close_on_enter = false;
446                         // inhibit open so the_game doesn't reopen immediately
447                         m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
448                         return true;
449                 }
450                 else if(event.KeyInput.Key == KEY_PRIOR)
451                 {
452                         if (!has_char) { // no num lock
453                                 m_chat_backend->scrollPageUp();
454                                 return true;
455                         }
456                 }
457                 else if(event.KeyInput.Key == KEY_NEXT)
458                 {
459                         if (!has_char) { // no num lock
460                                 m_chat_backend->scrollPageDown();
461                                 return true;
462                         }
463                 }
464                 else if(event.KeyInput.Key == KEY_RETURN)
465                 {
466                         prompt.addToHistory(prompt.getLine());
467                         std::wstring text = prompt.replace(L"");
468                         m_client->typeChatMessage(text);
469                         if (m_close_on_enter) {
470                                 closeConsoleAtOnce();
471                                 m_close_on_enter = false;
472                         }
473                         return true;
474                 }
475                 else if(event.KeyInput.Key == KEY_UP)
476                 {
477                         if (!has_char) { // no num lock
478                                 // Up pressed
479                                 // Move back in history
480                                 prompt.historyPrev();
481                                 return true;
482                         }
483                 }
484                 else if(event.KeyInput.Key == KEY_DOWN)
485                 {
486                         if (!has_char) { // no num lock
487                                 // Down pressed
488                                 // Move forward in history
489                                 prompt.historyNext();
490                                 return true;
491                         }
492                 }
493                 else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
494                 {
495                         if (!has_char) { // no num lock
496                                 // Left/right pressed
497                                 // Move/select character/word to the left depending on control and shift keys
498                                 ChatPrompt::CursorOp op = event.KeyInput.Shift ?
499                                         ChatPrompt::CURSOROP_SELECT :
500                                         ChatPrompt::CURSOROP_MOVE;
501                                 ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
502                                         ChatPrompt::CURSOROP_DIR_LEFT :
503                                         ChatPrompt::CURSOROP_DIR_RIGHT;
504                                 ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
505                                         ChatPrompt::CURSOROP_SCOPE_WORD :
506                                         ChatPrompt::CURSOROP_SCOPE_CHARACTER;
507                                 prompt.cursorOperation(op, dir, scope);
508                                 return true;
509                         }
510                 }
511                 else if(event.KeyInput.Key == KEY_HOME)
512                 {
513                         if (!has_char) { // no num lock
514                                 // Home pressed
515                                 // move to beginning of line
516                                 prompt.cursorOperation(
517                                         ChatPrompt::CURSOROP_MOVE,
518                                         ChatPrompt::CURSOROP_DIR_LEFT,
519                                         ChatPrompt::CURSOROP_SCOPE_LINE);
520                                 return true;
521                         }
522                 }
523                 else if(event.KeyInput.Key == KEY_END)
524                 {
525                         if (!has_char) { // no num lock
526                                 // End pressed
527                                 // move to end of line
528                                 prompt.cursorOperation(
529                                         ChatPrompt::CURSOROP_MOVE,
530                                         ChatPrompt::CURSOROP_DIR_RIGHT,
531                                         ChatPrompt::CURSOROP_SCOPE_LINE);
532                                 return true;
533                         }
534                 }
535                 else if(event.KeyInput.Key == KEY_BACK)
536                 {
537                         // Backspace or Ctrl-Backspace pressed
538                         // delete character / word to the left
539                         ChatPrompt::CursorOpScope scope =
540                                 event.KeyInput.Control ?
541                                 ChatPrompt::CURSOROP_SCOPE_WORD :
542                                 ChatPrompt::CURSOROP_SCOPE_CHARACTER;
543                         prompt.cursorOperation(
544                                 ChatPrompt::CURSOROP_DELETE,
545                                 ChatPrompt::CURSOROP_DIR_LEFT,
546                                 scope);
547                         return true;
548                 }
549                 else if(event.KeyInput.Key == KEY_DELETE)
550                 {
551                         if (!has_char) { // no num lock
552                                 // Delete or Ctrl-Delete pressed
553                                 // delete character / word to the right
554                                 ChatPrompt::CursorOpScope scope =
555                                         event.KeyInput.Control ?
556                                         ChatPrompt::CURSOROP_SCOPE_WORD :
557                                         ChatPrompt::CURSOROP_SCOPE_CHARACTER;
558                                 prompt.cursorOperation(
559                                         ChatPrompt::CURSOROP_DELETE,
560                                         ChatPrompt::CURSOROP_DIR_RIGHT,
561                                         scope);
562                                 return true;
563                         }
564                 }
565                 else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
566                 {
567                         // Ctrl-A pressed
568                         // Select all text
569                         prompt.cursorOperation(
570                                 ChatPrompt::CURSOROP_SELECT,
571                                 ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
572                                 ChatPrompt::CURSOROP_SCOPE_LINE);
573                         return true;
574                 }
575                 else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
576                 {
577                         // Ctrl-C pressed
578                         // Copy text to clipboard
579                         if (prompt.getCursorLength() <= 0)
580                                 return true;
581                         std::wstring wselected = prompt.getSelection();
582                         std::string selected = wide_to_utf8(wselected);
583                         Environment->getOSOperator()->copyToClipboard(selected.c_str());
584                         return true;
585                 }
586                 else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
587                 {
588                         // Ctrl-V pressed
589                         // paste text from clipboard
590                         if (prompt.getCursorLength() > 0) {
591                                 // Delete selected section of text
592                                 prompt.cursorOperation(
593                                         ChatPrompt::CURSOROP_DELETE,
594                                         ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
595                                         ChatPrompt::CURSOROP_SCOPE_SELECTION);
596                         }
597                         IOSOperator *os_operator = Environment->getOSOperator();
598                         const c8 *text = os_operator->getTextFromClipboard();
599                         if (!text)
600                                 return true;
601                         prompt.input(utf8_to_wide(text));
602                         return true;
603                 }
604                 else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
605                 {
606                         // Ctrl-X pressed
607                         // Cut text to clipboard
608                         if (prompt.getCursorLength() <= 0)
609                                 return true;
610                         std::wstring wselected = prompt.getSelection();
611                         std::string selected = wide_to_utf8(wselected);
612                         Environment->getOSOperator()->copyToClipboard(selected.c_str());
613                         prompt.cursorOperation(
614                                 ChatPrompt::CURSOROP_DELETE,
615                                 ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
616                                 ChatPrompt::CURSOROP_SCOPE_SELECTION);
617                         return true;
618                 }
619                 else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
620                 {
621                         // Ctrl-U pressed
622                         // kill line to left end
623                         prompt.cursorOperation(
624                                 ChatPrompt::CURSOROP_DELETE,
625                                 ChatPrompt::CURSOROP_DIR_LEFT,
626                                 ChatPrompt::CURSOROP_SCOPE_LINE);
627                         return true;
628                 }
629                 else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
630                 {
631                         // Ctrl-K pressed
632                         // kill line to right end
633                         prompt.cursorOperation(
634                                 ChatPrompt::CURSOROP_DELETE,
635                                 ChatPrompt::CURSOROP_DIR_RIGHT,
636                                 ChatPrompt::CURSOROP_SCOPE_LINE);
637                         return true;
638                 }
639                 else if(event.KeyInput.Key == KEY_TAB)
640                 {
641                         // Tab or Shift-Tab pressed
642                         // Nick completion
643                         std::list<std::string> names = m_client->getConnectedPlayerNames();
644                         bool backwards = event.KeyInput.Shift;
645                         prompt.nickCompletion(names, backwards);
646                         return true;
647                 }
648
649                 if (has_char) {
650                         prompt.input(event.KeyInput.Char);
651                         return true;
652                 }
653         }
654         else if(event.EventType == EET_MOUSE_INPUT_EVENT)
655         {
656                 if (event.MouseInput.Event == EMIE_MOUSE_WHEEL)
657                 {
658                         s32 rows = myround(-3.0 * event.MouseInput.Wheel);
659                         m_chat_backend->scroll(rows);
660                 }
661                 // Middle click or ctrl-click opens weblink, if enabled in config
662                 else if(m_cache_clickable_chat_weblinks && (
663                                 event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN ||
664                                 (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN && m_is_ctrl_down)
665                                 ))
666                 {
667                         // If clicked within console output region
668                         if (event.MouseInput.Y / m_fontsize.Y < (m_height / m_fontsize.Y) - 1 )
669                         {
670                                 // Translate pixel position to font position
671                                 middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y);
672                         }
673                 }
674         }
675         else if(event.EventType == EET_STRING_INPUT_EVENT)
676         {
677                 prompt.input(std::wstring(event.StringInput.Str->c_str()));
678                 return true;
679         }
680
681         return Parent ? Parent->OnEvent(event) : false;
682 }
683
684 void GUIChatConsole::setVisible(bool visible)
685 {
686         m_open = visible;
687         IGUIElement::setVisible(visible);
688         if (!visible) {
689                 m_height = 0;
690                 recalculateConsolePosition();
691         }
692 }
693
694 void GUIChatConsole::middleClick(s32 col, s32 row)
695 {
696         // Prevent accidental rapid clicking
697         static u64 s_oldtime = 0;
698         u64 newtime = porting::getTimeMs();
699
700         // 0.6 seconds should suffice
701         if (newtime - s_oldtime < 600)
702                 return;
703         s_oldtime = newtime;
704
705         const std::vector<ChatFormattedFragment> &
706                         frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments;
707         std::string weblink = ""; // from frag meta
708
709         // Identify targetted fragment, if exists
710         int indx = frags.size() - 1;
711         if (indx < 0) {
712                 // Invalid row, frags is empty
713                 return;
714         }
715         // Scan from right to left, offset by 1 font space because left margin
716         while (indx > -1 && (u32)col < frags[indx].column + 1) {
717                 --indx;
718         }
719         if (indx > -1) {
720                 weblink = frags[indx].weblink;
721                 // Note if(indx < 0) then a frag somehow had a corrupt column field
722         }
723
724         /*
725         // Debug help. Please keep this in case adjustments are made later.
726         std::string ws;
727         ws = "Middleclick: (" + std::to_string(col) + ',' + std::to_string(row) + ')' + " frags:";
728         // show all frags <position>(<length>) for the clicked row
729         for (u32 i=0;i<frags.size();++i) {
730                 if (indx == int(i))
731                         // tag the actual clicked frag
732                         ws += '*';
733                 ws += std::to_string(frags.at(i).column) + '('
734                         + std::to_string(frags.at(i).text.size()) + "),";
735         }
736         actionstream << ws << std::endl;
737         */
738
739         // User notification
740         if (weblink.size() != 0) {
741                 std::ostringstream msg;
742                 msg << " * ";
743                 if (porting::open_url(weblink)) {
744                         msg << gettext("Opening webpage");
745                 }
746                 else {
747                         msg << gettext("Failed to open webpage");
748                 }
749                 msg << " '" << weblink << "'";
750                 m_chat_backend->addUnparsedMessage(utf8_to_wide(msg.str()));
751         }
752 }