guiChatConsole.cpp 16 KB

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