guiChatConsole.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  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 "xCGUITTFont.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_desired_height = m_desired_height_fraction * m_screensize.Y;
  164. m_screensize = screensize;
  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. m_chat_backend->reformat(cols, rows);
  187. }
  188. void GUIChatConsole::recalculateConsolePosition()
  189. {
  190. core::rect<s32> rect(0, 0, m_screensize.X, m_height);
  191. DesiredRect = rect;
  192. recalculateAbsolutePosition(false);
  193. }
  194. void GUIChatConsole::animate(u32 msec)
  195. {
  196. // animate the console height
  197. s32 goal = m_open ? m_desired_height : 0;
  198. // Set invisible if close animation finished (reset by openConsole)
  199. // This function (animate()) is never called once its visibility becomes false so do not
  200. // actually set visible to false before the inhibited period is over
  201. if (!m_open && m_height == 0 && m_open_inhibited == 0)
  202. IGUIElement::setVisible(false);
  203. if (m_height != goal)
  204. {
  205. s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
  206. if (max_change == 0)
  207. max_change = 1;
  208. if (m_height < goal)
  209. {
  210. // increase height
  211. if (m_height + max_change < goal)
  212. m_height += max_change;
  213. else
  214. m_height = goal;
  215. }
  216. else
  217. {
  218. // decrease height
  219. if (m_height > goal + max_change)
  220. m_height -= max_change;
  221. else
  222. m_height = goal;
  223. }
  224. recalculateConsolePosition();
  225. }
  226. // blink the cursor
  227. if (m_cursor_blink_speed != 0.0)
  228. {
  229. u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
  230. if (blink_increase == 0)
  231. blink_increase = 1;
  232. m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
  233. }
  234. // decrease open inhibit counter
  235. if (m_open_inhibited > msec)
  236. m_open_inhibited -= msec;
  237. else
  238. m_open_inhibited = 0;
  239. }
  240. void GUIChatConsole::drawBackground()
  241. {
  242. video::IVideoDriver* driver = Environment->getVideoDriver();
  243. if (m_background != NULL)
  244. {
  245. core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
  246. driver->draw2DImage(
  247. m_background,
  248. v2s32(0, 0),
  249. sourcerect,
  250. &AbsoluteClippingRect,
  251. m_background_color,
  252. false);
  253. }
  254. else
  255. {
  256. driver->draw2DRectangle(
  257. m_background_color,
  258. core::rect<s32>(0, 0, m_screensize.X, m_height),
  259. &AbsoluteClippingRect);
  260. }
  261. }
  262. void GUIChatConsole::drawText()
  263. {
  264. if (m_font == NULL)
  265. return;
  266. ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
  267. for (u32 row = 0; row < buf.getRows(); ++row)
  268. {
  269. const ChatFormattedLine& line = buf.getFormattedLine(row);
  270. if (line.fragments.empty())
  271. continue;
  272. s32 line_height = m_fontsize.Y;
  273. s32 y = row * line_height + m_height - m_desired_height;
  274. if (y + line_height < 0)
  275. continue;
  276. for (u32 i = 0; i < line.fragments.size(); ++i)
  277. {
  278. const ChatFormattedFragment& fragment = line.fragments[i];
  279. s32 x = (fragment.column + 1) * m_fontsize.X;
  280. core::rect<s32> destrect(
  281. x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
  282. #if USE_FREETYPE
  283. // Draw colored text if FreeType is enabled
  284. irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(m_font);
  285. tmp->draw(
  286. fragment.text,
  287. destrect,
  288. video::SColor(255, 255, 255, 255),
  289. false,
  290. false,
  291. &AbsoluteClippingRect);
  292. #else
  293. // Otherwise use standard text
  294. m_font->draw(
  295. fragment.text.c_str(),
  296. destrect,
  297. video::SColor(255, 255, 255, 255),
  298. false,
  299. false,
  300. &AbsoluteClippingRect);
  301. #endif
  302. }
  303. }
  304. }
  305. void GUIChatConsole::drawPrompt()
  306. {
  307. if (!m_font)
  308. return;
  309. u32 row = m_chat_backend->getConsoleBuffer().getRows();
  310. s32 line_height = m_fontsize.Y;
  311. s32 y = row * line_height + m_height - m_desired_height;
  312. ChatPrompt& prompt = m_chat_backend->getPrompt();
  313. std::wstring prompt_text = prompt.getVisiblePortion();
  314. // FIXME Draw string at once, not character by character
  315. // That will only work with the cursor once we have a monospace font
  316. for (u32 i = 0; i < prompt_text.size(); ++i)
  317. {
  318. wchar_t ws[2] = {prompt_text[i], 0};
  319. s32 x = (1 + i) * m_fontsize.X;
  320. core::rect<s32> destrect(
  321. x, y, x + m_fontsize.X, y + m_fontsize.Y);
  322. m_font->draw(
  323. ws,
  324. destrect,
  325. video::SColor(255, 255, 255, 255),
  326. false,
  327. false,
  328. &AbsoluteClippingRect);
  329. }
  330. // Draw the cursor during on periods
  331. if ((m_cursor_blink & 0x8000) != 0)
  332. {
  333. s32 cursor_pos = prompt.getVisibleCursorPosition();
  334. if (cursor_pos >= 0)
  335. {
  336. s32 cursor_len = prompt.getCursorLength();
  337. video::IVideoDriver* driver = Environment->getVideoDriver();
  338. s32 x = (1 + cursor_pos) * m_fontsize.X;
  339. core::rect<s32> destrect(
  340. x,
  341. y + m_fontsize.Y * (1.0 - m_cursor_height),
  342. x + m_fontsize.X * MYMAX(cursor_len, 1),
  343. y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
  344. );
  345. video::SColor cursor_color(255,255,255,255);
  346. driver->draw2DRectangle(
  347. cursor_color,
  348. destrect,
  349. &AbsoluteClippingRect);
  350. }
  351. }
  352. }
  353. bool GUIChatConsole::OnEvent(const SEvent& event)
  354. {
  355. ChatPrompt &prompt = m_chat_backend->getPrompt();
  356. if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
  357. {
  358. // Key input
  359. if(KeyPress(event.KeyInput) == getKeySetting("keymap_console"))
  360. {
  361. closeConsole();
  362. // inhibit open so the_game doesn't reopen immediately
  363. m_open_inhibited = 50;
  364. m_close_on_enter = false;
  365. return true;
  366. }
  367. else if(event.KeyInput.Key == KEY_ESCAPE)
  368. {
  369. closeConsoleAtOnce();
  370. m_close_on_enter = false;
  371. // inhibit open so the_game doesn't reopen immediately
  372. m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
  373. return true;
  374. }
  375. else if(event.KeyInput.Key == KEY_PRIOR)
  376. {
  377. m_chat_backend->scrollPageUp();
  378. return true;
  379. }
  380. else if(event.KeyInput.Key == KEY_NEXT)
  381. {
  382. m_chat_backend->scrollPageDown();
  383. return true;
  384. }
  385. else if(event.KeyInput.Key == KEY_RETURN)
  386. {
  387. prompt.addToHistory(prompt.getLine());
  388. std::wstring text = prompt.replace(L"");
  389. m_client->typeChatMessage(text);
  390. if (m_close_on_enter) {
  391. closeConsoleAtOnce();
  392. m_close_on_enter = false;
  393. }
  394. return true;
  395. }
  396. else if(event.KeyInput.Key == KEY_UP)
  397. {
  398. // Up pressed
  399. // Move back in history
  400. prompt.historyPrev();
  401. return true;
  402. }
  403. else if(event.KeyInput.Key == KEY_DOWN)
  404. {
  405. // Down pressed
  406. // Move forward in history
  407. prompt.historyNext();
  408. return true;
  409. }
  410. else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
  411. {
  412. // Left/right pressed
  413. // Move/select character/word to the left depending on control and shift keys
  414. ChatPrompt::CursorOp op = event.KeyInput.Shift ?
  415. ChatPrompt::CURSOROP_SELECT :
  416. ChatPrompt::CURSOROP_MOVE;
  417. ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
  418. ChatPrompt::CURSOROP_DIR_LEFT :
  419. ChatPrompt::CURSOROP_DIR_RIGHT;
  420. ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
  421. ChatPrompt::CURSOROP_SCOPE_WORD :
  422. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  423. prompt.cursorOperation(op, dir, scope);
  424. return true;
  425. }
  426. else if(event.KeyInput.Key == KEY_HOME)
  427. {
  428. // Home pressed
  429. // move to beginning of line
  430. prompt.cursorOperation(
  431. ChatPrompt::CURSOROP_MOVE,
  432. ChatPrompt::CURSOROP_DIR_LEFT,
  433. ChatPrompt::CURSOROP_SCOPE_LINE);
  434. return true;
  435. }
  436. else if(event.KeyInput.Key == KEY_END)
  437. {
  438. // End pressed
  439. // move to end of line
  440. prompt.cursorOperation(
  441. ChatPrompt::CURSOROP_MOVE,
  442. ChatPrompt::CURSOROP_DIR_RIGHT,
  443. ChatPrompt::CURSOROP_SCOPE_LINE);
  444. return true;
  445. }
  446. else if(event.KeyInput.Key == KEY_BACK)
  447. {
  448. // Backspace or Ctrl-Backspace pressed
  449. // delete character / word to the left
  450. ChatPrompt::CursorOpScope scope =
  451. event.KeyInput.Control ?
  452. ChatPrompt::CURSOROP_SCOPE_WORD :
  453. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  454. prompt.cursorOperation(
  455. ChatPrompt::CURSOROP_DELETE,
  456. ChatPrompt::CURSOROP_DIR_LEFT,
  457. scope);
  458. return true;
  459. }
  460. else if(event.KeyInput.Key == KEY_DELETE)
  461. {
  462. // Delete or Ctrl-Delete pressed
  463. // delete character / word to the right
  464. ChatPrompt::CursorOpScope scope =
  465. event.KeyInput.Control ?
  466. ChatPrompt::CURSOROP_SCOPE_WORD :
  467. ChatPrompt::CURSOROP_SCOPE_CHARACTER;
  468. prompt.cursorOperation(
  469. ChatPrompt::CURSOROP_DELETE,
  470. ChatPrompt::CURSOROP_DIR_RIGHT,
  471. scope);
  472. return true;
  473. }
  474. else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
  475. {
  476. // Ctrl-A pressed
  477. // Select all text
  478. prompt.cursorOperation(
  479. ChatPrompt::CURSOROP_SELECT,
  480. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  481. ChatPrompt::CURSOROP_SCOPE_LINE);
  482. return true;
  483. }
  484. else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
  485. {
  486. // Ctrl-C pressed
  487. // Copy text to clipboard
  488. if (prompt.getCursorLength() <= 0)
  489. return true;
  490. std::wstring wselected = prompt.getSelection();
  491. std::string selected(wselected.begin(), wselected.end());
  492. Environment->getOSOperator()->copyToClipboard(selected.c_str());
  493. return true;
  494. }
  495. else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
  496. {
  497. // Ctrl-V pressed
  498. // paste text from clipboard
  499. if (prompt.getCursorLength() > 0) {
  500. // Delete selected section of text
  501. prompt.cursorOperation(
  502. ChatPrompt::CURSOROP_DELETE,
  503. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  504. ChatPrompt::CURSOROP_SCOPE_SELECTION);
  505. }
  506. IOSOperator *os_operator = Environment->getOSOperator();
  507. const c8 *text = os_operator->getTextFromClipboard();
  508. if (!text)
  509. return true;
  510. std::basic_string<unsigned char> str((const unsigned char*)text);
  511. prompt.input(std::wstring(str.begin(), str.end()));
  512. return true;
  513. }
  514. else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
  515. {
  516. // Ctrl-X pressed
  517. // Cut text to clipboard
  518. if (prompt.getCursorLength() <= 0)
  519. return true;
  520. std::wstring wselected = prompt.getSelection();
  521. std::string selected(wselected.begin(), wselected.end());
  522. Environment->getOSOperator()->copyToClipboard(selected.c_str());
  523. prompt.cursorOperation(
  524. ChatPrompt::CURSOROP_DELETE,
  525. ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
  526. ChatPrompt::CURSOROP_SCOPE_SELECTION);
  527. return true;
  528. }
  529. else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
  530. {
  531. // Ctrl-U pressed
  532. // kill line to left end
  533. prompt.cursorOperation(
  534. ChatPrompt::CURSOROP_DELETE,
  535. ChatPrompt::CURSOROP_DIR_LEFT,
  536. ChatPrompt::CURSOROP_SCOPE_LINE);
  537. return true;
  538. }
  539. else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
  540. {
  541. // Ctrl-K pressed
  542. // kill line to right end
  543. prompt.cursorOperation(
  544. ChatPrompt::CURSOROP_DELETE,
  545. ChatPrompt::CURSOROP_DIR_RIGHT,
  546. ChatPrompt::CURSOROP_SCOPE_LINE);
  547. return true;
  548. }
  549. else if(event.KeyInput.Key == KEY_TAB)
  550. {
  551. // Tab or Shift-Tab pressed
  552. // Nick completion
  553. std::list<std::string> names = m_client->getConnectedPlayerNames();
  554. bool backwards = event.KeyInput.Shift;
  555. prompt.nickCompletion(names, backwards);
  556. return true;
  557. } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
  558. #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
  559. wchar_t wc = L'_';
  560. mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
  561. prompt.input(wc);
  562. #else
  563. prompt.input(event.KeyInput.Char);
  564. #endif
  565. return true;
  566. }
  567. }
  568. else if(event.EventType == EET_MOUSE_INPUT_EVENT)
  569. {
  570. if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
  571. {
  572. s32 rows = myround(-3.0 * event.MouseInput.Wheel);
  573. m_chat_backend->scroll(rows);
  574. }
  575. }
  576. return Parent ? Parent->OnEvent(event) : false;
  577. }
  578. void GUIChatConsole::setVisible(bool visible)
  579. {
  580. m_open = visible;
  581. IGUIElement::setVisible(visible);
  582. if (!visible) {
  583. m_height = 0;
  584. recalculateConsolePosition();
  585. }
  586. }