This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

source: ps/trunk/source/gui/ObjectTypes/CInput.cpp

Last change on this file was 27965, checked in by Vladislav Belov, 13 months ago

Revert non-ASCII characters from source and configuration files introduced in rP27786.

Fixes #6846

Differential Revision: https://code.wildfiregames.com/D5185

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