guiChatConsole.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "guiChatConsole.h"
  17. #include "chat.h"
  18. #include "client/client.h"
  19. #include "debug.h"
  20. #include "gettime.h"
  21. #include "client/keycode.h"
  22. #include "settings.h"
  23. #include "porting.h"
  24. #include "client/tile.h"
  25. #include "client/fontengine.h"
  26. #include "log.h"
  27. #include "gettext.h"
  28. #include <string>
  29. #if USE_FREETYPE
  30. #include "irrlicht_changes/CGUITTFont.h"
  31. #endif
  32. inline u32 clamp_u8(s32 value)
  33. {
  34. return (u32) MYMIN(MYMAX(value, 0), 255);
  35. }
  36. GUIChatConsole::GUIChatConsole(
  37. gui::IGUIEnvironment* env,
  38. gui::IGUIElement* parent,
  39. s32 id,
  40. ChatBackend* backend,
  41. Client* client,
  42. IMenuManager* menumgr
  43. ):
  44. IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
  45. core::rect<s32>(0,0,100,100)),
  46. m_chat_backend(backend),
  47. m_client(client),
  48. m_menumgr(menumgr),
  49. m_animate_time_old(porting::getTimeMs())
  50. {
  51. // load background settings
  52. s32 console_alpha = g_settings->getS32("console_alpha");
  53. m_background_color.setAlpha(clamp_u8(console_alpha));
  54. // load the background texture depending on settings
  55. ITextureSource *tsrc = client->getTextureSource();
  56. if (tsrc->isKnownSourceImage("background_chat.jpg")) {
  57. m_background = tsrc->getTexture("background_chat.jpg");
  58. m_background_color.setRed(255);
  59. m_background_color.setGreen(255);
  60. m_background_color.setBlue(255);
  61. } else {
  62. v3f console_color = g_settings->getV3F("console_color");
  63. m_background_color.setRed(clamp_u8(myround(console_color.X)));
  64. m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
  65. m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
  66. }
  67. u16 chat_font_size = g_settings->getU16("chat_font_size");
  68. m_font = g_fontengine->getFont(chat_font_size != 0 ?
  69. chat_font_size : FONT_SIZE_UNSPECIFIED, FM_Mono);
  70. if (!m_font) {
  71. errorstream << "GUIChatConsole: Unable to load mono font" << std::endl;
  72. } else {
  73. core::dimension2d<u32> dim = m_font->getDimension(L"M");
  74. m_fontsize = v2u32(dim.Width, dim.Height);
  75. m_font->grab();
  76. }
  77. m_fontsize.X = MYMAX(m_fontsize.X, 1);
  78. m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
  79. // set default cursor options
  80. setCursor(true, true, 2.0, 0.1);
  81. }
  82. GUIChatConsole::~GUIChatConsole()
  83. {
  84. if (m_font)
  85. m_font->drop();
  86. }
  87. void GUIChatConsole::openConsole(f32 scale)
  88. {
  89. assert(scale > 0.0f && scale <= 1.0f);
  90. m_open = true;
  91. m_desired_height_fraction = scale;
  92. m_desired_height = scale * m_screensize.Y;
  93. reformatConsole();
  94. m_animate_time_old = porting::getTimeMs();
  95. IGUIElement::setVisible(true);
  96. Environment->setFocus(this);
  97. m_menumgr->createdMenu(this);
  98. }
  99. bool GUIChatConsole::isOpen() const
  100. {
  101. return m_open;
  102. }
  103. bool GUIChatConsole::isOpenInhibited() const
  104. {
  105. return m_open_inhibited > 0;
  106. }
  107. void GUIChatConsole::closeConsole()
  108. {
  109. m_open = false;
  110. Environment->removeFocus(this);
  111. m_menumgr->deletingMenu(this);
  112. }
  113. void GUIChatConsole::closeConsoleAtOnce()
  114. {
  115. closeConsole();
  116. m_height = 0;
  117. recalculateConsolePosition();
  118. }
  119. f32 GUIChatConsole::getDesiredHeight() const
  120. {
  121. return m_desired_height_fraction;
  122. }
  123. void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line)
  124. {
  125. ChatPrompt& prompt = m_chat_backend->getPrompt();
  126. prompt.addToHistory(prompt.getLine());
  127. prompt.replace(line);
  128. }
  129. void GUIChatConsole::setCursor(
  130. bool visible, bool blinking, f32 blink_speed, f32 relative_height)
  131. {
  132. if (visible)
  133. {
  134. if (blinking)
  135. {
  136. // leave m_cursor_blink unchanged
  137. m_cursor_blink_speed = blink_speed;
  138. }
  139. else
  140. {
  141. m_cursor_blink = 0x8000; // on
  142. m_cursor_blink_speed = 0.0;
  143. }
  144. }
  145. else
  146. {
  147. m_cursor_blink = 0; // off
  148. m_cursor_blink_speed = 0.0;
  149. }
  150. m_cursor_height = relative_height;
  151. }
  152. void GUIChatConsole::draw()
  153. {
  154. if(!IsVisible)
  155. return;
  156. video::IVideoDriver* driver = Environment->getVideoDriver();
  157. // Check screen size
  158. v2u32 screensize = driver->getScreenSize();
  159. if (screensize != m_screensize)
  160. {
  161. // screen size has changed
  162. // scale current console height to new window size
  163. if (m_screensize.Y != 0)
  164. m_height = m_height * screensize.Y / m_screensize.Y;
  165. m_screensize = screensize;
  166. m_desired_height = m_desired_height_fraction * m_screensize.Y;
  167. reformatConsole();
  168. }
  169. // Animation
  170. u64 now = porting::getTimeMs();
  171. animate(now - m_animate_time_old);
  172. m_animate_time_old = now;
  173. // Draw console elements if visible
  174. if (m_height > 0)
  175. {
  176. drawBackground();
  177. drawText();
  178. drawPrompt();
  179. }
  180. gui::IGUIElement::draw();
  181. }
  182. void GUIChatConsole::reformatConsole()
  183. {
  184. s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
  185. s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
  186. if (cols <= 0 || rows <= 0)
  187. cols = rows = 0;
  188. recalculateConsolePosition();
  189. m_chat_backend->reformat(cols, rows);
  190. }
  191. void GUIChatConsole::recalculateConsolePosition()
  192. {
  193. core::rect<s32> rect(0, 0, m_screensize.X, m_height);
  194. DesiredRect = rect;
  195. recalculateAbsolutePosition(false);
  196. }
  197. void GUIChatConsole::animate(u32 msec)
  198. {
  199. // animate the console height
  200. s32 goal = m_open ? m_desired_height : 0;
  201. // Set invisible if close animation finished (reset by openConsole)
  202. // This function (animate()) is never called once its visibility becomes false so do not
  203. // actually set visible to false before the inhibited period is over
  204. if (!m_open && m_height == 0 && m_open_inhibited == 0)
  205. IGUIElement::setVisible(false);
  206. if (m_height != goal)
  207. {
  208. s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
  209. if (max_change == 0)
  210. max_change = 1;
  211. if (m_height < goal)
  212. {
  213. // increase height
  214. if (m_height + max_change < goal)
  215. m_height += max_change;
  216. else
  217. m_height = goal;
  218. }
  219. else
  220. {
  221. // decrease height
  222. if (m_height > goal + max_change)
  223. m_height -= max_change;
  224. else
  225. m_height = goal;
  226. }
  227. recalculateConsolePosition();
  228. }
  229. // blink the cursor
  230. if (m_cursor_blink_speed != 0.0)
  231. {
  232. u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
  233. if (blink_increase == 0)
  234. blink_increase = 1;
  235. m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
  236. }
  237. // decrease open inhibit counter
  238. if (m_open_inhibited > msec)
  239. m_open_inhibited -= msec;
  240. else
  241. m_open_inhibited = 0;
  242. }
  243. void GUIChatConsole::drawBackground()
  244. {
  245. video::IVideoDriver* driver = Environment->getVideoDriver();
  246. if (m_background != NULL)
  247. {
  248. core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
  249. driver->draw2DImage(
  250. m_background,
  251. v2s32(0, 0),
  252. sourcerect,
  253. &AbsoluteClippingRect,
  254. m_background_color,
  255. false);
  256. }
  257. else
  258. {
  259. driver->draw2DRectangle(
  260. m_background_color,
  261. core::rect<s32>(0, 0, m_screensize.X, m_height),
  262. &AbsoluteClippingRect);
  263. }
  264. }
  265. void GUIChatConsole::drawText()
  266. {
  267. if (m_font == NULL)
  268. return;
  269. ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
  270. for (u32 row = 0; row < buf.getRows(); ++row)
  271. {
  272. const ChatFormattedLine& line = buf.getFormattedLine(row);
  273. if (line.fragments.empty())
  274. continue;
  275. s32 line_height = m_fontsize.Y;
  276. s32 y = row * line_height + m_height - m_desired_height;
  277. if (y + line_height < 0)
  278. continue;
  279. for (const ChatFormattedFragment &fragment : line.fragments) {
  280. s32 x = (fragment.column + 1) * m_fontsize.X;
  281. core::rect<s32> destrect(
  282. x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
  283. #if USE_FREETYPE
  284. if (m_font->getType() == irr::gui::EGFT_CUSTOM) {
  285. // Draw colored text if FreeType is enabled
  286. irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
  287. tmp->draw(
  288. fragment.text,
  289. destrect,
  290. video::SColor(255, 255, 255, 255),
  291. false,
  292. false,
  293. &AbsoluteClippingRect);
  294. } else
  295. #endif
  296. {
  297. // Otherwise use standard text
  298. m_font->draw(
  299. fragment.text.c_str(),
  300. destrect,
  301. video::SColor(255, 255, 255, 255),
  302. false,
  303. false,
  304. &AbsoluteClippingRect);
  305. }
  306. }
  307. }
  308. }
  309. void GUIChatConsole::drawPrompt()
  310. {
  311. if (!m_font)
  312. return;
  313. u32 row = m_chat_backend->getConsoleBuffer().getRows();
  314. s32 line_height = m_fontsize.Y;
  315. s32 y = row * line_height + m_height - m_desired_height;
  316. ChatPrompt& prompt = m_chat_backend->getPrompt();
  317. std::wstring prompt_text = prompt.getVisiblePortion();
  318. // FIXME Draw string at once, not character by character
  319. // That will only work with the cursor once we have a monospace font
  320. for (u32 i = 0; i < prompt_text.size(); ++i)
  321. {
  322. wchar_t ws[2] = {prompt_text[i], 0};
  323. s32 x = (1 + i) * m_fontsize.X;
  324. core::rect<s32> destrect(
  325. x, y, x + m_fontsize.X, y + m_fontsize.Y);
  326. m_font->draw(
  327. ws,
  328. destrect,
  329. video::SColor(255, 255, 255, 255),
  330. false,
  331. false,
  332. &AbsoluteClippingRect);
  333. }
  334. // Draw the cursor during on periods
  335. if ((m_cursor_blink & 0x8000) != 0)
  336. {
  337. s32 cursor_pos = prompt.getVisibleCursorPosition();
  338. if (cursor_pos >= 0)
  339. {
  340. s32 cursor_len = prompt.getCursorLength();
  341. video::IVideoDriver* driver = Environment->getVideoDriver();
  342. s32 x = (1 + cursor_pos) * m_fontsize.X;
  343. core::rect<s32> destrect(
  344. x,
  345. y + m_fontsize.Y * (1.0 - m_cursor_height),
  346. x + m_fontsize.X * MYMAX(cursor_len, 1),
  347. y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
  348. );
  349. video::SColor cursor_color(255,255,255,255);
  350. driver->draw2DRectangle(
  351. cursor_color,
  352. destrect,
  353. &AbsoluteClippingRect);
  354. }
  355. }
  356. }
  357. bool GUIChatConsole::OnEvent(const SEvent& event)
  358. {
  359. ChatPrompt &prompt = m_chat_backend->getPrompt();
  360. if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
  361. {
  362. // Key input
  363. if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
  364. closeConsole();
  365. // inhibit open so the_game doesn't reopen immediately
  366. m_open_inhibited = 50;
  367. m_close_on_enter = false;
  368. return true;
  369. }
  370. if (event.KeyInput.Key == KEY_ESCAPE) {
  371. closeConsoleAtOnce();
  372. m_close_on_enter = false;
  373. // inhibit open so the_game doesn't reopen immediately
  374. m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
  375. return true;
  376. }
  377. else if(event.KeyInput.Key == KEY_PRIOR)
  378. {
  379. m_chat_backend->scrollPageUp();
  380. return true;
  381. }
  382. else if(event.KeyInput.Key == KEY_NEXT)
  383. {
  384. m_chat_backend->scrollPageDown();
  385. return true;
  386. }
  387. else if(event.KeyInput.Key == KEY_RETURN)
  388. {
  389. prompt.addToHistory(prompt.getLine());
  390. std::wstring text = prompt.replace(L"");
  391. m_client->typeChatMessage(text);
  392. if (m_close_on_enter) {
  393. closeConsoleAtOnce();
  394. m_close_on_enter = false;
  395. }
  396. return true;
  397. }
  398. else if(event.KeyInput.Key == KEY_UP)
  399. {
  400. // Up pressed
  401. // Move back in history
  402. prompt.historyPrev();
  403. return true;
  404. }
  405. else if(event.KeyInput.Key == KEY_DOWN)
  406. {
  407. // Down pressed
  408. // Move forward in history
  409. prompt.historyNext();
  410. return true;
  411. }
  412. else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
  413. {
  414. // Left/right pressed
  415. // Move/select character/word to the left depending on control and shift keys
  416. ChatPrompt::CursorOp op = event.KeyInput.Shift ?
  417. ChatPrompt::CURSOROP_SELECT :
  418. ChatPrompt::CURSOROP_MOVE;
  419. ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
  420. ChatPrompt::CURSOROP_DIR_LEFT :
  421. ChatPrompt::CURSOROP_DIR_RIGHT;
  422. ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
  423. ChatPrompt::CURSOROP_SCOPE_WORD :
  424. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  425. prompt.cursorOperation(op, dir, scope);
  426. return true;
  427. }
  428. else if(event.KeyInput.Key == KEY_HOME)
  429. {
  430. // Home pressed
  431. // move to beginning of line
  432. prompt.cursorOperation(
  433. ChatPrompt::CURSOROP_MOVE,
  434. ChatPrompt::CURSOROP_DIR_LEFT,
  435. ChatPrompt::CURSOROP_SCOPE_LINE);
  436. return true;
  437. }
  438. else if(event.KeyInput.Key == KEY_END)
  439. {
  440. // End pressed
  441. // move to end of line
  442. prompt.cursorOperation(
  443. ChatPrompt::CURSOROP_MOVE,
  444. ChatPrompt::CURSOROP_DIR_RIGHT,
  445. ChatPrompt::CURSOROP_SCOPE_LINE);
  446. return true;
  447. }
  448. else if(event.KeyInput.Key == KEY_BACK)
  449. {
  450. // Backspace or Ctrl-Backspace pressed
  451. // delete character / word to the left
  452. ChatPrompt::CursorOpScope scope =
  453. event.KeyInput.Control ?
  454. ChatPrompt::CURSOROP_SCOPE_WORD :
  455. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  456. prompt.cursorOperation(
  457. ChatPrompt::CURSOROP_DELETE,
  458. ChatPrompt::CURSOROP_DIR_LEFT,
  459. scope);
  460. return true;
  461. }
  462. else if(event.KeyInput.Key == KEY_DELETE)
  463. {
  464. // Delete or Ctrl-Delete pressed
  465. // delete character / word to the right
  466. ChatPrompt::CursorOpScope scope =
  467. event.KeyInput.Control ?
  468. ChatPrompt::CURSOROP_SCOPE_WORD :
  469. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  470. prompt.cursorOperation(
  471. ChatPrompt::CURSOROP_DELETE,
  472. ChatPrompt::CURSOROP_DIR_RIGHT,
  473. scope);
  474. return true;
  475. }
  476. else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
  477. {
  478. // Ctrl-A pressed
  479. // Select all text
  480. prompt.cursorOperation(
  481. ChatPrompt::CURSOROP_SELECT,
  482. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  483. ChatPrompt::CURSOROP_SCOPE_LINE);
  484. return true;
  485. }
  486. else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
  487. {
  488. // Ctrl-C pressed
  489. // Copy text to clipboard
  490. if (prompt.getCursorLength() <= 0)
  491. return true;
  492. std::wstring wselected = prompt.getSelection();
  493. std::string selected(wselected.begin(), wselected.end());
  494. Environment->getOSOperator()->copyToClipboard(selected.c_str());
  495. return true;
  496. }
  497. else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
  498. {
  499. // Ctrl-V pressed
  500. // paste text from clipboard
  501. if (prompt.getCursorLength() > 0) {
  502. // Delete selected section of text
  503. prompt.cursorOperation(
  504. ChatPrompt::CURSOROP_DELETE,
  505. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  506. ChatPrompt::CURSOROP_SCOPE_SELECTION);
  507. }
  508. IOSOperator *os_operator = Environment->getOSOperator();
  509. const c8 *text = os_operator->getTextFromClipboard();
  510. if (!text)
  511. return true;
  512. std::basic_string<unsigned char> str((const unsigned char*)text);
  513. prompt.input(std::wstring(str.begin(), str.end()));
  514. return true;
  515. }
  516. else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
  517. {
  518. // Ctrl-X pressed
  519. // Cut text to clipboard
  520. if (prompt.getCursorLength() <= 0)
  521. return true;
  522. std::wstring wselected = prompt.getSelection();
  523. std::string selected(wselected.begin(), wselected.end());
  524. Environment->getOSOperator()->copyToClipboard(selected.c_str());
  525. prompt.cursorOperation(
  526. ChatPrompt::CURSOROP_DELETE,
  527. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  528. ChatPrompt::CURSOROP_SCOPE_SELECTION);
  529. return true;
  530. }
  531. else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
  532. {
  533. // Ctrl-U pressed
  534. // kill line to left end
  535. prompt.cursorOperation(
  536. ChatPrompt::CURSOROP_DELETE,
  537. ChatPrompt::CURSOROP_DIR_LEFT,
  538. ChatPrompt::CURSOROP_SCOPE_LINE);
  539. return true;
  540. }
  541. else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
  542. {
  543. // Ctrl-K pressed
  544. // kill line to right end
  545. prompt.cursorOperation(
  546. ChatPrompt::CURSOROP_DELETE,
  547. ChatPrompt::CURSOROP_DIR_RIGHT,
  548. ChatPrompt::CURSOROP_SCOPE_LINE);
  549. return true;
  550. }
  551. else if(event.KeyInput.Key == KEY_TAB)
  552. {
  553. // Tab or Shift-Tab pressed
  554. // Nick completion
  555. std::list<std::string> names = m_client->getConnectedPlayerNames();
  556. bool backwards = event.KeyInput.Shift;
  557. prompt.nickCompletion(names, backwards);
  558. return true;
  559. } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
  560. #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
  561. wchar_t wc = L'_';
  562. mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
  563. prompt.input(wc);
  564. #else
  565. prompt.input(event.KeyInput.Char);
  566. #endif
  567. return true;
  568. }
  569. }
  570. else if(event.EventType == EET_MOUSE_INPUT_EVENT)
  571. {
  572. if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
  573. {
  574. s32 rows = myround(-3.0 * event.MouseInput.Wheel);
  575. m_chat_backend->scroll(rows);
  576. }
  577. }
  578. return Parent ? Parent->OnEvent(event) : false;
  579. }
  580. void GUIChatConsole::setVisible(bool visible)
  581. {
  582. m_open = visible;
  583. IGUIElement::setVisible(visible);
  584. if (!visible) {
  585. m_height = 0;
  586. recalculateConsolePosition();
  587. }
  588. }