diff options
Diffstat (limited to 'libraries/irrlicht-1.8/source/Irrlicht/CGUIEditBox.cpp')
-rw-r--r-- | libraries/irrlicht-1.8/source/Irrlicht/CGUIEditBox.cpp | 1555 |
1 files changed, 1555 insertions, 0 deletions
diff --git a/libraries/irrlicht-1.8/source/Irrlicht/CGUIEditBox.cpp b/libraries/irrlicht-1.8/source/Irrlicht/CGUIEditBox.cpp new file mode 100644 index 0000000..a937cc2 --- /dev/null +++ b/libraries/irrlicht-1.8/source/Irrlicht/CGUIEditBox.cpp | |||
@@ -0,0 +1,1555 @@ | |||
1 | // Copyright (C) 2002-2012 Nikolaus Gebhardt | ||
2 | // This file is part of the "Irrlicht Engine". | ||
3 | // For conditions of distribution and use, see copyright notice in irrlicht.h | ||
4 | |||
5 | #include "CGUIEditBox.h" | ||
6 | #ifdef _IRR_COMPILE_WITH_GUI_ | ||
7 | |||
8 | #include "IGUISkin.h" | ||
9 | #include "IGUIEnvironment.h" | ||
10 | #include "IGUIFont.h" | ||
11 | #include "IVideoDriver.h" | ||
12 | #include "rect.h" | ||
13 | #include "os.h" | ||
14 | #include "Keycodes.h" | ||
15 | |||
16 | /* | ||
17 | todo: | ||
18 | optional scrollbars | ||
19 | ctrl+left/right to select word | ||
20 | double click/ctrl click: word select + drag to select whole words, triple click to select line | ||
21 | optional? dragging selected text | ||
22 | numerical | ||
23 | */ | ||
24 | |||
25 | namespace irr | ||
26 | { | ||
27 | namespace gui | ||
28 | { | ||
29 | |||
30 | //! constructor | ||
31 | CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, | ||
32 | IGUIEnvironment* environment, IGUIElement* parent, s32 id, | ||
33 | const core::rect<s32>& rectangle) | ||
34 | : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), | ||
35 | Border(border), Background(true), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), | ||
36 | OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), | ||
37 | Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), | ||
38 | WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), | ||
39 | PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), | ||
40 | CurrentTextRect(0,0,1,1), FrameRect(rectangle) | ||
41 | { | ||
42 | #ifdef _DEBUG | ||
43 | setDebugName("CGUIEditBox"); | ||
44 | #endif | ||
45 | |||
46 | Text = text; | ||
47 | |||
48 | if (Environment) | ||
49 | Operator = Environment->getOSOperator(); | ||
50 | |||
51 | if (Operator) | ||
52 | Operator->grab(); | ||
53 | |||
54 | // this element can be tabbed to | ||
55 | setTabStop(true); | ||
56 | setTabOrder(-1); | ||
57 | |||
58 | calculateFrameRect(); | ||
59 | breakText(); | ||
60 | |||
61 | calculateScrollPos(); | ||
62 | } | ||
63 | |||
64 | |||
65 | //! destructor | ||
66 | CGUIEditBox::~CGUIEditBox() | ||
67 | { | ||
68 | if (OverrideFont) | ||
69 | OverrideFont->drop(); | ||
70 | |||
71 | if (Operator) | ||
72 | Operator->drop(); | ||
73 | } | ||
74 | |||
75 | |||
76 | //! Sets another skin independent font. | ||
77 | void CGUIEditBox::setOverrideFont(IGUIFont* font) | ||
78 | { | ||
79 | if (OverrideFont == font) | ||
80 | return; | ||
81 | |||
82 | if (OverrideFont) | ||
83 | OverrideFont->drop(); | ||
84 | |||
85 | OverrideFont = font; | ||
86 | |||
87 | if (OverrideFont) | ||
88 | OverrideFont->grab(); | ||
89 | |||
90 | breakText(); | ||
91 | } | ||
92 | |||
93 | //! Gets the override font (if any) | ||
94 | IGUIFont * CGUIEditBox::getOverrideFont() const | ||
95 | { | ||
96 | return OverrideFont; | ||
97 | } | ||
98 | |||
99 | //! Get the font which is used right now for drawing | ||
100 | IGUIFont* CGUIEditBox::getActiveFont() const | ||
101 | { | ||
102 | if ( OverrideFont ) | ||
103 | return OverrideFont; | ||
104 | IGUISkin* skin = Environment->getSkin(); | ||
105 | if (skin) | ||
106 | return skin->getFont(); | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | //! Sets another color for the text. | ||
111 | void CGUIEditBox::setOverrideColor(video::SColor color) | ||
112 | { | ||
113 | OverrideColor = color; | ||
114 | OverrideColorEnabled = true; | ||
115 | } | ||
116 | |||
117 | |||
118 | video::SColor CGUIEditBox::getOverrideColor() const | ||
119 | { | ||
120 | return OverrideColor; | ||
121 | } | ||
122 | |||
123 | |||
124 | //! Turns the border on or off | ||
125 | void CGUIEditBox::setDrawBorder(bool border) | ||
126 | { | ||
127 | Border = border; | ||
128 | } | ||
129 | |||
130 | //! Sets whether to draw the background | ||
131 | void CGUIEditBox::setDrawBackground(bool draw) | ||
132 | { | ||
133 | Background = draw; | ||
134 | } | ||
135 | |||
136 | //! Sets if the text should use the overide color or the color in the gui skin. | ||
137 | void CGUIEditBox::enableOverrideColor(bool enable) | ||
138 | { | ||
139 | OverrideColorEnabled = enable; | ||
140 | } | ||
141 | |||
142 | bool CGUIEditBox::isOverrideColorEnabled() const | ||
143 | { | ||
144 | _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; | ||
145 | return OverrideColorEnabled; | ||
146 | } | ||
147 | |||
148 | //! Enables or disables word wrap | ||
149 | void CGUIEditBox::setWordWrap(bool enable) | ||
150 | { | ||
151 | WordWrap = enable; | ||
152 | breakText(); | ||
153 | } | ||
154 | |||
155 | |||
156 | void CGUIEditBox::updateAbsolutePosition() | ||
157 | { | ||
158 | core::rect<s32> oldAbsoluteRect(AbsoluteRect); | ||
159 | IGUIElement::updateAbsolutePosition(); | ||
160 | if ( oldAbsoluteRect != AbsoluteRect ) | ||
161 | { | ||
162 | calculateFrameRect(); | ||
163 | breakText(); | ||
164 | calculateScrollPos(); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | |||
169 | //! Checks if word wrap is enabled | ||
170 | bool CGUIEditBox::isWordWrapEnabled() const | ||
171 | { | ||
172 | _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; | ||
173 | return WordWrap; | ||
174 | } | ||
175 | |||
176 | |||
177 | //! Enables or disables newlines. | ||
178 | void CGUIEditBox::setMultiLine(bool enable) | ||
179 | { | ||
180 | MultiLine = enable; | ||
181 | } | ||
182 | |||
183 | |||
184 | //! Checks if multi line editing is enabled | ||
185 | bool CGUIEditBox::isMultiLineEnabled() const | ||
186 | { | ||
187 | _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; | ||
188 | return MultiLine; | ||
189 | } | ||
190 | |||
191 | |||
192 | void CGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) | ||
193 | { | ||
194 | PasswordBox = passwordBox; | ||
195 | if (PasswordBox) | ||
196 | { | ||
197 | PasswordChar = passwordChar; | ||
198 | setMultiLine(false); | ||
199 | setWordWrap(false); | ||
200 | BrokenText.clear(); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | |||
205 | bool CGUIEditBox::isPasswordBox() const | ||
206 | { | ||
207 | _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; | ||
208 | return PasswordBox; | ||
209 | } | ||
210 | |||
211 | |||
212 | //! Sets text justification | ||
213 | void CGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) | ||
214 | { | ||
215 | HAlign = horizontal; | ||
216 | VAlign = vertical; | ||
217 | } | ||
218 | |||
219 | |||
220 | //! called if an event happened. | ||
221 | bool CGUIEditBox::OnEvent(const SEvent& event) | ||
222 | { | ||
223 | if (isEnabled()) | ||
224 | { | ||
225 | |||
226 | switch(event.EventType) | ||
227 | { | ||
228 | case EET_GUI_EVENT: | ||
229 | if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) | ||
230 | { | ||
231 | if (event.GUIEvent.Caller == this) | ||
232 | { | ||
233 | MouseMarking = false; | ||
234 | setTextMarkers(0,0); | ||
235 | } | ||
236 | } | ||
237 | break; | ||
238 | case EET_KEY_INPUT_EVENT: | ||
239 | if (processKey(event)) | ||
240 | return true; | ||
241 | break; | ||
242 | case EET_MOUSE_INPUT_EVENT: | ||
243 | if (processMouse(event)) | ||
244 | return true; | ||
245 | break; | ||
246 | default: | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | return IGUIElement::OnEvent(event); | ||
252 | } | ||
253 | |||
254 | |||
255 | bool CGUIEditBox::processKey(const SEvent& event) | ||
256 | { | ||
257 | if (!event.KeyInput.PressedDown) | ||
258 | return false; | ||
259 | |||
260 | bool textChanged = false; | ||
261 | s32 newMarkBegin = MarkBegin; | ||
262 | s32 newMarkEnd = MarkEnd; | ||
263 | |||
264 | // control shortcut handling | ||
265 | |||
266 | if (event.KeyInput.Control) | ||
267 | { | ||
268 | // german backlash '\' entered with control + '?' | ||
269 | if ( event.KeyInput.Char == '\\' ) | ||
270 | { | ||
271 | inputChar(event.KeyInput.Char); | ||
272 | return true; | ||
273 | } | ||
274 | |||
275 | switch(event.KeyInput.Key) | ||
276 | { | ||
277 | case KEY_KEY_A: | ||
278 | // select all | ||
279 | newMarkBegin = 0; | ||
280 | newMarkEnd = Text.size(); | ||
281 | break; | ||
282 | case KEY_KEY_C: | ||
283 | // copy to clipboard | ||
284 | if (!PasswordBox && Operator && MarkBegin != MarkEnd) | ||
285 | { | ||
286 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
287 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
288 | |||
289 | core::stringc s; | ||
290 | s = Text.subString(realmbgn, realmend - realmbgn).c_str(); | ||
291 | Operator->copyToClipboard(s.c_str()); | ||
292 | } | ||
293 | break; | ||
294 | case KEY_KEY_X: | ||
295 | // cut to the clipboard | ||
296 | if (!PasswordBox && Operator && MarkBegin != MarkEnd) | ||
297 | { | ||
298 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
299 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
300 | |||
301 | // copy | ||
302 | core::stringc sc; | ||
303 | sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); | ||
304 | Operator->copyToClipboard(sc.c_str()); | ||
305 | |||
306 | if (isEnabled()) | ||
307 | { | ||
308 | // delete | ||
309 | core::stringw s; | ||
310 | s = Text.subString(0, realmbgn); | ||
311 | s.append( Text.subString(realmend, Text.size()-realmend) ); | ||
312 | Text = s; | ||
313 | |||
314 | CursorPos = realmbgn; | ||
315 | newMarkBegin = 0; | ||
316 | newMarkEnd = 0; | ||
317 | textChanged = true; | ||
318 | } | ||
319 | } | ||
320 | break; | ||
321 | case KEY_KEY_V: | ||
322 | if ( !isEnabled() ) | ||
323 | break; | ||
324 | |||
325 | // paste from the clipboard | ||
326 | if (Operator) | ||
327 | { | ||
328 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
329 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
330 | |||
331 | // add new character | ||
332 | const c8* p = Operator->getTextFromClipboard(); | ||
333 | if (p) | ||
334 | { | ||
335 | if (MarkBegin == MarkEnd) | ||
336 | { | ||
337 | // insert text | ||
338 | core::stringw s = Text.subString(0, CursorPos); | ||
339 | s.append(p); | ||
340 | s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); | ||
341 | |||
342 | if (!Max || s.size()<=Max) // thx to Fish FH for fix | ||
343 | { | ||
344 | Text = s; | ||
345 | s = p; | ||
346 | CursorPos += s.size(); | ||
347 | } | ||
348 | } | ||
349 | else | ||
350 | { | ||
351 | // replace text | ||
352 | |||
353 | core::stringw s = Text.subString(0, realmbgn); | ||
354 | s.append(p); | ||
355 | s.append( Text.subString(realmend, Text.size()-realmend) ); | ||
356 | |||
357 | if (!Max || s.size()<=Max) // thx to Fish FH for fix | ||
358 | { | ||
359 | Text = s; | ||
360 | s = p; | ||
361 | CursorPos = realmbgn + s.size(); | ||
362 | } | ||
363 | } | ||
364 | } | ||
365 | |||
366 | newMarkBegin = 0; | ||
367 | newMarkEnd = 0; | ||
368 | textChanged = true; | ||
369 | } | ||
370 | break; | ||
371 | case KEY_HOME: | ||
372 | // move/highlight to start of text | ||
373 | if (event.KeyInput.Shift) | ||
374 | { | ||
375 | newMarkEnd = CursorPos; | ||
376 | newMarkBegin = 0; | ||
377 | CursorPos = 0; | ||
378 | } | ||
379 | else | ||
380 | { | ||
381 | CursorPos = 0; | ||
382 | newMarkBegin = 0; | ||
383 | newMarkEnd = 0; | ||
384 | } | ||
385 | break; | ||
386 | case KEY_END: | ||
387 | // move/highlight to end of text | ||
388 | if (event.KeyInput.Shift) | ||
389 | { | ||
390 | newMarkBegin = CursorPos; | ||
391 | newMarkEnd = Text.size(); | ||
392 | CursorPos = 0; | ||
393 | } | ||
394 | else | ||
395 | { | ||
396 | CursorPos = Text.size(); | ||
397 | newMarkBegin = 0; | ||
398 | newMarkEnd = 0; | ||
399 | } | ||
400 | break; | ||
401 | default: | ||
402 | return false; | ||
403 | } | ||
404 | } | ||
405 | // default keyboard handling | ||
406 | else | ||
407 | switch(event.KeyInput.Key) | ||
408 | { | ||
409 | case KEY_END: | ||
410 | { | ||
411 | s32 p = Text.size(); | ||
412 | if (WordWrap || MultiLine) | ||
413 | { | ||
414 | p = getLineFromPos(CursorPos); | ||
415 | p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); | ||
416 | if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) | ||
417 | p-=1; | ||
418 | } | ||
419 | |||
420 | if (event.KeyInput.Shift) | ||
421 | { | ||
422 | if (MarkBegin == MarkEnd) | ||
423 | newMarkBegin = CursorPos; | ||
424 | |||
425 | newMarkEnd = p; | ||
426 | } | ||
427 | else | ||
428 | { | ||
429 | newMarkBegin = 0; | ||
430 | newMarkEnd = 0; | ||
431 | } | ||
432 | CursorPos = p; | ||
433 | BlinkStartTime = os::Timer::getTime(); | ||
434 | } | ||
435 | break; | ||
436 | case KEY_HOME: | ||
437 | { | ||
438 | |||
439 | s32 p = 0; | ||
440 | if (WordWrap || MultiLine) | ||
441 | { | ||
442 | p = getLineFromPos(CursorPos); | ||
443 | p = BrokenTextPositions[p]; | ||
444 | } | ||
445 | |||
446 | if (event.KeyInput.Shift) | ||
447 | { | ||
448 | if (MarkBegin == MarkEnd) | ||
449 | newMarkBegin = CursorPos; | ||
450 | newMarkEnd = p; | ||
451 | } | ||
452 | else | ||
453 | { | ||
454 | newMarkBegin = 0; | ||
455 | newMarkEnd = 0; | ||
456 | } | ||
457 | CursorPos = p; | ||
458 | BlinkStartTime = os::Timer::getTime(); | ||
459 | } | ||
460 | break; | ||
461 | case KEY_RETURN: | ||
462 | if (MultiLine) | ||
463 | { | ||
464 | inputChar(L'\n'); | ||
465 | } | ||
466 | else | ||
467 | { | ||
468 | calculateScrollPos(); | ||
469 | sendGuiEvent( EGET_EDITBOX_ENTER ); | ||
470 | } | ||
471 | return true; | ||
472 | case KEY_LEFT: | ||
473 | |||
474 | if (event.KeyInput.Shift) | ||
475 | { | ||
476 | if (CursorPos > 0) | ||
477 | { | ||
478 | if (MarkBegin == MarkEnd) | ||
479 | newMarkBegin = CursorPos; | ||
480 | |||
481 | newMarkEnd = CursorPos-1; | ||
482 | } | ||
483 | } | ||
484 | else | ||
485 | { | ||
486 | newMarkBegin = 0; | ||
487 | newMarkEnd = 0; | ||
488 | } | ||
489 | |||
490 | if (CursorPos > 0) CursorPos--; | ||
491 | BlinkStartTime = os::Timer::getTime(); | ||
492 | break; | ||
493 | |||
494 | case KEY_RIGHT: | ||
495 | if (event.KeyInput.Shift) | ||
496 | { | ||
497 | if (Text.size() > (u32)CursorPos) | ||
498 | { | ||
499 | if (MarkBegin == MarkEnd) | ||
500 | newMarkBegin = CursorPos; | ||
501 | |||
502 | newMarkEnd = CursorPos+1; | ||
503 | } | ||
504 | } | ||
505 | else | ||
506 | { | ||
507 | newMarkBegin = 0; | ||
508 | newMarkEnd = 0; | ||
509 | } | ||
510 | |||
511 | if (Text.size() > (u32)CursorPos) CursorPos++; | ||
512 | BlinkStartTime = os::Timer::getTime(); | ||
513 | break; | ||
514 | case KEY_UP: | ||
515 | if (MultiLine || (WordWrap && BrokenText.size() > 1) ) | ||
516 | { | ||
517 | s32 lineNo = getLineFromPos(CursorPos); | ||
518 | s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); | ||
519 | if (lineNo > 0) | ||
520 | { | ||
521 | s32 cp = CursorPos - BrokenTextPositions[lineNo]; | ||
522 | if ((s32)BrokenText[lineNo-1].size() < cp) | ||
523 | CursorPos = BrokenTextPositions[lineNo-1] + core::max_((u32)1, BrokenText[lineNo-1].size())-1; | ||
524 | else | ||
525 | CursorPos = BrokenTextPositions[lineNo-1] + cp; | ||
526 | } | ||
527 | |||
528 | if (event.KeyInput.Shift) | ||
529 | { | ||
530 | newMarkBegin = mb; | ||
531 | newMarkEnd = CursorPos; | ||
532 | } | ||
533 | else | ||
534 | { | ||
535 | newMarkBegin = 0; | ||
536 | newMarkEnd = 0; | ||
537 | } | ||
538 | |||
539 | } | ||
540 | else | ||
541 | { | ||
542 | return false; | ||
543 | } | ||
544 | break; | ||
545 | case KEY_DOWN: | ||
546 | if (MultiLine || (WordWrap && BrokenText.size() > 1) ) | ||
547 | { | ||
548 | s32 lineNo = getLineFromPos(CursorPos); | ||
549 | s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); | ||
550 | if (lineNo < (s32)BrokenText.size()-1) | ||
551 | { | ||
552 | s32 cp = CursorPos - BrokenTextPositions[lineNo]; | ||
553 | if ((s32)BrokenText[lineNo+1].size() < cp) | ||
554 | CursorPos = BrokenTextPositions[lineNo+1] + core::max_((u32)1, BrokenText[lineNo+1].size())-1; | ||
555 | else | ||
556 | CursorPos = BrokenTextPositions[lineNo+1] + cp; | ||
557 | } | ||
558 | |||
559 | if (event.KeyInput.Shift) | ||
560 | { | ||
561 | newMarkBegin = mb; | ||
562 | newMarkEnd = CursorPos; | ||
563 | } | ||
564 | else | ||
565 | { | ||
566 | newMarkBegin = 0; | ||
567 | newMarkEnd = 0; | ||
568 | } | ||
569 | |||
570 | } | ||
571 | else | ||
572 | { | ||
573 | return false; | ||
574 | } | ||
575 | break; | ||
576 | |||
577 | case KEY_BACK: | ||
578 | if ( !isEnabled() ) | ||
579 | break; | ||
580 | |||
581 | if (Text.size()) | ||
582 | { | ||
583 | core::stringw s; | ||
584 | |||
585 | if (MarkBegin != MarkEnd) | ||
586 | { | ||
587 | // delete marked text | ||
588 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
589 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
590 | |||
591 | s = Text.subString(0, realmbgn); | ||
592 | s.append( Text.subString(realmend, Text.size()-realmend) ); | ||
593 | Text = s; | ||
594 | |||
595 | CursorPos = realmbgn; | ||
596 | } | ||
597 | else | ||
598 | { | ||
599 | // delete text behind cursor | ||
600 | if (CursorPos>0) | ||
601 | s = Text.subString(0, CursorPos-1); | ||
602 | else | ||
603 | s = L""; | ||
604 | s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); | ||
605 | Text = s; | ||
606 | --CursorPos; | ||
607 | } | ||
608 | |||
609 | if (CursorPos < 0) | ||
610 | CursorPos = 0; | ||
611 | BlinkStartTime = os::Timer::getTime(); | ||
612 | newMarkBegin = 0; | ||
613 | newMarkEnd = 0; | ||
614 | textChanged = true; | ||
615 | } | ||
616 | break; | ||
617 | case KEY_DELETE: | ||
618 | if ( !isEnabled() ) | ||
619 | break; | ||
620 | |||
621 | if (Text.size() != 0) | ||
622 | { | ||
623 | core::stringw s; | ||
624 | |||
625 | if (MarkBegin != MarkEnd) | ||
626 | { | ||
627 | // delete marked text | ||
628 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
629 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
630 | |||
631 | s = Text.subString(0, realmbgn); | ||
632 | s.append( Text.subString(realmend, Text.size()-realmend) ); | ||
633 | Text = s; | ||
634 | |||
635 | CursorPos = realmbgn; | ||
636 | } | ||
637 | else | ||
638 | { | ||
639 | // delete text before cursor | ||
640 | s = Text.subString(0, CursorPos); | ||
641 | s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); | ||
642 | Text = s; | ||
643 | } | ||
644 | |||
645 | if (CursorPos > (s32)Text.size()) | ||
646 | CursorPos = (s32)Text.size(); | ||
647 | |||
648 | BlinkStartTime = os::Timer::getTime(); | ||
649 | newMarkBegin = 0; | ||
650 | newMarkEnd = 0; | ||
651 | textChanged = true; | ||
652 | } | ||
653 | break; | ||
654 | |||
655 | case KEY_ESCAPE: | ||
656 | case KEY_TAB: | ||
657 | case KEY_SHIFT: | ||
658 | case KEY_F1: | ||
659 | case KEY_F2: | ||
660 | case KEY_F3: | ||
661 | case KEY_F4: | ||
662 | case KEY_F5: | ||
663 | case KEY_F6: | ||
664 | case KEY_F7: | ||
665 | case KEY_F8: | ||
666 | case KEY_F9: | ||
667 | case KEY_F10: | ||
668 | case KEY_F11: | ||
669 | case KEY_F12: | ||
670 | case KEY_F13: | ||
671 | case KEY_F14: | ||
672 | case KEY_F15: | ||
673 | case KEY_F16: | ||
674 | case KEY_F17: | ||
675 | case KEY_F18: | ||
676 | case KEY_F19: | ||
677 | case KEY_F20: | ||
678 | case KEY_F21: | ||
679 | case KEY_F22: | ||
680 | case KEY_F23: | ||
681 | case KEY_F24: | ||
682 | // ignore these keys | ||
683 | return false; | ||
684 | |||
685 | default: | ||
686 | inputChar(event.KeyInput.Char); | ||
687 | return true; | ||
688 | } | ||
689 | |||
690 | // Set new text markers | ||
691 | setTextMarkers( newMarkBegin, newMarkEnd ); | ||
692 | |||
693 | // break the text if it has changed | ||
694 | if (textChanged) | ||
695 | { | ||
696 | breakText(); | ||
697 | calculateScrollPos(); | ||
698 | sendGuiEvent(EGET_EDITBOX_CHANGED); | ||
699 | } | ||
700 | else | ||
701 | { | ||
702 | calculateScrollPos(); | ||
703 | } | ||
704 | |||
705 | return true; | ||
706 | } | ||
707 | |||
708 | |||
709 | //! draws the element and its children | ||
710 | void CGUIEditBox::draw() | ||
711 | { | ||
712 | if (!IsVisible) | ||
713 | return; | ||
714 | |||
715 | const bool focus = Environment->hasFocus(this); | ||
716 | |||
717 | IGUISkin* skin = Environment->getSkin(); | ||
718 | if (!skin) | ||
719 | return; | ||
720 | |||
721 | EGUI_DEFAULT_COLOR bgCol = EGDC_GRAY_EDITABLE; | ||
722 | if ( isEnabled() ) | ||
723 | bgCol = focus ? EGDC_FOCUSED_EDITABLE : EGDC_EDITABLE; | ||
724 | |||
725 | if (!Border && Background) | ||
726 | { | ||
727 | skin->draw2DRectangle(this, skin->getColor(bgCol), AbsoluteRect, &AbsoluteClippingRect); | ||
728 | } | ||
729 | |||
730 | if (Border) | ||
731 | { | ||
732 | // draw the border | ||
733 | skin->draw3DSunkenPane(this, skin->getColor(bgCol), false, Background, AbsoluteRect, &AbsoluteClippingRect); | ||
734 | |||
735 | calculateFrameRect(); | ||
736 | } | ||
737 | |||
738 | core::rect<s32> localClipRect = FrameRect; | ||
739 | localClipRect.clipAgainst(AbsoluteClippingRect); | ||
740 | |||
741 | // draw the text | ||
742 | |||
743 | IGUIFont* font = getActiveFont(); | ||
744 | |||
745 | s32 cursorLine = 0; | ||
746 | s32 charcursorpos = 0; | ||
747 | |||
748 | if (font) | ||
749 | { | ||
750 | if (LastBreakFont != font) | ||
751 | { | ||
752 | breakText(); | ||
753 | } | ||
754 | |||
755 | // calculate cursor pos | ||
756 | |||
757 | core::stringw *txtLine = &Text; | ||
758 | s32 startPos = 0; | ||
759 | |||
760 | core::stringw s, s2; | ||
761 | |||
762 | // get mark position | ||
763 | const bool ml = (!PasswordBox && (WordWrap || MultiLine)); | ||
764 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
765 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
766 | const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; | ||
767 | const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; | ||
768 | const s32 lineCount = ml ? BrokenText.size() : 1; | ||
769 | |||
770 | // Save the override color information. | ||
771 | // Then, alter it if the edit box is disabled. | ||
772 | const bool prevOver = OverrideColorEnabled; | ||
773 | const video::SColor prevColor = OverrideColor; | ||
774 | |||
775 | if (Text.size()) | ||
776 | { | ||
777 | if (!isEnabled() && !OverrideColorEnabled) | ||
778 | { | ||
779 | OverrideColorEnabled = true; | ||
780 | OverrideColor = skin->getColor(EGDC_GRAY_TEXT); | ||
781 | } | ||
782 | |||
783 | for (s32 i=0; i < lineCount; ++i) | ||
784 | { | ||
785 | setTextRect(i); | ||
786 | |||
787 | // clipping test - don't draw anything outside the visible area | ||
788 | core::rect<s32> c = localClipRect; | ||
789 | c.clipAgainst(CurrentTextRect); | ||
790 | if (!c.isValid()) | ||
791 | continue; | ||
792 | |||
793 | // get current line | ||
794 | if (PasswordBox) | ||
795 | { | ||
796 | if (BrokenText.size() != 1) | ||
797 | { | ||
798 | BrokenText.clear(); | ||
799 | BrokenText.push_back(core::stringw()); | ||
800 | } | ||
801 | if (BrokenText[0].size() != Text.size()) | ||
802 | { | ||
803 | BrokenText[0] = Text; | ||
804 | for (u32 q = 0; q < Text.size(); ++q) | ||
805 | { | ||
806 | BrokenText[0] [q] = PasswordChar; | ||
807 | } | ||
808 | } | ||
809 | txtLine = &BrokenText[0]; | ||
810 | startPos = 0; | ||
811 | } | ||
812 | else | ||
813 | { | ||
814 | txtLine = ml ? &BrokenText[i] : &Text; | ||
815 | startPos = ml ? BrokenTextPositions[i] : 0; | ||
816 | } | ||
817 | |||
818 | |||
819 | // draw normal text | ||
820 | font->draw(txtLine->c_str(), CurrentTextRect, | ||
821 | OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), | ||
822 | false, true, &localClipRect); | ||
823 | |||
824 | // draw mark and marked text | ||
825 | if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) | ||
826 | { | ||
827 | |||
828 | s32 mbegin = 0, mend = 0; | ||
829 | s32 lineStartPos = 0, lineEndPos = txtLine->size(); | ||
830 | |||
831 | if (i == hlineStart) | ||
832 | { | ||
833 | // highlight start is on this line | ||
834 | s = txtLine->subString(0, realmbgn - startPos); | ||
835 | mbegin = font->getDimension(s.c_str()).Width; | ||
836 | |||
837 | // deal with kerning | ||
838 | mbegin += font->getKerningWidth( | ||
839 | &((*txtLine)[realmbgn - startPos]), | ||
840 | realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); | ||
841 | |||
842 | lineStartPos = realmbgn - startPos; | ||
843 | } | ||
844 | if (i == hlineStart + hlineCount - 1) | ||
845 | { | ||
846 | // highlight end is on this line | ||
847 | s2 = txtLine->subString(0, realmend - startPos); | ||
848 | mend = font->getDimension(s2.c_str()).Width; | ||
849 | lineEndPos = (s32)s2.size(); | ||
850 | } | ||
851 | else | ||
852 | mend = font->getDimension(txtLine->c_str()).Width; | ||
853 | |||
854 | CurrentTextRect.UpperLeftCorner.X += mbegin; | ||
855 | CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; | ||
856 | |||
857 | // draw mark | ||
858 | skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); | ||
859 | |||
860 | // draw marked text | ||
861 | s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); | ||
862 | |||
863 | if (s.size()) | ||
864 | font->draw(s.c_str(), CurrentTextRect, | ||
865 | OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), | ||
866 | false, true, &localClipRect); | ||
867 | |||
868 | } | ||
869 | } | ||
870 | |||
871 | // Return the override color information to its previous settings. | ||
872 | OverrideColorEnabled = prevOver; | ||
873 | OverrideColor = prevColor; | ||
874 | } | ||
875 | |||
876 | // draw cursor | ||
877 | if ( IsEnabled ) | ||
878 | { | ||
879 | if (WordWrap || MultiLine) | ||
880 | { | ||
881 | cursorLine = getLineFromPos(CursorPos); | ||
882 | txtLine = &BrokenText[cursorLine]; | ||
883 | startPos = BrokenTextPositions[cursorLine]; | ||
884 | } | ||
885 | s = txtLine->subString(0,CursorPos-startPos); | ||
886 | charcursorpos = font->getDimension(s.c_str()).Width + | ||
887 | font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); | ||
888 | |||
889 | if (focus && (os::Timer::getTime() - BlinkStartTime) % 700 < 350) | ||
890 | { | ||
891 | setTextRect(cursorLine); | ||
892 | CurrentTextRect.UpperLeftCorner.X += charcursorpos; | ||
893 | |||
894 | font->draw(L"_", CurrentTextRect, | ||
895 | OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), | ||
896 | false, true, &localClipRect); | ||
897 | } | ||
898 | } | ||
899 | } | ||
900 | |||
901 | // draw children | ||
902 | IGUIElement::draw(); | ||
903 | } | ||
904 | |||
905 | |||
906 | //! Sets the new caption of this element. | ||
907 | void CGUIEditBox::setText(const wchar_t* text) | ||
908 | { | ||
909 | Text = text; | ||
910 | if (u32(CursorPos) > Text.size()) | ||
911 | CursorPos = Text.size(); | ||
912 | HScrollPos = 0; | ||
913 | breakText(); | ||
914 | } | ||
915 | |||
916 | |||
917 | //! Enables or disables automatic scrolling with cursor position | ||
918 | //! \param enable: If set to true, the text will move around with the cursor position | ||
919 | void CGUIEditBox::setAutoScroll(bool enable) | ||
920 | { | ||
921 | AutoScroll = enable; | ||
922 | } | ||
923 | |||
924 | |||
925 | //! Checks to see if automatic scrolling is enabled | ||
926 | //! \return true if automatic scrolling is enabled, false if not | ||
927 | bool CGUIEditBox::isAutoScrollEnabled() const | ||
928 | { | ||
929 | _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; | ||
930 | return AutoScroll; | ||
931 | } | ||
932 | |||
933 | |||
934 | //! Gets the area of the text in the edit box | ||
935 | //! \return Returns the size in pixels of the text | ||
936 | core::dimension2du CGUIEditBox::getTextDimension() | ||
937 | { | ||
938 | core::rect<s32> ret; | ||
939 | |||
940 | setTextRect(0); | ||
941 | ret = CurrentTextRect; | ||
942 | |||
943 | for (u32 i=1; i < BrokenText.size(); ++i) | ||
944 | { | ||
945 | setTextRect(i); | ||
946 | ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); | ||
947 | ret.addInternalPoint(CurrentTextRect.LowerRightCorner); | ||
948 | } | ||
949 | |||
950 | return core::dimension2du(ret.getSize()); | ||
951 | } | ||
952 | |||
953 | |||
954 | //! Sets the maximum amount of characters which may be entered in the box. | ||
955 | //! \param max: Maximum amount of characters. If 0, the character amount is | ||
956 | //! infinity. | ||
957 | void CGUIEditBox::setMax(u32 max) | ||
958 | { | ||
959 | Max = max; | ||
960 | |||
961 | if (Text.size() > Max && Max != 0) | ||
962 | Text = Text.subString(0, Max); | ||
963 | } | ||
964 | |||
965 | |||
966 | //! Returns maximum amount of characters, previously set by setMax(); | ||
967 | u32 CGUIEditBox::getMax() const | ||
968 | { | ||
969 | return Max; | ||
970 | } | ||
971 | |||
972 | |||
973 | bool CGUIEditBox::processMouse(const SEvent& event) | ||
974 | { | ||
975 | switch(event.MouseInput.Event) | ||
976 | { | ||
977 | case irr::EMIE_LMOUSE_LEFT_UP: | ||
978 | if (Environment->hasFocus(this)) | ||
979 | { | ||
980 | CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); | ||
981 | if (MouseMarking) | ||
982 | { | ||
983 | setTextMarkers( MarkBegin, CursorPos ); | ||
984 | } | ||
985 | MouseMarking = false; | ||
986 | calculateScrollPos(); | ||
987 | return true; | ||
988 | } | ||
989 | break; | ||
990 | case irr::EMIE_MOUSE_MOVED: | ||
991 | { | ||
992 | if (MouseMarking) | ||
993 | { | ||
994 | CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); | ||
995 | setTextMarkers( MarkBegin, CursorPos ); | ||
996 | calculateScrollPos(); | ||
997 | return true; | ||
998 | } | ||
999 | } | ||
1000 | break; | ||
1001 | case EMIE_LMOUSE_PRESSED_DOWN: | ||
1002 | if (!Environment->hasFocus(this)) | ||
1003 | { | ||
1004 | BlinkStartTime = os::Timer::getTime(); | ||
1005 | MouseMarking = true; | ||
1006 | CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); | ||
1007 | setTextMarkers(CursorPos, CursorPos ); | ||
1008 | calculateScrollPos(); | ||
1009 | return true; | ||
1010 | } | ||
1011 | else | ||
1012 | { | ||
1013 | if (!AbsoluteClippingRect.isPointInside( | ||
1014 | core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) | ||
1015 | { | ||
1016 | return false; | ||
1017 | } | ||
1018 | else | ||
1019 | { | ||
1020 | // move cursor | ||
1021 | CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); | ||
1022 | |||
1023 | s32 newMarkBegin = MarkBegin; | ||
1024 | if (!MouseMarking) | ||
1025 | newMarkBegin = CursorPos; | ||
1026 | |||
1027 | MouseMarking = true; | ||
1028 | setTextMarkers( newMarkBegin, CursorPos); | ||
1029 | calculateScrollPos(); | ||
1030 | return true; | ||
1031 | } | ||
1032 | } | ||
1033 | default: | ||
1034 | break; | ||
1035 | } | ||
1036 | |||
1037 | return false; | ||
1038 | } | ||
1039 | |||
1040 | |||
1041 | s32 CGUIEditBox::getCursorPos(s32 x, s32 y) | ||
1042 | { | ||
1043 | IGUIFont* font = getActiveFont(); | ||
1044 | |||
1045 | const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; | ||
1046 | |||
1047 | core::stringw *txtLine=0; | ||
1048 | s32 startPos=0; | ||
1049 | x+=3; | ||
1050 | |||
1051 | for (u32 i=0; i < lineCount; ++i) | ||
1052 | { | ||
1053 | setTextRect(i); | ||
1054 | if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) | ||
1055 | y = CurrentTextRect.UpperLeftCorner.Y; | ||
1056 | if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y ) | ||
1057 | y = CurrentTextRect.LowerRightCorner.Y; | ||
1058 | |||
1059 | // is it inside this region? | ||
1060 | if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) | ||
1061 | { | ||
1062 | // we've found the clicked line | ||
1063 | txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text; | ||
1064 | startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; | ||
1065 | break; | ||
1066 | } | ||
1067 | } | ||
1068 | |||
1069 | if (x < CurrentTextRect.UpperLeftCorner.X) | ||
1070 | x = CurrentTextRect.UpperLeftCorner.X; | ||
1071 | |||
1072 | if ( !txtLine ) | ||
1073 | return 0; | ||
1074 | |||
1075 | s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X); | ||
1076 | |||
1077 | // click was on or left of the line | ||
1078 | if (idx != -1) | ||
1079 | return idx + startPos; | ||
1080 | |||
1081 | // click was off the right edge of the line, go to end. | ||
1082 | return txtLine->size() + startPos; | ||
1083 | } | ||
1084 | |||
1085 | |||
1086 | //! Breaks the single text line. | ||
1087 | void CGUIEditBox::breakText() | ||
1088 | { | ||
1089 | if ((!WordWrap && !MultiLine)) | ||
1090 | return; | ||
1091 | |||
1092 | BrokenText.clear(); // need to reallocate :/ | ||
1093 | BrokenTextPositions.set_used(0); | ||
1094 | |||
1095 | IGUIFont* font = getActiveFont(); | ||
1096 | if (!font) | ||
1097 | return; | ||
1098 | |||
1099 | LastBreakFont = font; | ||
1100 | |||
1101 | core::stringw line; | ||
1102 | core::stringw word; | ||
1103 | core::stringw whitespace; | ||
1104 | s32 lastLineStart = 0; | ||
1105 | s32 size = Text.size(); | ||
1106 | s32 length = 0; | ||
1107 | s32 elWidth = RelativeRect.getWidth() - 6; | ||
1108 | wchar_t c; | ||
1109 | |||
1110 | for (s32 i=0; i<size; ++i) | ||
1111 | { | ||
1112 | c = Text[i]; | ||
1113 | bool lineBreak = false; | ||
1114 | |||
1115 | if (c == L'\r') // Mac or Windows breaks | ||
1116 | { | ||
1117 | lineBreak = true; | ||
1118 | c = 0; | ||
1119 | if (Text[i+1] == L'\n') // Windows breaks | ||
1120 | { | ||
1121 | // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason. | ||
1122 | // Instead rework the cursor positioning to be able to handle this (but not in stable release | ||
1123 | // branch as users might already expect this behavior). | ||
1124 | Text.erase(i+1); | ||
1125 | --size; | ||
1126 | if ( CursorPos > i ) | ||
1127 | --CursorPos; | ||
1128 | } | ||
1129 | } | ||
1130 | else if (c == L'\n') // Unix breaks | ||
1131 | { | ||
1132 | lineBreak = true; | ||
1133 | c = 0; | ||
1134 | } | ||
1135 | |||
1136 | // don't break if we're not a multi-line edit box | ||
1137 | if (!MultiLine) | ||
1138 | lineBreak = false; | ||
1139 | |||
1140 | if (c == L' ' || c == 0 || i == (size-1)) | ||
1141 | { | ||
1142 | // here comes the next whitespace, look if | ||
1143 | // we can break the last word to the next line | ||
1144 | // We also break whitespace, otherwise cursor would vanish beside the right border. | ||
1145 | s32 whitelgth = font->getDimension(whitespace.c_str()).Width; | ||
1146 | s32 worldlgth = font->getDimension(word.c_str()).Width; | ||
1147 | |||
1148 | if (WordWrap && length + worldlgth + whitelgth > elWidth && line.size() > 0) | ||
1149 | { | ||
1150 | // break to next line | ||
1151 | length = worldlgth; | ||
1152 | BrokenText.push_back(line); | ||
1153 | BrokenTextPositions.push_back(lastLineStart); | ||
1154 | lastLineStart = i - (s32)word.size(); | ||
1155 | line = word; | ||
1156 | } | ||
1157 | else | ||
1158 | { | ||
1159 | // add word to line | ||
1160 | line += whitespace; | ||
1161 | line += word; | ||
1162 | length += whitelgth + worldlgth; | ||
1163 | } | ||
1164 | |||
1165 | word = L""; | ||
1166 | whitespace = L""; | ||
1167 | |||
1168 | |||
1169 | if ( c ) | ||
1170 | whitespace += c; | ||
1171 | |||
1172 | // compute line break | ||
1173 | if (lineBreak) | ||
1174 | { | ||
1175 | line += whitespace; | ||
1176 | line += word; | ||
1177 | BrokenText.push_back(line); | ||
1178 | BrokenTextPositions.push_back(lastLineStart); | ||
1179 | lastLineStart = i+1; | ||
1180 | line = L""; | ||
1181 | word = L""; | ||
1182 | whitespace = L""; | ||
1183 | length = 0; | ||
1184 | } | ||
1185 | } | ||
1186 | else | ||
1187 | { | ||
1188 | // yippee this is a word.. | ||
1189 | word += c; | ||
1190 | } | ||
1191 | } | ||
1192 | |||
1193 | line += whitespace; | ||
1194 | line += word; | ||
1195 | BrokenText.push_back(line); | ||
1196 | BrokenTextPositions.push_back(lastLineStart); | ||
1197 | } | ||
1198 | |||
1199 | // TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom) | ||
1200 | // but HAlign according to line-width (pixels) and not by row. | ||
1201 | // Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling. | ||
1202 | // But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling). | ||
1203 | void CGUIEditBox::setTextRect(s32 line) | ||
1204 | { | ||
1205 | if ( line < 0 ) | ||
1206 | return; | ||
1207 | |||
1208 | IGUIFont* font = getActiveFont(); | ||
1209 | if (!font) | ||
1210 | return; | ||
1211 | |||
1212 | core::dimension2du d; | ||
1213 | |||
1214 | // get text dimension | ||
1215 | const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; | ||
1216 | if (WordWrap || MultiLine) | ||
1217 | { | ||
1218 | d = font->getDimension(BrokenText[line].c_str()); | ||
1219 | } | ||
1220 | else | ||
1221 | { | ||
1222 | d = font->getDimension(Text.c_str()); | ||
1223 | d.Height = AbsoluteRect.getHeight(); | ||
1224 | } | ||
1225 | d.Height += font->getKerningHeight(); | ||
1226 | |||
1227 | // justification | ||
1228 | switch (HAlign) | ||
1229 | { | ||
1230 | case EGUIA_CENTER: | ||
1231 | // align to h centre | ||
1232 | CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); | ||
1233 | CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); | ||
1234 | break; | ||
1235 | case EGUIA_LOWERRIGHT: | ||
1236 | // align to right edge | ||
1237 | CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width; | ||
1238 | CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth(); | ||
1239 | break; | ||
1240 | default: | ||
1241 | // align to left edge | ||
1242 | CurrentTextRect.UpperLeftCorner.X = 0; | ||
1243 | CurrentTextRect.LowerRightCorner.X = d.Width; | ||
1244 | |||
1245 | } | ||
1246 | |||
1247 | switch (VAlign) | ||
1248 | { | ||
1249 | case EGUIA_CENTER: | ||
1250 | // align to v centre | ||
1251 | CurrentTextRect.UpperLeftCorner.Y = | ||
1252 | (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; | ||
1253 | break; | ||
1254 | case EGUIA_LOWERRIGHT: | ||
1255 | // align to bottom edge | ||
1256 | CurrentTextRect.UpperLeftCorner.Y = | ||
1257 | FrameRect.getHeight() - lineCount*d.Height + d.Height*line; | ||
1258 | break; | ||
1259 | default: | ||
1260 | // align to top edge | ||
1261 | CurrentTextRect.UpperLeftCorner.Y = d.Height*line; | ||
1262 | break; | ||
1263 | } | ||
1264 | |||
1265 | CurrentTextRect.UpperLeftCorner.X -= HScrollPos; | ||
1266 | CurrentTextRect.LowerRightCorner.X -= HScrollPos; | ||
1267 | CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; | ||
1268 | CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; | ||
1269 | |||
1270 | CurrentTextRect += FrameRect.UpperLeftCorner; | ||
1271 | |||
1272 | } | ||
1273 | |||
1274 | |||
1275 | s32 CGUIEditBox::getLineFromPos(s32 pos) | ||
1276 | { | ||
1277 | if (!WordWrap && !MultiLine) | ||
1278 | return 0; | ||
1279 | |||
1280 | s32 i=0; | ||
1281 | while (i < (s32)BrokenTextPositions.size()) | ||
1282 | { | ||
1283 | if (BrokenTextPositions[i] > pos) | ||
1284 | return i-1; | ||
1285 | ++i; | ||
1286 | } | ||
1287 | return (s32)BrokenTextPositions.size() - 1; | ||
1288 | } | ||
1289 | |||
1290 | |||
1291 | void CGUIEditBox::inputChar(wchar_t c) | ||
1292 | { | ||
1293 | if (!isEnabled()) | ||
1294 | return; | ||
1295 | |||
1296 | if (c != 0) | ||
1297 | { | ||
1298 | if (Text.size() < Max || Max == 0) | ||
1299 | { | ||
1300 | core::stringw s; | ||
1301 | |||
1302 | if (MarkBegin != MarkEnd) | ||
1303 | { | ||
1304 | // replace marked text | ||
1305 | const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; | ||
1306 | const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; | ||
1307 | |||
1308 | s = Text.subString(0, realmbgn); | ||
1309 | s.append(c); | ||
1310 | s.append( Text.subString(realmend, Text.size()-realmend) ); | ||
1311 | Text = s; | ||
1312 | CursorPos = realmbgn+1; | ||
1313 | } | ||
1314 | else | ||
1315 | { | ||
1316 | // add new character | ||
1317 | s = Text.subString(0, CursorPos); | ||
1318 | s.append(c); | ||
1319 | s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); | ||
1320 | Text = s; | ||
1321 | ++CursorPos; | ||
1322 | } | ||
1323 | |||
1324 | BlinkStartTime = os::Timer::getTime(); | ||
1325 | setTextMarkers(0, 0); | ||
1326 | } | ||
1327 | } | ||
1328 | breakText(); | ||
1329 | calculateScrollPos(); | ||
1330 | sendGuiEvent(EGET_EDITBOX_CHANGED); | ||
1331 | } | ||
1332 | |||
1333 | // calculate autoscroll | ||
1334 | void CGUIEditBox::calculateScrollPos() | ||
1335 | { | ||
1336 | if (!AutoScroll) | ||
1337 | return; | ||
1338 | |||
1339 | IGUISkin* skin = Environment->getSkin(); | ||
1340 | if (!skin) | ||
1341 | return; | ||
1342 | IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); | ||
1343 | if (!font) | ||
1344 | return; | ||
1345 | |||
1346 | s32 cursLine = getLineFromPos(CursorPos); | ||
1347 | if ( cursLine < 0 ) | ||
1348 | return; | ||
1349 | setTextRect(cursLine); | ||
1350 | const bool hasBrokenText = MultiLine || WordWrap; | ||
1351 | |||
1352 | // Check horizonal scrolling | ||
1353 | // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row | ||
1354 | { | ||
1355 | // get cursor position | ||
1356 | IGUIFont* font = getActiveFont(); | ||
1357 | if (!font) | ||
1358 | return; | ||
1359 | |||
1360 | // get cursor area | ||
1361 | irr::u32 cursorWidth = font->getDimension(L"_").Width; | ||
1362 | core::stringw *txtLine = hasBrokenText ? &BrokenText[cursLine] : &Text; | ||
1363 | s32 cPos = hasBrokenText ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; // column | ||
1364 | s32 cStart = font->getDimension(txtLine->subString(0, cPos).c_str()).Width; // pixels from text-start | ||
1365 | s32 cEnd = cStart + cursorWidth; | ||
1366 | s32 txtWidth = font->getDimension(txtLine->c_str()).Width; | ||
1367 | |||
1368 | if ( txtWidth < FrameRect.getWidth() ) | ||
1369 | { | ||
1370 | // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom. | ||
1371 | // This check just fixes the case where it was most noticable (text smaller than clipping area). | ||
1372 | |||
1373 | HScrollPos = 0; | ||
1374 | setTextRect(cursLine); | ||
1375 | } | ||
1376 | |||
1377 | if ( CurrentTextRect.UpperLeftCorner.X+cStart < FrameRect.UpperLeftCorner.X ) | ||
1378 | { | ||
1379 | // cursor to the left of the clipping area | ||
1380 | HScrollPos -= FrameRect.UpperLeftCorner.X-(CurrentTextRect.UpperLeftCorner.X+cStart); | ||
1381 | setTextRect(cursLine); | ||
1382 | |||
1383 | // TODO: should show more characters to the left when we're scrolling left | ||
1384 | // and the cursor reaches the border. | ||
1385 | } | ||
1386 | else if ( CurrentTextRect.UpperLeftCorner.X+cEnd > FrameRect.LowerRightCorner.X) | ||
1387 | { | ||
1388 | // cursor to the right of the clipping area | ||
1389 | HScrollPos += (CurrentTextRect.UpperLeftCorner.X+cEnd)-FrameRect.LowerRightCorner.X; | ||
1390 | setTextRect(cursLine); | ||
1391 | } | ||
1392 | } | ||
1393 | |||
1394 | // calculate vertical scrolling | ||
1395 | if (hasBrokenText) | ||
1396 | { | ||
1397 | irr::u32 lineHeight = font->getDimension(L"A").Height + font->getKerningHeight(); | ||
1398 | // only up to 1 line fits? | ||
1399 | if ( lineHeight >= (irr::u32)FrameRect.getHeight() ) | ||
1400 | { | ||
1401 | VScrollPos = 0; | ||
1402 | setTextRect(cursLine); | ||
1403 | s32 unscrolledPos = CurrentTextRect.UpperLeftCorner.Y; | ||
1404 | s32 pivot = FrameRect.UpperLeftCorner.Y; | ||
1405 | switch (VAlign) | ||
1406 | { | ||
1407 | case EGUIA_CENTER: | ||
1408 | pivot += FrameRect.getHeight()/2; | ||
1409 | unscrolledPos += lineHeight/2; | ||
1410 | break; | ||
1411 | case EGUIA_LOWERRIGHT: | ||
1412 | pivot += FrameRect.getHeight(); | ||
1413 | unscrolledPos += lineHeight; | ||
1414 | break; | ||
1415 | default: | ||
1416 | break; | ||
1417 | } | ||
1418 | VScrollPos = unscrolledPos-pivot; | ||
1419 | setTextRect(cursLine); | ||
1420 | } | ||
1421 | else | ||
1422 | { | ||
1423 | // First 2 checks are necessary when people delete lines | ||
1424 | setTextRect(0); | ||
1425 | if ( CurrentTextRect.UpperLeftCorner.Y > FrameRect.UpperLeftCorner.Y && VAlign != EGUIA_LOWERRIGHT) | ||
1426 | { | ||
1427 | // first line is leaving a gap on top | ||
1428 | VScrollPos = 0; | ||
1429 | } | ||
1430 | else if (VAlign != EGUIA_UPPERLEFT) | ||
1431 | { | ||
1432 | u32 lastLine = BrokenTextPositions.empty() ? 0 : BrokenTextPositions.size()-1; | ||
1433 | setTextRect(lastLine); | ||
1434 | if ( CurrentTextRect.LowerRightCorner.Y < FrameRect.LowerRightCorner.Y) | ||
1435 | { | ||
1436 | // last line is leaving a gap on bottom | ||
1437 | VScrollPos -= FrameRect.LowerRightCorner.Y-CurrentTextRect.LowerRightCorner.Y; | ||
1438 | } | ||
1439 | } | ||
1440 | |||
1441 | setTextRect(cursLine); | ||
1442 | if ( CurrentTextRect.UpperLeftCorner.Y < FrameRect.UpperLeftCorner.Y ) | ||
1443 | { | ||
1444 | // text above valid area | ||
1445 | VScrollPos -= FrameRect.UpperLeftCorner.Y-CurrentTextRect.UpperLeftCorner.Y; | ||
1446 | setTextRect(cursLine); | ||
1447 | } | ||
1448 | else if ( CurrentTextRect.LowerRightCorner.Y > FrameRect.LowerRightCorner.Y) | ||
1449 | { | ||
1450 | // text below valid area | ||
1451 | VScrollPos += CurrentTextRect.LowerRightCorner.Y-FrameRect.LowerRightCorner.Y; | ||
1452 | setTextRect(cursLine); | ||
1453 | } | ||
1454 | } | ||
1455 | } | ||
1456 | } | ||
1457 | |||
1458 | void CGUIEditBox::calculateFrameRect() | ||
1459 | { | ||
1460 | FrameRect = AbsoluteRect; | ||
1461 | IGUISkin *skin = 0; | ||
1462 | if (Environment) | ||
1463 | skin = Environment->getSkin(); | ||
1464 | if (Border && skin) | ||
1465 | { | ||
1466 | FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; | ||
1467 | FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; | ||
1468 | FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; | ||
1469 | FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; | ||
1470 | } | ||
1471 | } | ||
1472 | |||
1473 | //! set text markers | ||
1474 | void CGUIEditBox::setTextMarkers(s32 begin, s32 end) | ||
1475 | { | ||
1476 | if ( begin != MarkBegin || end != MarkEnd ) | ||
1477 | { | ||
1478 | MarkBegin = begin; | ||
1479 | MarkEnd = end; | ||
1480 | sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); | ||
1481 | } | ||
1482 | } | ||
1483 | |||
1484 | //! send some gui event to parent | ||
1485 | void CGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) | ||
1486 | { | ||
1487 | if ( Parent ) | ||
1488 | { | ||
1489 | SEvent e; | ||
1490 | e.EventType = EET_GUI_EVENT; | ||
1491 | e.GUIEvent.Caller = this; | ||
1492 | e.GUIEvent.Element = 0; | ||
1493 | e.GUIEvent.EventType = type; | ||
1494 | |||
1495 | Parent->OnEvent(e); | ||
1496 | } | ||
1497 | } | ||
1498 | |||
1499 | //! Writes attributes of the element. | ||
1500 | void CGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const | ||
1501 | { | ||
1502 | // IGUIEditBox::serializeAttributes(out,options); | ||
1503 | |||
1504 | out->addBool ("Border", Border); | ||
1505 | out->addBool ("Background", Background); | ||
1506 | out->addBool ("OverrideColorEnabled", OverrideColorEnabled ); | ||
1507 | out->addColor ("OverrideColor", OverrideColor); | ||
1508 | // out->addFont("OverrideFont", OverrideFont); | ||
1509 | out->addInt ("MaxChars", Max); | ||
1510 | out->addBool ("WordWrap", WordWrap); | ||
1511 | out->addBool ("MultiLine", MultiLine); | ||
1512 | out->addBool ("AutoScroll", AutoScroll); | ||
1513 | out->addBool ("PasswordBox", PasswordBox); | ||
1514 | core::stringw ch = L" "; | ||
1515 | ch[0] = PasswordChar; | ||
1516 | out->addString("PasswordChar", ch.c_str()); | ||
1517 | out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); | ||
1518 | out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); | ||
1519 | |||
1520 | IGUIEditBox::serializeAttributes(out,options); | ||
1521 | } | ||
1522 | |||
1523 | |||
1524 | //! Reads attributes of the element | ||
1525 | void CGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) | ||
1526 | { | ||
1527 | IGUIEditBox::deserializeAttributes(in,options); | ||
1528 | |||
1529 | setDrawBorder( in->getAttributeAsBool("Border") ); | ||
1530 | setDrawBackground( in->getAttributeAsBool("Background") ); | ||
1531 | setOverrideColor(in->getAttributeAsColor("OverrideColor")); | ||
1532 | enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); | ||
1533 | setMax(in->getAttributeAsInt("MaxChars")); | ||
1534 | setWordWrap(in->getAttributeAsBool("WordWrap")); | ||
1535 | setMultiLine(in->getAttributeAsBool("MultiLine")); | ||
1536 | setAutoScroll(in->getAttributeAsBool("AutoScroll")); | ||
1537 | core::stringw ch = in->getAttributeAsStringW("PasswordChar"); | ||
1538 | |||
1539 | if (!ch.size()) | ||
1540 | setPasswordBox(in->getAttributeAsBool("PasswordBox")); | ||
1541 | else | ||
1542 | setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); | ||
1543 | |||
1544 | setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), | ||
1545 | (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); | ||
1546 | |||
1547 | // setOverrideFont(in->getAttributeAsFont("OverrideFont")); | ||
1548 | } | ||
1549 | |||
1550 | |||
1551 | } // end namespace gui | ||
1552 | } // end namespace irr | ||
1553 | |||
1554 | #endif // _IRR_COMPILE_WITH_GUI_ | ||
1555 | |||