1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
5 #include "CGUIEditBox.h"
8 #include "IGUIEnvironment.h"
10 #include "IVideoDriver.h"
18 ctrl+left/right to select word
19 double click/ctrl click: word select + drag to select whole words, triple click to select line
20 optional? dragging selected text
30 CGUIEditBox::CGUIEditBox(const wchar_t *text
, bool border
,
31 IGUIEnvironment
*environment
, IGUIElement
*parent
, s32 id
,
32 const core::rect
<s32
> &rectangle
) :
33 IGUIEditBox(environment
, parent
, id
, rectangle
),
34 OverwriteMode(false), MouseMarking(false),
35 Border(border
), Background(true), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0),
36 OverrideColor(video::SColor(101, 255, 255, 255)), OverrideFont(0), LastBreakFont(0),
37 Operator(0), BlinkStartTime(0), CursorBlinkTime(350), CursorChar(L
"_"), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0),
38 WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false),
39 PasswordChar(L
'*'), HAlign(EGUIA_UPPERLEFT
), VAlign(EGUIA_CENTER
),
40 CurrentTextRect(0, 0, 1, 1), FrameRect(rectangle
)
43 setDebugName("CGUIEditBox");
49 Operator
= Environment
->getOSOperator();
54 // this element can be tabbed to
65 CGUIEditBox::~CGUIEditBox()
74 //! Sets another skin independent font.
75 void CGUIEditBox::setOverrideFont(IGUIFont
*font
)
77 if (OverrideFont
== font
)
91 //! Gets the override font (if any)
92 IGUIFont
*CGUIEditBox::getOverrideFont() const
97 //! Get the font which is used right now for drawing
98 IGUIFont
*CGUIEditBox::getActiveFont() const
102 IGUISkin
*skin
= Environment
->getSkin();
104 return skin
->getFont();
108 //! Sets another color for the text.
109 void CGUIEditBox::setOverrideColor(video::SColor color
)
111 OverrideColor
= color
;
112 OverrideColorEnabled
= true;
115 video::SColor
CGUIEditBox::getOverrideColor() const
117 return OverrideColor
;
120 //! Turns the border on or off
121 void CGUIEditBox::setDrawBorder(bool border
)
126 //! Checks if border drawing is enabled
127 bool CGUIEditBox::isDrawBorderEnabled() const
132 //! Sets whether to draw the background
133 void CGUIEditBox::setDrawBackground(bool draw
)
138 //! Checks if background drawing is enabled
139 bool CGUIEditBox::isDrawBackgroundEnabled() const
144 //! Sets if the text should use the override color or the color in the gui skin.
145 void CGUIEditBox::enableOverrideColor(bool enable
)
147 OverrideColorEnabled
= enable
;
150 bool CGUIEditBox::isOverrideColorEnabled() const
152 return OverrideColorEnabled
;
155 //! Enables or disables word wrap
156 void CGUIEditBox::setWordWrap(bool enable
)
162 void CGUIEditBox::updateAbsolutePosition()
164 core::rect
<s32
> oldAbsoluteRect(AbsoluteRect
);
165 IGUIElement::updateAbsolutePosition();
166 if (oldAbsoluteRect
!= AbsoluteRect
) {
167 calculateFrameRect();
169 calculateScrollPos();
173 //! Checks if word wrap is enabled
174 bool CGUIEditBox::isWordWrapEnabled() const
179 //! Enables or disables newlines.
180 void CGUIEditBox::setMultiLine(bool enable
)
186 //! Checks if multi line editing is enabled
187 bool CGUIEditBox::isMultiLineEnabled() const
192 void CGUIEditBox::setPasswordBox(bool passwordBox
, wchar_t passwordChar
)
194 PasswordBox
= passwordBox
;
196 PasswordChar
= passwordChar
;
203 bool CGUIEditBox::isPasswordBox() const
208 //! Sets text justification
209 void CGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal
, EGUI_ALIGNMENT vertical
)
215 //! called if an event happened.
216 bool CGUIEditBox::OnEvent(const SEvent
&event
)
220 switch (event
.EventType
) {
222 if (event
.GUIEvent
.EventType
== EGET_ELEMENT_FOCUS_LOST
) {
223 if (event
.GUIEvent
.Caller
== this) {
224 MouseMarking
= false;
225 setTextMarkers(0, 0);
229 case EET_KEY_INPUT_EVENT
:
230 if (processKey(event
))
233 case EET_MOUSE_INPUT_EVENT
:
234 if (processMouse(event
))
237 case EET_STRING_INPUT_EVENT
:
238 inputString(*event
.StringInput
.Str
);
246 return IGUIElement::OnEvent(event
);
249 bool CGUIEditBox::processKey(const SEvent
&event
)
251 if (!event
.KeyInput
.PressedDown
)
254 bool textChanged
= false;
255 s32 newMarkBegin
= MarkBegin
;
256 s32 newMarkEnd
= MarkEnd
;
258 // control shortcut handling
260 if (event
.KeyInput
.Control
) {
261 // german backlash '\' entered with control + '?'
262 if (event
.KeyInput
.Char
== '\\') {
263 inputChar(event
.KeyInput
.Char
);
267 switch (event
.KeyInput
.Key
) {
271 newMarkEnd
= Text
.size();
275 if (!PasswordBox
&& Operator
&& MarkBegin
!= MarkEnd
) {
276 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
277 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
280 wStringToUTF8(s
, Text
.subString(realmbgn
, realmend
- realmbgn
));
281 Operator
->copyToClipboard(s
.c_str());
285 // cut to the clipboard
286 if (!PasswordBox
&& Operator
&& MarkBegin
!= MarkEnd
) {
287 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
288 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
292 wStringToUTF8(sc
, Text
.subString(realmbgn
, realmend
- realmbgn
));
293 Operator
->copyToClipboard(sc
.c_str());
298 s
= Text
.subString(0, realmbgn
);
299 s
.append(Text
.subString(realmend
, Text
.size() - realmend
));
302 CursorPos
= realmbgn
;
313 // paste from the clipboard
315 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
316 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
319 const c8
*p
= Operator
->getTextFromClipboard();
321 irr::core::stringw widep
;
322 core::utf8ToWString(widep
, p
);
324 if (MarkBegin
== MarkEnd
) {
326 core::stringw s
= Text
.subString(0, CursorPos
);
328 s
.append(Text
.subString(CursorPos
, Text
.size() - CursorPos
));
330 if (!Max
|| s
.size() <= Max
) { // thx to Fish FH for fix
333 CursorPos
+= s
.size();
338 core::stringw s
= Text
.subString(0, realmbgn
);
340 s
.append(Text
.subString(realmend
, Text
.size() - realmend
));
342 if (!Max
|| s
.size() <= Max
) { // thx to Fish FH for fix
345 CursorPos
= realmbgn
+ s
.size();
356 // move/highlight to start of text
357 if (event
.KeyInput
.Shift
) {
358 newMarkEnd
= CursorPos
;
368 // move/highlight to end of text
369 if (event
.KeyInput
.Shift
) {
370 newMarkBegin
= CursorPos
;
371 newMarkEnd
= Text
.size();
374 CursorPos
= Text
.size();
383 // 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
384 else if (event
.KeyInput
.Char
== 0) {
385 switch (event
.KeyInput
.Key
) {
388 if (WordWrap
|| MultiLine
) {
389 p
= getLineFromPos(CursorPos
);
390 p
= BrokenTextPositions
[p
] + (s32
)BrokenText
[p
].size();
391 if (p
> 0 && (Text
[p
- 1] == L
'\r' || Text
[p
- 1] == L
'\n'))
395 if (event
.KeyInput
.Shift
) {
396 if (MarkBegin
== MarkEnd
)
397 newMarkBegin
= CursorPos
;
405 BlinkStartTime
= os::Timer::getTime();
410 if (WordWrap
|| MultiLine
) {
411 p
= getLineFromPos(CursorPos
);
412 p
= BrokenTextPositions
[p
];
415 if (event
.KeyInput
.Shift
) {
416 if (MarkBegin
== MarkEnd
)
417 newMarkBegin
= CursorPos
;
424 BlinkStartTime
= os::Timer::getTime();
428 if (event
.KeyInput
.Shift
) {
430 if (MarkBegin
== MarkEnd
)
431 newMarkBegin
= CursorPos
;
433 newMarkEnd
= CursorPos
- 1;
442 BlinkStartTime
= os::Timer::getTime();
446 if (event
.KeyInput
.Shift
) {
447 if (Text
.size() > (u32
)CursorPos
) {
448 if (MarkBegin
== MarkEnd
)
449 newMarkBegin
= CursorPos
;
451 newMarkEnd
= CursorPos
+ 1;
458 if (Text
.size() > (u32
)CursorPos
)
460 BlinkStartTime
= os::Timer::getTime();
463 if (MultiLine
|| (WordWrap
&& BrokenText
.size() > 1)) {
464 s32 lineNo
= getLineFromPos(CursorPos
);
465 s32 mb
= (MarkBegin
== MarkEnd
) ? CursorPos
: (MarkBegin
> MarkEnd
? MarkBegin
: MarkEnd
);
467 s32 cp
= CursorPos
- BrokenTextPositions
[lineNo
];
468 if ((s32
)BrokenText
[lineNo
- 1].size() < cp
)
469 CursorPos
= BrokenTextPositions
[lineNo
- 1] + core::max_((u32
)1, BrokenText
[lineNo
- 1].size()) - 1;
471 CursorPos
= BrokenTextPositions
[lineNo
- 1] + cp
;
474 if (event
.KeyInput
.Shift
) {
476 newMarkEnd
= CursorPos
;
487 if (MultiLine
|| (WordWrap
&& BrokenText
.size() > 1)) {
488 s32 lineNo
= getLineFromPos(CursorPos
);
489 s32 mb
= (MarkBegin
== MarkEnd
) ? CursorPos
: (MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
);
490 if (lineNo
< (s32
)BrokenText
.size() - 1) {
491 s32 cp
= CursorPos
- BrokenTextPositions
[lineNo
];
492 if ((s32
)BrokenText
[lineNo
+ 1].size() < cp
)
493 CursorPos
= BrokenTextPositions
[lineNo
+ 1] + core::max_((u32
)1, BrokenText
[lineNo
+ 1].size()) - 1;
495 CursorPos
= BrokenTextPositions
[lineNo
+ 1] + cp
;
498 if (event
.KeyInput
.Shift
) {
500 newMarkEnd
= CursorPos
;
514 OverwriteMode
= !OverwriteMode
;
521 BlinkStartTime
= os::Timer::getTime();
531 // default keyboard handling
532 switch (event
.KeyInput
.Key
) {
537 calculateScrollPos();
538 sendGuiEvent(EGET_EDITBOX_ENTER
);
549 if (MarkBegin
!= MarkEnd
) {
550 // delete marked text
551 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
552 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
554 s
= Text
.subString(0, realmbgn
);
555 s
.append(Text
.subString(realmend
, Text
.size() - realmend
));
558 CursorPos
= realmbgn
;
560 // delete text behind cursor
562 s
= Text
.subString(0, CursorPos
- 1);
565 s
.append(Text
.subString(CursorPos
, Text
.size() - CursorPos
));
572 BlinkStartTime
= os::Timer::getTime();
581 // At least on X11 we get a char with 127 when the delete key is pressed.
582 // 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).
583 // We get a keykode != 127 when delete key on numlock is pressed with numlock on.
584 if (event
.KeyInput
.Char
== 127) {
589 BlinkStartTime
= os::Timer::getTime();
596 inputChar(event
.KeyInput
.Char
);
631 inputChar(event
.KeyInput
.Char
);
636 // Set new text markers
637 setTextMarkers(newMarkBegin
, newMarkEnd
);
639 // break the text if it has changed
642 calculateScrollPos();
643 sendGuiEvent(EGET_EDITBOX_CHANGED
);
645 calculateScrollPos();
651 bool CGUIEditBox::keyDelete()
653 if (Text
.size() != 0) {
656 if (MarkBegin
!= MarkEnd
) {
657 // delete marked text
658 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
659 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
661 s
= Text
.subString(0, realmbgn
);
662 s
.append(Text
.subString(realmend
, Text
.size() - realmend
));
665 CursorPos
= realmbgn
;
667 // delete text before cursor
668 s
= Text
.subString(0, CursorPos
);
669 s
.append(Text
.subString(CursorPos
+ 1, Text
.size() - CursorPos
- 1));
673 if (CursorPos
> (s32
)Text
.size())
674 CursorPos
= (s32
)Text
.size();
682 //! draws the element and its children
683 void CGUIEditBox::draw()
688 const bool focus
= Environment
->hasFocus(this);
690 IGUISkin
*skin
= Environment
->getSkin();
694 EGUI_DEFAULT_COLOR bgCol
= EGDC_GRAY_EDITABLE
;
696 bgCol
= focus
? EGDC_FOCUSED_EDITABLE
: EGDC_EDITABLE
;
698 if (!Border
&& Background
) {
699 skin
->draw2DRectangle(this, skin
->getColor(bgCol
), AbsoluteRect
, &AbsoluteClippingRect
);
704 skin
->draw3DSunkenPane(this, skin
->getColor(bgCol
), false, Background
, AbsoluteRect
, &AbsoluteClippingRect
);
706 calculateFrameRect();
709 core::rect
<s32
> localClipRect
= FrameRect
;
710 localClipRect
.clipAgainst(AbsoluteClippingRect
);
714 IGUIFont
*font
= getActiveFont();
717 s32 charcursorpos
= 0;
720 if (LastBreakFont
!= font
) {
724 // calculate cursor pos
726 core::stringw
*txtLine
= &Text
;
732 const bool ml
= (!PasswordBox
&& (WordWrap
|| MultiLine
));
733 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
734 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
735 const s32 hlineStart
= ml
? getLineFromPos(realmbgn
) : 0;
736 const s32 hlineCount
= ml
? getLineFromPos(realmend
) - hlineStart
+ 1 : 1;
737 const s32 lineCount
= ml
? BrokenText
.size() : 1;
739 // Save the override color information.
740 // Then, alter it if the edit box is disabled.
741 const bool prevOver
= OverrideColorEnabled
;
742 const video::SColor prevColor
= OverrideColor
;
745 if (!isEnabled() && !OverrideColorEnabled
) {
746 OverrideColorEnabled
= true;
747 OverrideColor
= skin
->getColor(EGDC_GRAY_TEXT
);
750 for (s32 i
= 0; i
< lineCount
; ++i
) {
753 // clipping test - don't draw anything outside the visible area
754 core::rect
<s32
> c
= localClipRect
;
755 c
.clipAgainst(CurrentTextRect
);
761 if (BrokenText
.size() != 1) {
763 BrokenText
.push_back(core::stringw());
765 if (BrokenText
[0].size() != Text
.size()) {
766 BrokenText
[0] = Text
;
767 for (u32 q
= 0; q
< Text
.size(); ++q
) {
768 BrokenText
[0][q
] = PasswordChar
;
771 txtLine
= &BrokenText
[0];
774 txtLine
= ml
? &BrokenText
[i
] : &Text
;
775 startPos
= ml
? BrokenTextPositions
[i
] : 0;
779 font
->draw(txtLine
->c_str(), CurrentTextRect
,
780 OverrideColorEnabled
? OverrideColor
: skin
->getColor(EGDC_BUTTON_TEXT
),
781 false, true, &localClipRect
);
783 // draw mark and marked text
784 if (focus
&& MarkBegin
!= MarkEnd
&& i
>= hlineStart
&& i
< hlineStart
+ hlineCount
) {
786 s32 mbegin
= 0, mend
= 0;
787 s32 lineStartPos
= 0, lineEndPos
= txtLine
->size();
789 if (i
== hlineStart
) {
790 // highlight start is on this line
791 s
= txtLine
->subString(0, realmbgn
- startPos
);
792 mbegin
= font
->getDimension(s
.c_str()).Width
;
795 mbegin
+= font
->getKerningWidth(
796 &((*txtLine
)[realmbgn
- startPos
]),
797 realmbgn
- startPos
> 0 ? &((*txtLine
)[realmbgn
- startPos
- 1]) : 0);
799 lineStartPos
= realmbgn
- startPos
;
801 if (i
== hlineStart
+ hlineCount
- 1) {
802 // highlight end is on this line
803 s2
= txtLine
->subString(0, realmend
- startPos
);
804 mend
= font
->getDimension(s2
.c_str()).Width
;
805 lineEndPos
= (s32
)s2
.size();
807 mend
= font
->getDimension(txtLine
->c_str()).Width
;
809 CurrentTextRect
.UpperLeftCorner
.X
+= mbegin
;
810 CurrentTextRect
.LowerRightCorner
.X
= CurrentTextRect
.UpperLeftCorner
.X
+ mend
- mbegin
;
813 skin
->draw2DRectangle(this, skin
->getColor(EGDC_HIGH_LIGHT
), CurrentTextRect
, &localClipRect
);
816 s
= txtLine
->subString(lineStartPos
, lineEndPos
- lineStartPos
);
819 font
->draw(s
.c_str(), CurrentTextRect
,
820 OverrideColorEnabled
? OverrideColor
: skin
->getColor(EGDC_HIGH_LIGHT_TEXT
),
821 false, true, &localClipRect
);
825 // Return the override color information to its previous settings.
826 OverrideColorEnabled
= prevOver
;
827 OverrideColor
= prevColor
;
832 if (WordWrap
|| MultiLine
) {
833 cursorLine
= getLineFromPos(CursorPos
);
834 txtLine
= &BrokenText
[cursorLine
];
835 startPos
= BrokenTextPositions
[cursorLine
];
837 s
= txtLine
->subString(0, CursorPos
- startPos
);
838 charcursorpos
= font
->getDimension(s
.c_str()).Width
+
839 font
->getKerningWidth(CursorChar
.c_str(), CursorPos
- startPos
> 0 ? &((*txtLine
)[CursorPos
- startPos
- 1]) : 0);
841 if (focus
&& (CursorBlinkTime
== 0 || (os::Timer::getTime() - BlinkStartTime
) % (2 * CursorBlinkTime
) < CursorBlinkTime
)) {
842 setTextRect(cursorLine
);
843 CurrentTextRect
.UpperLeftCorner
.X
+= charcursorpos
;
846 core::stringw character
= Text
.subString(CursorPos
, 1);
847 s32 mend
= font
->getDimension(character
.c_str()).Width
;
848 // Make sure the cursor box has at least some width to it
850 mend
= font
->getDimension(CursorChar
.c_str()).Width
;
851 CurrentTextRect
.LowerRightCorner
.X
= CurrentTextRect
.UpperLeftCorner
.X
+ mend
;
852 skin
->draw2DRectangle(this, skin
->getColor(EGDC_HIGH_LIGHT
), CurrentTextRect
, &localClipRect
);
853 font
->draw(character
.c_str(), CurrentTextRect
,
854 OverrideColorEnabled
? OverrideColor
: skin
->getColor(EGDC_HIGH_LIGHT_TEXT
),
855 false, true, &localClipRect
);
857 font
->draw(CursorChar
, CurrentTextRect
,
858 OverrideColorEnabled
? OverrideColor
: skin
->getColor(EGDC_BUTTON_TEXT
),
859 false, true, &localClipRect
);
869 //! Sets the new caption of this element.
870 void CGUIEditBox::setText(const wchar_t *text
)
873 if (u32(CursorPos
) > Text
.size())
874 CursorPos
= Text
.size();
879 //! Enables or disables automatic scrolling with cursor position
880 //! \param enable: If set to true, the text will move around with the cursor position
881 void CGUIEditBox::setAutoScroll(bool enable
)
886 //! Checks to see if automatic scrolling is enabled
887 //! \return true if automatic scrolling is enabled, false if not
888 bool CGUIEditBox::isAutoScrollEnabled() const
893 //! Gets the area of the text in the edit box
894 //! \return Returns the size in pixels of the text
895 core::dimension2du
CGUIEditBox::getTextDimension()
900 ret
= CurrentTextRect
;
902 for (u32 i
= 1; i
< BrokenText
.size(); ++i
) {
904 ret
.addInternalPoint(CurrentTextRect
.UpperLeftCorner
);
905 ret
.addInternalPoint(CurrentTextRect
.LowerRightCorner
);
908 return core::dimension2du(ret
.getSize());
911 //! Sets the maximum amount of characters which may be entered in the box.
912 //! \param max: Maximum amount of characters. If 0, the character amount is
914 void CGUIEditBox::setMax(u32 max
)
918 if (Text
.size() > Max
&& Max
!= 0)
919 Text
= Text
.subString(0, Max
);
922 //! Returns maximum amount of characters, previously set by setMax();
923 u32
CGUIEditBox::getMax() const
928 //! Set the character used for the cursor.
929 /** By default it's "_" */
930 void CGUIEditBox::setCursorChar(const wchar_t cursorChar
)
932 CursorChar
[0] = cursorChar
;
935 //! Get the character used for the cursor.
936 wchar_t CGUIEditBox::getCursorChar() const
938 return CursorChar
[0];
941 //! Set the blinktime for the cursor. 2x blinktime is one full cycle.
942 void CGUIEditBox::setCursorBlinkTime(irr::u32 timeMs
)
944 CursorBlinkTime
= timeMs
;
947 //! Get the cursor blinktime
948 irr::u32
CGUIEditBox::getCursorBlinkTime() const
950 return CursorBlinkTime
;
953 bool CGUIEditBox::processMouse(const SEvent
&event
)
955 switch (event
.MouseInput
.Event
) {
956 case irr::EMIE_LMOUSE_LEFT_UP
:
957 if (Environment
->hasFocus(this)) {
958 CursorPos
= getCursorPos(event
.MouseInput
.X
, event
.MouseInput
.Y
);
960 setTextMarkers(MarkBegin
, CursorPos
);
962 MouseMarking
= false;
963 calculateScrollPos();
967 case irr::EMIE_MOUSE_MOVED
: {
969 CursorPos
= getCursorPos(event
.MouseInput
.X
, event
.MouseInput
.Y
);
970 setTextMarkers(MarkBegin
, CursorPos
);
971 calculateScrollPos();
975 case EMIE_LMOUSE_PRESSED_DOWN
:
976 if (!Environment
->hasFocus(this)) { // can happen when events are manually send to the element
977 BlinkStartTime
= os::Timer::getTime();
979 CursorPos
= getCursorPos(event
.MouseInput
.X
, event
.MouseInput
.Y
);
980 setTextMarkers(CursorPos
, CursorPos
);
981 calculateScrollPos();
984 if (!AbsoluteClippingRect
.isPointInside(
985 core::position2d
<s32
>(event
.MouseInput
.X
, event
.MouseInput
.Y
))) {
989 CursorPos
= getCursorPos(event
.MouseInput
.X
, event
.MouseInput
.Y
);
991 s32 newMarkBegin
= MarkBegin
;
993 newMarkBegin
= CursorPos
;
996 setTextMarkers(newMarkBegin
, CursorPos
);
997 calculateScrollPos();
1001 case EMIE_MMOUSE_PRESSED_DOWN
: {
1002 if (!AbsoluteClippingRect
.isPointInside(core::position2d
<s32
>(
1003 event
.MouseInput
.X
, event
.MouseInput
.Y
)))
1006 if (!Environment
->hasFocus(this)) {
1007 BlinkStartTime
= os::Timer::getTime();
1010 // move cursor and disable marking
1011 CursorPos
= getCursorPos(event
.MouseInput
.X
, event
.MouseInput
.Y
);
1012 MouseMarking
= false;
1013 setTextMarkers(CursorPos
, CursorPos
);
1015 // paste from the primary selection
1017 irr::core::stringw inserted_text
;
1019 return inserted_text
;
1020 const c8
*inserted_text_utf8
= Operator
->getTextFromPrimarySelection();
1021 if (!inserted_text_utf8
)
1022 return inserted_text
;
1023 core::utf8ToWString(inserted_text
, inserted_text_utf8
);
1024 return inserted_text
;
1036 s32
CGUIEditBox::getCursorPos(s32 x
, s32 y
)
1038 IGUIFont
*font
= getActiveFont();
1040 const u32 lineCount
= (WordWrap
|| MultiLine
) ? BrokenText
.size() : 1;
1042 core::stringw
*txtLine
= 0;
1046 for (u32 i
= 0; i
< lineCount
; ++i
) {
1048 if (i
== 0 && y
< CurrentTextRect
.UpperLeftCorner
.Y
)
1049 y
= CurrentTextRect
.UpperLeftCorner
.Y
;
1050 if (i
== lineCount
- 1 && y
> CurrentTextRect
.LowerRightCorner
.Y
)
1051 y
= CurrentTextRect
.LowerRightCorner
.Y
;
1053 // is it inside this region?
1054 if (y
>= CurrentTextRect
.UpperLeftCorner
.Y
&& y
<= CurrentTextRect
.LowerRightCorner
.Y
) {
1055 // we've found the clicked line
1056 txtLine
= (WordWrap
|| MultiLine
) ? &BrokenText
[i
] : &Text
;
1057 startPos
= (WordWrap
|| MultiLine
) ? BrokenTextPositions
[i
] : 0;
1062 if (x
< CurrentTextRect
.UpperLeftCorner
.X
)
1063 x
= CurrentTextRect
.UpperLeftCorner
.X
;
1068 s32 idx
= font
->getCharacterFromPos(txtLine
->c_str(), x
- CurrentTextRect
.UpperLeftCorner
.X
);
1070 // click was on or left of the line
1072 return idx
+ startPos
;
1074 // click was off the right edge of the line, go to end.
1075 return txtLine
->size() + startPos
;
1078 //! Breaks the single text line.
1079 void CGUIEditBox::breakText()
1081 if ((!WordWrap
&& !MultiLine
))
1084 BrokenText
.clear(); // need to reallocate :/
1085 BrokenTextPositions
.set_used(0);
1087 IGUIFont
*font
= getActiveFont();
1091 LastBreakFont
= font
;
1095 core::stringw whitespace
;
1096 s32 lastLineStart
= 0;
1097 s32 size
= Text
.size();
1099 s32 elWidth
= RelativeRect
.getWidth() - 6;
1102 for (s32 i
= 0; i
< size
; ++i
) {
1104 bool lineBreak
= false;
1106 if (c
== L
'\r') { // Mac or Windows breaks
1109 if (Text
[i
+ 1] == L
'\n') { // Windows breaks
1110 // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
1111 // Instead rework the cursor positioning to be able to handle this (but not in stable release
1112 // branch as users might already expect this behavior).
1118 } else if (c
== L
'\n') { // Unix breaks
1123 // don't break if we're not a multi-line edit box
1127 if (c
== L
' ' || c
== 0 || i
== (size
- 1)) {
1128 // here comes the next whitespace, look if
1129 // we can break the last word to the next line
1130 // We also break whitespace, otherwise cursor would vanish beside the right border.
1131 s32 whitelgth
= font
->getDimension(whitespace
.c_str()).Width
;
1132 s32 worldlgth
= font
->getDimension(word
.c_str()).Width
;
1134 if (WordWrap
&& length
+ worldlgth
+ whitelgth
> elWidth
&& line
.size() > 0) {
1135 // break to next line
1137 BrokenText
.push_back(line
);
1138 BrokenTextPositions
.push_back(lastLineStart
);
1139 lastLineStart
= i
- (s32
)word
.size();
1145 length
+= whitelgth
+ worldlgth
;
1154 // compute line break
1158 BrokenText
.push_back(line
);
1159 BrokenTextPositions
.push_back(lastLineStart
);
1160 lastLineStart
= i
+ 1;
1167 // yippee this is a word..
1174 BrokenText
.push_back(line
);
1175 BrokenTextPositions
.push_back(lastLineStart
);
1178 // TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
1179 // but HAlign according to line-width (pixels) and not by row.
1180 // Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
1181 // But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
1182 void CGUIEditBox::setTextRect(s32 line
)
1187 IGUIFont
*font
= getActiveFont();
1191 core::dimension2du d
;
1193 // get text dimension
1194 const u32 lineCount
= (WordWrap
|| MultiLine
) ? BrokenText
.size() : 1;
1195 if (WordWrap
|| MultiLine
) {
1196 d
= font
->getDimension(BrokenText
[line
].c_str());
1198 d
= font
->getDimension(Text
.c_str());
1199 d
.Height
= AbsoluteRect
.getHeight();
1201 d
.Height
+= font
->getKerningHeight();
1206 // align to h centre
1207 CurrentTextRect
.UpperLeftCorner
.X
= (FrameRect
.getWidth() / 2) - (d
.Width
/ 2);
1208 CurrentTextRect
.LowerRightCorner
.X
= (FrameRect
.getWidth() / 2) + (d
.Width
/ 2);
1210 case EGUIA_LOWERRIGHT
:
1211 // align to right edge
1212 CurrentTextRect
.UpperLeftCorner
.X
= FrameRect
.getWidth() - d
.Width
;
1213 CurrentTextRect
.LowerRightCorner
.X
= FrameRect
.getWidth();
1216 // align to left edge
1217 CurrentTextRect
.UpperLeftCorner
.X
= 0;
1218 CurrentTextRect
.LowerRightCorner
.X
= d
.Width
;
1223 // align to v centre
1224 CurrentTextRect
.UpperLeftCorner
.Y
=
1225 (FrameRect
.getHeight() / 2) - (lineCount
* d
.Height
) / 2 + d
.Height
* line
;
1227 case EGUIA_LOWERRIGHT
:
1228 // align to bottom edge
1229 CurrentTextRect
.UpperLeftCorner
.Y
=
1230 FrameRect
.getHeight() - lineCount
* d
.Height
+ d
.Height
* line
;
1233 // align to top edge
1234 CurrentTextRect
.UpperLeftCorner
.Y
= d
.Height
* line
;
1238 CurrentTextRect
.UpperLeftCorner
.X
-= HScrollPos
;
1239 CurrentTextRect
.LowerRightCorner
.X
-= HScrollPos
;
1240 CurrentTextRect
.UpperLeftCorner
.Y
-= VScrollPos
;
1241 CurrentTextRect
.LowerRightCorner
.Y
= CurrentTextRect
.UpperLeftCorner
.Y
+ d
.Height
;
1243 CurrentTextRect
+= FrameRect
.UpperLeftCorner
;
1246 s32
CGUIEditBox::getLineFromPos(s32 pos
)
1248 if (!WordWrap
&& !MultiLine
)
1252 while (i
< (s32
)BrokenTextPositions
.size()) {
1253 if (BrokenTextPositions
[i
] > pos
)
1257 return (s32
)BrokenTextPositions
.size() - 1;
1260 void CGUIEditBox::inputChar(wchar_t c
)
1264 core::stringw
s(&c
, 1);
1268 void CGUIEditBox::inputString(const core::stringw
&str
)
1274 u32 len
= str
.size();
1276 if (MarkBegin
!= MarkEnd
) {
1277 // replace marked text
1278 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
1279 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
1281 s
= Text
.subString(0, realmbgn
);
1283 s
.append(Text
.subString(realmend
, Text
.size() - realmend
));
1285 CursorPos
= realmbgn
+ len
;
1286 } else if (OverwriteMode
) {
1287 // check to see if we are at the end of the text
1288 if ((u32
)CursorPos
+ len
< Text
.size()) {
1291 for (u32 i
= CursorPos
; i
< CursorPos
+ len
&& i
< Max
; i
++) {
1292 if (Text
[i
] == L
'\n' || Text
[i
] == L
'\r') {
1298 if (!isEOL
|| Text
.size() + len
<= Max
|| Max
== 0) {
1299 s
= Text
.subString(0, CursorPos
);
1302 // just keep appending to the current line
1303 // This follows the behavior of other gui libraries behaviors
1304 s
.append(Text
.subString(EOLPos
, Text
.size() - EOLPos
));
1306 // replace the next character
1307 s
.append(Text
.subString(CursorPos
+ len
, Text
.size() - CursorPos
- len
));
1312 } else if (Text
.size() + len
<= Max
|| Max
== 0) {
1313 // add new character because we are at the end of the string
1314 s
= Text
.subString(0, CursorPos
);
1316 s
.append(Text
.subString(CursorPos
+ len
, Text
.size() - CursorPos
- len
));
1320 } else if (Text
.size() + len
<= Max
|| Max
== 0) {
1321 // add new character
1322 s
= Text
.subString(0, CursorPos
);
1324 s
.append(Text
.subString(CursorPos
, Text
.size() - CursorPos
));
1329 BlinkStartTime
= os::Timer::getTime();
1330 setTextMarkers(0, 0);
1333 calculateScrollPos();
1334 sendGuiEvent(EGET_EDITBOX_CHANGED
);
1337 // calculate autoscroll
1338 void CGUIEditBox::calculateScrollPos()
1343 IGUIFont
*font
= getActiveFont();
1347 s32 cursLine
= getLineFromPos(CursorPos
);
1350 setTextRect(cursLine
);
1351 const bool hasBrokenText
= MultiLine
|| WordWrap
;
1353 // Check horizonal scrolling
1354 // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
1356 // get cursor position
1358 irr::u32 cursorWidth
= font
->getDimension(CursorChar
.c_str()).Width
;
1359 core::stringw
*txtLine
= hasBrokenText
? &BrokenText
[cursLine
] : &Text
;
1360 s32 cPos
= hasBrokenText
? CursorPos
- BrokenTextPositions
[cursLine
] : CursorPos
; // column
1361 s32 cStart
= font
->getDimension(txtLine
->subString(0, cPos
).c_str()).Width
; // pixels from text-start
1362 s32 cEnd
= cStart
+ cursorWidth
;
1363 s32 txtWidth
= font
->getDimension(txtLine
->c_str()).Width
;
1365 if (txtWidth
< FrameRect
.getWidth()) {
1366 // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
1367 // This check just fixes the case where it was most noticable (text smaller than clipping area).
1370 setTextRect(cursLine
);
1373 if (CurrentTextRect
.UpperLeftCorner
.X
+ cStart
< FrameRect
.UpperLeftCorner
.X
) {
1374 // cursor to the left of the clipping area
1375 HScrollPos
-= FrameRect
.UpperLeftCorner
.X
- (CurrentTextRect
.UpperLeftCorner
.X
+ cStart
);
1376 setTextRect(cursLine
);
1378 // TODO: should show more characters to the left when we're scrolling left
1379 // and the cursor reaches the border.
1380 } else if (CurrentTextRect
.UpperLeftCorner
.X
+ cEnd
> FrameRect
.LowerRightCorner
.X
) {
1381 // cursor to the right of the clipping area
1382 HScrollPos
+= (CurrentTextRect
.UpperLeftCorner
.X
+ cEnd
) - FrameRect
.LowerRightCorner
.X
;
1383 setTextRect(cursLine
);
1387 // calculate vertical scrolling
1388 if (hasBrokenText
) {
1389 irr::u32 lineHeight
= font
->getDimension(L
"A").Height
+ font
->getKerningHeight();
1390 // only up to 1 line fits?
1391 if (lineHeight
>= (irr::u32
)FrameRect
.getHeight()) {
1393 setTextRect(cursLine
);
1394 s32 unscrolledPos
= CurrentTextRect
.UpperLeftCorner
.Y
;
1395 s32 pivot
= FrameRect
.UpperLeftCorner
.Y
;
1398 pivot
+= FrameRect
.getHeight() / 2;
1399 unscrolledPos
+= lineHeight
/ 2;
1401 case EGUIA_LOWERRIGHT
:
1402 pivot
+= FrameRect
.getHeight();
1403 unscrolledPos
+= lineHeight
;
1408 VScrollPos
= unscrolledPos
- pivot
;
1409 setTextRect(cursLine
);
1411 // First 2 checks are necessary when people delete lines
1413 if (CurrentTextRect
.UpperLeftCorner
.Y
> FrameRect
.UpperLeftCorner
.Y
&& VAlign
!= EGUIA_LOWERRIGHT
) {
1414 // first line is leaving a gap on top
1416 } else if (VAlign
!= EGUIA_UPPERLEFT
) {
1417 u32 lastLine
= BrokenTextPositions
.empty() ? 0 : BrokenTextPositions
.size() - 1;
1418 setTextRect(lastLine
);
1419 if (CurrentTextRect
.LowerRightCorner
.Y
< FrameRect
.LowerRightCorner
.Y
) {
1420 // last line is leaving a gap on bottom
1421 VScrollPos
-= FrameRect
.LowerRightCorner
.Y
- CurrentTextRect
.LowerRightCorner
.Y
;
1425 setTextRect(cursLine
);
1426 if (CurrentTextRect
.UpperLeftCorner
.Y
< FrameRect
.UpperLeftCorner
.Y
) {
1427 // text above valid area
1428 VScrollPos
-= FrameRect
.UpperLeftCorner
.Y
- CurrentTextRect
.UpperLeftCorner
.Y
;
1429 setTextRect(cursLine
);
1430 } else if (CurrentTextRect
.LowerRightCorner
.Y
> FrameRect
.LowerRightCorner
.Y
) {
1431 // text below valid area
1432 VScrollPos
+= CurrentTextRect
.LowerRightCorner
.Y
- FrameRect
.LowerRightCorner
.Y
;
1433 setTextRect(cursLine
);
1439 void CGUIEditBox::calculateFrameRect()
1441 FrameRect
= AbsoluteRect
;
1444 skin
= Environment
->getSkin();
1445 if (Border
&& skin
) {
1446 FrameRect
.UpperLeftCorner
.X
+= skin
->getSize(EGDS_TEXT_DISTANCE_X
) + 1;
1447 FrameRect
.UpperLeftCorner
.Y
+= skin
->getSize(EGDS_TEXT_DISTANCE_Y
) + 1;
1448 FrameRect
.LowerRightCorner
.X
-= skin
->getSize(EGDS_TEXT_DISTANCE_X
) + 1;
1449 FrameRect
.LowerRightCorner
.Y
-= skin
->getSize(EGDS_TEXT_DISTANCE_Y
) + 1;
1453 //! set text markers
1454 void CGUIEditBox::setTextMarkers(s32 begin
, s32 end
)
1456 if (begin
!= MarkBegin
|| end
!= MarkEnd
) {
1460 if (!PasswordBox
&& Operator
&& MarkBegin
!= MarkEnd
) {
1461 // copy to primary selection
1462 const s32 realmbgn
= MarkBegin
< MarkEnd
? MarkBegin
: MarkEnd
;
1463 const s32 realmend
= MarkBegin
< MarkEnd
? MarkEnd
: MarkBegin
;
1466 wStringToUTF8(s
, Text
.subString(realmbgn
, realmend
- realmbgn
));
1467 Operator
->copyToPrimarySelection(s
.c_str());
1470 sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED
);
1474 //! send some gui event to parent
1475 void CGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type
)
1479 e
.EventType
= EET_GUI_EVENT
;
1480 e
.GUIEvent
.Caller
= this;
1481 e
.GUIEvent
.Element
= 0;
1482 e
.GUIEvent
.EventType
= type
;
1488 //! Returns whether the element takes input from the IME
1489 bool CGUIEditBox::acceptsIME()
1494 } // end namespace gui
1495 } // end namespace irr