LCOV - code coverage report
Current view: top level - source/gui/ObjectTypes - CInput.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 4 974 0.4 %
Date: 2023-01-19 00:18:29 Functions: 2 25 8.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "CInput.h"
      21             : 
      22             : #include "graphics/Canvas2D.h"
      23             : #include "graphics/FontMetrics.h"
      24             : #include "graphics/TextRenderer.h"
      25             : #include "gui/CGUI.h"
      26             : #include "gui/CGUIScrollBarVertical.h"
      27             : #include "lib/timer.h"
      28             : #include "lib/utf8.h"
      29             : #include "ps/ConfigDB.h"
      30             : #include "ps/CStrInternStatic.h"
      31             : #include "ps/GameSetup/Config.h"
      32             : #include "ps/Globals.h"
      33             : #include "ps/Hotkey.h"
      34             : #include "ps/VideoMode.h"
      35             : #include "renderer/backend/IDeviceCommandContext.h"
      36             : #include "renderer/Renderer.h"
      37             : 
      38             : #include <sstream>
      39             : 
      40             : extern int g_yres;
      41             : 
      42           1 : const CStr CInput::EventNameTextEdit = "TextEdit";
      43           1 : const CStr CInput::EventNamePress = "Press";
      44           1 : const CStr CInput::EventNameTab = "Tab";
      45             : 
      46           0 : CInput::CInput(CGUI& pGUI)
      47             :     :
      48             :     IGUIObject(pGUI),
      49             :     IGUIScrollBarOwner(*static_cast<IGUIObject*>(this)),
      50             :     m_iBufferPos(-1),
      51             :     m_iBufferPos_Tail(-1),
      52             :     m_SelectingText(),
      53             :     m_HorizontalScroll(),
      54             :     m_PrevTime(),
      55             :     m_CursorVisState(true),
      56             :     m_CursorBlinkRate(0.5),
      57             :     m_ComposingText(),
      58             :     m_GeneratedPlaceholderTextValid(false),
      59             :     m_iComposedLength(),
      60             :     m_iComposedPos(),
      61             :     m_iInsertPos(),
      62             :     m_BufferPosition(this, "buffer_position"),
      63             :     m_BufferZone(this, "buffer_zone"),
      64             :     m_Caption(this, "caption"),
      65             :     m_Font(this, "font"),
      66             :     m_MaskChar(this, "mask_char"),
      67             :     m_Mask(this, "mask"),
      68             :     m_MaxLength(this, "max_length"),
      69             :     m_MultiLine(this, "multiline"),
      70             :     m_Readonly(this, "readonly"),
      71             :     m_ScrollBar(this, "scrollbar"),
      72             :     m_ScrollBarStyle(this, "scrollbar_style"),
      73             :     m_Sprite(this, "sprite"),
      74             :     m_SpriteOverlay(this, "sprite_overlay"),
      75             :     m_SpriteSelectArea(this, "sprite_selectarea"),
      76             :     m_TextColor(this, "textcolor"),
      77             :     m_TextColorSelected(this, "textcolor_selected"),
      78             :     m_PlaceholderText(this, "placeholder_text"),
      79           0 :     m_PlaceholderColor(this, "placeholder_color")
      80             : {
      81           0 :     CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate);
      82             : 
      83           0 :     auto bar = std::make_unique<CGUIScrollBarVertical>(pGUI);
      84           0 :     bar->SetRightAligned(true);
      85           0 :     AddScrollBar(std::move(bar));
      86           0 : }
      87             : 
      88           0 : CInput::~CInput()
      89             : {
      90           0 : }
      91             : 
      92           0 : void CInput::UpdateBufferPositionSetting()
      93             : {
      94           0 :     m_BufferPosition.Set(m_iBufferPos, false);
      95           0 : }
      96             : 
      97           0 : void CInput::ClearComposedText()
      98             : {
      99           0 :     m_Caption.GetMutable().erase(m_iInsertPos, m_iComposedLength);
     100           0 :     m_iBufferPos = m_iInsertPos;
     101           0 :     UpdateBufferPositionSetting();
     102           0 :     m_iComposedLength = 0;
     103           0 :     m_iComposedPos = 0;
     104           0 : }
     105             : 
     106           0 : InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
     107             : {
     108           0 :     ENSURE(m_iBufferPos != -1);
     109             : 
     110             :     // Get direct access to silently mutate m_Caption.
     111             :     // (Messages don't currently need to be sent)
     112           0 :     CStrW& caption = m_Caption.GetMutable();
     113             : 
     114           0 :     switch (ev->ev.type)
     115             :     {
     116           0 :     case SDL_HOTKEYDOWN:
     117             :     {
     118           0 :         if (m_ComposingText)
     119           0 :             return IN_HANDLED;
     120             : 
     121           0 :         return ManuallyHandleHotkeyEvent(ev);
     122             :     }
     123             :     // SDL2 has a new method of text input that better supports Unicode and CJK
     124             :     // see https://wiki.libsdl.org/Tutorials/TextInput
     125           0 :     case SDL_TEXTINPUT:
     126             :     {
     127           0 :         if (m_Readonly)
     128           0 :             return IN_PASS;
     129             : 
     130             :         // Text has been committed, either single key presses or through an IME
     131           0 :         std::wstring text = wstring_from_utf8(ev->ev.text.text);
     132             : 
     133             :         // Check max length
     134           0 :         if (m_MaxLength != 0 && caption.length() + text.length() > static_cast<size_t>(m_MaxLength))
     135           0 :             return IN_HANDLED;
     136             : 
     137           0 :         m_WantedX = 0.0f;
     138             : 
     139           0 :         if (SelectingText())
     140           0 :             DeleteCurSelection();
     141             : 
     142           0 :         if (m_ComposingText)
     143             :         {
     144           0 :             ClearComposedText();
     145           0 :             m_ComposingText = false;
     146             :         }
     147             : 
     148           0 :         if (m_iBufferPos == static_cast<int>(caption.length()))
     149           0 :             caption.append(text);
     150             :         else
     151           0 :             caption.insert(m_iBufferPos, text);
     152             : 
     153           0 :         UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
     154             : 
     155           0 :         m_iBufferPos += text.length();
     156           0 :         UpdateBufferPositionSetting();
     157           0 :         m_iBufferPos_Tail = -1;
     158             : 
     159           0 :         UpdateAutoScroll();
     160           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     161             : 
     162           0 :         return IN_HANDLED;
     163             :     }
     164           0 :     case SDL_TEXTEDITING:
     165             :     {
     166           0 :         if (m_Readonly)
     167           0 :             return IN_PASS;
     168             : 
     169             :         // Text is being composed with an IME
     170             :         // TODO: indicate this by e.g. underlining the uncommitted text
     171           0 :         const char* rawText = ev->ev.edit.text;
     172           0 :         int rawLength = strlen(rawText);
     173           0 :         std::wstring wtext = wstring_from_utf8(rawText);
     174             : 
     175           0 :         m_WantedX = 0.0f;
     176             : 
     177           0 :         if (SelectingText())
     178           0 :             DeleteCurSelection();
     179             : 
     180             :         // Remember cursor position when text composition begins
     181           0 :         if (!m_ComposingText)
     182           0 :             m_iInsertPos = m_iBufferPos;
     183             :         else
     184             :         {
     185             :             // Composed text is replaced each time
     186           0 :             ClearComposedText();
     187             :         }
     188             : 
     189           0 :         m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0;
     190           0 :         if (m_ComposingText)
     191             :         {
     192           0 :             caption.insert(m_iInsertPos, wtext);
     193             : 
     194             :             // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start
     195             :             // increases without limit, so don't let it advance beyond the composed text length
     196           0 :             m_iComposedLength = wtext.length();
     197           0 :             m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength;
     198           0 :             m_iBufferPos = m_iInsertPos + m_iComposedPos;
     199             : 
     200             :             // TODO: composed text selection - what does ev.edit.length do?
     201           0 :             m_iBufferPos_Tail = -1;
     202             :         }
     203             : 
     204           0 :         UpdateBufferPositionSetting();
     205           0 :         UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
     206             : 
     207           0 :         UpdateAutoScroll();
     208           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     209             : 
     210           0 :         return IN_HANDLED;
     211             :     }
     212           0 :     case SDL_KEYDOWN:
     213             :     case SDL_KEYUP:
     214             :     {
     215             :         // Since the GUI framework doesn't handle to set settings
     216             :         //  in Unicode (CStrW), we'll simply retrieve the actual
     217             :         //  pointer and edit that.
     218           0 :         SDL_Keycode keyCode = ev->ev.key.keysym.sym;
     219             : 
     220             :         // We have a probably printable key - we should return HANDLED so it can't trigger hotkeys.
     221             :         // However, if Ctrl/Meta modifiers are active, just pass it through instead,
     222             :         // assuming that we are indeed trying to trigger hotkeys (e.g. copy/paste).
     223             :         // Escape & the "cancel" hotkey are also passed through to allow closing dialogs easily.
     224             :         // See also similar logic in CConsole.cpp
     225             :         // NB: this assumes that Ctrl/GUI aren't used in the Manually* functions below,
     226             :         // as those code paths would obviously never be taken.
     227           0 :         if (keyCode == SDLK_ESCAPE || EventWillFireHotkey(ev, "cancel") ||
     228           0 :              g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL] ||
     229           0 :              g_scancodes[SDL_SCANCODE_LGUI] || g_scancodes[SDL_SCANCODE_RGUI])
     230           0 :             return IN_PASS;
     231             : 
     232           0 :         if (m_ComposingText)
     233           0 :             return IN_HANDLED;
     234             : 
     235           0 :         if (ev->ev.type == SDL_KEYDOWN)
     236             :         {
     237           0 :             ManuallyImmutableHandleKeyDownEvent(keyCode);
     238           0 :             ManuallyMutableHandleKeyDownEvent(keyCode);
     239             : 
     240           0 :             UpdateBufferPositionSetting();
     241             :         }
     242           0 :         return IN_HANDLED;
     243             :     }
     244           0 :     default:
     245             :     {
     246           0 :         return IN_PASS;
     247             :     }
     248             :     }
     249             : }
     250             : 
     251           0 : void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode)
     252             : {
     253           0 :     if (m_Readonly)
     254           0 :         return;
     255             : 
     256           0 :     wchar_t cooked = 0;
     257             : 
     258             :     // Get direct access to silently mutate m_Caption.
     259             :     // (Messages don't currently need to be sent)
     260           0 :     CStrW& caption = m_Caption.GetMutable();
     261             : 
     262           0 :     switch (keyCode)
     263             :     {
     264           0 :     case SDLK_TAB:
     265             :     {
     266           0 :         SendEvent(GUIM_TAB, EventNameTab);
     267             :         // Don't send a textedit event, because it should only
     268             :         // be sent if the GUI control changes the text
     269           0 :         break;
     270             :     }
     271           0 :     case SDLK_BACKSPACE:
     272             :     {
     273           0 :         m_WantedX = 0.0f;
     274             : 
     275           0 :         if (SelectingText())
     276           0 :             DeleteCurSelection();
     277             :         else
     278             :         {
     279           0 :             m_iBufferPos_Tail = -1;
     280             : 
     281           0 :             if (caption.empty() || m_iBufferPos == 0)
     282           0 :                 break;
     283             : 
     284           0 :             if (m_iBufferPos == static_cast<int>(caption.length()))
     285           0 :                 caption = caption.Left(static_cast<long>(caption.length()) - 1);
     286             :             else
     287           0 :                 caption =
     288           0 :                     caption.Left(m_iBufferPos - 1) +
     289           0 :                     caption.Right(static_cast<long>(caption.length()) - m_iBufferPos);
     290             : 
     291           0 :             --m_iBufferPos;
     292             : 
     293           0 :             UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
     294             :         }
     295             : 
     296           0 :         UpdateAutoScroll();
     297           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     298           0 :         break;
     299             :     }
     300           0 :     case SDLK_DELETE:
     301             :     {
     302           0 :         m_WantedX = 0.0f;
     303             : 
     304           0 :         if (SelectingText())
     305           0 :             DeleteCurSelection();
     306             :         else
     307             :         {
     308           0 :             if (caption.empty() || m_iBufferPos == static_cast<int>(caption.length()))
     309           0 :                 break;
     310             : 
     311           0 :             caption =
     312           0 :                 caption.Left(m_iBufferPos) +
     313           0 :                 caption.Right(static_cast<long>(caption.length()) - (m_iBufferPos + 1));
     314             : 
     315           0 :             UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
     316             :         }
     317             : 
     318           0 :         UpdateAutoScroll();
     319           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     320           0 :         break;
     321             :     }
     322           0 :     case SDLK_KP_ENTER:
     323             :     case SDLK_RETURN:
     324             :     {
     325             :         // 'Return' should do a Press event for single liners (e.g. submitting forms)
     326             :         //  otherwise a '\n' character will be added.
     327           0 :         if (!m_MultiLine)
     328             :         {
     329           0 :             SendEvent(GUIM_PRESSED, EventNamePress);
     330           0 :             break;
     331             :         }
     332             : 
     333           0 :         cooked = '\n'; // Change to '\n' and do default:
     334             :         FALLTHROUGH;
     335             :     }
     336           0 :     default: // Insert a character
     337             :     {
     338             :         // Regular input is handled via SDL_TEXTINPUT, so we should ignore it here.
     339           0 :         if (cooked == 0)
     340           0 :             return;
     341             : 
     342             :         // Check max length
     343           0 :         if (m_MaxLength != 0 && caption.length() >= static_cast<size_t>(m_MaxLength))
     344           0 :             break;
     345             : 
     346           0 :         m_WantedX = 0.0f;
     347             : 
     348           0 :         if (SelectingText())
     349           0 :             DeleteCurSelection();
     350           0 :         m_iBufferPos_Tail = -1;
     351             : 
     352           0 :         if (m_iBufferPos == static_cast<int>(caption.length()))
     353           0 :             caption += cooked;
     354             :         else
     355           0 :             caption =
     356           0 :                 caption.Left(m_iBufferPos) + cooked +
     357           0 :                 caption.Right(static_cast<long>(caption.length()) - m_iBufferPos);
     358             : 
     359           0 :         UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1);
     360             : 
     361           0 :         ++m_iBufferPos;
     362             : 
     363           0 :         UpdateAutoScroll();
     364           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     365           0 :         break;
     366             :     }
     367             :     }
     368             : }
     369             : 
     370           0 : void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode)
     371             : {
     372           0 :     bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
     373             : 
     374           0 :     const CStrW& caption = *m_Caption;
     375             : 
     376           0 :     switch (keyCode)
     377             :     {
     378           0 :     case SDLK_HOME:
     379             :     {
     380             :         // If there's not a selection, we should create one now
     381           0 :         if (!shiftKeyPressed)
     382             :         {
     383             :             // Make sure a selection isn't created.
     384           0 :             m_iBufferPos_Tail = -1;
     385             :         }
     386           0 :         else if (!SelectingText())
     387             :         {
     388             :             // Place tail at the current point:
     389           0 :             m_iBufferPos_Tail = m_iBufferPos;
     390             :         }
     391             : 
     392           0 :         m_iBufferPos = 0;
     393           0 :         m_WantedX = 0.0f;
     394             : 
     395           0 :         UpdateAutoScroll();
     396           0 :         break;
     397             :     }
     398           0 :     case SDLK_END:
     399             :     {
     400             :         // If there's not a selection, we should create one now
     401           0 :         if (!shiftKeyPressed)
     402             :         {
     403             :             // Make sure a selection isn't created.
     404           0 :             m_iBufferPos_Tail = -1;
     405             :         }
     406           0 :         else if (!SelectingText())
     407             :         {
     408             :             // Place tail at the current point:
     409           0 :             m_iBufferPos_Tail = m_iBufferPos;
     410             :         }
     411             : 
     412           0 :         m_iBufferPos = static_cast<long>(caption.length());
     413           0 :         m_WantedX = 0.0f;
     414             : 
     415           0 :         UpdateAutoScroll();
     416           0 :         break;
     417             :     }
     418             :     /**
     419             :      * Conventions for Left/Right when text is selected:
     420             :      *
     421             :      * References:
     422             :      *
     423             :      * Visual Studio
     424             :      *  Visual Studio has the 'newer' approach, used by newer versions of
     425             :      * things, and in newer applications. A left press will always place
     426             :      * the pointer on the left edge of the selection, and then of course
     427             :      * remove the selection. Right will do the exact same thing.
     428             :      * If you have the pointer on the right edge and press right, it will
     429             :      * in other words just remove the selection.
     430             :      *
     431             :      * Windows (eg. Notepad)
     432             :      *  A left press always takes the pointer a step to the left and
     433             :      * removes the selection as if it were never there in the first place.
     434             :      * Right of course does the same thing but to the right.
     435             :      *
     436             :      * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN
     437             :      * Messenger.
     438             :      */
     439           0 :     case SDLK_LEFT:
     440             :     {
     441           0 :         m_WantedX = 0.f;
     442             : 
     443           0 :         if (shiftKeyPressed || !SelectingText())
     444             :         {
     445           0 :             if (!shiftKeyPressed)
     446           0 :                 m_iBufferPos_Tail = -1;
     447           0 :             else if (!SelectingText())
     448           0 :                 m_iBufferPos_Tail = m_iBufferPos;
     449             : 
     450           0 :             if (m_iBufferPos > 0)
     451           0 :                 --m_iBufferPos;
     452             :         }
     453             :         else
     454             :         {
     455           0 :             if (m_iBufferPos_Tail < m_iBufferPos)
     456           0 :                 m_iBufferPos = m_iBufferPos_Tail;
     457             : 
     458           0 :             m_iBufferPos_Tail = -1;
     459             :         }
     460             : 
     461           0 :         UpdateAutoScroll();
     462           0 :         break;
     463             :     }
     464           0 :     case SDLK_RIGHT:
     465             :     {
     466           0 :         m_WantedX = 0.0f;
     467             : 
     468           0 :         if (shiftKeyPressed || !SelectingText())
     469             :         {
     470           0 :             if (!shiftKeyPressed)
     471           0 :                 m_iBufferPos_Tail = -1;
     472           0 :             else if (!SelectingText())
     473           0 :                 m_iBufferPos_Tail = m_iBufferPos;
     474             : 
     475           0 :             if (m_iBufferPos < static_cast<int>(caption.length()))
     476           0 :                 ++m_iBufferPos;
     477             :         }
     478             :         else
     479             :         {
     480           0 :             if (m_iBufferPos_Tail > m_iBufferPos)
     481           0 :                 m_iBufferPos = m_iBufferPos_Tail;
     482             : 
     483           0 :             m_iBufferPos_Tail = -1;
     484             :         }
     485             : 
     486           0 :         UpdateAutoScroll();
     487           0 :         break;
     488             :     }
     489             :     /**
     490             :      * Conventions for Up/Down when text is selected:
     491             :      *
     492             :      * References:
     493             :      *
     494             :      * Visual Studio
     495             :      *  Visual Studio has a very strange approach, down takes you below the
     496             :      * selection to the next row, and up to the one prior to the whole
     497             :      * selection. The weird part is that it is always aligned as the
     498             :      * 'pointer'. I decided this is to much work for something that is
     499             :      * a bit arbitrary
     500             :      *
     501             :      * Windows (eg. Notepad)
     502             :      *  Just like with left/right, the selection is destroyed and it moves
     503             :      * just as if there never were a selection.
     504             :      *
     505             :      * I chose the Notepad convention even though I use the VS convention with
     506             :      * left/right.
     507             :      */
     508           0 :     case SDLK_UP:
     509             :     {
     510           0 :         if (!shiftKeyPressed)
     511           0 :             m_iBufferPos_Tail = -1;
     512           0 :         else if (!SelectingText())
     513           0 :             m_iBufferPos_Tail = m_iBufferPos;
     514             : 
     515           0 :         std::list<SRow>::iterator current = m_CharacterPositions.begin();
     516           0 :         while (current != m_CharacterPositions.end())
     517             :         {
     518           0 :             if (m_iBufferPos >= current->m_ListStart &&
     519           0 :                 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
     520           0 :                 break;
     521             : 
     522           0 :             ++current;
     523             :         }
     524             : 
     525             :         float pos_x;
     526           0 :         if (m_iBufferPos - current->m_ListStart == 0)
     527           0 :             pos_x = 0.f;
     528             :         else
     529           0 :             pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
     530             : 
     531           0 :         if (m_WantedX > pos_x)
     532           0 :             pos_x = m_WantedX;
     533             : 
     534             :         // Now change row:
     535           0 :         if (current != m_CharacterPositions.begin())
     536             :         {
     537           0 :             --current;
     538             : 
     539             :             // Find X-position:
     540           0 :             m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
     541             :         }
     542             :         // else we can't move up
     543             : 
     544           0 :         UpdateAutoScroll();
     545           0 :         break;
     546             :     }
     547           0 :     case SDLK_DOWN:
     548             :     {
     549           0 :         if (!shiftKeyPressed)
     550           0 :             m_iBufferPos_Tail = -1;
     551           0 :         else if (!SelectingText())
     552           0 :             m_iBufferPos_Tail = m_iBufferPos;
     553             : 
     554           0 :         std::list<SRow>::iterator current = m_CharacterPositions.begin();
     555           0 :         while (current != m_CharacterPositions.end())
     556             :         {
     557           0 :             if (m_iBufferPos >= current->m_ListStart &&
     558           0 :                 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
     559           0 :                 break;
     560             : 
     561           0 :             ++current;
     562             :         }
     563             : 
     564             :         float pos_x;
     565             : 
     566           0 :         if (m_iBufferPos - current->m_ListStart == 0)
     567           0 :             pos_x = 0.f;
     568             :         else
     569           0 :             pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
     570             : 
     571           0 :         if (m_WantedX > pos_x)
     572           0 :             pos_x = m_WantedX;
     573             : 
     574             :         // Now change row:
     575             :         // Add first, so we can check if it's .end()
     576           0 :         ++current;
     577           0 :         if (current != m_CharacterPositions.end())
     578             :         {
     579             :             // Find X-position:
     580           0 :             m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
     581             :         }
     582             :         // else we can't move up
     583             : 
     584           0 :         UpdateAutoScroll();
     585           0 :         break;
     586             :     }
     587           0 :     case SDLK_PAGEUP:
     588             :     {
     589           0 :         GetScrollBar(0).ScrollMinusPlenty();
     590           0 :         UpdateAutoScroll();
     591           0 :         break;
     592             :     }
     593           0 :     case SDLK_PAGEDOWN:
     594             :     {
     595           0 :         GetScrollBar(0).ScrollPlusPlenty();
     596           0 :         UpdateAutoScroll();
     597           0 :         break;
     598             :     }
     599           0 :     default:
     600             :     {
     601           0 :         break;
     602             :     }
     603             :     }
     604           0 : }
     605             : 
     606           0 : void CInput::SetupGeneratedPlaceholderText()
     607             : {
     608           0 :     m_GeneratedPlaceholderText = CGUIText(m_pGUI, m_PlaceholderText, m_Font, 0, m_BufferZone, EAlign::LEFT, this);
     609           0 :     m_GeneratedPlaceholderTextValid = true;
     610           0 : }
     611             : 
     612           0 : InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
     613             : {
     614           0 :     bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
     615             : 
     616           0 :     std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
     617             : 
     618             :     // Get direct access to silently mutate m_Caption.
     619             :     // (Messages don't currently need to be sent)
     620           0 :     CStrW& caption = m_Caption.GetMutable();
     621             : 
     622           0 :     if (hotkey == "paste")
     623             :     {
     624           0 :         if (m_Readonly)
     625           0 :             return IN_PASS;
     626             : 
     627           0 :         m_WantedX = 0.0f;
     628             : 
     629           0 :         char* utf8_text = SDL_GetClipboardText();
     630           0 :         if (!utf8_text)
     631           0 :             return IN_HANDLED;
     632             : 
     633           0 :         std::wstring text = wstring_from_utf8(utf8_text);
     634           0 :         SDL_free(utf8_text);
     635             : 
     636             :         // Check max length
     637           0 :         if (m_MaxLength != 0 && caption.length() + text.length() > static_cast<size_t>(m_MaxLength))
     638           0 :             text.erase(static_cast<size_t>(m_MaxLength) - caption.length());
     639             : 
     640           0 :         if (SelectingText())
     641           0 :             DeleteCurSelection();
     642             : 
     643           0 :         if (m_iBufferPos == static_cast<int>(caption.length()))
     644           0 :             caption += text;
     645             :         else
     646           0 :             caption =
     647           0 :                 caption.Left(m_iBufferPos) + text +
     648           0 :                 caption.Right(static_cast<long>(caption.length()) - m_iBufferPos);
     649             : 
     650           0 :         UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
     651             : 
     652           0 :         m_iBufferPos += static_cast<int>(text.size());
     653           0 :         UpdateAutoScroll();
     654           0 :         UpdateBufferPositionSetting();
     655             : 
     656           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     657             : 
     658           0 :         return IN_HANDLED;
     659             :     }
     660           0 :     else if (hotkey == "copy" || hotkey == "cut")
     661             :     {
     662           0 :         if (m_Readonly && hotkey == "cut")
     663           0 :             return IN_PASS;
     664             : 
     665           0 :         m_WantedX = 0.0f;
     666             : 
     667           0 :         if (SelectingText())
     668             :         {
     669             :             int virtualFrom;
     670             :             int virtualTo;
     671             : 
     672           0 :             if (m_iBufferPos_Tail >= m_iBufferPos)
     673             :             {
     674           0 :                 virtualFrom = m_iBufferPos;
     675           0 :                 virtualTo = m_iBufferPos_Tail;
     676             :             }
     677             :             else
     678             :             {
     679           0 :                 virtualFrom = m_iBufferPos_Tail;
     680           0 :                 virtualTo = m_iBufferPos;
     681             :             }
     682             : 
     683           0 :             CStrW text = caption.Left(virtualTo).Right(virtualTo - virtualFrom);
     684             : 
     685           0 :             SDL_SetClipboardText(text.ToUTF8().c_str());
     686             : 
     687           0 :             if (hotkey == "cut")
     688             :             {
     689           0 :                 DeleteCurSelection();
     690           0 :                 UpdateAutoScroll();
     691           0 :                 SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     692             :             }
     693             :         }
     694             : 
     695           0 :         return IN_HANDLED;
     696             :     }
     697           0 :     else if (hotkey == "text.delete.left")
     698             :     {
     699           0 :         if (m_Readonly)
     700           0 :             return IN_PASS;
     701             : 
     702           0 :         m_WantedX = 0.0f;
     703             : 
     704           0 :         if (SelectingText())
     705           0 :             DeleteCurSelection();
     706             : 
     707           0 :         if (!caption.empty() && m_iBufferPos != 0)
     708             :         {
     709           0 :             m_iBufferPos_Tail = m_iBufferPos;
     710           0 :             CStrW searchString = caption.Left(m_iBufferPos);
     711             : 
     712             :             // If we are starting in whitespace, adjust position until we get a non whitespace
     713           0 :             while (m_iBufferPos > 0)
     714             :             {
     715           0 :                 if (!iswspace(searchString[m_iBufferPos - 1]))
     716           0 :                     break;
     717             : 
     718           0 :                 m_iBufferPos--;
     719             :             }
     720             : 
     721             :             // If we end up on a punctuation char we just delete it (treat punct like a word)
     722           0 :             if (iswpunct(searchString[m_iBufferPos - 1]))
     723           0 :                 m_iBufferPos--;
     724             :             else
     725             :             {
     726             :                 // Now we are on a non white space character, adjust position to char after next whitespace char is found
     727           0 :                 while (m_iBufferPos > 0)
     728             :                 {
     729           0 :                     if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
     730           0 :                         break;
     731             : 
     732           0 :                     m_iBufferPos--;
     733             :                 }
     734             :             }
     735             : 
     736           0 :             UpdateBufferPositionSetting();
     737           0 :             DeleteCurSelection();
     738           0 :             SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     739             :         }
     740           0 :         UpdateAutoScroll();
     741           0 :         return IN_HANDLED;
     742             :     }
     743           0 :     else if (hotkey == "text.delete.right")
     744             :     {
     745           0 :         if (m_Readonly)
     746           0 :             return IN_PASS;
     747             : 
     748           0 :         m_WantedX = 0.0f;
     749             : 
     750           0 :         if (SelectingText())
     751           0 :             DeleteCurSelection();
     752             : 
     753           0 :         if (!caption.empty() && m_iBufferPos < static_cast<int>(caption.length()))
     754             :         {
     755             :             // Delete the word to the right of the cursor
     756           0 :             m_iBufferPos_Tail = m_iBufferPos;
     757             : 
     758             :             // Delete chars to the right unit we hit whitespace
     759           0 :             while (++m_iBufferPos < static_cast<int>(caption.length()))
     760             :             {
     761           0 :                 if (iswspace(caption[m_iBufferPos]) || iswpunct(caption[m_iBufferPos]))
     762           0 :                     break;
     763             :             }
     764             : 
     765             :             // Eliminate any whitespace behind the word we just deleted
     766           0 :             while (m_iBufferPos < static_cast<int>(caption.length()))
     767             :             {
     768           0 :                 if (!iswspace(caption[m_iBufferPos]))
     769           0 :                     break;
     770             : 
     771           0 :                 ++m_iBufferPos;
     772             :             }
     773           0 :             UpdateBufferPositionSetting();
     774           0 :             DeleteCurSelection();
     775             :         }
     776           0 :         UpdateAutoScroll();
     777           0 :         SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
     778           0 :         return IN_HANDLED;
     779             :     }
     780           0 :     else if (hotkey == "text.move.left")
     781             :     {
     782           0 :         m_WantedX = 0.0f;
     783             : 
     784           0 :         if (shiftKeyPressed || !SelectingText())
     785             :         {
     786           0 :             if (!shiftKeyPressed)
     787           0 :                 m_iBufferPos_Tail = -1;
     788           0 :             else if (!SelectingText())
     789           0 :                 m_iBufferPos_Tail = m_iBufferPos;
     790             : 
     791           0 :             if (!caption.empty() && m_iBufferPos != 0)
     792             :             {
     793           0 :                 CStrW searchString = caption.Left(m_iBufferPos);
     794             : 
     795             :                 // If we are starting in whitespace, adjust position until we get a non whitespace
     796           0 :                 while (m_iBufferPos > 0)
     797             :                 {
     798           0 :                     if (!iswspace(searchString[m_iBufferPos - 1]))
     799           0 :                         break;
     800             : 
     801           0 :                     m_iBufferPos--;
     802             :                 }
     803             : 
     804             :                 // If we end up on a puctuation char we just select it (treat punct like a word)
     805           0 :                 if (iswpunct(searchString[m_iBufferPos - 1]))
     806           0 :                     m_iBufferPos--;
     807             :                 else
     808             :                 {
     809             :                     // Now we are on a non white space character, adjust position to char after next whitespace char is found
     810           0 :                     while (m_iBufferPos > 0)
     811             :                     {
     812           0 :                         if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
     813           0 :                             break;
     814             : 
     815           0 :                         m_iBufferPos--;
     816             :                     }
     817             :                 }
     818             :             }
     819             :         }
     820             :         else
     821             :         {
     822           0 :             if (m_iBufferPos_Tail < m_iBufferPos)
     823           0 :                 m_iBufferPos = m_iBufferPos_Tail;
     824             : 
     825           0 :             m_iBufferPos_Tail = -1;
     826             :         }
     827             : 
     828           0 :         UpdateBufferPositionSetting();
     829           0 :         UpdateAutoScroll();
     830             : 
     831           0 :         return IN_HANDLED;
     832             :     }
     833           0 :     else if (hotkey == "text.move.right")
     834             :     {
     835           0 :         m_WantedX = 0.0f;
     836             : 
     837           0 :         if (shiftKeyPressed || !SelectingText())
     838             :         {
     839           0 :             if (!shiftKeyPressed)
     840           0 :                 m_iBufferPos_Tail = -1;
     841           0 :             else if (!SelectingText())
     842           0 :                 m_iBufferPos_Tail = m_iBufferPos;
     843             : 
     844           0 :             if (!caption.empty() && m_iBufferPos < static_cast<int>(caption.length()))
     845             :             {
     846             :                 // Select chars to the right until we hit whitespace
     847           0 :                 while (++m_iBufferPos < static_cast<int>(caption.length()))
     848             :                 {
     849           0 :                     if (iswspace(caption[m_iBufferPos]) || iswpunct(caption[m_iBufferPos]))
     850           0 :                         break;
     851             :                 }
     852             : 
     853             :                 // Also select any whitespace following the word we just selected
     854           0 :                 while (m_iBufferPos < static_cast<int>(caption.length()))
     855             :                 {
     856           0 :                     if (!iswspace(caption[m_iBufferPos]))
     857           0 :                         break;
     858             : 
     859           0 :                     ++m_iBufferPos;
     860             :                 }
     861             :             }
     862             :         }
     863             :         else
     864             :         {
     865           0 :             if (m_iBufferPos_Tail > m_iBufferPos)
     866           0 :                 m_iBufferPos = m_iBufferPos_Tail;
     867             : 
     868           0 :             m_iBufferPos_Tail = -1;
     869             :         }
     870             : 
     871           0 :         UpdateBufferPositionSetting();
     872           0 :         UpdateAutoScroll();
     873             : 
     874           0 :         return IN_HANDLED;
     875             :     }
     876             : 
     877           0 :     return IN_PASS;
     878             : }
     879             : 
     880           0 : void CInput::ResetStates()
     881             : {
     882           0 :     IGUIObject::ResetStates();
     883           0 :     IGUIScrollBarOwner::ResetStates();
     884           0 : }
     885             : 
     886           0 : void CInput::HandleMessage(SGUIMessage& Message)
     887             : {
     888           0 :     IGUIObject::HandleMessage(Message);
     889           0 :     IGUIScrollBarOwner::HandleMessage(Message);
     890             : 
     891             :     // Cleans up operator[] usage.
     892           0 :     const CStrW& caption = *m_Caption;
     893             : 
     894           0 :     switch (Message.type)
     895             :     {
     896           0 :     case GUIM_SETTINGS_UPDATED:
     897             :     {
     898             :         // Update scroll-bar
     899             :         // TODO Gee: (2004-09-01) Is this really updated each time it should?
     900           0 :         if (m_ScrollBar &&
     901           0 :             (Message.value == "size" ||
     902           0 :              Message.value == "z" ||
     903           0 :              Message.value == "absolute"))
     904             :         {
     905           0 :             GetScrollBar(0).SetX(m_CachedActualSize.right);
     906           0 :             GetScrollBar(0).SetY(m_CachedActualSize.top);
     907           0 :             GetScrollBar(0).SetZ(GetBufferedZ());
     908           0 :             GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
     909             :         }
     910             : 
     911             :         // Update scrollbar
     912           0 :         if (Message.value == "scrollbar_style")
     913           0 :             GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
     914             : 
     915           0 :         if (Message.value == "buffer_position")
     916             :         {
     917           0 :             m_iBufferPos = m_MaxLength != 0 ? std::min(m_MaxLength, m_BufferPosition) : m_BufferPosition;
     918           0 :             m_iBufferPos_Tail = -1; // position change resets selection
     919             :         }
     920             : 
     921           0 :         if (Message.value == "size" ||
     922           0 :             Message.value == "z" ||
     923           0 :             Message.value == "font" ||
     924           0 :             Message.value == "absolute" ||
     925           0 :             Message.value == "caption" ||
     926           0 :             Message.value == "scrollbar" ||
     927           0 :             Message.value == "scrollbar_style")
     928             :         {
     929           0 :             UpdateText();
     930             :         }
     931             : 
     932           0 :         if (Message.value == "multiline")
     933             :         {
     934           0 :             if (!m_MultiLine)
     935           0 :                 GetScrollBar(0).SetLength(0.f);
     936             :             else
     937           0 :                 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
     938             : 
     939           0 :             UpdateText();
     940             :         }
     941             : 
     942           0 :         if (Message.value == "placeholder_text" ||
     943           0 :             Message.value == "size" ||
     944           0 :             Message.value == "font" ||
     945           0 :             Message.value == "z" ||
     946           0 :             Message.value == "text_valign")
     947             :         {
     948           0 :             m_GeneratedPlaceholderTextValid = false;
     949             :         }
     950             : 
     951           0 :         UpdateAutoScroll();
     952             : 
     953           0 :         break;
     954             :     }
     955           0 :     case GUIM_MOUSE_PRESS_LEFT:
     956             :     {
     957             :         // Check if we're selecting the scrollbar
     958           0 :         if (m_ScrollBar &&
     959           0 :             m_MultiLine &&
     960           0 :             GetScrollBar(0).GetStyle())
     961             :         {
     962           0 :             if (m_pGUI.GetMousePos().X > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width)
     963           0 :                 break;
     964             :         }
     965             : 
     966           0 :         if (m_ComposingText)
     967           0 :             break;
     968             : 
     969             :         // Okay, this section is about pressing the mouse and
     970             :         //  choosing where the point should be placed. For
     971             :         //  instance, if we press between a and b, the point
     972             :         //  should of course be placed accordingly. Other
     973             :         //  special cases are handled like the input box norms.
     974           0 :         if (g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT])
     975           0 :             m_iBufferPos = GetMouseHoveringTextPosition();
     976             :         else
     977           0 :             m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
     978             : 
     979           0 :         m_SelectingText = true;
     980             : 
     981           0 :         UpdateAutoScroll();
     982             : 
     983             :         // If we immediately release the button it will just be seen as a click
     984             :         //  for the user though.
     985           0 :         break;
     986             :     }
     987           0 :     case GUIM_MOUSE_DBLCLICK_LEFT:
     988             :     {
     989           0 :         if (m_ComposingText)
     990           0 :             break;
     991             : 
     992           0 :         if (caption.empty())
     993           0 :             break;
     994             : 
     995           0 :         m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
     996             : 
     997           0 :         if (m_iBufferPos >= (int)caption.length())
     998           0 :             m_iBufferPos = m_iBufferPos_Tail = caption.length() - 1;
     999             : 
    1000             :         // See if we are clicking over whitespace
    1001           0 :         if (iswspace(caption[m_iBufferPos]))
    1002             :         {
    1003             :             // see if we are in a section of whitespace greater than one character
    1004           0 :             if ((m_iBufferPos + 1 < (int) caption.length() && iswspace(caption[m_iBufferPos + 1])) ||
    1005           0 :                 (m_iBufferPos - 1 > 0 && iswspace(caption[m_iBufferPos - 1])))
    1006             :             {
    1007             :                 //
    1008             :                 // We are clicking in an area with more than one whitespace character
    1009             :                 // so we select both the word to the left and then the word to the right
    1010             :                 //
    1011             :                 // [1] First the left
    1012             :                 // skip the whitespace
    1013           0 :                 while (m_iBufferPos > 0)
    1014             :                 {
    1015           0 :                     if (!iswspace(caption[m_iBufferPos - 1]))
    1016           0 :                         break;
    1017             : 
    1018           0 :                     m_iBufferPos--;
    1019             :                 }
    1020             :                 // now go until we hit white space or punctuation
    1021           0 :                 while (m_iBufferPos > 0)
    1022             :                 {
    1023           0 :                     if (iswspace(caption[m_iBufferPos - 1]))
    1024           0 :                         break;
    1025             : 
    1026           0 :                     m_iBufferPos--;
    1027             : 
    1028           0 :                     if (iswpunct(caption[m_iBufferPos]))
    1029           0 :                         break;
    1030             :                 }
    1031             : 
    1032             :                 // [2] Then the right
    1033             :                 // go right until we are not in whitespace
    1034           0 :                 while (++m_iBufferPos_Tail < static_cast<int>(caption.length()))
    1035             :                 {
    1036           0 :                     if (!iswspace(caption[m_iBufferPos_Tail]))
    1037           0 :                         break;
    1038             :                 }
    1039             : 
    1040           0 :                 if (m_iBufferPos_Tail == static_cast<int>(caption.length()))
    1041           0 :                     break;
    1042             : 
    1043             :                 // now go to the right until we hit whitespace or punctuation
    1044           0 :                 while (++m_iBufferPos_Tail < static_cast<int>(caption.length()))
    1045             :                 {
    1046           0 :                     if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
    1047           0 :                         break;
    1048             :                 }
    1049             :             }
    1050             :             else
    1051             :             {
    1052             :                 // single whitespace so select word to the right
    1053           0 :                 while (++m_iBufferPos_Tail < static_cast<int>(caption.length()))
    1054             :                 {
    1055           0 :                     if (!iswspace(caption[m_iBufferPos_Tail]))
    1056           0 :                         break;
    1057             :                 }
    1058             : 
    1059           0 :                 if (m_iBufferPos_Tail == static_cast<int>(caption.length()))
    1060           0 :                     break;
    1061             : 
    1062             :                 // Don't include the leading whitespace
    1063           0 :                 m_iBufferPos = m_iBufferPos_Tail;
    1064             : 
    1065             :                 // now go to the right until we hit whitespace or punctuation
    1066           0 :                 while (++m_iBufferPos_Tail < static_cast<int>(caption.length()))
    1067             :                 {
    1068           0 :                     if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
    1069           0 :                         break;
    1070             :                 }
    1071             :             }
    1072             :         }
    1073             :         else
    1074             :         {
    1075             :             // clicked on non-whitespace so select current word
    1076             :             // go until we hit white space or punctuation
    1077           0 :             while (m_iBufferPos > 0)
    1078             :             {
    1079           0 :                 if (iswspace(caption[m_iBufferPos - 1]))
    1080           0 :                     break;
    1081             : 
    1082           0 :                 m_iBufferPos--;
    1083             : 
    1084           0 :                 if (iswpunct(caption[m_iBufferPos]))
    1085           0 :                     break;
    1086             :             }
    1087             :             // go to the right until we hit whitespace or punctuation
    1088           0 :             while (++m_iBufferPos_Tail < static_cast<int>(caption.length()))
    1089           0 :                 if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
    1090           0 :                     break;
    1091             :         }
    1092           0 :         UpdateAutoScroll();
    1093           0 :         break;
    1094             :     }
    1095           0 :     case GUIM_MOUSE_RELEASE_LEFT:
    1096             :     {
    1097           0 :         if (m_SelectingText)
    1098           0 :             m_SelectingText = false;
    1099           0 :         break;
    1100             :     }
    1101           0 :     case GUIM_MOUSE_MOTION:
    1102             :     {
    1103             :         // If we just pressed down and started to move before releasing
    1104             :         //  this is one way of selecting larger portions of text.
    1105           0 :         if (m_SelectingText)
    1106             :         {
    1107             :             // Actually, first we need to re-check that the mouse button is
    1108             :             //  really pressed (it can be released while outside the control.
    1109           0 :             if (!g_mouse_buttons[SDL_BUTTON_LEFT])
    1110           0 :                 m_SelectingText = false;
    1111             :             else
    1112           0 :                 m_iBufferPos = GetMouseHoveringTextPosition();
    1113           0 :             UpdateAutoScroll();
    1114             :         }
    1115           0 :         break;
    1116             :     }
    1117           0 :     case GUIM_LOAD:
    1118             :     {
    1119           0 :         GetScrollBar(0).SetX(m_CachedActualSize.right);
    1120           0 :         GetScrollBar(0).SetY(m_CachedActualSize.top);
    1121           0 :         GetScrollBar(0).SetZ(GetBufferedZ());
    1122           0 :         GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
    1123           0 :         GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
    1124             : 
    1125           0 :         UpdateText();
    1126           0 :         UpdateAutoScroll();
    1127             : 
    1128           0 :         break;
    1129             :     }
    1130           0 :     case GUIM_GOT_FOCUS:
    1131             :     {
    1132           0 :         m_iBufferPos = 0;
    1133           0 :         m_PrevTime = 0.0;
    1134           0 :         m_CursorVisState = false;
    1135             : 
    1136           0 :         ResetActiveHotkeys();
    1137             : 
    1138             :         // Tell the IME where to draw the candidate list
    1139             :         SDL_Rect rect;
    1140           0 :         rect.h = m_CachedActualSize.GetSize().Height;
    1141           0 :         rect.w = m_CachedActualSize.GetSize().Width;
    1142           0 :         rect.x = m_CachedActualSize.TopLeft().X;
    1143           0 :         rect.y = m_CachedActualSize.TopLeft().Y;
    1144           0 :         SDL_SetTextInputRect(&rect);
    1145           0 :         SDL_StartTextInput();
    1146           0 :         break;
    1147             :     }
    1148           0 :     case GUIM_LOST_FOCUS:
    1149             :     {
    1150           0 :         if (m_ComposingText)
    1151             :         {
    1152             :             // Simulate a final text editing event to clear the composition
    1153             :             SDL_Event_ evt;
    1154           0 :             evt.ev.type = SDL_TEXTEDITING;
    1155           0 :             evt.ev.edit.length = 0;
    1156           0 :             evt.ev.edit.start = 0;
    1157           0 :             evt.ev.edit.text[0] = 0;
    1158           0 :             ManuallyHandleKeys(&evt);
    1159             :         }
    1160           0 :         SDL_StopTextInput();
    1161             : 
    1162           0 :         m_iBufferPos = -1;
    1163           0 :         m_iBufferPos_Tail = -1;
    1164           0 :         break;
    1165             :     }
    1166           0 :     default:
    1167             :     {
    1168           0 :         break;
    1169             :     }
    1170             :     }
    1171           0 :     UpdateBufferPositionSetting();
    1172           0 : }
    1173             : 
    1174           0 : void CInput::UpdateCachedSize()
    1175             : {
    1176             :     // If an ancestor's size changed, this will let us intercept the change and
    1177             :     // update our scrollbar positions
    1178             : 
    1179           0 :     IGUIObject::UpdateCachedSize();
    1180             : 
    1181           0 :     if (m_ScrollBar)
    1182             :     {
    1183           0 :         GetScrollBar(0).SetX(m_CachedActualSize.right);
    1184           0 :         GetScrollBar(0).SetY(m_CachedActualSize.top);
    1185           0 :         GetScrollBar(0).SetZ(GetBufferedZ());
    1186           0 :         GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
    1187             :     }
    1188             : 
    1189           0 :     m_GeneratedPlaceholderTextValid = false;
    1190           0 : }
    1191             : 
    1192           0 : void CInput::DrawContent(CCanvas2D& canvas)
    1193             : {
    1194           0 :     if (m_CursorBlinkRate > 0.0)
    1195             :     {
    1196             :         // check if the cursor visibility state needs to be changed
    1197           0 :         double currTime = timer_Time();
    1198           0 :         if (currTime - m_PrevTime >= m_CursorBlinkRate)
    1199             :         {
    1200           0 :             m_CursorVisState = !m_CursorVisState;
    1201           0 :             m_PrevTime = currTime;
    1202             :         }
    1203             :     }
    1204             :     else
    1205             :         // should always be visible
    1206           0 :         m_CursorVisState = true;
    1207             : 
    1208           0 :     CStrIntern font_name(m_Font->ToUTF8());
    1209             : 
    1210           0 :     wchar_t mask_char = L'*';
    1211           0 :     if (m_Mask && m_MaskChar->length() > 0)
    1212           0 :         mask_char = (*m_MaskChar)[0];
    1213             : 
    1214           0 :     m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize);
    1215             : 
    1216           0 :     float scroll = 0.f;
    1217           0 :     if (m_ScrollBar && m_MultiLine)
    1218           0 :         scroll = GetScrollBar(0).GetPos();
    1219             : 
    1220           0 :     CFontMetrics font(font_name);
    1221             : 
    1222             :     // These are useful later.
    1223             :     int VirtualFrom, VirtualTo;
    1224             : 
    1225           0 :     if (m_iBufferPos_Tail >= m_iBufferPos)
    1226             :     {
    1227           0 :         VirtualFrom = m_iBufferPos;
    1228           0 :         VirtualTo = m_iBufferPos_Tail;
    1229             :     }
    1230             :     else
    1231             :     {
    1232           0 :         VirtualFrom = m_iBufferPos_Tail;
    1233           0 :         VirtualTo = m_iBufferPos;
    1234             :     }
    1235             : 
    1236             :     // Get the height of this font.
    1237           0 :     float h = (float)font.GetHeight();
    1238           0 :     float ls = (float)font.GetLineSpacing();
    1239             : 
    1240           0 :     CTextRenderer textRenderer;
    1241           0 :     textRenderer.SetCurrentFont(font_name);
    1242             : 
    1243           0 :     textRenderer.Translate(
    1244           0 :         (float)(int)(m_CachedActualSize.left) + m_BufferZone,
    1245           0 :         (float)(int)(m_CachedActualSize.top + h) + m_BufferZone);
    1246             : 
    1247             :     // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
    1248             :     // (sort of like a | which is aligned to the left of most characters)
    1249             : 
    1250           0 :     float buffered_y = -scroll + m_BufferZone;
    1251             : 
    1252             :     // When selecting larger areas, we need to draw a rectangle box
    1253             :     //  around it, and this is to keep track of where the box
    1254             :     //  started, because we need to follow the iteration until we
    1255             :     //  reach the end, before we can actually draw it.
    1256           0 :     bool drawing_box = false;
    1257           0 :     float box_x = 0.f;
    1258             : 
    1259           0 :     float x_pointer = 0.f;
    1260             : 
    1261             :     // If we have a selecting box (i.e. when you have selected letters, not just when
    1262             :     //  the pointer is between two letters) we need to process all letters once
    1263             :     //  before we do it the second time and render all the text. We can't do it
    1264             :     //  in the same loop because text will have been drawn, so it will disappear when
    1265             :     //  drawn behind the text that has already been drawn. Confusing, well it's necessary
    1266             :     //  (I think).
    1267             : 
    1268           0 :     if (SelectingText())
    1269             :     {
    1270             :         // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos,
    1271             :         //  just like you can select from right to left, as you can
    1272             :         //  left to right. Is there a difference? Yes, the pointer
    1273             :         //  be placed accordingly, so that if you select shift and
    1274             :         //  expand this selection, it will expand on appropriate side.
    1275             :         // Anyway, since the drawing procedure needs "To" to be
    1276             :         //  greater than from, we need virtual values that might switch
    1277             :         //  place.
    1278           0 :         int virtualFrom = 0;
    1279           0 :         int virtualTo = 0;
    1280             : 
    1281           0 :         if (m_iBufferPos_Tail >= m_iBufferPos)
    1282             :         {
    1283           0 :             virtualFrom = m_iBufferPos;
    1284           0 :             virtualTo = m_iBufferPos_Tail;
    1285             :         }
    1286             :         else
    1287             :         {
    1288           0 :             virtualFrom = m_iBufferPos_Tail;
    1289           0 :             virtualTo = m_iBufferPos;
    1290             :         }
    1291             : 
    1292             : 
    1293           0 :         bool done = false;
    1294           0 :         for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
    1295           0 :             it != m_CharacterPositions.end();
    1296           0 :             ++it, buffered_y += ls, x_pointer = 0.f)
    1297             :         {
    1298           0 :             if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight())
    1299           0 :                 break;
    1300             : 
    1301             :             // We might as well use 'i' here to iterate, because we need it
    1302             :             // (often compared against ints, so don't make it size_t)
    1303           0 :             for (int i = 0; i < (int)it->m_ListOfX.size() + 2; ++i)
    1304             :             {
    1305           0 :                 if (it->m_ListStart + i == virtualFrom)
    1306             :                 {
    1307             :                     // we won't actually draw it now, because we don't
    1308             :                     //  know the width of each glyph to that position.
    1309             :                     //  we need to go along with the iteration, and
    1310             :                     //  make a mark where the box started:
    1311           0 :                     drawing_box = true; // will turn false when finally rendered.
    1312             : 
    1313             :                     // Get current x position
    1314           0 :                     box_x = x_pointer;
    1315             :                 }
    1316             : 
    1317           0 :                 const bool at_end = (i == (int)it->m_ListOfX.size() + 1);
    1318             : 
    1319           0 :                 if (drawing_box && (it->m_ListStart + i == virtualTo || at_end))
    1320             :                 {
    1321             :                     // Depending on if it's just a row change, or if it's
    1322             :                     //  the end of the select box, do slightly different things.
    1323           0 :                     if (at_end)
    1324             :                     {
    1325           0 :                         if (it->m_ListStart + i != virtualFrom)
    1326             :                             // and actually add a white space! yes, this is done in any common input
    1327           0 :                             x_pointer += font.GetCharacterWidth(L' ');
    1328             :                     }
    1329             :                     else
    1330             :                     {
    1331           0 :                         drawing_box = false;
    1332           0 :                         done = true;
    1333             :                     }
    1334             : 
    1335           0 :                     CRect rect;
    1336             :                     // Set 'rect' depending on if it's a multiline control, or a one-line control
    1337           0 :                     if (m_MultiLine)
    1338             :                     {
    1339           0 :                         rect = CRect(
    1340           0 :                             m_CachedActualSize.left + box_x + m_BufferZone,
    1341           0 :                             m_CachedActualSize.top + buffered_y + (h - ls) / 2,
    1342           0 :                             m_CachedActualSize.left + x_pointer + m_BufferZone,
    1343           0 :                             m_CachedActualSize.top + buffered_y + (h + ls) / 2);
    1344             : 
    1345           0 :                         if (rect.bottom < m_CachedActualSize.top)
    1346           0 :                             continue;
    1347             : 
    1348           0 :                         if (rect.top < m_CachedActualSize.top)
    1349           0 :                             rect.top = m_CachedActualSize.top;
    1350             : 
    1351           0 :                         if (rect.bottom > m_CachedActualSize.bottom)
    1352           0 :                             rect.bottom = m_CachedActualSize.bottom;
    1353             :                     }
    1354             :                     else // if one-line
    1355             :                     {
    1356           0 :                         rect = CRect(
    1357           0 :                             m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll,
    1358           0 :                             m_CachedActualSize.top + buffered_y + (h - ls) / 2,
    1359           0 :                             m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll,
    1360           0 :                             m_CachedActualSize.top + buffered_y + (h + ls) / 2);
    1361             : 
    1362           0 :                         if (rect.left < m_CachedActualSize.left)
    1363           0 :                             rect.left = m_CachedActualSize.left;
    1364             : 
    1365           0 :                         if (rect.right > m_CachedActualSize.right)
    1366           0 :                             rect.right = m_CachedActualSize.right;
    1367             :                     }
    1368             : 
    1369           0 :                     m_pGUI.DrawSprite(m_SpriteSelectArea, canvas, rect);
    1370             :                 }
    1371             : 
    1372           0 :                 if (i < (int)it->m_ListOfX.size())
    1373             :                 {
    1374           0 :                     if (!m_Mask)
    1375           0 :                         x_pointer += font.GetCharacterWidth((*m_Caption)[it->m_ListStart + i]);
    1376             :                     else
    1377           0 :                         x_pointer += font.GetCharacterWidth(mask_char);
    1378             :                 }
    1379             :             }
    1380             : 
    1381           0 :             if (done)
    1382           0 :                 break;
    1383             : 
    1384             :             // If we're about to draw a box, and all of a sudden changes
    1385             :             //  line, we need to draw that line's box, and then reset
    1386             :             //  the box drawing to the beginning of the new line.
    1387           0 :             if (drawing_box)
    1388           0 :                 box_x = 0.f;
    1389             :         }
    1390             :     }
    1391             : 
    1392             :     // Reset some from previous run
    1393           0 :     buffered_y = -scroll;
    1394             : 
    1395             :     // Setup initial color (then it might change and change back, when drawing selected area)
    1396           0 :     textRenderer.SetCurrentColor(m_TextColor);
    1397             : 
    1398           0 :     bool using_selected_color = false;
    1399             : 
    1400           0 :     for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
    1401           0 :         it != m_CharacterPositions.end();
    1402           0 :         ++it, buffered_y += ls)
    1403             :     {
    1404           0 :         if (buffered_y + m_BufferZone >= -ls || !m_MultiLine)
    1405             :         {
    1406           0 :             if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight())
    1407           0 :                 break;
    1408             : 
    1409           0 :             const CVector2D savedTranslate = textRenderer.GetTranslate();
    1410             : 
    1411             :             // Text must always be drawn in integer values. So we have to convert scroll
    1412           0 :             if (m_MultiLine)
    1413           0 :                 textRenderer.Translate(0.f, -(float)(int)scroll);
    1414             :             else
    1415           0 :                 textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f);
    1416             : 
    1417             :             // We might as well use 'i' here, because we need it
    1418             :             // (often compared against ints, so don't make it size_t)
    1419           0 :             for (int i = 0; i < (int)it->m_ListOfX.size() + 1; ++i)
    1420             :             {
    1421           0 :                 if (!m_MultiLine && i < (int)it->m_ListOfX.size())
    1422             :                 {
    1423           0 :                     if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone)
    1424             :                     {
    1425             :                         // We still need to translate the OpenGL matrix
    1426           0 :                         if (i == 0)
    1427           0 :                             textRenderer.Translate(it->m_ListOfX[i], 0.f);
    1428             :                         else
    1429           0 :                             textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i - 1], 0.f);
    1430             : 
    1431           0 :                         continue;
    1432             :                     }
    1433             :                 }
    1434             : 
    1435             :                 // End of selected area, change back color
    1436           0 :                 if (SelectingText() && it->m_ListStart + i == VirtualTo)
    1437             :                 {
    1438           0 :                     using_selected_color = false;
    1439           0 :                     textRenderer.SetCurrentColor(m_TextColor);
    1440             :                 }
    1441             : 
    1442             :                 // selecting only one, then we need only to draw a cursor.
    1443           0 :                 if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState && !m_Readonly)
    1444           0 :                     textRenderer.Put(0.0f, 0.0f, L"_");
    1445             : 
    1446             :                 // Drawing selected area
    1447           0 :                 if (SelectingText() &&
    1448           0 :                     it->m_ListStart + i >= VirtualFrom &&
    1449           0 :                     it->m_ListStart + i < VirtualTo &&
    1450           0 :                     !using_selected_color)
    1451             :                 {
    1452           0 :                     using_selected_color = true;
    1453           0 :                     textRenderer.SetCurrentColor(m_TextColorSelected);
    1454             :                 }
    1455             : 
    1456           0 :                 if (i != (int)it->m_ListOfX.size())
    1457             :                 {
    1458           0 :                     if (!m_Mask)
    1459           0 :                         textRenderer.PrintfAdvance(L"%lc", (*m_Caption)[it->m_ListStart + i]);
    1460             :                     else
    1461           0 :                         textRenderer.PrintfAdvance(L"%lc", mask_char);
    1462             :                 }
    1463             : 
    1464             :                 // check it's now outside a one-liner, then we'll break
    1465           0 :                 if (!m_MultiLine && i < (int)it->m_ListOfX.size() &&
    1466           0 :                     it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone)
    1467           0 :                     break;
    1468             :             }
    1469             : 
    1470           0 :             if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos)
    1471             :             {
    1472           0 :                 textRenderer.SetCurrentColor(m_TextColor);
    1473           0 :                 if (m_CursorVisState && !m_Readonly)
    1474           0 :                     textRenderer.PutAdvance(L"_");
    1475             : 
    1476           0 :                 if (using_selected_color)
    1477           0 :                     textRenderer.SetCurrentColor(m_TextColorSelected);
    1478             :             }
    1479             : 
    1480           0 :             textRenderer.ResetTranslate(savedTranslate);
    1481             :         }
    1482             : 
    1483           0 :         textRenderer.Translate(0.f, ls);
    1484             :     }
    1485             : 
    1486           0 :     canvas.DrawText(textRenderer);
    1487           0 : }
    1488             : 
    1489           0 : void CInput::Draw(CCanvas2D& canvas)
    1490             : {
    1491             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext =
    1492           0 :         g_Renderer.GetDeviceCommandContext();
    1493             : 
    1494             :     // We'll have to setup clipping manually, since we're doing the rendering manually.
    1495           0 :     CRect cliparea(m_CachedActualSize);
    1496             : 
    1497             :     // First we'll figure out the clipping area, which is the cached actual size
    1498             :     //  substracted by an optional scrollbar
    1499           0 :     if (m_ScrollBar)
    1500             :     {
    1501             :         // substract scrollbar from cliparea
    1502           0 :         if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
    1503           0 :             cliparea.right <= GetScrollBar(0).GetOuterRect().right)
    1504           0 :             cliparea.right = GetScrollBar(0).GetOuterRect().left;
    1505             : 
    1506           0 :         if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
    1507           0 :             cliparea.left < GetScrollBar(0).GetOuterRect().right)
    1508           0 :             cliparea.left = GetScrollBar(0).GetOuterRect().right;
    1509             :     }
    1510             : 
    1511           0 :     const bool isClipped = cliparea != CRect();
    1512           0 :     if (isClipped)
    1513             :     {
    1514           0 :         if (cliparea.GetWidth() <= 0.0f || cliparea.GetHeight() <= 0.0f)
    1515           0 :             return;
    1516           0 :         const float scale = g_VideoMode.GetScale();
    1517             :         Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
    1518           0 :         scissorRect.x = cliparea.left * scale;
    1519           0 :         scissorRect.y = g_yres - cliparea.bottom * scale;
    1520           0 :         scissorRect.width = cliparea.GetWidth() * scale;
    1521           0 :         scissorRect.height = cliparea.GetHeight() * scale;
    1522             :         // TODO: move scissors to CCanvas2D.
    1523           0 :         deviceCommandContext->SetScissors(1, &scissorRect);
    1524             :     }
    1525             : 
    1526           0 :     DrawContent(canvas);
    1527             : 
    1528           0 :     if (isClipped)
    1529           0 :         deviceCommandContext->SetScissors(0, nullptr);
    1530             : 
    1531           0 :     if (m_Caption->empty() && !m_PlaceholderText->GetRawString().empty())
    1532           0 :         DrawPlaceholderText(canvas, cliparea);
    1533             : 
    1534             :     // Draw scrollbars on top of the content
    1535           0 :     if (m_ScrollBar && m_MultiLine)
    1536           0 :         IGUIScrollBarOwner::Draw(canvas);
    1537             : 
    1538             :     // Draw the overlays last
    1539           0 :     m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize);
    1540             : }
    1541             : 
    1542           0 : void CInput::DrawPlaceholderText(CCanvas2D& canvas, const CRect& clipping)
    1543             : {
    1544           0 :     if (!m_GeneratedPlaceholderTextValid)
    1545           0 :         SetupGeneratedPlaceholderText();
    1546             : 
    1547           0 :     m_GeneratedPlaceholderText.Draw(m_pGUI, canvas, m_PlaceholderColor, m_CachedActualSize.TopLeft(), clipping);
    1548           0 : }
    1549             : 
    1550           0 : void CInput::UpdateText(int from, int to_before, int to_after)
    1551             : {
    1552           0 :     CStrW& caption = m_Caption.GetMutable();
    1553             : 
    1554           0 :     if (m_MaxLength != 0 && caption.length() > static_cast<size_t>(m_MaxLength))
    1555           0 :         caption.erase(m_MaxLength);
    1556             : 
    1557           0 :     CStrIntern font_name(m_Font->ToUTF8());
    1558             : 
    1559           0 :     wchar_t mask_char = L'*';
    1560           0 :     if (m_Mask && m_MaskChar->length() > 0)
    1561           0 :         mask_char = (*m_MaskChar)[0];
    1562             : 
    1563             :     // Ensure positions are valid after caption changes
    1564           0 :     m_iBufferPos = std::min(m_iBufferPos, static_cast<int>(caption.size()));
    1565           0 :     m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast<int>(caption.size()));
    1566           0 :     UpdateBufferPositionSetting();
    1567             : 
    1568           0 :     if (font_name.empty())
    1569             :     {
    1570             :         // Destroy everything stored, there's no font, so there can be no data.
    1571           0 :         m_CharacterPositions.clear();
    1572           0 :         return;
    1573             :     }
    1574             : 
    1575           0 :     SRow row;
    1576           0 :     row.m_ListStart = 0;
    1577             : 
    1578           0 :     int to = 0; // make sure it's initialized
    1579             : 
    1580           0 :     if (to_before == -1)
    1581           0 :         to = static_cast<int>(caption.length());
    1582             : 
    1583           0 :     CFontMetrics font(font_name);
    1584             : 
    1585           0 :     std::list<SRow>::iterator current_line;
    1586             : 
    1587             :     // Used to ... TODO
    1588           0 :     int check_point_row_start = -1;
    1589           0 :     int check_point_row_end = -1;
    1590             : 
    1591             :     // Reset
    1592           0 :     if (from == 0 && to_before == -1)
    1593             :     {
    1594           0 :         m_CharacterPositions.clear();
    1595           0 :         current_line = m_CharacterPositions.begin();
    1596             :     }
    1597             :     else
    1598             :     {
    1599           0 :         ENSURE(to_before != -1);
    1600             : 
    1601           0 :         std::list<SRow>::iterator destroy_row_from;
    1602           0 :         std::list<SRow>::iterator destroy_row_to;
    1603             :         // Used to check if the above has been set to anything,
    1604             :         //  previously a comparison like:
    1605             :         //  destroy_row_from == std::list<SRow>::iterator()
    1606             :         // ... was used, but it didn't work with GCC.
    1607           0 :         bool destroy_row_from_used = false;
    1608           0 :         bool destroy_row_to_used = false;
    1609             : 
    1610             :         // Iterate, and remove everything between 'from' and 'to_before'
    1611             :         //  actually remove the entire lines they are on, it'll all have
    1612             :         //  to be redone. And when going along, we'll delete a row at a time
    1613             :         //  when continuing to see how much more after 'to' we need to remake.
    1614           0 :         int i = 0;
    1615           0 :         for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
    1616           0 :              it != m_CharacterPositions.end();
    1617             :              ++it, ++i)
    1618             :         {
    1619           0 :             if (!destroy_row_from_used && it->m_ListStart > from)
    1620             :             {
    1621             :                 // Destroy the previous line, and all to 'to_before'
    1622           0 :                 destroy_row_from = it;
    1623           0 :                 --destroy_row_from;
    1624             : 
    1625           0 :                 destroy_row_from_used = true;
    1626             : 
    1627             :                 // For the rare case that we might remove characters to a word
    1628             :                 //  so that it suddenly fits on the previous row,
    1629             :                 //  we need to by standards re-do the whole previous line too
    1630             :                 //  (if one exists)
    1631           0 :                 if (destroy_row_from != m_CharacterPositions.begin())
    1632           0 :                     --destroy_row_from;
    1633             :             }
    1634             : 
    1635           0 :             if (!destroy_row_to_used && it->m_ListStart > to_before)
    1636             :             {
    1637           0 :                 destroy_row_to = it;
    1638           0 :                 destroy_row_to_used = true;
    1639             : 
    1640             :                 // If it isn't the last row, we'll add another row to delete,
    1641             :                 //  just so we can see if the last restorted line is
    1642             :                 //  identical to what it was before. If it isn't, then we'll
    1643             :                 //  have to continue.
    1644             :                 // 'check_point_row_start' is where we store how the that
    1645             :                 //  line looked.
    1646           0 :                 if (destroy_row_to != m_CharacterPositions.end())
    1647             :                 {
    1648           0 :                     check_point_row_start = destroy_row_to->m_ListStart;
    1649           0 :                     check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
    1650           0 :                     if (destroy_row_to->m_ListOfX.empty())
    1651           0 :                         ++check_point_row_end;
    1652             :                 }
    1653             : 
    1654           0 :                 ++destroy_row_to;
    1655           0 :                 break;
    1656             :             }
    1657             :         }
    1658             : 
    1659           0 :         if (!destroy_row_from_used)
    1660             :         {
    1661           0 :             destroy_row_from = m_CharacterPositions.end();
    1662           0 :             --destroy_row_from;
    1663             : 
    1664             :             // As usual, let's destroy another row back
    1665           0 :             if (destroy_row_from != m_CharacterPositions.begin())
    1666           0 :                 --destroy_row_from;
    1667             : 
    1668           0 :             current_line = destroy_row_from;
    1669             :         }
    1670             : 
    1671           0 :         if (!destroy_row_to_used)
    1672             :         {
    1673           0 :             destroy_row_to = m_CharacterPositions.end();
    1674           0 :             check_point_row_start = -1;
    1675             :         }
    1676             : 
    1677             :         // set 'from' to the row we'll destroy from
    1678             :         //  and 'to' to the row we'll destroy to
    1679           0 :         from = destroy_row_from->m_ListStart;
    1680             : 
    1681           0 :         if (destroy_row_to != m_CharacterPositions.end())
    1682           0 :             to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
    1683             :         else
    1684           0 :             to = static_cast<int>(caption.length());
    1685             : 
    1686             : 
    1687             :         // Setup the first row
    1688           0 :         row.m_ListStart = destroy_row_from->m_ListStart;
    1689             : 
    1690           0 :         std::list<SRow>::iterator temp_it = destroy_row_to;
    1691           0 :         --temp_it;
    1692             : 
    1693           0 :         current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
    1694             : 
    1695             :         // If there has been a change in number of characters
    1696             :         //  we need to change all m_ListStart that comes after
    1697             :         //  the interval we just destroyed. We'll change all
    1698             :         //  values with the delta change of the string length.
    1699           0 :         int delta = to_after - to_before;
    1700           0 :         if (delta != 0)
    1701             :         {
    1702           0 :             for (std::list<SRow>::iterator it = current_line;
    1703           0 :                  it != m_CharacterPositions.end();
    1704             :                  ++it)
    1705           0 :                 it->m_ListStart += delta;
    1706             : 
    1707             :             // Update our check point too!
    1708           0 :             check_point_row_start += delta;
    1709           0 :             check_point_row_end += delta;
    1710             : 
    1711           0 :             if (to != static_cast<int>(caption.length()))
    1712           0 :                 to += delta;
    1713             :         }
    1714             :     }
    1715             : 
    1716           0 :     int last_word_started = from;
    1717           0 :     float x_pos = 0.f;
    1718             : 
    1719             :     //if (to_before != -1)
    1720             :     //  return;
    1721             : 
    1722           0 :     for (int i = from; i < to; ++i)
    1723             :     {
    1724           0 :         if (caption[i] == L'\n' && m_MultiLine)
    1725             :         {
    1726           0 :             if (i == to-1 && to != static_cast<int>(caption.length()))
    1727           0 :                 break; // it will be added outside
    1728             : 
    1729           0 :             current_line = m_CharacterPositions.insert(current_line, row);
    1730           0 :             ++current_line;
    1731             : 
    1732             :             // Setup the next row:
    1733           0 :             row.m_ListOfX.clear();
    1734           0 :             row.m_ListStart = i+1;
    1735           0 :             x_pos = 0.f;
    1736             :         }
    1737             :         else
    1738             :         {
    1739           0 :             if (caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix.
    1740             :                 caption[i] == L'-'*/)
    1741           0 :                 last_word_started = i+1;
    1742             : 
    1743           0 :             if (!m_Mask)
    1744           0 :                 x_pos += font.GetCharacterWidth(caption[i]);
    1745             :             else
    1746           0 :                 x_pos += font.GetCharacterWidth(mask_char);
    1747             : 
    1748           0 :             if (x_pos >= GetTextAreaWidth() && m_MultiLine)
    1749             :             {
    1750             :                 // The following decides whether it will word-wrap a word,
    1751             :                 //  or if it's only one word on the line, where it has to
    1752             :                 //  break the word apart.
    1753           0 :                 if (last_word_started == row.m_ListStart)
    1754             :                 {
    1755           0 :                     last_word_started = i;
    1756           0 :                     row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started));
    1757             :                     //row.m_ListOfX.push_back(x_pos);
    1758             :                     //continue;
    1759             :                 }
    1760             :                 else
    1761             :                 {
    1762             :                     // regular word-wrap
    1763           0 :                     row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
    1764             :                 }
    1765             : 
    1766             :                 // Now, create a new line:
    1767             :                 //  notice: when we enter a newline, you can stand with the cursor
    1768             :                 //  both before and after that character, being on different
    1769             :                 //  rows. With automatic word-wrapping, that is not possible. Which
    1770             :                 //  is intuitively correct.
    1771             : 
    1772           0 :                 current_line = m_CharacterPositions.insert(current_line, row);
    1773           0 :                 ++current_line;
    1774             : 
    1775             :                 // Setup the next row:
    1776           0 :                 row.m_ListOfX.clear();
    1777           0 :                 row.m_ListStart = last_word_started;
    1778             : 
    1779           0 :                 i = last_word_started-1;
    1780             : 
    1781           0 :                 x_pos = 0.f;
    1782             :             }
    1783             :             else
    1784             :                 // Get width of this character:
    1785           0 :                 row.m_ListOfX.push_back(x_pos);
    1786             :         }
    1787             : 
    1788             :         // Check if it's the last iteration, and we're not revising the whole string
    1789             :         //  because in that case, more word-wrapping might be needed.
    1790             :         //  also check if the current line isn't the end
    1791           0 :         if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end())
    1792             :         {
    1793             :             // check all rows and see if any existing
    1794           0 :             if (row.m_ListStart != check_point_row_start)
    1795             :             {
    1796           0 :                 std::list<SRow>::iterator destroy_row_from;
    1797           0 :                 std::list<SRow>::iterator destroy_row_to;
    1798             :                 // Are used to check if the above has been set to anything,
    1799             :                 //  previously a comparison like:
    1800             :                 //  destroy_row_from == std::list<SRow>::iterator()
    1801             :                 //  was used, but it didn't work with GCC.
    1802           0 :                 bool destroy_row_from_used = false;
    1803           0 :                 bool destroy_row_to_used = false;
    1804             : 
    1805             :                 // Iterate, and remove everything between 'from' and 'to_before'
    1806             :                 //  actually remove the entire lines they are on, it'll all have
    1807             :                 //  to be redone. And when going along, we'll delete a row at a time
    1808             :                 //  when continuing to see how much more after 'to' we need to remake.
    1809             : 
    1810           0 :                 for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
    1811           0 :                      it != m_CharacterPositions.end();
    1812             :                      ++it)
    1813             :                 {
    1814           0 :                     if (!destroy_row_from_used && it->m_ListStart > check_point_row_start)
    1815             :                     {
    1816             :                         // Destroy the previous line, and all to 'to_before'
    1817             :                         //if (i >= 2)
    1818             :                         //  destroy_row_from = it-2;
    1819             :                         //else
    1820             :                         //  destroy_row_from = it-1;
    1821           0 :                         destroy_row_from = it;
    1822           0 :                         destroy_row_from_used = true;
    1823             :                         //--destroy_row_from;
    1824             :                     }
    1825             : 
    1826           0 :                     if (!destroy_row_to_used && it->m_ListStart > check_point_row_end)
    1827             :                     {
    1828           0 :                         destroy_row_to = it;
    1829           0 :                         destroy_row_to_used = true;
    1830             : 
    1831             :                         // If it isn't the last row, we'll add another row to delete,
    1832             :                         //  just so we can see if the last restorted line is
    1833             :                         //  identical to what it was before. If it isn't, then we'll
    1834             :                         //  have to continue.
    1835             :                         // 'check_point_row_start' is where we store how the that
    1836             :                         //  line looked.
    1837           0 :                         if (destroy_row_to != m_CharacterPositions.end())
    1838             :                         {
    1839           0 :                             check_point_row_start = destroy_row_to->m_ListStart;
    1840           0 :                             check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
    1841           0 :                             if (destroy_row_to->m_ListOfX.empty())
    1842           0 :                                 ++check_point_row_end;
    1843             :                         }
    1844             :                         else
    1845           0 :                             check_point_row_start = check_point_row_end = -1;
    1846             : 
    1847           0 :                         ++destroy_row_to;
    1848           0 :                         break;
    1849             :                     }
    1850             :                 }
    1851             : 
    1852           0 :                 if (!destroy_row_from_used)
    1853             :                 {
    1854           0 :                     destroy_row_from = m_CharacterPositions.end();
    1855           0 :                     --destroy_row_from;
    1856             : 
    1857           0 :                     current_line = destroy_row_from;
    1858             :                 }
    1859             : 
    1860           0 :                 if (!destroy_row_to_used)
    1861             :                 {
    1862           0 :                     destroy_row_to = m_CharacterPositions.end();
    1863           0 :                     check_point_row_start = check_point_row_end = -1;
    1864             :                 }
    1865             : 
    1866           0 :                 if (destroy_row_to != m_CharacterPositions.end())
    1867           0 :                     to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
    1868             :                 else
    1869           0 :                     to = static_cast<int>(caption.length());
    1870             : 
    1871             : 
    1872             :                 // Set current line, new rows will be added before current_line, so
    1873             :                 //  we'll choose the destroy_row_to, because it won't be deleted
    1874             :                 //  in the coming erase.
    1875           0 :                 current_line = destroy_row_to;
    1876             : 
    1877           0 :                 m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
    1878             :             }
    1879             :             // else, the for loop will end naturally.
    1880             :         }
    1881             :     }
    1882             :     // This is kind of special, when we renew a some lines, then the last
    1883             :     //  one will sometimes end with a space (' '), that really should
    1884             :     //  be omitted when word-wrapping. So we'll check if the last row
    1885             :     //  we'll add has got the same value as the next row.
    1886           0 :     if (current_line != m_CharacterPositions.end())
    1887             :     {
    1888           0 :         if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart)
    1889           0 :             row.m_ListOfX.resize(row.m_ListOfX.size()-1);
    1890             :     }
    1891             : 
    1892             :     // add the final row (even if empty)
    1893           0 :     m_CharacterPositions.insert(current_line, row);
    1894             : 
    1895           0 :     if (m_ScrollBar)
    1896             :     {
    1897           0 :         GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f);
    1898           0 :         GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
    1899             :     }
    1900             : }
    1901             : 
    1902           0 : int CInput::GetMouseHoveringTextPosition() const
    1903             : {
    1904           0 :     if (m_CharacterPositions.empty())
    1905           0 :         return 0;
    1906             : 
    1907             :     // Return position
    1908             :     int retPosition;
    1909             : 
    1910           0 :     std::list<SRow>::const_iterator current = m_CharacterPositions.begin();
    1911             : 
    1912           0 :     CVector2D mouse = m_pGUI.GetMousePos();
    1913             : 
    1914           0 :     if (m_MultiLine)
    1915             :     {
    1916           0 :         float scroll = 0.f;
    1917           0 :         if (m_ScrollBar)
    1918           0 :             scroll = GetScrollBarPos(0);
    1919             : 
    1920             :         // Now get the height of the font.
    1921             :                         // TODO: Get the real font
    1922           0 :         CFontMetrics font(CStrIntern(m_Font->ToUTF8()));
    1923           0 :         float spacing = (float)font.GetLineSpacing();
    1924             : 
    1925             :         // Change mouse position relative to text.
    1926           0 :         mouse -= m_CachedActualSize.TopLeft();
    1927           0 :         mouse.X -= m_BufferZone;
    1928           0 :         mouse.Y += scroll - m_BufferZone;
    1929             : 
    1930           0 :         int row = (int)((mouse.Y) / spacing);
    1931             : 
    1932           0 :         if (row < 0)
    1933           0 :             row = 0;
    1934             : 
    1935           0 :         if (row > (int)m_CharacterPositions.size()-1)
    1936           0 :             row = (int)m_CharacterPositions.size()-1;
    1937             : 
    1938             :         // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
    1939             :         //  be able to get the specific element here. This is hopefully a temporary hack.
    1940             : 
    1941           0 :         for (int i = 0; i < row; ++i)
    1942           0 :             ++current;
    1943             :     }
    1944             :     else
    1945             :     {
    1946             :         // current is already set to begin,
    1947             :         //  but we'll change the mouse.x to fit our horizontal scrolling
    1948           0 :         mouse -= m_CachedActualSize.TopLeft();
    1949           0 :         mouse.X -= m_BufferZone - m_HorizontalScroll;
    1950             :         // mouse.y is moot
    1951             :     }
    1952             : 
    1953           0 :     retPosition = current->m_ListStart;
    1954             : 
    1955             :     // Okay, now loop through the glyphs to find the appropriate X position
    1956             :     float dummy;
    1957           0 :     retPosition += GetXTextPosition(current, mouse.X, dummy);
    1958             : 
    1959           0 :     return retPosition;
    1960             : }
    1961             : 
    1962             : // Does not process horizontal scrolling, 'x' must be modified before inputted.
    1963           0 : int CInput::GetXTextPosition(const std::list<SRow>::const_iterator& current, const float& x, float& wanted) const
    1964             : {
    1965           0 :     int ret = 0;
    1966           0 :     float previous = 0.f;
    1967           0 :     int i = 0;
    1968             : 
    1969           0 :     for (std::vector<float>::const_iterator it = current->m_ListOfX.begin();
    1970           0 :          it != current->m_ListOfX.end();
    1971             :          ++it, ++i)
    1972             :     {
    1973           0 :         if (*it >= x)
    1974             :         {
    1975           0 :             if (x - previous >= *it - x)
    1976           0 :                 ret += i+1;
    1977             :             else
    1978           0 :                 ret += i;
    1979             : 
    1980           0 :             break;
    1981             :         }
    1982           0 :         previous = *it;
    1983             :     }
    1984             :     // If a position wasn't found, we will assume the last
    1985             :     //  character of that line.
    1986           0 :     if (i == (int)current->m_ListOfX.size())
    1987             :     {
    1988           0 :         ret += i;
    1989           0 :         wanted = x;
    1990             :     }
    1991             :     else
    1992           0 :         wanted = 0.f;
    1993             : 
    1994           0 :     return ret;
    1995             : }
    1996             : 
    1997           0 : void CInput::DeleteCurSelection()
    1998             : {
    1999             :     int virtualFrom;
    2000             :     int virtualTo;
    2001             : 
    2002           0 :     if (m_iBufferPos_Tail >= m_iBufferPos)
    2003             :     {
    2004           0 :         virtualFrom = m_iBufferPos;
    2005           0 :         virtualTo = m_iBufferPos_Tail;
    2006             :     }
    2007             :     else
    2008             :     {
    2009           0 :         virtualFrom = m_iBufferPos_Tail;
    2010           0 :         virtualTo = m_iBufferPos;
    2011             :     }
    2012             : 
    2013             :     // Silently change.
    2014           0 :     m_Caption.Set(m_Caption->Left(virtualFrom) + m_Caption->Right(static_cast<long>(m_Caption->length()) - virtualTo),
    2015             :                   false);
    2016             : 
    2017           0 :     UpdateText(virtualFrom, virtualTo, virtualFrom);
    2018             : 
    2019             :     // Remove selection
    2020           0 :     m_iBufferPos_Tail = -1;
    2021           0 :     m_iBufferPos = virtualFrom;
    2022           0 :     UpdateBufferPositionSetting();
    2023           0 : }
    2024             : 
    2025           0 : bool CInput::SelectingText() const
    2026             : {
    2027           0 :     return m_iBufferPos_Tail != -1 &&
    2028           0 :            m_iBufferPos_Tail != m_iBufferPos;
    2029             : }
    2030             : 
    2031           0 : float CInput::GetTextAreaWidth()
    2032             : {
    2033           0 :     if (m_ScrollBar && GetScrollBar(0).GetStyle())
    2034           0 :         return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width;
    2035             : 
    2036           0 :     return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f;
    2037             : }
    2038             : 
    2039           0 : void CInput::UpdateAutoScroll()
    2040             : {
    2041             :     // Autoscrolling up and down
    2042           0 :     if (m_MultiLine)
    2043             :     {
    2044           0 :         if (!m_ScrollBar)
    2045           0 :             return;
    2046             : 
    2047           0 :         const float scroll = GetScrollBar(0).GetPos();
    2048             : 
    2049             :         // Now get the height of the font.
    2050             :                         // TODO: Get the real font
    2051           0 :         CFontMetrics font(CStrIntern(m_Font->ToUTF8()));
    2052           0 :         float spacing = (float)font.GetLineSpacing();
    2053             :         //float height = font.GetHeight();
    2054             : 
    2055             :         // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
    2056             :         //  be able to get the specific element here. This is hopefully a temporary hack.
    2057             : 
    2058           0 :         std::list<SRow>::iterator current = m_CharacterPositions.begin();
    2059           0 :         int row = 0;
    2060           0 :         while (current != m_CharacterPositions.end())
    2061             :         {
    2062           0 :             if (m_iBufferPos >= current->m_ListStart &&
    2063           0 :                 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
    2064           0 :                 break;
    2065             : 
    2066           0 :             ++current;
    2067           0 :             ++row;
    2068             :         }
    2069             : 
    2070             :         // If scrolling down
    2071           0 :         if (-scroll + static_cast<float>(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight())
    2072             :         {
    2073             :             // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
    2074           0 :             GetScrollBar(0).SetPos(static_cast<float>(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f);
    2075             :         }
    2076             :         // If scrolling up
    2077           0 :         else if (-scroll + (float)row * spacing < 0.f)
    2078             :         {
    2079             :             // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
    2080           0 :             GetScrollBar(0).SetPos((float)row * spacing);
    2081             :         }
    2082             :     }
    2083             :     else // autoscrolling left and right
    2084             :     {
    2085             :         // Get X position of position:
    2086           0 :         if (m_CharacterPositions.empty())
    2087           0 :             return;
    2088             : 
    2089           0 :         float x_position = 0.f;
    2090           0 :         float x_total = 0.f;
    2091           0 :         if (!m_CharacterPositions.begin()->m_ListOfX.empty())
    2092             :         {
    2093             : 
    2094             :             // Get position of m_iBufferPos
    2095           0 :             if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos &&
    2096           0 :                 m_iBufferPos > 0)
    2097           0 :                 x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1];
    2098             : 
    2099             :             // Get complete length:
    2100           0 :             x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1];
    2101             :         }
    2102             : 
    2103             :         // Check if outside to the right
    2104           0 :         if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth())
    2105           0 :             m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
    2106             : 
    2107             :         // Check if outside to the left
    2108           0 :         if (x_position - m_HorizontalScroll < 0.f)
    2109           0 :             m_HorizontalScroll = x_position;
    2110             : 
    2111             :         // Check if the text doesn't even fill up to the right edge even though scrolling is done.
    2112           0 :         if (m_HorizontalScroll != 0.f &&
    2113           0 :             x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
    2114           0 :             m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
    2115             : 
    2116             :         // Now this is the fail-safe, if x_total isn't even the length of the control,
    2117             :         //  remove all scrolling
    2118           0 :         if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
    2119           0 :             m_HorizontalScroll = 0.f;
    2120             :     }
    2121           3 : }

Generated by: LCOV version 1.13