]> git.lizzy.rs Git - dragonfireclient.git/blob - src/gui/guiEditBox.cpp
Make edit boxes respond to string input (IME) (#11156)
[dragonfireclient.git] / src / gui / guiEditBox.cpp
1 /*
2 Minetest
3 Copyright (C) 2021 Minetest
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "guiEditBox.h"
21
22 #include "IrrCompileConfig.h"
23 #include "IGUISkin.h"
24 #include "IGUIEnvironment.h"
25 #include "IGUIFont.h"
26
27 #include "porting.h"
28
29 GUIEditBox::~GUIEditBox()
30 {
31         if (m_override_font)
32                 m_override_font->drop();
33
34         if (m_operator)
35                 m_operator->drop();
36
37         if (m_vscrollbar)
38                 m_vscrollbar->drop();
39 }
40
41 void GUIEditBox::setOverrideFont(IGUIFont *font)
42 {
43         if (m_override_font == font)
44                 return;
45
46         if (m_override_font)
47                 m_override_font->drop();
48
49         m_override_font = font;
50
51         if (m_override_font)
52                 m_override_font->grab();
53
54         breakText();
55 }
56
57 //! Get the font which is used right now for drawing
58 IGUIFont *GUIEditBox::getActiveFont() const
59 {
60         if (m_override_font)
61                 return m_override_font;
62         IGUISkin *skin = Environment->getSkin();
63         if (skin)
64                 return skin->getFont();
65         return 0;
66 }
67
68 //! Sets another color for the text.
69 void GUIEditBox::setOverrideColor(video::SColor color)
70 {
71         m_override_color = color;
72         m_override_color_enabled = true;
73 }
74
75 video::SColor GUIEditBox::getOverrideColor() const
76 {
77         return m_override_color;
78 }
79
80 //! Sets if the text should use the overide color or the color in the gui skin.
81 void GUIEditBox::enableOverrideColor(bool enable)
82 {
83         m_override_color_enabled = enable;
84 }
85
86 //! Enables or disables word wrap
87 void GUIEditBox::setWordWrap(bool enable)
88 {
89         m_word_wrap = enable;
90         breakText();
91 }
92
93 //! Enables or disables newlines.
94 void GUIEditBox::setMultiLine(bool enable)
95 {
96         m_multiline = enable;
97 }
98
99 //! Enables or disables automatic scrolling with cursor position
100 //! \param enable: If set to true, the text will move around with the cursor position
101 void GUIEditBox::setAutoScroll(bool enable)
102 {
103         m_autoscroll = enable;
104 }
105
106 void GUIEditBox::setPasswordBox(bool password_box, wchar_t password_char)
107 {
108         m_passwordbox = password_box;
109         if (m_passwordbox) {
110                 m_passwordchar = password_char;
111                 setMultiLine(false);
112                 setWordWrap(false);
113                 m_broken_text.clear();
114         }
115 }
116
117 //! Sets text justification
118 void GUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
119 {
120         m_halign = horizontal;
121         m_valign = vertical;
122 }
123
124 //! Sets the new caption of this element.
125 void GUIEditBox::setText(const wchar_t *text)
126 {
127         Text = text;
128         if (u32(m_cursor_pos) > Text.size())
129                 m_cursor_pos = Text.size();
130         m_hscroll_pos = 0;
131         breakText();
132 }
133
134 //! Sets the maximum amount of characters which may be entered in the box.
135 //! \param max: Maximum amount of characters. If 0, the character amount is
136 //! infinity.
137 void GUIEditBox::setMax(u32 max)
138 {
139         m_max = max;
140
141         if (Text.size() > m_max && m_max != 0)
142                 Text = Text.subString(0, m_max);
143 }
144
145 //! Gets the area of the text in the edit box
146 //! \return Returns the size in pixels of the text
147 core::dimension2du GUIEditBox::getTextDimension()
148 {
149         core::rect<s32> ret;
150
151         setTextRect(0);
152         ret = m_current_text_rect;
153
154         for (u32 i = 1; i < m_broken_text.size(); ++i) {
155                 setTextRect(i);
156                 ret.addInternalPoint(m_current_text_rect.UpperLeftCorner);
157                 ret.addInternalPoint(m_current_text_rect.LowerRightCorner);
158         }
159
160         return core::dimension2du(ret.getSize());
161 }
162
163 //! Turns the border on or off
164 void GUIEditBox::setDrawBorder(bool border)
165 {
166         m_border = border;
167 }
168
169 void GUIEditBox::setWritable(bool can_write_text)
170 {
171         m_writable = can_write_text;
172 }
173
174 //! set text markers
175 void GUIEditBox::setTextMarkers(s32 begin, s32 end)
176 {
177         if (begin != m_mark_begin || end != m_mark_end) {
178                 m_mark_begin = begin;
179                 m_mark_end = end;
180                 sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
181         }
182 }
183
184 //! send some gui event to parent
185 void GUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
186 {
187         if (Parent) {
188                 SEvent e;
189                 e.EventType = EET_GUI_EVENT;
190                 e.GUIEvent.Caller = this;
191                 e.GUIEvent.Element = 0;
192                 e.GUIEvent.EventType = type;
193
194                 Parent->OnEvent(e);
195         }
196 }
197
198 //! called if an event happened.
199 bool GUIEditBox::OnEvent(const SEvent &event)
200 {
201         if (isEnabled()) {
202
203                 switch (event.EventType) {
204                 case EET_GUI_EVENT:
205                         if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) {
206                                 if (event.GUIEvent.Caller == this) {
207                                         m_mouse_marking = false;
208                                         setTextMarkers(0, 0);
209                                 }
210                         }
211                         break;
212                 case EET_KEY_INPUT_EVENT:
213                         if (processKey(event))
214                                 return true;
215                         break;
216                 case EET_MOUSE_INPUT_EVENT:
217                         if (processMouse(event))
218                                 return true;
219                         break;
220 #if (IRRLICHT_VERSION_MT_REVISION >= 2)
221                 case EET_STRING_INPUT_EVENT:
222                         inputString(*event.StringInput.Str);
223                         return true;
224 #endif
225                 default:
226                         break;
227                 }
228         }
229
230         return IGUIElement::OnEvent(event);
231 }
232
233 bool GUIEditBox::processKey(const SEvent &event)
234 {
235         if (!m_writable) {
236                 return false;
237         }
238
239         if (!event.KeyInput.PressedDown)
240                 return false;
241
242         bool text_changed = false;
243         s32 new_mark_begin = m_mark_begin;
244         s32 new_mark_end = m_mark_end;
245
246         // control shortcut handling
247         if (event.KeyInput.Control) {
248
249                 // german backlash '\' entered with control + '?'
250                 if (event.KeyInput.Char == '\\') {
251                         inputChar(event.KeyInput.Char);
252                         return true;
253                 }
254
255                 switch (event.KeyInput.Key) {
256                 case KEY_KEY_A:
257                         // select all
258                         new_mark_begin = 0;
259                         new_mark_end = Text.size();
260                         break;
261                 case KEY_KEY_C:
262                         onKeyControlC(event);
263                         break;
264                 case KEY_KEY_X:
265                         text_changed = onKeyControlX(event, new_mark_begin, new_mark_end);
266                         break;
267                 case KEY_KEY_V:
268                         text_changed = onKeyControlV(event, new_mark_begin, new_mark_end);
269                         break;
270                 case KEY_HOME:
271                         // move/highlight to start of text
272                         if (event.KeyInput.Shift) {
273                                 new_mark_end = m_cursor_pos;
274                                 new_mark_begin = 0;
275                                 m_cursor_pos = 0;
276                         } else {
277                                 m_cursor_pos = 0;
278                                 new_mark_begin = 0;
279                                 new_mark_end = 0;
280                         }
281                         break;
282                 case KEY_END:
283                         // move/highlight to end of text
284                         if (event.KeyInput.Shift) {
285                                 new_mark_begin = m_cursor_pos;
286                                 new_mark_end = Text.size();
287                                 m_cursor_pos = 0;
288                         } else {
289                                 m_cursor_pos = Text.size();
290                                 new_mark_begin = 0;
291                                 new_mark_end = 0;
292                         }
293                         break;
294                 default:
295                         return false;
296                 }
297         } else {
298                 switch (event.KeyInput.Key) {
299                 case KEY_END: {
300                         s32 p = Text.size();
301                         if (m_word_wrap || m_multiline) {
302                                 p = getLineFromPos(m_cursor_pos);
303                                 p = m_broken_text_positions[p] +
304                                     (s32)m_broken_text[p].size();
305                                 if (p > 0 && (Text[p - 1] == L'\r' ||
306                                                              Text[p - 1] == L'\n'))
307                                         p -= 1;
308                         }
309
310                         if (event.KeyInput.Shift) {
311                                 if (m_mark_begin == m_mark_end)
312                                         new_mark_begin = m_cursor_pos;
313
314                                 new_mark_end = p;
315                         } else {
316                                 new_mark_begin = 0;
317                                 new_mark_end = 0;
318                         }
319                         m_cursor_pos = p;
320                         m_blink_start_time = porting::getTimeMs();
321                 } break;
322                 case KEY_HOME: {
323
324                         s32 p = 0;
325                         if (m_word_wrap || m_multiline) {
326                                 p = getLineFromPos(m_cursor_pos);
327                                 p = m_broken_text_positions[p];
328                         }
329
330                         if (event.KeyInput.Shift) {
331                                 if (m_mark_begin == m_mark_end)
332                                         new_mark_begin = m_cursor_pos;
333                                 new_mark_end = p;
334                         } else {
335                                 new_mark_begin = 0;
336                                 new_mark_end = 0;
337                         }
338                         m_cursor_pos = p;
339                         m_blink_start_time = porting::getTimeMs();
340                 } break;
341                 case KEY_RETURN:
342                         if (m_multiline) {
343                                 inputChar(L'\n');
344                         } else {
345                                 calculateScrollPos();
346                                 sendGuiEvent(EGET_EDITBOX_ENTER);
347                         }
348                         return true;
349                 case KEY_LEFT:
350                         if (event.KeyInput.Shift) {
351                                 if (m_cursor_pos > 0) {
352                                         if (m_mark_begin == m_mark_end)
353                                                 new_mark_begin = m_cursor_pos;
354
355                                         new_mark_end = m_cursor_pos - 1;
356                                 }
357                         } else {
358                                 new_mark_begin = 0;
359                                 new_mark_end = 0;
360                         }
361
362                         if (m_cursor_pos > 0)
363                                 m_cursor_pos--;
364                         m_blink_start_time = porting::getTimeMs();
365                         break;
366                 case KEY_RIGHT:
367                         if (event.KeyInput.Shift) {
368                                 if (Text.size() > (u32)m_cursor_pos) {
369                                         if (m_mark_begin == m_mark_end)
370                                                 new_mark_begin = m_cursor_pos;
371
372                                         new_mark_end = m_cursor_pos + 1;
373                                 }
374                         } else {
375                                 new_mark_begin = 0;
376                                 new_mark_end = 0;
377                         }
378
379                         if (Text.size() > (u32)m_cursor_pos)
380                                 m_cursor_pos++;
381                         m_blink_start_time = porting::getTimeMs();
382                         break;
383                 case KEY_UP:
384                         if (!onKeyUp(event, new_mark_begin, new_mark_end)) {
385                                 return false;
386                         }
387                         break;
388                 case KEY_DOWN:
389                         if (!onKeyDown(event, new_mark_begin, new_mark_end)) {
390                                 return false;
391                         }
392                         break;
393                 case KEY_BACK:
394                         text_changed = onKeyBack(event, new_mark_begin, new_mark_end);
395                         break;
396
397                 case KEY_DELETE:
398                         text_changed = onKeyDelete(event, new_mark_begin, new_mark_end);
399                         break;
400
401                 case KEY_ESCAPE:
402                 case KEY_TAB:
403                 case KEY_SHIFT:
404                 case KEY_F1:
405                 case KEY_F2:
406                 case KEY_F3:
407                 case KEY_F4:
408                 case KEY_F5:
409                 case KEY_F6:
410                 case KEY_F7:
411                 case KEY_F8:
412                 case KEY_F9:
413                 case KEY_F10:
414                 case KEY_F11:
415                 case KEY_F12:
416                 case KEY_F13:
417                 case KEY_F14:
418                 case KEY_F15:
419                 case KEY_F16:
420                 case KEY_F17:
421                 case KEY_F18:
422                 case KEY_F19:
423                 case KEY_F20:
424                 case KEY_F21:
425                 case KEY_F22:
426                 case KEY_F23:
427                 case KEY_F24:
428                         // ignore these keys
429                         return false;
430
431                 default:
432                         inputChar(event.KeyInput.Char);
433                         return true;
434                 }
435         }
436
437         // Set new text markers
438         setTextMarkers(new_mark_begin, new_mark_end);
439
440         // break the text if it has changed
441         if (text_changed) {
442                 breakText();
443                 sendGuiEvent(EGET_EDITBOX_CHANGED);
444         }
445
446         calculateScrollPos();
447
448         return true;
449 }
450
451 bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end)
452 {
453         // clang-format off
454         if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
455                 s32 lineNo = getLineFromPos(m_cursor_pos);
456                 s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos :
457                         (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end);
458                 if (lineNo > 0) {
459                         s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
460                         if ((s32)m_broken_text[lineNo - 1].size() < cp) {
461                                 m_cursor_pos = m_broken_text_positions[lineNo - 1] +
462                                         core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1;
463                         }
464                         else
465                                 m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp;
466                 }
467
468                 if (event.KeyInput.Shift) {
469                         mark_begin = mb;
470                         mark_end = m_cursor_pos;
471                 } else {
472                         mark_begin = 0;
473                         mark_end = 0;
474                 }
475
476                 return true;
477         }
478
479         // clang-format on
480         return false;
481 }
482
483 bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end)
484 {
485         // clang-format off
486         if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
487                 s32 lineNo = getLineFromPos(m_cursor_pos);
488                 s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos :
489                         (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end);
490                 if (lineNo < (s32)m_broken_text.size() - 1) {
491                         s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
492                         if ((s32)m_broken_text[lineNo + 1].size() < cp) {
493                                 m_cursor_pos = m_broken_text_positions[lineNo + 1] +
494                                         core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1;
495                         }
496                         else
497                                 m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp;
498                 }
499
500                 if (event.KeyInput.Shift) {
501                         mark_begin = mb;
502                         mark_end = m_cursor_pos;
503                 } else {
504                         mark_begin = 0;
505                         mark_end = 0;
506                 }
507
508                 return true;
509         }
510
511         // clang-format on
512         return false;
513 }
514
515 void GUIEditBox::onKeyControlC(const SEvent &event)
516 {
517         // copy to clipboard
518         if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end)
519                 return;
520
521         const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
522         const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
523
524         core::stringc s;
525         s = Text.subString(realmbgn, realmend - realmbgn).c_str();
526         m_operator->copyToClipboard(s.c_str());
527 }
528
529 bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_end)
530 {
531         // First copy to clipboard
532         onKeyControlC(event);
533
534         if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end)
535                 return false;
536
537         const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
538         const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
539
540         // Now remove from box if enabled
541         if (isEnabled()) {
542                 // delete
543                 core::stringw s;
544                 s = Text.subString(0, realmbgn);
545                 s.append(Text.subString(realmend, Text.size() - realmend));
546                 Text = s;
547
548                 m_cursor_pos = realmbgn;
549                 mark_begin = 0;
550                 mark_end = 0;
551                 return true;
552         }
553
554         return false;
555 }
556
557 bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end)
558 {
559         if (!isEnabled())
560                 return false;
561
562         // paste from the clipboard
563         if (!m_operator)
564                 return false;
565
566         const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
567         const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
568
569         // add new character
570         if (const c8 *p = m_operator->getTextFromClipboard()) {
571                 if (m_mark_begin == m_mark_end) {
572                         // insert text
573                         core::stringw s = Text.subString(0, m_cursor_pos);
574                         s.append(p);
575                         s.append(Text.subString(
576                                         m_cursor_pos, Text.size() - m_cursor_pos));
577
578                         if (!m_max || s.size() <= m_max) {
579                                 Text = s;
580                                 s = p;
581                                 m_cursor_pos += s.size();
582                         }
583                 } else {
584                         // replace text
585
586                         core::stringw s = Text.subString(0, realmbgn);
587                         s.append(p);
588                         s.append(Text.subString(realmend, Text.size() - realmend));
589
590                         if (!m_max || s.size() <= m_max) {
591                                 Text = s;
592                                 s = p;
593                                 m_cursor_pos = realmbgn + s.size();
594                         }
595                 }
596         }
597
598         mark_begin = 0;
599         mark_end = 0;
600         return true;
601 }
602
603 bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end)
604 {
605         if (!isEnabled() || Text.empty())
606                 return false;
607
608         core::stringw s;
609
610         if (m_mark_begin != m_mark_end) {
611                 // delete marked text
612                 const s32 realmbgn =
613                                 m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
614                 const s32 realmend =
615                                 m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
616
617                 s = Text.subString(0, realmbgn);
618                 s.append(Text.subString(realmend, Text.size() - realmend));
619                 Text = s;
620
621                 m_cursor_pos = realmbgn;
622         } else {
623                 // delete text behind cursor
624                 if (m_cursor_pos > 0)
625                         s = Text.subString(0, m_cursor_pos - 1);
626                 else
627                         s = L"";
628                 s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
629                 Text = s;
630                 --m_cursor_pos;
631         }
632
633         if (m_cursor_pos < 0)
634                 m_cursor_pos = 0;
635         m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
636         mark_begin = 0;
637         mark_end = 0;
638         return true;
639 }
640
641 bool GUIEditBox::onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end)
642 {
643         if (!isEnabled() || Text.empty())
644                 return false;
645
646         core::stringw s;
647
648         if (m_mark_begin != m_mark_end) {
649                 // delete marked text
650                 const s32 realmbgn =
651                                 m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
652                 const s32 realmend =
653                                 m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
654
655                 s = Text.subString(0, realmbgn);
656                 s.append(Text.subString(realmend, Text.size() - realmend));
657                 Text = s;
658
659                 m_cursor_pos = realmbgn;
660         } else {
661                 // delete text before cursor
662                 s = Text.subString(0, m_cursor_pos);
663                 s.append(Text.subString(
664                                 m_cursor_pos + 1, Text.size() - m_cursor_pos - 1));
665                 Text = s;
666         }
667
668         if (m_cursor_pos > (s32)Text.size())
669                 m_cursor_pos = (s32)Text.size();
670
671         m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
672         mark_begin = 0;
673         mark_end = 0;
674         return true;
675 }
676
677 void GUIEditBox::inputChar(wchar_t c)
678 {
679         if (c == 0)
680                 return;
681         core::stringw s(&c, 1);
682         inputString(s);
683 }
684
685 void GUIEditBox::inputString(const core::stringw &str)
686 {
687         if (!isEnabled() || !m_writable)
688                 return;
689
690         u32 len = str.size();
691         if (Text.size()+len <= m_max || m_max == 0) {
692                 core::stringw s;
693                 if (m_mark_begin != m_mark_end) {
694                         // replace marked text
695                         s32 real_begin = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
696                         s32 real_end = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
697
698                         s = Text.subString(0, real_begin);
699                         s.append(str);
700                         s.append(Text.subString(real_end, Text.size() - real_end));
701                         Text = s;
702                         m_cursor_pos = real_begin + len;
703                 } else {
704                         // append string
705                         s = Text.subString(0, m_cursor_pos);
706                         s.append(str);
707                         s.append(Text.subString(m_cursor_pos,
708                                         Text.size() - m_cursor_pos));
709                         Text = s;
710                         m_cursor_pos += len;
711                 }
712
713                 m_blink_start_time = porting::getTimeMs();
714                 setTextMarkers(0, 0);
715         }
716
717         breakText();
718         sendGuiEvent(EGET_EDITBOX_CHANGED);
719         calculateScrollPos();
720 }
721
722 bool GUIEditBox::processMouse(const SEvent &event)
723 {
724         switch (event.MouseInput.Event) {
725         case irr::EMIE_LMOUSE_LEFT_UP:
726                 if (Environment->hasFocus(this)) {
727                         m_cursor_pos = getCursorPos(
728                                         event.MouseInput.X, event.MouseInput.Y);
729                         if (m_mouse_marking) {
730                                 setTextMarkers(m_mark_begin, m_cursor_pos);
731                         }
732                         m_mouse_marking = false;
733                         calculateScrollPos();
734                         return true;
735                 }
736                 break;
737         case irr::EMIE_MOUSE_MOVED: {
738                 if (m_mouse_marking) {
739                         m_cursor_pos = getCursorPos(
740                                         event.MouseInput.X, event.MouseInput.Y);
741                         setTextMarkers(m_mark_begin, m_cursor_pos);
742                         calculateScrollPos();
743                         return true;
744                 }
745         } break;
746         case EMIE_LMOUSE_PRESSED_DOWN:
747
748                 if (!Environment->hasFocus(this)) {
749                         m_blink_start_time = porting::getTimeMs();
750                         m_mouse_marking = true;
751                         m_cursor_pos = getCursorPos(
752                                         event.MouseInput.X, event.MouseInput.Y);
753                         setTextMarkers(m_cursor_pos, m_cursor_pos);
754                         calculateScrollPos();
755                         return true;
756                 } else {
757                         if (!AbsoluteClippingRect.isPointInside(core::position2d<s32>(
758                                             event.MouseInput.X, event.MouseInput.Y))) {
759                                 return false;
760                         } else {
761                                 // move cursor
762                                 m_cursor_pos = getCursorPos(
763                                                 event.MouseInput.X, event.MouseInput.Y);
764
765                                 s32 newMarkBegin = m_mark_begin;
766                                 if (!m_mouse_marking)
767                                         newMarkBegin = m_cursor_pos;
768
769                                 m_mouse_marking = true;
770                                 setTextMarkers(newMarkBegin, m_cursor_pos);
771                                 calculateScrollPos();
772                                 return true;
773                         }
774                 }
775         case EMIE_MOUSE_WHEEL:
776                 if (m_vscrollbar && m_vscrollbar->isVisible()) {
777                         s32 pos = m_vscrollbar->getPos();
778                         s32 step = m_vscrollbar->getSmallStep();
779                         m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
780                         return true;
781                 }
782                 break;
783         default:
784                 break;
785         }
786
787         return false;
788 }
789
790 s32 GUIEditBox::getLineFromPos(s32 pos)
791 {
792         if (!m_word_wrap && !m_multiline)
793                 return 0;
794
795         s32 i = 0;
796         while (i < (s32)m_broken_text_positions.size()) {
797                 if (m_broken_text_positions[i] > pos)
798                         return i - 1;
799                 ++i;
800         }
801         return (s32)m_broken_text_positions.size() - 1;
802 }
803
804 void GUIEditBox::updateVScrollBar()
805 {
806         if (!m_vscrollbar) {
807                 return;
808         }
809
810         // OnScrollBarChanged(...)
811         if (m_vscrollbar->getPos() != m_vscroll_pos) {
812                 s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos;
813                 m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY;
814                 m_current_text_rect.LowerRightCorner.Y -= deltaScrollY;
815
816                 s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
817                 if (scrollymax != m_vscrollbar->getMax()) {
818                         // manage a newline or a deleted line
819                         m_vscrollbar->setMax(scrollymax);
820                         m_vscrollbar->setPageSize(s32(getTextDimension().Height));
821                         calculateScrollPos();
822                 } else {
823                         // manage a newline or a deleted line
824                         m_vscroll_pos = m_vscrollbar->getPos();
825                 }
826         }
827
828         // check if a vertical scrollbar is needed ?
829         if (getTextDimension().Height > (u32)m_frame_rect.getHeight()) {
830                 m_frame_rect.LowerRightCorner.X -= m_scrollbar_width;
831
832                 s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
833                 if (scrollymax != m_vscrollbar->getMax()) {
834                         m_vscrollbar->setMax(scrollymax);
835                         m_vscrollbar->setPageSize(s32(getTextDimension().Height));
836                 }
837
838                 if (!m_vscrollbar->isVisible()) {
839                         m_vscrollbar->setVisible(true);
840                 }
841         } else {
842                 if (m_vscrollbar->isVisible()) {
843                         m_vscrollbar->setVisible(false);
844                         m_vscroll_pos = 0;
845                         m_vscrollbar->setPos(0);
846                         m_vscrollbar->setMax(1);
847                         m_vscrollbar->setPageSize(s32(getTextDimension().Height));
848                 }
849         }
850 }
851
852 void GUIEditBox::deserializeAttributes(
853                 io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0)
854 {
855         IGUIEditBox::deserializeAttributes(in, options);
856
857         setOverrideColor(in->getAttributeAsColor("OverrideColor"));
858         enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
859         setMax(in->getAttributeAsInt("MaxChars"));
860         setWordWrap(in->getAttributeAsBool("WordWrap"));
861         setMultiLine(in->getAttributeAsBool("MultiLine"));
862         setAutoScroll(in->getAttributeAsBool("AutoScroll"));
863         core::stringw ch = in->getAttributeAsStringW("PasswordChar");
864
865         if (ch.empty())
866                 setPasswordBox(in->getAttributeAsBool("PasswordBox"));
867         else
868                 setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
869
870         setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration(
871                                          "HTextAlign", GUIAlignmentNames),
872                         (EGUI_ALIGNMENT)in->getAttributeAsEnumeration(
873                                         "VTextAlign", GUIAlignmentNames));
874
875         setWritable(in->getAttributeAsBool("Writable"));
876         // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
877 }
878
879 //! Writes attributes of the element.
880 void GUIEditBox::serializeAttributes(
881                 io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const
882 {
883         // IGUIEditBox::serializeAttributes(out,options);
884
885         out->addBool("OverrideColorEnabled", m_override_color_enabled);
886         out->addColor("OverrideColor", m_override_color);
887         // out->addFont("OverrideFont",m_override_font);
888         out->addInt("MaxChars", m_max);
889         out->addBool("WordWrap", m_word_wrap);
890         out->addBool("MultiLine", m_multiline);
891         out->addBool("AutoScroll", m_autoscroll);
892         out->addBool("PasswordBox", m_passwordbox);
893         core::stringw ch = L" ";
894         ch[0] = m_passwordchar;
895         out->addString("PasswordChar", ch.c_str());
896         out->addEnum("HTextAlign", m_halign, GUIAlignmentNames);
897         out->addEnum("VTextAlign", m_valign, GUIAlignmentNames);
898         out->addBool("Writable", m_writable);
899
900         IGUIEditBox::serializeAttributes(out, options);
901 }