Colored chat working as expected for both freetype and non-freetype builds. @nerzhul...
authorTriBlade9 <triblade9@mail.com>
Fri, 16 Jan 2015 06:54:26 +0000 (14:54 +0800)
committerEkdohibs <nathanael.courant@laposte.net>
Tue, 31 May 2016 15:34:29 +0000 (17:34 +0200)
15 files changed:
builtin/game/chatcommands.lua
builtin/game/misc.lua
src/chat.cpp
src/chat.h
src/client/CMakeLists.txt
src/client/guiChatConsole.cpp [new file with mode: 0644]
src/client/guiChatConsole.h [new file with mode: 0644]
src/game.cpp
src/guiChatConsole.cpp [deleted file]
src/guiChatConsole.h [deleted file]
src/util/CMakeLists.txt
src/util/coloredstring.cpp [new file with mode: 0644]
src/util/coloredstring.h [new file with mode: 0644]
src/util/statictext.cpp [new file with mode: 0644]
src/util/statictext.h [new file with mode: 0644]

index 3350140eea918d608d5cccef7f997ba23b6e8c58..2627559a5f0a8289c7472ca86a531bf875c2505b 100644 (file)
@@ -102,7 +102,7 @@ core.register_chatcommand("help", {
        description = "Get help for commands or list privileges",
        func = function(name, param)
                local function format_help_line(cmd, def)
-                       local msg = "/"..cmd
+                       local msg = core.colorize("00ffff", "/"..cmd)
                        if def.params and def.params ~= "" then
                                msg = msg .. " " .. def.params
                        end
index de41cfc91161a44a10fdbbb69710db342a38bc79..8d5c80216ce173975b6c87c525b515d4ee1abc14 100644 (file)
@@ -197,3 +197,20 @@ function core.http_add_fetch(httpenv)
 
        return httpenv
 end
+
+function core.get_color_escape_sequence(color)
+       --if string.len(color) == 3 then
+       --      local r = string.sub(color, 1, 1)
+       --      local g = string.sub(color, 2, 2)
+       --      local b = string.sub(color, 3, 3)
+       --      color = r ..  r .. g .. g .. b .. b
+       --end
+
+       --assert(#color == 6, "Color must be six characters in length.")
+       --return "\v" .. color
+       return "\v(color;" .. color .. ")"
+end
+
+function core.colorize(color, message)
+       return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("ffffff")
+end
index cebe312257cc335c1fc21f2ec21992a0a01e1d9d..958389df517eebb20625f8fc611bfe5a107be6ad 100644 (file)
@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "chat.h"
 #include "debug.h"
+#include "config.h"
 #include "util/strfnd.h"
 #include <cctype>
 #include <sstream>
@@ -251,8 +252,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
        u32 hanging_indentation = 0;
 
        // Format the sender name and produce fragments
-       if (!line.name.empty())
-       {
+       if (!line.name.empty()) {
                temp_frag.text = L"<";
                temp_frag.column = 0;
                //temp_frag.bold = 0;
@@ -267,28 +267,28 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
                next_frags.push_back(temp_frag);
        }
 
+       std::wstring name_sanitized = removeEscapes(line.name);
+
        // Choose an indentation level
-       if (line.name.empty())
-       {
+       if (line.name.empty()) {
                // Server messages
                hanging_indentation = 0;
        }
-       else if (line.name.size() + 3 <= cols/2)
-       {
+       else if (name_sanitized.size() + 3 <= cols/2) {
                // Names shorter than about half the console width
                hanging_indentation = line.name.size() + 3;
        }
-       else
-       {
+       else {
                // Very long names
                hanging_indentation = 2;
        }
+       ColoredString line_text(line.text);
 
        next_line.first = true;
        bool text_processing = false;
 
        // Produce fragments and layout them into lines
-       while (!next_frags.empty() || in_pos < line.text.size())
+       while (!next_frags.empty() || in_pos < line_text.size())
        {
                // Layout fragments into lines
                while (!next_frags.empty())
@@ -326,9 +326,9 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
                }
 
                // Produce fragment
-               if (in_pos < line.text.size())
+               if (in_pos < line_text.size())
                {
-                       u32 remaining_in_input = line.text.size() - in_pos;
+                       u32 remaining_in_input = line_text.size() - in_pos;
                        u32 remaining_in_output = cols - out_column;
 
                        // Determine a fragment length <= the minimum of
@@ -338,14 +338,14 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
                        while (frag_length < remaining_in_input &&
                                        frag_length < remaining_in_output)
                        {
-                               if (isspace(line.text[in_pos + frag_length]))
+                               if (isspace(line_text[in_pos + frag_length]))
                                        space_pos = frag_length;
                                ++frag_length;
                        }
                        if (space_pos != 0 && frag_length < remaining_in_input)
                                frag_length = space_pos + 1;
 
-                       temp_frag.text = line.text.substr(in_pos, frag_length);
+                       temp_frag.text = line_text.substr(in_pos, frag_length);
                        temp_frag.column = 0;
                        //temp_frag.bold = 0;
                        next_frags.push_back(temp_frag);
@@ -686,9 +686,6 @@ ChatBackend::~ChatBackend()
 
 void ChatBackend::addMessage(std::wstring name, std::wstring text)
 {
-       name = unescape_enriched(name);
-       text = unescape_enriched(text);
-
        // Note: A message may consist of multiple lines, for example the MOTD.
        WStrfnd fnd(text);
        while (!fnd.at_end())
index db4146d35ba8ffd59dd6d2cf0159959a90a727b2..661cafc82e09c777db1d53003d1fea2a416c4f4a 100644 (file)
@@ -20,11 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef CHAT_HEADER
 #define CHAT_HEADER
 
-#include "irrlichttypes.h"
 #include <string>
 #include <vector>
 #include <list>
 
+#include "irrlichttypes.h"
+#include "util/coloredstring.h"
+
 // Chat console related classes
 
 struct ChatLine
@@ -34,7 +36,7 @@ struct ChatLine
        // name of sending player, or empty if sent by server
        std::wstring name;
        // message text
-       std::wstring text;
+       ColoredString text;
 
        ChatLine(std::wstring a_name, std::wstring a_text):
                age(0.0),
index a1ec37fe39cee399c1b8bc6da41591376fbe6375..bcf114760e3be842579e2cef63017bcb8a37e005 100644 (file)
@@ -1,5 +1,6 @@
 set(client_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
        PARENT_SCOPE
 )
diff --git a/src/client/guiChatConsole.cpp b/src/client/guiChatConsole.cpp
new file mode 100644 (file)
index 0000000..d883755
--- /dev/null
@@ -0,0 +1,664 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiChatConsole.h"
+#include "chat.h"
+#include "client.h"
+#include "debug.h"
+#include "gettime.h"
+#include "keycode.h"
+#include "settings.h"
+#include "porting.h"
+#include "client/tile.h"
+#include "fontengine.h"
+#include "log.h"
+#include "gettext.h"
+#include <string>
+
+#if USE_FREETYPE
+       #include "xCGUITTFont.h"
+#endif
+
+inline u32 clamp_u8(s32 value)
+{
+       return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+
+GUIChatConsole::GUIChatConsole(
+               gui::IGUIEnvironment* env,
+               gui::IGUIElement* parent,
+               s32 id,
+               ChatBackend* backend,
+               Client* client,
+               IMenuManager* menumgr
+):
+       IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+                       core::rect<s32>(0,0,100,100)),
+       m_chat_backend(backend),
+       m_client(client),
+       m_menumgr(menumgr),
+       m_screensize(v2u32(0,0)),
+       m_animate_time_old(0),
+       m_open(false),
+       m_close_on_enter(false),
+       m_height(0),
+       m_desired_height(0),
+       m_desired_height_fraction(0.0),
+       m_height_speed(5.0),
+       m_open_inhibited(0),
+       m_cursor_blink(0.0),
+       m_cursor_blink_speed(0.0),
+       m_cursor_height(0.0),
+       m_background(NULL),
+       m_background_color(255, 0, 0, 0),
+       m_font(NULL),
+       m_fontsize(0, 0)
+{
+       m_animate_time_old = getTimeMs();
+
+       // load background settings
+       s32 console_alpha = g_settings->getS32("console_alpha");
+       m_background_color.setAlpha(clamp_u8(console_alpha));
+
+       // load the background texture depending on settings
+       ITextureSource *tsrc = client->getTextureSource();
+       if (tsrc->isKnownSourceImage("background_chat.jpg")) {
+               m_background = tsrc->getTexture("background_chat.jpg");
+               m_background_color.setRed(255);
+               m_background_color.setGreen(255);
+               m_background_color.setBlue(255);
+       } else {
+               v3f console_color = g_settings->getV3F("console_color");
+               m_background_color.setRed(clamp_u8(myround(console_color.X)));
+               m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
+               m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
+       }
+
+       m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
+
+       if (m_font == NULL)
+       {
+               errorstream << "GUIChatConsole: Unable to load mono font ";
+       }
+       else
+       {
+               core::dimension2d<u32> dim = m_font->getDimension(L"M");
+               m_fontsize = v2u32(dim.Width, dim.Height);
+               m_font->grab();
+       }
+       m_fontsize.X = MYMAX(m_fontsize.X, 1);
+       m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
+
+       // set default cursor options
+       setCursor(true, true, 2.0, 0.1);
+}
+
+GUIChatConsole::~GUIChatConsole()
+{
+       if (m_font)
+               m_font->drop();
+}
+
+void GUIChatConsole::openConsole(f32 height)
+{
+       m_open = true;
+       m_desired_height_fraction = height;
+       m_desired_height = height * m_screensize.Y;
+       reformatConsole();
+       m_animate_time_old = getTimeMs();
+       IGUIElement::setVisible(true);
+       Environment->setFocus(this);
+       m_menumgr->createdMenu(this);
+}
+
+bool GUIChatConsole::isOpen() const
+{
+       return m_open;
+}
+
+bool GUIChatConsole::isOpenInhibited() const
+{
+       return m_open_inhibited > 0;
+}
+
+void GUIChatConsole::closeConsole()
+{
+       m_open = false;
+       Environment->removeFocus(this);
+       m_menumgr->deletingMenu(this);
+}
+
+void GUIChatConsole::closeConsoleAtOnce()
+{
+       closeConsole();
+       m_height = 0;
+       recalculateConsolePosition();
+}
+
+f32 GUIChatConsole::getDesiredHeight() const
+{
+       return m_desired_height_fraction;
+}
+
+void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
+{
+       ChatPrompt& prompt = m_chat_backend->getPrompt();
+       prompt.addToHistory(prompt.getLine());
+       prompt.replace(line);
+}
+
+
+void GUIChatConsole::setCursor(
+       bool visible, bool blinking, f32 blink_speed, f32 relative_height)
+{
+       if (visible)
+       {
+               if (blinking)
+               {
+                       // leave m_cursor_blink unchanged
+                       m_cursor_blink_speed = blink_speed;
+               }
+               else
+               {
+                       m_cursor_blink = 0x8000;  // on
+                       m_cursor_blink_speed = 0.0;
+               }
+       }
+       else
+       {
+               m_cursor_blink = 0;  // off
+               m_cursor_blink_speed = 0.0;
+       }
+       m_cursor_height = relative_height;
+}
+
+void GUIChatConsole::draw()
+{
+       if(!IsVisible)
+               return;
+
+       video::IVideoDriver* driver = Environment->getVideoDriver();
+
+       // Check screen size
+       v2u32 screensize = driver->getScreenSize();
+       if (screensize != m_screensize)
+       {
+               // screen size has changed
+               // scale current console height to new window size
+               if (m_screensize.Y != 0)
+                       m_height = m_height * screensize.Y / m_screensize.Y;
+               m_desired_height = m_desired_height_fraction * m_screensize.Y;
+               m_screensize = screensize;
+               reformatConsole();
+       }
+
+       // Animation
+       u32 now = getTimeMs();
+       animate(now - m_animate_time_old);
+       m_animate_time_old = now;
+
+       // Draw console elements if visible
+       if (m_height > 0)
+       {
+               drawBackground();
+               drawText();
+               drawPrompt();
+       }
+
+       gui::IGUIElement::draw();
+}
+
+void GUIChatConsole::reformatConsole()
+{
+       s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
+       s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
+       if (cols <= 0 || rows <= 0)
+               cols = rows = 0;
+       m_chat_backend->reformat(cols, rows);
+}
+
+void GUIChatConsole::recalculateConsolePosition()
+{
+       core::rect<s32> rect(0, 0, m_screensize.X, m_height);
+       DesiredRect = rect;
+       recalculateAbsolutePosition(false);
+}
+
+void GUIChatConsole::animate(u32 msec)
+{
+       // animate the console height
+       s32 goal = m_open ? m_desired_height : 0;
+
+       // Set invisible if close animation finished (reset by openConsole)
+       // This function (animate()) is never called once its visibility becomes false so do not
+       //              actually set visible to false before the inhibited period is over
+       if (!m_open && m_height == 0 && m_open_inhibited == 0)
+               IGUIElement::setVisible(false);
+
+       if (m_height != goal)
+       {
+               s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
+               if (max_change == 0)
+                       max_change = 1;
+
+               if (m_height < goal)
+               {
+                       // increase height
+                       if (m_height + max_change < goal)
+                               m_height += max_change;
+                       else
+                               m_height = goal;
+               }
+               else
+               {
+                       // decrease height
+                       if (m_height > goal + max_change)
+                               m_height -= max_change;
+                       else
+                               m_height = goal;
+               }
+
+               recalculateConsolePosition();
+       }
+
+       // blink the cursor
+       if (m_cursor_blink_speed != 0.0)
+       {
+               u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
+               if (blink_increase == 0)
+                       blink_increase = 1;
+               m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
+       }
+
+       // decrease open inhibit counter
+       if (m_open_inhibited > msec)
+               m_open_inhibited -= msec;
+       else
+               m_open_inhibited = 0;
+}
+
+void GUIChatConsole::drawBackground()
+{
+       video::IVideoDriver* driver = Environment->getVideoDriver();
+       if (m_background != NULL)
+       {
+               core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
+               driver->draw2DImage(
+                       m_background,
+                       v2s32(0, 0),
+                       sourcerect,
+                       &AbsoluteClippingRect,
+                       m_background_color,
+                       false);
+       }
+       else
+       {
+               driver->draw2DRectangle(
+                       m_background_color,
+                       core::rect<s32>(0, 0, m_screensize.X, m_height),
+                       &AbsoluteClippingRect);
+       }
+}
+
+void GUIChatConsole::drawText()
+{
+       if (m_font == NULL)
+               return;
+
+       ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
+       for (u32 row = 0; row < buf.getRows(); ++row)
+       {
+               const ChatFormattedLine& line = buf.getFormattedLine(row);
+               if (line.fragments.empty())
+                       continue;
+
+               s32 line_height = m_fontsize.Y;
+               s32 y = row * line_height + m_height - m_desired_height;
+               if (y + line_height < 0)
+                       continue;
+
+               for (u32 i = 0; i < line.fragments.size(); ++i)
+               {
+                       const ChatFormattedFragment& fragment = line.fragments[i];
+                       s32 x = (fragment.column + 1) * m_fontsize.X;
+                       core::rect<s32> destrect(
+                               x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
+
+
+                       #if USE_FREETYPE
+                       // Draw colored text if FreeType is enabled
+                               irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(m_font);
+                               tmp->draw(
+                                       fragment.text.c_str(),
+                                       destrect,
+                                       fragment.text.getColors(),
+                                       false,
+                                       false,
+                                       &AbsoluteClippingRect);
+                       #else
+                       // Otherwise use standard text
+                               m_font->draw(
+                                       fragment.text.c_str(),
+                                       destrect,
+                                       video::SColor(255, 255, 255, 255),
+                                       false,
+                                       false,
+                                       &AbsoluteClippingRect);
+                       #endif
+               }
+       }
+}
+
+void GUIChatConsole::drawPrompt()
+{
+       if (m_font == NULL)
+               return;
+
+       u32 row = m_chat_backend->getConsoleBuffer().getRows();
+       s32 line_height = m_fontsize.Y;
+       s32 y = row * line_height + m_height - m_desired_height;
+
+       ChatPrompt& prompt = m_chat_backend->getPrompt();
+       std::wstring prompt_text = prompt.getVisiblePortion();
+
+       // FIXME Draw string at once, not character by character
+       // That will only work with the cursor once we have a monospace font
+       for (u32 i = 0; i < prompt_text.size(); ++i)
+       {
+               wchar_t ws[2] = {prompt_text[i], 0};
+               s32 x = (1 + i) * m_fontsize.X;
+               core::rect<s32> destrect(
+                       x, y, x + m_fontsize.X, y + m_fontsize.Y);
+               m_font->draw(
+                       ws,
+                       destrect,
+                       video::SColor(255, 255, 255, 255),
+                       false,
+                       false,
+                       &AbsoluteClippingRect);
+       }
+
+       // Draw the cursor during on periods
+       if ((m_cursor_blink & 0x8000) != 0)
+       {
+               s32 cursor_pos = prompt.getVisibleCursorPosition();
+               if (cursor_pos >= 0)
+               {
+                       s32 cursor_len = prompt.getCursorLength();
+                       video::IVideoDriver* driver = Environment->getVideoDriver();
+                       s32 x = (1 + cursor_pos) * m_fontsize.X;
+                       core::rect<s32> destrect(
+                               x,
+                               y + m_fontsize.Y * (1.0 - m_cursor_height),
+                               x + m_fontsize.X * MYMAX(cursor_len, 1),
+                               y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
+                       );
+                       video::SColor cursor_color(255,255,255,255);
+                       driver->draw2DRectangle(
+                               cursor_color,
+                               destrect,
+                               &AbsoluteClippingRect);
+               }
+       }
+
+}
+
+bool GUIChatConsole::OnEvent(const SEvent& event)
+{
+
+       ChatPrompt &prompt = m_chat_backend->getPrompt();
+
+       if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
+       {
+               // Key input
+               if(KeyPress(event.KeyInput) == getKeySetting("keymap_console"))
+               {
+                       closeConsole();
+
+                       // inhibit open so the_game doesn't reopen immediately
+                       m_open_inhibited = 50;
+                       m_close_on_enter = false;
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_ESCAPE)
+               {
+                       closeConsoleAtOnce();
+                       m_close_on_enter = false;
+                       // inhibit open so the_game doesn't reopen immediately
+                       m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_PRIOR)
+               {
+                       m_chat_backend->scrollPageUp();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_NEXT)
+               {
+                       m_chat_backend->scrollPageDown();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_RETURN)
+               {
+                       prompt.addToHistory(prompt.getLine());
+                       std::wstring text = prompt.replace(L"");
+                       m_client->typeChatMessage(text);
+                       if (m_close_on_enter) {
+                               closeConsoleAtOnce();
+                               m_close_on_enter = false;
+                       }
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_UP)
+               {
+                       // Up pressed
+                       // Move back in history
+                       prompt.historyPrev();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_DOWN)
+               {
+                       // Down pressed
+                       // Move forward in history
+                       prompt.historyNext();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
+               {
+                       // Left/right pressed
+                       // Move/select character/word to the left depending on control and shift keys
+                       ChatPrompt::CursorOp op = event.KeyInput.Shift ?
+                               ChatPrompt::CURSOROP_SELECT :
+                               ChatPrompt::CURSOROP_MOVE;
+                       ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
+                               ChatPrompt::CURSOROP_DIR_LEFT :
+                               ChatPrompt::CURSOROP_DIR_RIGHT;
+                       ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       prompt.cursorOperation(op, dir, scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_HOME)
+               {
+                       // Home pressed
+                       // move to beginning of line
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_END)
+               {
+                       // End pressed
+                       // move to end of line
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_BACK)
+               {
+                       // Backspace or Ctrl-Backspace pressed
+                       // delete character / word to the left
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_DELETE)
+               {
+                       // Delete or Ctrl-Delete pressed
+                       // delete character / word to the right
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
+               {
+                       // Ctrl-A pressed
+                       // Select all text
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_SELECT,
+                               ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
+               {
+                       // Ctrl-C pressed
+                       // Copy text to clipboard
+                       if (prompt.getCursorLength() <= 0)
+                               return true;
+                       std::wstring wselected = prompt.getSelection();
+                       std::string selected(wselected.begin(), wselected.end());
+                       Environment->getOSOperator()->copyToClipboard(selected.c_str());
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
+               {
+                       // Ctrl-V pressed
+                       // paste text from clipboard
+                       if (prompt.getCursorLength() > 0) {
+                               // Delete selected section of text
+                               prompt.cursorOperation(
+                                       ChatPrompt::CURSOROP_DELETE,
+                                       ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+                                       ChatPrompt::CURSOROP_SCOPE_SELECTION);
+                       }
+                       IOSOperator *os_operator = Environment->getOSOperator();
+                       const c8 *text = os_operator->getTextFromClipboard();
+                       if (!text)
+                               return true;
+                       std::basic_string<unsigned char> str((const unsigned char*)text);
+                       prompt.input(std::wstring(str.begin(), str.end()));
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
+               {
+                       // Ctrl-X pressed
+                       // Cut text to clipboard
+                       if (prompt.getCursorLength() <= 0)
+                               return true;
+                       std::wstring wselected = prompt.getSelection();
+                       std::string selected(wselected.begin(), wselected.end());
+                       Environment->getOSOperator()->copyToClipboard(selected.c_str());
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+                               ChatPrompt::CURSOROP_SCOPE_SELECTION);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
+               {
+                       // Ctrl-U pressed
+                       // kill line to left end
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
+               {
+                       // Ctrl-K pressed
+                       // kill line to right end
+                       prompt.cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_TAB)
+               {
+                       // Tab or Shift-Tab pressed
+                       // Nick completion
+                       std::list<std::string> names = m_client->getConnectedPlayerNames();
+                       bool backwards = event.KeyInput.Shift;
+                       prompt.nickCompletion(names, backwards);
+                       return true;
+               }
+               else if(event.KeyInput.Char != 0 && !event.KeyInput.Control)
+               {
+                       #if (defined(linux) || defined(__linux))
+                               wchar_t wc = L'_';
+                               mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
+                               prompt.input(wc);
+                       #else
+                               prompt.input(event.KeyInput.Char);
+                       #endif
+                       return true;
+               }
+       }
+       else if(event.EventType == EET_MOUSE_INPUT_EVENT)
+       {
+               if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+               {
+                       s32 rows = myround(-3.0 * event.MouseInput.Wheel);
+                       m_chat_backend->scroll(rows);
+               }
+       }
+
+       return Parent ? Parent->OnEvent(event) : false;
+}
+
+void GUIChatConsole::setVisible(bool visible)
+{
+       m_open = visible;
+       IGUIElement::setVisible(visible);
+       if (!visible) {
+               m_height = 0;
+               recalculateConsolePosition();
+       }
+}
+
diff --git a/src/client/guiChatConsole.h b/src/client/guiChatConsole.h
new file mode 100644 (file)
index 0000000..3013a1d
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef GUICHATCONSOLE_HEADER
+#define GUICHATCONSOLE_HEADER
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include "chat.h"
+#include "config.h"
+
+class Client;
+
+class GUIChatConsole : public gui::IGUIElement
+{
+public:
+       GUIChatConsole(gui::IGUIEnvironment* env,
+                       gui::IGUIElement* parent,
+                       s32 id,
+                       ChatBackend* backend,
+                       Client* client,
+                       IMenuManager* menumgr);
+       virtual ~GUIChatConsole();
+
+       // Open the console (height = desired fraction of screen size)
+       // This doesn't open immediately but initiates an animation.
+       // You should call isOpenInhibited() before this.
+       void openConsole(f32 height);
+
+       bool isOpen() const;
+
+       // Check if the console should not be opened at the moment
+       // This is to avoid reopening the console immediately after closing
+       bool isOpenInhibited() const;
+       // Close the console, equivalent to openConsole(0).
+       // This doesn't close immediately but initiates an animation.
+       void closeConsole();
+       // Close the console immediately, without animation.
+       void closeConsoleAtOnce();
+       // Set whether to close the console after the user presses enter.
+       void setCloseOnEnter(bool close) { m_close_on_enter = close; }
+
+       // Return the desired height (fraction of screen size)
+       // Zero if the console is closed or getting closed
+       f32 getDesiredHeight() const;
+
+       // Replace actual line when adding the actual to the history (if there is any)
+       void replaceAndAddToHistory(std::wstring line);
+
+       // Change how the cursor looks
+       void setCursor(
+               bool visible,
+               bool blinking = false,
+               f32 blink_speed = 1.0,
+               f32 relative_height = 1.0);
+
+       // Irrlicht draw method
+       virtual void draw();
+
+       bool canTakeFocus(gui::IGUIElement* element) { return false; }
+
+       virtual bool OnEvent(const SEvent& event);
+
+       virtual void setVisible(bool visible);
+
+private:
+       void reformatConsole();
+       void recalculateConsolePosition();
+
+       // These methods are called by draw
+       void animate(u32 msec);
+       void drawBackground();
+       void drawText();
+       void drawPrompt();
+
+private:
+       ChatBackend* m_chat_backend;
+       Client* m_client;
+       IMenuManager* m_menumgr;
+
+       // current screen size
+       v2u32 m_screensize;
+
+       // used to compute how much time passed since last animate()
+       u32 m_animate_time_old;
+
+       // should the console be opened or closed?
+       bool m_open;
+       // should it close after you press enter?
+       bool m_close_on_enter;
+       // current console height [pixels]
+       s32 m_height;
+       // desired height [pixels]
+       f32 m_desired_height;
+       // desired height [screen height fraction]
+       f32 m_desired_height_fraction;
+       // console open/close animation speed [screen height fraction / second]
+       f32 m_height_speed;
+       // if nonzero, opening the console is inhibited [milliseconds]
+       u32 m_open_inhibited;
+
+       // cursor blink frame (16-bit value)
+       // cursor is off during [0,32767] and on during [32768,65535]
+       u32 m_cursor_blink;
+       // cursor blink speed [on/off toggles / second]
+       f32 m_cursor_blink_speed;
+       // cursor height [line height]
+       f32 m_cursor_height;
+
+       // background texture
+       video::ITexture* m_background;
+       // background color (including alpha)
+       video::SColor m_background_color;
+
+       // font
+       gui::IGUIFont* m_font;
+       v2u32 m_fontsize;
+};
+
+
+#endif
+
index c5211a042d2f7f8d3ab53f731d6c914f9ffb9bda..71a04aef57c1fa5bf2c3b66108ae3e9ed1eff5ea 100644 (file)
@@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "filesys.h"
 #include "gettext.h"
-#include "guiChatConsole.h"
+#include "client/guiChatConsole.h"
 #include "guiFormSpecMenu.h"
 #include "guiKeyChangeMenu.h"
 #include "guiPasswordChange.h"
@@ -59,6 +59,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "minimap.h"
 #include "mapblock_mesh.h"
 
+#if USE_FREETYPE
+       #include "util/statictext.h"
+#endif
+
 #include "sound.h"
 
 #if USE_SOUND
@@ -2239,12 +2243,20 @@ bool Game::initGui()
                        false, false, guiroot);
        guitext_status->setVisible(false);
 
+#if USE_FREETYPE
+       // Colored chat support when using FreeType
+       guitext_chat = new gui::StaticText(L"", false, guienv, guiroot, -1, core::rect<s32>(0, 0, 0, 0), false);
+       guitext_chat->setWordWrap(true);
+       guitext_chat->drop();
+#else
+       // Standard chat when FreeType is disabled
        // Chat text
        guitext_chat = guienv->addStaticText(
                        L"",
                        core::rect<s32>(0, 0, 0, 0),
                        //false, false); // Disable word wrap as of now
                        false, true, guiroot);
+#endif
        // Remove stale "recent" chat messages from previous connections
        chat_backend->clearRecentChat();
 
diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp
deleted file mode 100644 (file)
index 17a1689..0000000
+++ /dev/null
@@ -1,649 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "guiChatConsole.h"
-#include "chat.h"
-#include "client.h"
-#include "debug.h"
-#include "gettime.h"
-#include "keycode.h"
-#include "settings.h"
-#include "porting.h"
-#include "client/tile.h"
-#include "fontengine.h"
-#include "log.h"
-#include "gettext.h"
-#include <string>
-
-#if USE_FREETYPE
-#include "xCGUITTFont.h"
-#endif
-
-inline u32 clamp_u8(s32 value)
-{
-       return (u32) MYMIN(MYMAX(value, 0), 255);
-}
-
-
-GUIChatConsole::GUIChatConsole(
-               gui::IGUIEnvironment* env,
-               gui::IGUIElement* parent,
-               s32 id,
-               ChatBackend* backend,
-               Client* client,
-               IMenuManager* menumgr
-):
-       IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
-                       core::rect<s32>(0,0,100,100)),
-       m_chat_backend(backend),
-       m_client(client),
-       m_menumgr(menumgr),
-       m_screensize(v2u32(0,0)),
-       m_animate_time_old(0),
-       m_open(false),
-       m_close_on_enter(false),
-       m_height(0),
-       m_desired_height(0),
-       m_desired_height_fraction(0.0),
-       m_height_speed(5.0),
-       m_open_inhibited(0),
-       m_cursor_blink(0.0),
-       m_cursor_blink_speed(0.0),
-       m_cursor_height(0.0),
-       m_background(NULL),
-       m_background_color(255, 0, 0, 0),
-       m_font(NULL),
-       m_fontsize(0, 0)
-{
-       m_animate_time_old = getTimeMs();
-
-       // load background settings
-       s32 console_alpha = g_settings->getS32("console_alpha");
-       m_background_color.setAlpha(clamp_u8(console_alpha));
-
-       // load the background texture depending on settings
-       ITextureSource *tsrc = client->getTextureSource();
-       if (tsrc->isKnownSourceImage("background_chat.jpg")) {
-               m_background = tsrc->getTexture("background_chat.jpg");
-               m_background_color.setRed(255);
-               m_background_color.setGreen(255);
-               m_background_color.setBlue(255);
-       } else {
-               v3f console_color = g_settings->getV3F("console_color");
-               m_background_color.setRed(clamp_u8(myround(console_color.X)));
-               m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
-               m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
-       }
-
-       m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
-
-       if (m_font == NULL)
-       {
-               errorstream << "GUIChatConsole: Unable to load mono font ";
-       }
-       else
-       {
-               core::dimension2d<u32> dim = m_font->getDimension(L"M");
-               m_fontsize = v2u32(dim.Width, dim.Height);
-               m_font->grab();
-       }
-       m_fontsize.X = MYMAX(m_fontsize.X, 1);
-       m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
-
-       // set default cursor options
-       setCursor(true, true, 2.0, 0.1);
-}
-
-GUIChatConsole::~GUIChatConsole()
-{
-       if (m_font)
-               m_font->drop();
-}
-
-void GUIChatConsole::openConsole(f32 height)
-{
-       m_open = true;
-       m_desired_height_fraction = height;
-       m_desired_height = height * m_screensize.Y;
-       reformatConsole();
-       m_animate_time_old = getTimeMs();
-       IGUIElement::setVisible(true);
-       Environment->setFocus(this);
-       m_menumgr->createdMenu(this);
-}
-
-bool GUIChatConsole::isOpen() const
-{
-       return m_open;
-}
-
-bool GUIChatConsole::isOpenInhibited() const
-{
-       return m_open_inhibited > 0;
-}
-
-void GUIChatConsole::closeConsole()
-{
-       m_open = false;
-       Environment->removeFocus(this);
-       m_menumgr->deletingMenu(this);
-}
-
-void GUIChatConsole::closeConsoleAtOnce()
-{
-       closeConsole();
-       m_height = 0;
-       recalculateConsolePosition();
-}
-
-f32 GUIChatConsole::getDesiredHeight() const
-{
-       return m_desired_height_fraction;
-}
-
-void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
-{
-       ChatPrompt& prompt = m_chat_backend->getPrompt();
-       prompt.addToHistory(prompt.getLine());
-       prompt.replace(line);
-}
-
-
-void GUIChatConsole::setCursor(
-       bool visible, bool blinking, f32 blink_speed, f32 relative_height)
-{
-       if (visible)
-       {
-               if (blinking)
-               {
-                       // leave m_cursor_blink unchanged
-                       m_cursor_blink_speed = blink_speed;
-               }
-               else
-               {
-                       m_cursor_blink = 0x8000;  // on
-                       m_cursor_blink_speed = 0.0;
-               }
-       }
-       else
-       {
-               m_cursor_blink = 0;  // off
-               m_cursor_blink_speed = 0.0;
-       }
-       m_cursor_height = relative_height;
-}
-
-void GUIChatConsole::draw()
-{
-       if(!IsVisible)
-               return;
-
-       video::IVideoDriver* driver = Environment->getVideoDriver();
-
-       // Check screen size
-       v2u32 screensize = driver->getScreenSize();
-       if (screensize != m_screensize)
-       {
-               // screen size has changed
-               // scale current console height to new window size
-               if (m_screensize.Y != 0)
-                       m_height = m_height * screensize.Y / m_screensize.Y;
-               m_desired_height = m_desired_height_fraction * m_screensize.Y;
-               m_screensize = screensize;
-               reformatConsole();
-       }
-
-       // Animation
-       u32 now = getTimeMs();
-       animate(now - m_animate_time_old);
-       m_animate_time_old = now;
-
-       // Draw console elements if visible
-       if (m_height > 0)
-       {
-               drawBackground();
-               drawText();
-               drawPrompt();
-       }
-
-       gui::IGUIElement::draw();
-}
-
-void GUIChatConsole::reformatConsole()
-{
-       s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
-       s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
-       if (cols <= 0 || rows <= 0)
-               cols = rows = 0;
-       m_chat_backend->reformat(cols, rows);
-}
-
-void GUIChatConsole::recalculateConsolePosition()
-{
-       core::rect<s32> rect(0, 0, m_screensize.X, m_height);
-       DesiredRect = rect;
-       recalculateAbsolutePosition(false);
-}
-
-void GUIChatConsole::animate(u32 msec)
-{
-       // animate the console height
-       s32 goal = m_open ? m_desired_height : 0;
-
-       // Set invisible if close animation finished (reset by openConsole)
-       // This function (animate()) is never called once its visibility becomes false so do not
-       //              actually set visible to false before the inhibited period is over
-       if (!m_open && m_height == 0 && m_open_inhibited == 0)
-               IGUIElement::setVisible(false);
-
-       if (m_height != goal)
-       {
-               s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
-               if (max_change == 0)
-                       max_change = 1;
-
-               if (m_height < goal)
-               {
-                       // increase height
-                       if (m_height + max_change < goal)
-                               m_height += max_change;
-                       else
-                               m_height = goal;
-               }
-               else
-               {
-                       // decrease height
-                       if (m_height > goal + max_change)
-                               m_height -= max_change;
-                       else
-                               m_height = goal;
-               }
-
-               recalculateConsolePosition();
-       }
-
-       // blink the cursor
-       if (m_cursor_blink_speed != 0.0)
-       {
-               u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
-               if (blink_increase == 0)
-                       blink_increase = 1;
-               m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
-       }
-
-       // decrease open inhibit counter
-       if (m_open_inhibited > msec)
-               m_open_inhibited -= msec;
-       else
-               m_open_inhibited = 0;
-}
-
-void GUIChatConsole::drawBackground()
-{
-       video::IVideoDriver* driver = Environment->getVideoDriver();
-       if (m_background != NULL)
-       {
-               core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
-               driver->draw2DImage(
-                       m_background,
-                       v2s32(0, 0),
-                       sourcerect,
-                       &AbsoluteClippingRect,
-                       m_background_color,
-                       false);
-       }
-       else
-       {
-               driver->draw2DRectangle(
-                       m_background_color,
-                       core::rect<s32>(0, 0, m_screensize.X, m_height),
-                       &AbsoluteClippingRect);
-       }
-}
-
-void GUIChatConsole::drawText()
-{
-       if (m_font == NULL)
-               return;
-
-       ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
-       for (u32 row = 0; row < buf.getRows(); ++row)
-       {
-               const ChatFormattedLine& line = buf.getFormattedLine(row);
-               if (line.fragments.empty())
-                       continue;
-
-               s32 line_height = m_fontsize.Y;
-               s32 y = row * line_height + m_height - m_desired_height;
-               if (y + line_height < 0)
-                       continue;
-
-               for (u32 i = 0; i < line.fragments.size(); ++i)
-               {
-                       const ChatFormattedFragment& fragment = line.fragments[i];
-                       s32 x = (fragment.column + 1) * m_fontsize.X;
-                       core::rect<s32> destrect(
-                               x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
-                       m_font->draw(
-                               fragment.text.c_str(),
-                               destrect,
-                               video::SColor(255, 255, 255, 255),
-                               false,
-                               false,
-                               &AbsoluteClippingRect);
-               }
-       }
-}
-
-void GUIChatConsole::drawPrompt()
-{
-       if (m_font == NULL)
-               return;
-
-       u32 row = m_chat_backend->getConsoleBuffer().getRows();
-       s32 line_height = m_fontsize.Y;
-       s32 y = row * line_height + m_height - m_desired_height;
-
-       ChatPrompt& prompt = m_chat_backend->getPrompt();
-       std::wstring prompt_text = prompt.getVisiblePortion();
-
-       // FIXME Draw string at once, not character by character
-       // That will only work with the cursor once we have a monospace font
-       for (u32 i = 0; i < prompt_text.size(); ++i)
-       {
-               wchar_t ws[2] = {prompt_text[i], 0};
-               s32 x = (1 + i) * m_fontsize.X;
-               core::rect<s32> destrect(
-                       x, y, x + m_fontsize.X, y + m_fontsize.Y);
-               m_font->draw(
-                       ws,
-                       destrect,
-                       video::SColor(255, 255, 255, 255),
-                       false,
-                       false,
-                       &AbsoluteClippingRect);
-       }
-
-       // Draw the cursor during on periods
-       if ((m_cursor_blink & 0x8000) != 0)
-       {
-               s32 cursor_pos = prompt.getVisibleCursorPosition();
-               if (cursor_pos >= 0)
-               {
-                       s32 cursor_len = prompt.getCursorLength();
-                       video::IVideoDriver* driver = Environment->getVideoDriver();
-                       s32 x = (1 + cursor_pos) * m_fontsize.X;
-                       core::rect<s32> destrect(
-                               x,
-                               y + m_fontsize.Y * (1.0 - m_cursor_height),
-                               x + m_fontsize.X * MYMAX(cursor_len, 1),
-                               y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
-                       );
-                       video::SColor cursor_color(255,255,255,255);
-                       driver->draw2DRectangle(
-                               cursor_color,
-                               destrect,
-                               &AbsoluteClippingRect);
-               }
-       }
-
-}
-
-bool GUIChatConsole::OnEvent(const SEvent& event)
-{
-
-       ChatPrompt &prompt = m_chat_backend->getPrompt();
-
-       if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
-       {
-               // Key input
-               if(KeyPress(event.KeyInput) == getKeySetting("keymap_console"))
-               {
-                       closeConsole();
-
-                       // inhibit open so the_game doesn't reopen immediately
-                       m_open_inhibited = 50;
-                       m_close_on_enter = false;
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_ESCAPE)
-               {
-                       closeConsoleAtOnce();
-                       m_close_on_enter = false;
-                       // inhibit open so the_game doesn't reopen immediately
-                       m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_PRIOR)
-               {
-                       m_chat_backend->scrollPageUp();
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_NEXT)
-               {
-                       m_chat_backend->scrollPageDown();
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_RETURN)
-               {
-                       prompt.addToHistory(prompt.getLine());
-                       std::wstring text = prompt.replace(L"");
-                       m_client->typeChatMessage(text);
-                       if (m_close_on_enter) {
-                               closeConsoleAtOnce();
-                               m_close_on_enter = false;
-                       }
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_UP)
-               {
-                       // Up pressed
-                       // Move back in history
-                       prompt.historyPrev();
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_DOWN)
-               {
-                       // Down pressed
-                       // Move forward in history
-                       prompt.historyNext();
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
-               {
-                       // Left/right pressed
-                       // Move/select character/word to the left depending on control and shift keys
-                       ChatPrompt::CursorOp op = event.KeyInput.Shift ?
-                               ChatPrompt::CURSOROP_SELECT :
-                               ChatPrompt::CURSOROP_MOVE;
-                       ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
-                               ChatPrompt::CURSOROP_DIR_LEFT :
-                               ChatPrompt::CURSOROP_DIR_RIGHT;
-                       ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
-                               ChatPrompt::CURSOROP_SCOPE_WORD :
-                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
-                       prompt.cursorOperation(op, dir, scope);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_HOME)
-               {
-                       // Home pressed
-                       // move to beginning of line
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_MOVE,
-                               ChatPrompt::CURSOROP_DIR_LEFT,
-                               ChatPrompt::CURSOROP_SCOPE_LINE);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_END)
-               {
-                       // End pressed
-                       // move to end of line
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_MOVE,
-                               ChatPrompt::CURSOROP_DIR_RIGHT,
-                               ChatPrompt::CURSOROP_SCOPE_LINE);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_BACK)
-               {
-                       // Backspace or Ctrl-Backspace pressed
-                       // delete character / word to the left
-                       ChatPrompt::CursorOpScope scope =
-                               event.KeyInput.Control ?
-                               ChatPrompt::CURSOROP_SCOPE_WORD :
-                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_DELETE,
-                               ChatPrompt::CURSOROP_DIR_LEFT,
-                               scope);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_DELETE)
-               {
-                       // Delete or Ctrl-Delete pressed
-                       // delete character / word to the right
-                       ChatPrompt::CursorOpScope scope =
-                               event.KeyInput.Control ?
-                               ChatPrompt::CURSOROP_SCOPE_WORD :
-                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_DELETE,
-                               ChatPrompt::CURSOROP_DIR_RIGHT,
-                               scope);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
-               {
-                       // Ctrl-A pressed
-                       // Select all text
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_SELECT,
-                               ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
-                               ChatPrompt::CURSOROP_SCOPE_LINE);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
-               {
-                       // Ctrl-C pressed
-                       // Copy text to clipboard
-                       if (prompt.getCursorLength() <= 0)
-                               return true;
-                       std::wstring wselected = prompt.getSelection();
-                       std::string selected(wselected.begin(), wselected.end());
-                       Environment->getOSOperator()->copyToClipboard(selected.c_str());
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
-               {
-                       // Ctrl-V pressed
-                       // paste text from clipboard
-                       if (prompt.getCursorLength() > 0) {
-                               // Delete selected section of text
-                               prompt.cursorOperation(
-                                       ChatPrompt::CURSOROP_DELETE,
-                                       ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
-                                       ChatPrompt::CURSOROP_SCOPE_SELECTION);
-                       }
-                       IOSOperator *os_operator = Environment->getOSOperator();
-                       const c8 *text = os_operator->getTextFromClipboard();
-                       if (!text)
-                               return true;
-                       std::basic_string<unsigned char> str((const unsigned char*)text);
-                       prompt.input(std::wstring(str.begin(), str.end()));
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
-               {
-                       // Ctrl-X pressed
-                       // Cut text to clipboard
-                       if (prompt.getCursorLength() <= 0)
-                               return true;
-                       std::wstring wselected = prompt.getSelection();
-                       std::string selected(wselected.begin(), wselected.end());
-                       Environment->getOSOperator()->copyToClipboard(selected.c_str());
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_DELETE,
-                               ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
-                               ChatPrompt::CURSOROP_SCOPE_SELECTION);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
-               {
-                       // Ctrl-U pressed
-                       // kill line to left end
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_DELETE,
-                               ChatPrompt::CURSOROP_DIR_LEFT,
-                               ChatPrompt::CURSOROP_SCOPE_LINE);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
-               {
-                       // Ctrl-K pressed
-                       // kill line to right end
-                       prompt.cursorOperation(
-                               ChatPrompt::CURSOROP_DELETE,
-                               ChatPrompt::CURSOROP_DIR_RIGHT,
-                               ChatPrompt::CURSOROP_SCOPE_LINE);
-                       return true;
-               }
-               else if(event.KeyInput.Key == KEY_TAB)
-               {
-                       // Tab or Shift-Tab pressed
-                       // Nick completion
-                       std::list<std::string> names = m_client->getConnectedPlayerNames();
-                       bool backwards = event.KeyInput.Shift;
-                       prompt.nickCompletion(names, backwards);
-                       return true;
-               }
-               else if(event.KeyInput.Char != 0 && !event.KeyInput.Control)
-               {
-                       #if (defined(linux) || defined(__linux))
-                               wchar_t wc = L'_';
-                               mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
-                               prompt.input(wc);
-                       #else
-                               prompt.input(event.KeyInput.Char);
-                       #endif
-                       return true;
-               }
-       }
-       else if(event.EventType == EET_MOUSE_INPUT_EVENT)
-       {
-               if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
-               {
-                       s32 rows = myround(-3.0 * event.MouseInput.Wheel);
-                       m_chat_backend->scroll(rows);
-               }
-       }
-
-       return Parent ? Parent->OnEvent(event) : false;
-}
-
-void GUIChatConsole::setVisible(bool visible)
-{
-       m_open = visible;
-       IGUIElement::setVisible(visible);
-       if (!visible) {
-               m_height = 0;
-               recalculateConsolePosition();
-       }
-}
-
diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h
deleted file mode 100644 (file)
index 3013a1d..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#ifndef GUICHATCONSOLE_HEADER
-#define GUICHATCONSOLE_HEADER
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include "chat.h"
-#include "config.h"
-
-class Client;
-
-class GUIChatConsole : public gui::IGUIElement
-{
-public:
-       GUIChatConsole(gui::IGUIEnvironment* env,
-                       gui::IGUIElement* parent,
-                       s32 id,
-                       ChatBackend* backend,
-                       Client* client,
-                       IMenuManager* menumgr);
-       virtual ~GUIChatConsole();
-
-       // Open the console (height = desired fraction of screen size)
-       // This doesn't open immediately but initiates an animation.
-       // You should call isOpenInhibited() before this.
-       void openConsole(f32 height);
-
-       bool isOpen() const;
-
-       // Check if the console should not be opened at the moment
-       // This is to avoid reopening the console immediately after closing
-       bool isOpenInhibited() const;
-       // Close the console, equivalent to openConsole(0).
-       // This doesn't close immediately but initiates an animation.
-       void closeConsole();
-       // Close the console immediately, without animation.
-       void closeConsoleAtOnce();
-       // Set whether to close the console after the user presses enter.
-       void setCloseOnEnter(bool close) { m_close_on_enter = close; }
-
-       // Return the desired height (fraction of screen size)
-       // Zero if the console is closed or getting closed
-       f32 getDesiredHeight() const;
-
-       // Replace actual line when adding the actual to the history (if there is any)
-       void replaceAndAddToHistory(std::wstring line);
-
-       // Change how the cursor looks
-       void setCursor(
-               bool visible,
-               bool blinking = false,
-               f32 blink_speed = 1.0,
-               f32 relative_height = 1.0);
-
-       // Irrlicht draw method
-       virtual void draw();
-
-       bool canTakeFocus(gui::IGUIElement* element) { return false; }
-
-       virtual bool OnEvent(const SEvent& event);
-
-       virtual void setVisible(bool visible);
-
-private:
-       void reformatConsole();
-       void recalculateConsolePosition();
-
-       // These methods are called by draw
-       void animate(u32 msec);
-       void drawBackground();
-       void drawText();
-       void drawPrompt();
-
-private:
-       ChatBackend* m_chat_backend;
-       Client* m_client;
-       IMenuManager* m_menumgr;
-
-       // current screen size
-       v2u32 m_screensize;
-
-       // used to compute how much time passed since last animate()
-       u32 m_animate_time_old;
-
-       // should the console be opened or closed?
-       bool m_open;
-       // should it close after you press enter?
-       bool m_close_on_enter;
-       // current console height [pixels]
-       s32 m_height;
-       // desired height [pixels]
-       f32 m_desired_height;
-       // desired height [screen height fraction]
-       f32 m_desired_height_fraction;
-       // console open/close animation speed [screen height fraction / second]
-       f32 m_height_speed;
-       // if nonzero, opening the console is inhibited [milliseconds]
-       u32 m_open_inhibited;
-
-       // cursor blink frame (16-bit value)
-       // cursor is off during [0,32767] and on during [32768,65535]
-       u32 m_cursor_blink;
-       // cursor blink speed [on/off toggles / second]
-       f32 m_cursor_blink_speed;
-       // cursor height [line height]
-       f32 m_cursor_height;
-
-       // background texture
-       video::ITexture* m_background;
-       // background color (including alpha)
-       video::SColor m_background_color;
-
-       // font
-       gui::IGUIFont* m_font;
-       v2u32 m_fontsize;
-};
-
-
-#endif
-
index 0e7cbad07f9a35d1e2b2d9d57dd21ef46218e1e1..e028a0435b0df54547c7256dcfb177c05abf9669 100644 (file)
@@ -1,7 +1,16 @@
+if(USE_FREETYPE)
+       set(UTIL_FREETYPEDEP_SRCS
+               ${CMAKE_CURRENT_SOURCE_DIR}/statictext.cpp
+       )
+else()
+       set(UTIL_FREETYPEDEP_SRCS )
+endif(USE_FREETYPE)
+
 set(UTIL_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/coloredstring.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
@@ -11,5 +20,6 @@ set(UTIL_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp
+       ${UTIL_FREETYPEDEP_SRCS}
        PARENT_SCOPE)
 
diff --git a/src/util/coloredstring.cpp b/src/util/coloredstring.cpp
new file mode 100644 (file)
index 0000000..7db5865
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "coloredstring.h"
+#include "util/string.h"
+
+ColoredString::ColoredString()
+{}
+
+ColoredString::ColoredString(const std::wstring &string, const std::vector<SColor> &colors):
+       m_string(string),
+       m_colors(colors)
+{}
+
+ColoredString::ColoredString(const std::wstring &s) {
+       m_string = colorizeText(s, m_colors, SColor(255, 255, 255, 255));
+}
+
+void ColoredString::operator=(const wchar_t *str) {
+       m_string = colorizeText(str, m_colors, SColor(255, 255, 255, 255));
+}
+
+size_t ColoredString::size() const {
+       return m_string.size();
+}
+
+ColoredString ColoredString::substr(size_t pos, size_t len) const {
+       if (pos == m_string.length())
+               return ColoredString();
+       if (len == std::string::npos || pos + len > m_string.length()) {
+               return ColoredString(
+                          m_string.substr(pos, std::string::npos),
+                          std::vector<SColor>(m_colors.begin() + pos, m_colors.end())
+                      );
+       } else {
+               return ColoredString(
+                          m_string.substr(pos, len),
+                          std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len)
+                      );
+       }
+}
+
+const wchar_t *ColoredString::c_str() const {
+       return m_string.c_str();
+}
+
+const std::vector<SColor> &ColoredString::getColors() const {
+       return m_colors;
+}
+
+const std::wstring &ColoredString::getString() const {
+       return m_string;
+}
diff --git a/src/util/coloredstring.h b/src/util/coloredstring.h
new file mode 100644 (file)
index 0000000..a6d98db
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef COLOREDSTRING_HEADER
+#define COLOREDSTRING_HEADER
+
+#include <string>
+#include <vector>
+#include <SColor.h>
+
+using namespace irr::video;
+
+class ColoredString {
+public:
+       ColoredString();
+       ColoredString(const std::wstring &s);
+       ColoredString(const std::wstring &string, const std::vector<SColor> &colors);
+       void operator=(const wchar_t *str);
+       size_t size() const;
+       ColoredString substr(size_t pos = 0, size_t len = std::string::npos) const;
+       const wchar_t *c_str() const;
+       const std::vector<SColor> &getColors() const;
+       const std::wstring &getString() const;
+private:
+       std::wstring m_string;
+       std::vector<SColor> m_colors;
+};
+
+#endif
diff --git a/src/util/statictext.cpp b/src/util/statictext.cpp
new file mode 100644 (file)
index 0000000..b534b56
--- /dev/null
@@ -0,0 +1,654 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "statictext.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+//Only compile this if freetype is enabled.
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <IGUISkin.h>
+#include <IGUIEnvironment.h>
+#include <IGUIFont.h>
+#include <IVideoDriver.h>
+#include <rect.h>
+#include <SColor.h>
+
+#include "cguittfont/xCGUITTFont.h"
+#include "util/string.h"
+
+namespace irr
+{
+namespace gui
+{
+//! constructor
+StaticText::StaticText(const wchar_t* text, bool border,
+                       IGUIEnvironment* environment, IGUIElement* parent,
+                       s32 id, const core::rect<s32>& rectangle,
+                       bool background)
+: IGUIStaticText(environment, parent, id, rectangle),
+       HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT),
+       Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background),
+       RestrainTextInside(true), RightToLeft(false),
+       OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)),
+       OverrideFont(0), LastBreakFont(0)
+{
+       #ifdef _DEBUG
+       setDebugName("StaticText");
+       #endif
+
+       Text = text;
+       if (environment && environment->getSkin())
+       {
+               BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE);
+       }
+}
+
+
+//! destructor
+StaticText::~StaticText()
+{
+       if (OverrideFont)
+               OverrideFont->drop();
+}
+
+
+//! draws the element and its children
+void StaticText::draw()
+{
+       if (!IsVisible)
+               return;
+
+       IGUISkin* skin = Environment->getSkin();
+       if (!skin)
+               return;
+       video::IVideoDriver* driver = Environment->getVideoDriver();
+
+       core::rect<s32> frameRect(AbsoluteRect);
+
+       // draw background
+
+       if (Background)
+       {
+               if ( !OverrideBGColorEnabled )  // skin-colors can change
+                       BGColor = skin->getColor(gui::EGDC_3D_FACE);
+
+               driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect);
+       }
+
+       // draw the border
+
+       if (Border)
+       {
+               skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect);
+               frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X);
+       }
+
+       // draw the text
+       if (Text.size())
+       {
+               IGUIFont* font = getActiveFont();
+
+               if (font)
+               {
+                       if (!WordWrap)
+                       {
+                               // TODO: add colors here
+                               if (VAlign == EGUIA_LOWERRIGHT)
+                               {
+                                       frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y -
+                                               font->getDimension(L"A").Height - font->getKerningHeight();
+                               }
+                               if (HAlign == EGUIA_LOWERRIGHT)
+                               {
+                                       frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
+                                               font->getDimension(Text.c_str()).Width;
+                               }
+
+                               font->draw(Text.c_str(), frameRect,
+                                       OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
+                                       HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+                       }
+                       else
+                       {
+                               if (font != LastBreakFont)
+                                       breakText();
+
+                               core::rect<s32> r = frameRect;
+                               s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
+                               s32 totalHeight = height * BrokenText.size();
+                               if (VAlign == EGUIA_CENTER)
+                               {
+                                       r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2);
+                               }
+                               else if (VAlign == EGUIA_LOWERRIGHT)
+                               {
+                                       r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight;
+                               }
+
+                               irr::video::SColor previous_color(255, 255, 255, 255);
+                               for (u32 i=0; i<BrokenText.size(); ++i)
+                               {
+                                       if (HAlign == EGUIA_LOWERRIGHT)
+                                       {
+                                               r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
+                                                       font->getDimension(BrokenText[i].c_str()).Width;
+                                       }
+
+                                       std::vector<irr::video::SColor> colors;
+                                       std::wstring str;
+
+                                       str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
+                                       if (!colors.empty())
+                                               previous_color = colors[colors.size() - 1];
+
+                                       irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+                                       tmp->draw(str.c_str(), r,
+                                               colors,
+                                               HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+
+                                       r.LowerRightCorner.Y += height;
+                                       r.UpperLeftCorner.Y += height;
+                               }
+                       }
+               }
+       }
+
+       IGUIElement::draw();
+}
+
+
+//! Sets another skin independent font.
+void StaticText::setOverrideFont(IGUIFont* font)
+{
+       if (OverrideFont == font)
+               return;
+
+       if (OverrideFont)
+               OverrideFont->drop();
+
+       OverrideFont = font;
+
+       if (OverrideFont)
+               OverrideFont->grab();
+
+       breakText();
+}
+
+//! Gets the override font (if any)
+IGUIFont * StaticText::getOverrideFont() const
+{
+       return OverrideFont;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* StaticText::getActiveFont() const
+{
+       if ( OverrideFont )
+               return OverrideFont;
+       IGUISkin* skin = Environment->getSkin();
+       if (skin)
+               return skin->getFont();
+       return 0;
+}
+
+//! Sets another color for the text.
+void StaticText::setOverrideColor(video::SColor color)
+{
+       OverrideColor = color;
+       OverrideColorEnabled = true;
+}
+
+
+//! Sets another color for the text.
+void StaticText::setBackgroundColor(video::SColor color)
+{
+       BGColor = color;
+       OverrideBGColorEnabled = true;
+       Background = true;
+}
+
+
+//! Sets whether to draw the background
+void StaticText::setDrawBackground(bool draw)
+{
+       Background = draw;
+}
+
+
+//! Gets the background color
+video::SColor StaticText::getBackgroundColor() const
+{
+       return BGColor;
+}
+
+
+//! Checks if background drawing is enabled
+bool StaticText::isDrawBackgroundEnabled() const
+{
+       _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+       return Background;
+}
+
+
+//! Sets whether to draw the border
+void StaticText::setDrawBorder(bool draw)
+{
+       Border = draw;
+}
+
+
+//! Checks if border drawing is enabled
+bool StaticText::isDrawBorderEnabled() const
+{
+       _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+       return Border;
+}
+
+
+void StaticText::setTextRestrainedInside(bool restrainTextInside)
+{
+       RestrainTextInside = restrainTextInside;
+}
+
+
+bool StaticText::isTextRestrainedInside() const
+{
+       return RestrainTextInside;
+}
+
+
+void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+       HAlign = horizontal;
+       VAlign = vertical;
+}
+
+
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+const video::SColor& StaticText::getOverrideColor() const
+#else
+video::SColor StaticText::getOverrideColor() const
+#endif
+{
+       return OverrideColor;
+}
+
+
+//! Sets if the static text should use the overide color or the
+//! color in the gui skin.
+void StaticText::enableOverrideColor(bool enable)
+{
+       OverrideColorEnabled = enable;
+}
+
+
+bool StaticText::isOverrideColorEnabled() const
+{
+       _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+       return OverrideColorEnabled;
+}
+
+
+//! Enables or disables word wrap for using the static text as
+//! multiline text control.
+void StaticText::setWordWrap(bool enable)
+{
+       WordWrap = enable;
+       breakText();
+}
+
+
+bool StaticText::isWordWrapEnabled() const
+{
+       _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+       return WordWrap;
+}
+
+
+void StaticText::setRightToLeft(bool rtl)
+{
+       if (RightToLeft != rtl)
+       {
+               RightToLeft = rtl;
+               breakText();
+       }
+}
+
+
+bool StaticText::isRightToLeft() const
+{
+       return RightToLeft;
+}
+
+
+//! Breaks the single text line.
+void StaticText::breakText()
+{
+       if (!WordWrap)
+               return;
+
+       BrokenText.clear();
+
+       IGUISkin* skin = Environment->getSkin();
+       IGUIFont* font = getActiveFont();
+       if (!font)
+               return;
+
+       LastBreakFont = font;
+
+       core::stringw line;
+       core::stringw word;
+       core::stringw whitespace;
+       s32 size = Text.size();
+       s32 length = 0;
+       s32 elWidth = RelativeRect.getWidth();
+       if (Border)
+               elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X);
+       wchar_t c;
+
+       std::vector<irr::video::SColor> colors;
+
+       // We have to deal with right-to-left and left-to-right differently
+       // However, most parts of the following code is the same, it's just
+       // some order and boundaries which change.
+       if (!RightToLeft)
+       {
+               // regular (left-to-right)
+               for (s32 i=0; i<size; ++i)
+               {
+                       c = Text[i];
+                       bool lineBreak = false;
+
+                       if (c == L'\r') // Mac or Windows breaks
+                       {
+                               lineBreak = true;
+                               if (Text[i+1] == L'\n') // Windows breaks
+                               {
+                                       Text.erase(i+1);
+                                       --size;
+                               }
+                               c = '\0';
+                       }
+                       else if (c == L'\n') // Unix breaks
+                       {
+                               lineBreak = true;
+                               c = '\0';
+                       }
+
+                       bool isWhitespace = (c == L' ' || c == 0);
+                       if ( !isWhitespace )
+                       {
+                               // part of a word
+                               word += c;
+                       }
+
+                       if ( isWhitespace || i == (size-1))
+                       {
+                               if (word.size())
+                               {
+                                       // here comes the next whitespace, look if
+                                       // we must break the last word to the next line.
+                                       const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+                                       const std::wstring sanitized = removeEscapes(word.c_str());
+                                       const s32 wordlgth = font->getDimension(sanitized.c_str()).Width;
+
+                                       if (wordlgth > elWidth)
+                                       {
+                                               // This word is too long to fit in the available space, look for
+                                               // the Unicode Soft HYphen (SHY / 00AD) character for a place to
+                                               // break the word at
+                                               int where = word.findFirst( wchar_t(0x00AD) );
+                                               if (where != -1)
+                                               {
+                                                       core::stringw first  = word.subString(0, where);
+                                                       core::stringw second = word.subString(where, word.size() - where);
+                                                       BrokenText.push_back(line + first + L"-");
+                                                       const s32 secondLength = font->getDimension(second.c_str()).Width;
+
+                                                       length = secondLength;
+                                                       line = second;
+                                               }
+                                               else
+                                               {
+                                                       // No soft hyphen found, so there's nothing more we can do
+                                                       // break to next line
+                                                       if (length)
+                                                               BrokenText.push_back(line);
+                                                       length = wordlgth;
+                                                       line = word;
+                                               }
+                                       }
+                                       else if (length && (length + wordlgth + whitelgth > elWidth))
+                                       {
+                                               // break to next line
+                                               BrokenText.push_back(line);
+                                               length = wordlgth;
+                                               line = word;
+                                       }
+                                       else
+                                       {
+                                               // add word to line
+                                               line += whitespace;
+                                               line += word;
+                                               length += whitelgth + wordlgth;
+                                       }
+
+                                       word = L"";
+                                       whitespace = L"";
+                               }
+
+                               if ( isWhitespace )
+                               {
+                                       whitespace += c;
+                               }
+
+                               // compute line break
+                               if (lineBreak)
+                               {
+                                       line += whitespace;
+                                       line += word;
+                                       BrokenText.push_back(line);
+                                       line = L"";
+                                       word = L"";
+                                       whitespace = L"";
+                                       length = 0;
+                               }
+                       }
+               }
+
+               line += whitespace;
+               line += word;
+               BrokenText.push_back(line);
+       }
+       else
+       {
+               // right-to-left
+               for (s32 i=size; i>=0; --i)
+               {
+                       c = Text[i];
+                       bool lineBreak = false;
+
+                       if (c == L'\r') // Mac or Windows breaks
+                       {
+                               lineBreak = true;
+                               if ((i>0) && Text[i-1] == L'\n') // Windows breaks
+                               {
+                                       Text.erase(i-1);
+                                       --size;
+                               }
+                               c = '\0';
+                       }
+                       else if (c == L'\n') // Unix breaks
+                       {
+                               lineBreak = true;
+                               c = '\0';
+                       }
+
+                       if (c==L' ' || c==0 || i==0)
+                       {
+                               if (word.size())
+                               {
+                                       // here comes the next whitespace, look if
+                                       // we must break the last word to the next line.
+                                       const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+                                       const s32 wordlgth = font->getDimension(word.c_str()).Width;
+
+                                       if (length && (length + wordlgth + whitelgth > elWidth))
+                                       {
+                                               // break to next line
+                                               BrokenText.push_back(line);
+                                               length = wordlgth;
+                                               line = word;
+                                       }
+                                       else
+                                       {
+                                               // add word to line
+                                               line = whitespace + line;
+                                               line = word + line;
+                                               length += whitelgth + wordlgth;
+                                       }
+
+                                       word = L"";
+                                       whitespace = L"";
+                               }
+
+                               if (c != 0)
+                                       whitespace = core::stringw(&c, 1) + whitespace;
+
+                               // compute line break
+                               if (lineBreak)
+                               {
+                                       line = whitespace + line;
+                                       line = word + line;
+                                       BrokenText.push_back(line);
+                                       line = L"";
+                                       word = L"";
+                                       whitespace = L"";
+                                       length = 0;
+                               }
+                       }
+                       else
+                       {
+                               // yippee this is a word..
+                               word = core::stringw(&c, 1) + word;
+                       }
+               }
+
+               line = whitespace + line;
+               line = word + line;
+               BrokenText.push_back(line);
+       }
+}
+
+
+//! Sets the new caption of this element.
+void StaticText::setText(const wchar_t* text)
+{
+       IGUIElement::setText(text);
+       breakText();
+}
+
+
+void StaticText::updateAbsolutePosition()
+{
+       IGUIElement::updateAbsolutePosition();
+       breakText();
+}
+
+
+//! Returns the height of the text in pixels when it is drawn.
+s32 StaticText::getTextHeight() const
+{
+       IGUIFont* font = getActiveFont();
+       if (!font)
+               return 0;
+
+       s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
+
+       if (WordWrap)
+               height *= BrokenText.size();
+
+       return height;
+}
+
+
+s32 StaticText::getTextWidth() const
+{
+       IGUIFont * font = getActiveFont();
+       if(!font)
+               return 0;
+
+       if(WordWrap)
+       {
+               s32 widest = 0;
+
+               for(u32 line = 0; line < BrokenText.size(); ++line)
+               {
+                       s32 width = font->getDimension(BrokenText[line].c_str()).Width;
+
+                       if(width > widest)
+                               widest = width;
+               }
+
+               return widest;
+       }
+       else
+       {
+               return font->getDimension(Text.c_str()).Width;
+       }
+}
+
+
+//! Writes attributes of the element.
+//! Implement this to expose the attributes of your element for
+//! scripting languages, editors, debuggers or xml serialization purposes.
+void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
+{
+       IGUIStaticText::serializeAttributes(out,options);
+
+       out->addBool    ("Border",              Border);
+       out->addBool    ("OverrideColorEnabled",OverrideColorEnabled);
+       out->addBool    ("OverrideBGColorEnabled",OverrideBGColorEnabled);
+       out->addBool    ("WordWrap",            WordWrap);
+       out->addBool    ("Background",          Background);
+       out->addBool    ("RightToLeft",         RightToLeft);
+       out->addBool    ("RestrainTextInside",  RestrainTextInside);
+       out->addColor   ("OverrideColor",       OverrideColor);
+       out->addColor   ("BGColor",             BGColor);
+       out->addEnum    ("HTextAlign",          HAlign, GUIAlignmentNames);
+       out->addEnum    ("VTextAlign",          VAlign, GUIAlignmentNames);
+
+       // out->addFont ("OverrideFont",        OverrideFont);
+}
+
+
+//! Reads attributes of the element
+void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
+{
+       IGUIStaticText::deserializeAttributes(in,options);
+
+       Border = in->getAttributeAsBool("Border");
+       enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+       OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled");
+       setWordWrap(in->getAttributeAsBool("WordWrap"));
+       Background = in->getAttributeAsBool("Background");
+       RightToLeft = in->getAttributeAsBool("RightToLeft");
+       RestrainTextInside = in->getAttributeAsBool("RestrainTextInside");
+       OverrideColor = in->getAttributeAsColor("OverrideColor");
+       BGColor = in->getAttributeAsColor("BGColor");
+
+       setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+                      (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+       // OverrideFont = in->getAttributeAsFont("OverrideFont");
+}
+
+} // end namespace gui
+} // end namespace irr
+
+
+#endif // _IRR_COMPILE_WITH_GUI_
diff --git a/src/util/statictext.h b/src/util/statictext.h
new file mode 100644 (file)
index 0000000..8d2f879
--- /dev/null
@@ -0,0 +1,150 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef __C_GUI_STATIC_TEXT_H_INCLUDED__
+#define __C_GUI_STATIC_TEXT_H_INCLUDED__
+
+#include "IrrCompileConfig.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUIStaticText.h"
+#include "irrArray.h"
+
+#include <vector>
+
+namespace irr
+{
+namespace gui
+{
+       class StaticText : public IGUIStaticText
+       {
+       public:
+
+               //! constructor
+               StaticText(const wchar_t* text, bool border, IGUIEnvironment* environment,
+                       IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+                       bool background = false);
+
+               //! destructor
+               virtual ~StaticText();
+
+               //! draws the element and its children
+               virtual void draw();
+
+               //! Sets another skin independent font.
+               virtual void setOverrideFont(IGUIFont* font=0);
+
+               //! Gets the override font (if any)
+               virtual IGUIFont* getOverrideFont() const;
+
+               //! Get the font which is used right now for drawing
+               virtual IGUIFont* getActiveFont() const;
+
+               //! Sets another color for the text.
+               virtual void setOverrideColor(video::SColor color);
+
+               //! Sets another color for the background.
+               virtual void setBackgroundColor(video::SColor color);
+
+               //! Sets whether to draw the background
+               virtual void setDrawBackground(bool draw);
+
+               //! Gets the background color
+               virtual video::SColor getBackgroundColor() const;
+
+               //! Checks if background drawing is enabled
+               virtual bool isDrawBackgroundEnabled() const;
+
+               //! Sets whether to draw the border
+               virtual void setDrawBorder(bool draw);
+
+               //! Checks if border drawing is enabled
+               virtual bool isDrawBorderEnabled() const;
+
+               //! Sets alignment mode for text
+               virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+               //! Gets the override color
+               #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+               virtual const video::SColor& getOverrideColor() const;
+               #else
+               virtual video::SColor getOverrideColor() const;
+               #endif
+
+               //! Sets if the static text should use the overide color or the
+               //! color in the gui skin.
+               virtual void enableOverrideColor(bool enable);
+
+               //! Checks if an override color is enabled
+               virtual bool isOverrideColorEnabled() const;
+
+               //! Set whether the text in this label should be clipped if it goes outside bounds
+               virtual void setTextRestrainedInside(bool restrainedInside);
+
+               //! Checks if the text in this label should be clipped if it goes outside bounds
+               virtual bool isTextRestrainedInside() const;
+
+               //! Enables or disables word wrap for using the static text as
+               //! multiline text control.
+               virtual void setWordWrap(bool enable);
+
+               //! Checks if word wrap is enabled
+               virtual bool isWordWrapEnabled() const;
+
+               //! Sets the new caption of this element.
+               virtual void setText(const wchar_t* text);
+
+               //! Returns the height of the text in pixels when it is drawn.
+               virtual s32 getTextHeight() const;
+
+               //! Returns the width of the current text, in the current font
+               virtual s32 getTextWidth() const;
+
+               //! Updates the absolute position, splits text if word wrap is enabled
+               virtual void updateAbsolutePosition();
+
+               //! Set whether the string should be interpreted as right-to-left (RTL) text
+               /** \note This component does not implement the Unicode bidi standard, the
+               text of the component should be already RTL if you call this. The
+               main difference when RTL is enabled is that the linebreaks for multiline
+               elements are performed starting from the end.
+               */
+               virtual void setRightToLeft(bool rtl);
+
+               //! Checks if the text should be interpreted as right-to-left text
+               virtual bool isRightToLeft() const;
+
+               //! Writes attributes of the element.
+               virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+               //! Reads attributes of the element
+               virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+       private:
+
+               //! Breaks the single text line.
+               void breakText();
+
+               EGUI_ALIGNMENT HAlign, VAlign;
+               bool Border;
+               bool OverrideColorEnabled;
+               bool OverrideBGColorEnabled;
+               bool WordWrap;
+               bool Background;
+               bool RestrainTextInside;
+               bool RightToLeft;
+
+               video::SColor OverrideColor, BGColor;
+               gui::IGUIFont* OverrideFont;
+               gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated.
+
+               core::array< core::stringw > BrokenText;
+       };
+
+} // end namespace gui
+} // end namespace irr
+
+#endif // _IRR_COMPILE_WITH_GUI_
+
+#endif // C_GUI_STATIC_TEXT_H_INCLUDED