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 : }
|