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