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