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
5 #include "CGUIEditBox.h"
\r
6 #ifdef _IRR_COMPILE_WITH_GUI_
\r
8 #include "IGUISkin.h"
\r
9 #include "IGUIEnvironment.h"
\r
10 #include "IGUIFont.h"
\r
11 #include "IVideoDriver.h"
\r
14 #include "Keycodes.h"
\r
19 ctrl+left/right to select word
\r
20 double click/ctrl click: word select + drag to select whole words, triple click to select line
\r
21 optional? dragging selected text
\r
31 CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border,
\r
32 IGUIEnvironment* environment, IGUIElement* parent, s32 id,
\r
33 const core::rect<s32>& rectangle)
\r
34 : IGUIEditBox(environment, parent, id, rectangle), OverwriteMode(false), MouseMarking(false),
\r
35 Border(border), Background(true), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0),
\r
36 OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0),
\r
37 Operator(0), BlinkStartTime(0), CursorBlinkTime(350), CursorChar(L"_"), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0),
\r
38 WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false),
\r
39 PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER),
\r
40 CurrentTextRect(0,0,1,1), FrameRect(rectangle)
\r
43 setDebugName("CGUIEditBox");
\r
49 Operator = Environment->getOSOperator();
\r
54 // this element can be tabbed to
\r
58 calculateFrameRect();
\r
61 calculateScrollPos();
\r
66 CGUIEditBox::~CGUIEditBox()
\r
69 OverrideFont->drop();
\r
76 //! Sets another skin independent font.
\r
77 void CGUIEditBox::setOverrideFont(IGUIFont* font)
\r
79 if (OverrideFont == font)
\r
83 OverrideFont->drop();
\r
85 OverrideFont = font;
\r
88 OverrideFont->grab();
\r
93 //! Gets the override font (if any)
\r
94 IGUIFont * CGUIEditBox::getOverrideFont() const
\r
96 return OverrideFont;
\r
99 //! Get the font which is used right now for drawing
\r
100 IGUIFont* CGUIEditBox::getActiveFont() const
\r
102 if ( OverrideFont )
\r
103 return OverrideFont;
\r
104 IGUISkin* skin = Environment->getSkin();
\r
106 return skin->getFont();
\r
110 //! Sets another color for the text.
\r
111 void CGUIEditBox::setOverrideColor(video::SColor color)
\r
113 OverrideColor = color;
\r
114 OverrideColorEnabled = true;
\r
118 video::SColor CGUIEditBox::getOverrideColor() const
\r
120 return OverrideColor;
\r
124 //! Turns the border on or off
\r
125 void CGUIEditBox::setDrawBorder(bool border)
\r
130 //! Checks if border drawing is enabled
\r
131 bool CGUIEditBox::isDrawBorderEnabled() const
\r
136 //! Sets whether to draw the background
\r
137 void CGUIEditBox::setDrawBackground(bool draw)
\r
142 //! Checks if background drawing is enabled
\r
143 bool CGUIEditBox::isDrawBackgroundEnabled() const
\r
148 //! Sets if the text should use the override color or the color in the gui skin.
\r
149 void CGUIEditBox::enableOverrideColor(bool enable)
\r
151 OverrideColorEnabled = enable;
\r
154 bool CGUIEditBox::isOverrideColorEnabled() const
\r
156 return OverrideColorEnabled;
\r
159 //! Enables or disables word wrap
\r
160 void CGUIEditBox::setWordWrap(bool enable)
\r
167 void CGUIEditBox::updateAbsolutePosition()
\r
169 core::rect<s32> oldAbsoluteRect(AbsoluteRect);
\r
170 IGUIElement::updateAbsolutePosition();
\r
171 if ( oldAbsoluteRect != AbsoluteRect )
\r
173 calculateFrameRect();
\r
175 calculateScrollPos();
\r
180 //! Checks if word wrap is enabled
\r
181 bool CGUIEditBox::isWordWrapEnabled() const
\r
187 //! Enables or disables newlines.
\r
188 void CGUIEditBox::setMultiLine(bool enable)
\r
190 MultiLine = enable;
\r
195 //! Checks if multi line editing is enabled
\r
196 bool CGUIEditBox::isMultiLineEnabled() const
\r
202 void CGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar)
\r
204 PasswordBox = passwordBox;
\r
207 PasswordChar = passwordChar;
\r
208 setMultiLine(false);
\r
209 setWordWrap(false);
\r
210 BrokenText.clear();
\r
215 bool CGUIEditBox::isPasswordBox() const
\r
217 return PasswordBox;
\r
221 //! Sets text justification
\r
222 void CGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
\r
224 HAlign = horizontal;
\r
229 //! called if an event happened.
\r
230 bool CGUIEditBox::OnEvent(const SEvent& event)
\r
235 switch(event.EventType)
\r
237 case EET_GUI_EVENT:
\r
238 if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
\r
240 if (event.GUIEvent.Caller == this)
\r
242 MouseMarking = false;
\r
243 setTextMarkers(0,0);
\r
247 case EET_KEY_INPUT_EVENT:
\r
248 if (processKey(event))
\r
251 case EET_MOUSE_INPUT_EVENT:
\r
252 if (processMouse(event))
\r
260 return IGUIElement::OnEvent(event);
\r
264 bool CGUIEditBox::processKey(const SEvent& event)
\r
266 if (!event.KeyInput.PressedDown)
\r
269 bool textChanged = false;
\r
270 s32 newMarkBegin = MarkBegin;
\r
271 s32 newMarkEnd = MarkEnd;
\r
273 // control shortcut handling
\r
275 if (event.KeyInput.Control)
\r
277 // german backlash '\' entered with control + '?'
\r
278 if ( event.KeyInput.Char == '\\' )
\r
280 inputChar(event.KeyInput.Char);
\r
284 switch(event.KeyInput.Key)
\r
289 newMarkEnd = Text.size();
\r
292 // copy to clipboard
\r
293 if (!PasswordBox && Operator && MarkBegin != MarkEnd)
\r
295 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
296 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
299 s = Text.subString(realmbgn, realmend - realmbgn).c_str();
\r
300 Operator->copyToClipboard(s.c_str());
\r
304 // cut to the clipboard
\r
305 if (!PasswordBox && Operator && MarkBegin != MarkEnd)
\r
307 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
308 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
312 sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
\r
313 Operator->copyToClipboard(sc.c_str());
\r
319 s = Text.subString(0, realmbgn);
\r
320 s.append( Text.subString(realmend, Text.size()-realmend) );
\r
323 CursorPos = realmbgn;
\r
326 textChanged = true;
\r
331 if ( !isEnabled() )
\r
334 // paste from the clipboard
\r
337 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
338 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
340 // add new character
\r
341 const c8* p = Operator->getTextFromClipboard();
\r
344 irr::core::stringw widep;
\r
345 core::multibyteToWString(widep, p);
\r
347 if (MarkBegin == MarkEnd)
\r
350 core::stringw s = Text.subString(0, CursorPos);
\r
352 s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
\r
354 if (!Max || s.size()<=Max) // thx to Fish FH for fix
\r
358 CursorPos += s.size();
\r
365 core::stringw s = Text.subString(0, realmbgn);
\r
367 s.append( Text.subString(realmend, Text.size()-realmend) );
\r
369 if (!Max || s.size()<=Max) // thx to Fish FH for fix
\r
373 CursorPos = realmbgn + s.size();
\r
380 textChanged = true;
\r
384 // move/highlight to start of text
\r
385 if (event.KeyInput.Shift)
\r
387 newMarkEnd = CursorPos;
\r
399 // move/highlight to end of text
\r
400 if (event.KeyInput.Shift)
\r
402 newMarkBegin = CursorPos;
\r
403 newMarkEnd = Text.size();
\r
408 CursorPos = Text.size();
\r
417 // 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
418 else if (event.KeyInput.Char == 0)
\r
420 switch(event.KeyInput.Key)
\r
424 s32 p = Text.size();
\r
425 if (WordWrap || MultiLine)
\r
427 p = getLineFromPos(CursorPos);
\r
428 p = BrokenTextPositions[p] + (s32)BrokenText[p].size();
\r
429 if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' ))
\r
433 if (event.KeyInput.Shift)
\r
435 if (MarkBegin == MarkEnd)
\r
436 newMarkBegin = CursorPos;
\r
446 BlinkStartTime = os::Timer::getTime();
\r
453 if (WordWrap || MultiLine)
\r
455 p = getLineFromPos(CursorPos);
\r
456 p = BrokenTextPositions[p];
\r
459 if (event.KeyInput.Shift)
\r
461 if (MarkBegin == MarkEnd)
\r
462 newMarkBegin = CursorPos;
\r
471 BlinkStartTime = os::Timer::getTime();
\r
476 if (event.KeyInput.Shift)
\r
480 if (MarkBegin == MarkEnd)
\r
481 newMarkBegin = CursorPos;
\r
483 newMarkEnd = CursorPos-1;
\r
492 if (CursorPos > 0) CursorPos--;
\r
493 BlinkStartTime = os::Timer::getTime();
\r
497 if (event.KeyInput.Shift)
\r
499 if (Text.size() > (u32)CursorPos)
\r
501 if (MarkBegin == MarkEnd)
\r
502 newMarkBegin = CursorPos;
\r
504 newMarkEnd = CursorPos+1;
\r
513 if (Text.size() > (u32)CursorPos) CursorPos++;
\r
514 BlinkStartTime = os::Timer::getTime();
\r
517 if (MultiLine || (WordWrap && BrokenText.size() > 1) )
\r
519 s32 lineNo = getLineFromPos(CursorPos);
\r
520 s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd);
\r
523 s32 cp = CursorPos - BrokenTextPositions[lineNo];
\r
524 if ((s32)BrokenText[lineNo-1].size() < cp)
\r
525 CursorPos = BrokenTextPositions[lineNo-1] + core::max_((u32)1, BrokenText[lineNo-1].size())-1;
\r
527 CursorPos = BrokenTextPositions[lineNo-1] + cp;
\r
530 if (event.KeyInput.Shift)
\r
533 newMarkEnd = CursorPos;
\r
548 if (MultiLine || (WordWrap && BrokenText.size() > 1) )
\r
550 s32 lineNo = getLineFromPos(CursorPos);
\r
551 s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd);
\r
552 if (lineNo < (s32)BrokenText.size()-1)
\r
554 s32 cp = CursorPos - BrokenTextPositions[lineNo];
\r
555 if ((s32)BrokenText[lineNo+1].size() < cp)
\r
556 CursorPos = BrokenTextPositions[lineNo+1] + core::max_((u32)1, BrokenText[lineNo+1].size())-1;
\r
558 CursorPos = BrokenTextPositions[lineNo+1] + cp;
\r
561 if (event.KeyInput.Shift)
\r
564 newMarkEnd = CursorPos;
\r
579 if ( !isEnabled() )
\r
582 OverwriteMode = !OverwriteMode;
\r
585 if ( !isEnabled() )
\r
590 BlinkStartTime = os::Timer::getTime();
\r
593 textChanged = true;
\r
602 // default keyboard handling
\r
603 switch(event.KeyInput.Key)
\r
612 calculateScrollPos();
\r
613 sendGuiEvent( EGET_EDITBOX_ENTER );
\r
618 if ( !isEnabled() )
\r
625 if (MarkBegin != MarkEnd)
\r
627 // delete marked text
\r
628 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
629 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
631 s = Text.subString(0, realmbgn);
\r
632 s.append( Text.subString(realmend, Text.size()-realmend) );
\r
635 CursorPos = realmbgn;
\r
639 // delete text behind cursor
\r
641 s = Text.subString(0, CursorPos-1);
\r
644 s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
\r
651 BlinkStartTime = os::Timer::getTime();
\r
654 textChanged = true;
\r
660 // At least on X11 we get a char with 127 when the delete key is pressed.
\r
661 // 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
662 // We get a keykode != 127 when delete key on numlock is pressed with numlock on.
\r
663 if (event.KeyInput.Char == 127)
\r
665 if ( !isEnabled() )
\r
670 BlinkStartTime = os::Timer::getTime();
\r
673 textChanged = true;
\r
679 inputChar(event.KeyInput.Char);
\r
710 // ignore these keys
\r
714 inputChar(event.KeyInput.Char);
\r
719 // Set new text markers
\r
720 setTextMarkers( newMarkBegin, newMarkEnd );
\r
722 // break the text if it has changed
\r
726 calculateScrollPos();
\r
727 sendGuiEvent(EGET_EDITBOX_CHANGED);
\r
731 calculateScrollPos();
\r
737 bool CGUIEditBox::keyDelete()
\r
739 if (Text.size() != 0)
\r
743 if (MarkBegin != MarkEnd)
\r
745 // delete marked text
\r
746 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
747 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
749 s = Text.subString(0, realmbgn);
\r
750 s.append( Text.subString(realmend, Text.size()-realmend) );
\r
753 CursorPos = realmbgn;
\r
757 // delete text before cursor
\r
758 s = Text.subString(0, CursorPos);
\r
759 s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
\r
763 if (CursorPos > (s32)Text.size())
\r
764 CursorPos = (s32)Text.size();
\r
772 //! draws the element and its children
\r
773 void CGUIEditBox::draw()
\r
778 const bool focus = Environment->hasFocus(this);
\r
780 IGUISkin* skin = Environment->getSkin();
\r
784 EGUI_DEFAULT_COLOR bgCol = EGDC_GRAY_EDITABLE;
\r
786 bgCol = focus ? EGDC_FOCUSED_EDITABLE : EGDC_EDITABLE;
\r
788 if (!Border && Background)
\r
790 skin->draw2DRectangle(this, skin->getColor(bgCol), AbsoluteRect, &AbsoluteClippingRect);
\r
796 skin->draw3DSunkenPane(this, skin->getColor(bgCol), false, Background, AbsoluteRect, &AbsoluteClippingRect);
\r
798 calculateFrameRect();
\r
801 core::rect<s32> localClipRect = FrameRect;
\r
802 localClipRect.clipAgainst(AbsoluteClippingRect);
\r
806 IGUIFont* font = getActiveFont();
\r
808 s32 cursorLine = 0;
\r
809 s32 charcursorpos = 0;
\r
813 if (LastBreakFont != font)
\r
818 // calculate cursor pos
\r
820 core::stringw *txtLine = &Text;
\r
823 core::stringw s, s2;
\r
825 // get mark position
\r
826 const bool ml = (!PasswordBox && (WordWrap || MultiLine));
\r
827 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
828 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
829 const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0;
\r
830 const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1;
\r
831 const s32 lineCount = ml ? BrokenText.size() : 1;
\r
833 // Save the override color information.
\r
834 // Then, alter it if the edit box is disabled.
\r
835 const bool prevOver = OverrideColorEnabled;
\r
836 const video::SColor prevColor = OverrideColor;
\r
840 if (!isEnabled() && !OverrideColorEnabled)
\r
842 OverrideColorEnabled = true;
\r
843 OverrideColor = skin->getColor(EGDC_GRAY_TEXT);
\r
846 for (s32 i=0; i < lineCount; ++i)
\r
850 // clipping test - don't draw anything outside the visible area
\r
851 core::rect<s32> c = localClipRect;
\r
852 c.clipAgainst(CurrentTextRect);
\r
856 // get current line
\r
859 if (BrokenText.size() != 1)
\r
861 BrokenText.clear();
\r
862 BrokenText.push_back(core::stringw());
\r
864 if (BrokenText[0].size() != Text.size())
\r
866 BrokenText[0] = Text;
\r
867 for (u32 q = 0; q < Text.size(); ++q)
\r
869 BrokenText[0] [q] = PasswordChar;
\r
872 txtLine = &BrokenText[0];
\r
877 txtLine = ml ? &BrokenText[i] : &Text;
\r
878 startPos = ml ? BrokenTextPositions[i] : 0;
\r
882 // draw normal text
\r
883 font->draw(txtLine->c_str(), CurrentTextRect,
\r
884 OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
\r
885 false, true, &localClipRect);
\r
887 // draw mark and marked text
\r
888 if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
\r
891 s32 mbegin = 0, mend = 0;
\r
892 s32 lineStartPos = 0, lineEndPos = txtLine->size();
\r
894 if (i == hlineStart)
\r
896 // highlight start is on this line
\r
897 s = txtLine->subString(0, realmbgn - startPos);
\r
898 mbegin = font->getDimension(s.c_str()).Width;
\r
900 // deal with kerning
\r
901 mbegin += font->getKerningWidth(
\r
902 &((*txtLine)[realmbgn - startPos]),
\r
903 realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0);
\r
905 lineStartPos = realmbgn - startPos;
\r
907 if (i == hlineStart + hlineCount - 1)
\r
909 // highlight end is on this line
\r
910 s2 = txtLine->subString(0, realmend - startPos);
\r
911 mend = font->getDimension(s2.c_str()).Width;
\r
912 lineEndPos = (s32)s2.size();
\r
915 mend = font->getDimension(txtLine->c_str()).Width;
\r
917 CurrentTextRect.UpperLeftCorner.X += mbegin;
\r
918 CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin;
\r
921 skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);
\r
923 // draw marked text
\r
924 s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos);
\r
927 font->draw(s.c_str(), CurrentTextRect,
\r
928 OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
\r
929 false, true, &localClipRect);
\r
934 // Return the override color information to its previous settings.
\r
935 OverrideColorEnabled = prevOver;
\r
936 OverrideColor = prevColor;
\r
942 if (WordWrap || MultiLine)
\r
944 cursorLine = getLineFromPos(CursorPos);
\r
945 txtLine = &BrokenText[cursorLine];
\r
946 startPos = BrokenTextPositions[cursorLine];
\r
948 s = txtLine->subString(0,CursorPos-startPos);
\r
949 charcursorpos = font->getDimension(s.c_str()).Width +
\r
950 font->getKerningWidth(CursorChar.c_str(), CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0);
\r
952 if (focus && (CursorBlinkTime == 0 || (os::Timer::getTime() - BlinkStartTime) % (2*CursorBlinkTime) < CursorBlinkTime))
\r
954 setTextRect(cursorLine);
\r
955 CurrentTextRect.UpperLeftCorner.X += charcursorpos;
\r
957 if ( OverwriteMode )
\r
959 core::stringw character = Text.subString(CursorPos,1);
\r
960 s32 mend = font->getDimension(character.c_str()).Width;
\r
961 //Make sure the cursor box has at least some width to it
\r
963 mend = font->getDimension(CursorChar.c_str()).Width;
\r
964 CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend;
\r
965 skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);
\r
966 font->draw(character.c_str(), CurrentTextRect,
\r
967 OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
\r
968 false, true, &localClipRect);
\r
972 font->draw(CursorChar, CurrentTextRect,
\r
973 OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
\r
974 false, true, &localClipRect);
\r
981 IGUIElement::draw();
\r
985 //! Sets the new caption of this element.
\r
986 void CGUIEditBox::setText(const wchar_t* text)
\r
989 if (u32(CursorPos) > Text.size())
\r
990 CursorPos = Text.size();
\r
996 //! Enables or disables automatic scrolling with cursor position
\r
997 //! \param enable: If set to true, the text will move around with the cursor position
\r
998 void CGUIEditBox::setAutoScroll(bool enable)
\r
1000 AutoScroll = enable;
\r
1004 //! Checks to see if automatic scrolling is enabled
\r
1005 //! \return true if automatic scrolling is enabled, false if not
\r
1006 bool CGUIEditBox::isAutoScrollEnabled() const
\r
1008 return AutoScroll;
\r
1012 //! Gets the area of the text in the edit box
\r
1013 //! \return Returns the size in pixels of the text
\r
1014 core::dimension2du CGUIEditBox::getTextDimension()
\r
1016 core::rect<s32> ret;
\r
1019 ret = CurrentTextRect;
\r
1021 for (u32 i=1; i < BrokenText.size(); ++i)
\r
1024 ret.addInternalPoint(CurrentTextRect.UpperLeftCorner);
\r
1025 ret.addInternalPoint(CurrentTextRect.LowerRightCorner);
\r
1028 return core::dimension2du(ret.getSize());
\r
1032 //! Sets the maximum amount of characters which may be entered in the box.
\r
1033 //! \param max: Maximum amount of characters. If 0, the character amount is
\r
1035 void CGUIEditBox::setMax(u32 max)
\r
1039 if (Text.size() > Max && Max != 0)
\r
1040 Text = Text.subString(0, Max);
\r
1044 //! Returns maximum amount of characters, previously set by setMax();
\r
1045 u32 CGUIEditBox::getMax() const
\r
1050 //! Set the character used for the cursor.
\r
1051 /** By default it's "_" */
\r
1052 void CGUIEditBox::setCursorChar(const wchar_t cursorChar)
\r
1054 CursorChar[0] = cursorChar;
\r
1057 //! Get the character used for the cursor.
\r
1058 wchar_t CGUIEditBox::getCursorChar() const
\r
1060 return CursorChar[0];
\r
1063 //! Set the blinktime for the cursor. 2x blinktime is one full cycle.
\r
1064 void CGUIEditBox::setCursorBlinkTime(irr::u32 timeMs)
\r
1066 CursorBlinkTime = timeMs;
\r
1069 //! Get the cursor blinktime
\r
1070 irr::u32 CGUIEditBox::getCursorBlinkTime() const
\r
1072 return CursorBlinkTime;
\r
1075 bool CGUIEditBox::processMouse(const SEvent& event)
\r
1077 switch(event.MouseInput.Event)
\r
1079 case irr::EMIE_LMOUSE_LEFT_UP:
\r
1080 if (Environment->hasFocus(this))
\r
1082 CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
\r
1085 setTextMarkers( MarkBegin, CursorPos );
\r
1087 MouseMarking = false;
\r
1088 calculateScrollPos();
\r
1092 case irr::EMIE_MOUSE_MOVED:
\r
1096 CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
\r
1097 setTextMarkers( MarkBegin, CursorPos );
\r
1098 calculateScrollPos();
\r
1103 case EMIE_LMOUSE_PRESSED_DOWN:
\r
1104 if (!Environment->hasFocus(this)) // can happen when events are manually send to the element
\r
1106 BlinkStartTime = os::Timer::getTime();
\r
1107 MouseMarking = true;
\r
1108 CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
\r
1109 setTextMarkers(CursorPos, CursorPos );
\r
1110 calculateScrollPos();
\r
1115 if (!AbsoluteClippingRect.isPointInside(
\r
1116 core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)))
\r
1123 CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
\r
1125 s32 newMarkBegin = MarkBegin;
\r
1126 if (!MouseMarking)
\r
1127 newMarkBegin = CursorPos;
\r
1129 MouseMarking = true;
\r
1130 setTextMarkers( newMarkBegin, CursorPos);
\r
1131 calculateScrollPos();
\r
1143 s32 CGUIEditBox::getCursorPos(s32 x, s32 y)
\r
1145 IGUIFont* font = getActiveFont();
\r
1147 const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
\r
1149 core::stringw *txtLine=0;
\r
1153 for (u32 i=0; i < lineCount; ++i)
\r
1156 if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y)
\r
1157 y = CurrentTextRect.UpperLeftCorner.Y;
\r
1158 if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y )
\r
1159 y = CurrentTextRect.LowerRightCorner.Y;
\r
1161 // is it inside this region?
\r
1162 if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y)
\r
1164 // we've found the clicked line
\r
1165 txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text;
\r
1166 startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0;
\r
1171 if (x < CurrentTextRect.UpperLeftCorner.X)
\r
1172 x = CurrentTextRect.UpperLeftCorner.X;
\r
1177 s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X);
\r
1179 // click was on or left of the line
\r
1181 return idx + startPos;
\r
1183 // click was off the right edge of the line, go to end.
\r
1184 return txtLine->size() + startPos;
\r
1188 //! Breaks the single text line.
\r
1189 void CGUIEditBox::breakText()
\r
1191 if ((!WordWrap && !MultiLine))
\r
1194 BrokenText.clear(); // need to reallocate :/
\r
1195 BrokenTextPositions.set_used(0);
\r
1197 IGUIFont* font = getActiveFont();
\r
1201 LastBreakFont = font;
\r
1203 core::stringw line;
\r
1204 core::stringw word;
\r
1205 core::stringw whitespace;
\r
1206 s32 lastLineStart = 0;
\r
1207 s32 size = Text.size();
\r
1209 s32 elWidth = RelativeRect.getWidth() - 6;
\r
1212 for (s32 i=0; i<size; ++i)
\r
1215 bool lineBreak = false;
\r
1217 if (c == L'\r') // Mac or Windows breaks
\r
1221 if (Text[i+1] == L'\n') // Windows breaks
\r
1223 // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
\r
1224 // Instead rework the cursor positioning to be able to handle this (but not in stable release
\r
1225 // branch as users might already expect this behavior).
\r
1228 if ( CursorPos > i )
\r
1232 else if (c == L'\n') // Unix breaks
\r
1238 // don't break if we're not a multi-line edit box
\r
1240 lineBreak = false;
\r
1242 if (c == L' ' || c == 0 || i == (size-1))
\r
1244 // here comes the next whitespace, look if
\r
1245 // we can break the last word to the next line
\r
1246 // We also break whitespace, otherwise cursor would vanish beside the right border.
\r
1247 s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
\r
1248 s32 worldlgth = font->getDimension(word.c_str()).Width;
\r
1250 if (WordWrap && length + worldlgth + whitelgth > elWidth && line.size() > 0)
\r
1252 // break to next line
\r
1253 length = worldlgth;
\r
1254 BrokenText.push_back(line);
\r
1255 BrokenTextPositions.push_back(lastLineStart);
\r
1256 lastLineStart = i - (s32)word.size();
\r
1261 // add word to line
\r
1262 line += whitespace;
\r
1264 length += whitelgth + worldlgth;
\r
1274 // compute line break
\r
1277 line += whitespace;
\r
1279 BrokenText.push_back(line);
\r
1280 BrokenTextPositions.push_back(lastLineStart);
\r
1281 lastLineStart = i+1;
\r
1290 // yippee this is a word..
\r
1295 line += whitespace;
\r
1297 BrokenText.push_back(line);
\r
1298 BrokenTextPositions.push_back(lastLineStart);
\r
1301 // TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
\r
1302 // but HAlign according to line-width (pixels) and not by row.
\r
1303 // Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
\r
1304 // But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
\r
1305 void CGUIEditBox::setTextRect(s32 line)
\r
1310 IGUIFont* font = getActiveFont();
\r
1314 core::dimension2du d;
\r
1316 // get text dimension
\r
1317 const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
\r
1318 if (WordWrap || MultiLine)
\r
1320 d = font->getDimension(BrokenText[line].c_str());
\r
1324 d = font->getDimension(Text.c_str());
\r
1325 d.Height = AbsoluteRect.getHeight();
\r
1327 d.Height += font->getKerningHeight();
\r
1332 case EGUIA_CENTER:
\r
1333 // align to h centre
\r
1334 CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2);
\r
1335 CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2);
\r
1337 case EGUIA_LOWERRIGHT:
\r
1338 // align to right edge
\r
1339 CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width;
\r
1340 CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth();
\r
1343 // align to left edge
\r
1344 CurrentTextRect.UpperLeftCorner.X = 0;
\r
1345 CurrentTextRect.LowerRightCorner.X = d.Width;
\r
1351 case EGUIA_CENTER:
\r
1352 // align to v centre
\r
1353 CurrentTextRect.UpperLeftCorner.Y =
\r
1354 (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line;
\r
1356 case EGUIA_LOWERRIGHT:
\r
1357 // align to bottom edge
\r
1358 CurrentTextRect.UpperLeftCorner.Y =
\r
1359 FrameRect.getHeight() - lineCount*d.Height + d.Height*line;
\r
1362 // align to top edge
\r
1363 CurrentTextRect.UpperLeftCorner.Y = d.Height*line;
\r
1367 CurrentTextRect.UpperLeftCorner.X -= HScrollPos;
\r
1368 CurrentTextRect.LowerRightCorner.X -= HScrollPos;
\r
1369 CurrentTextRect.UpperLeftCorner.Y -= VScrollPos;
\r
1370 CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height;
\r
1372 CurrentTextRect += FrameRect.UpperLeftCorner;
\r
1377 s32 CGUIEditBox::getLineFromPos(s32 pos)
\r
1379 if (!WordWrap && !MultiLine)
\r
1383 while (i < (s32)BrokenTextPositions.size())
\r
1385 if (BrokenTextPositions[i] > pos)
\r
1389 return (s32)BrokenTextPositions.size() - 1;
\r
1393 void CGUIEditBox::inputChar(wchar_t c)
\r
1403 if (MarkBegin != MarkEnd)
\r
1405 // replace marked text
\r
1406 const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
\r
1407 const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
\r
1409 s = Text.subString(0, realmbgn);
\r
1411 s.append( Text.subString(realmend, Text.size()-realmend) );
\r
1413 CursorPos = realmbgn+1;
\r
1415 else if ( OverwriteMode )
\r
1417 //check to see if we are at the end of the text
\r
1418 if ( (u32)CursorPos != Text.size())
\r
1420 bool isEOL = (Text[CursorPos] == L'\n' ||Text[CursorPos] == L'\r' );
\r
1421 if (!isEOL || Text.size() < Max || Max == 0)
\r
1423 s = Text.subString(0, CursorPos);
\r
1427 //just keep appending to the current line
\r
1428 //This follows the behavior of other gui libraries behaviors
\r
1429 s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
\r
1433 //replace the next character
\r
1434 s.append( Text.subString(CursorPos + 1,Text.size() - CursorPos + 1));
\r
1440 else if (Text.size() < Max || Max == 0)
\r
1442 // add new character because we are at the end of the string
\r
1443 s = Text.subString(0, CursorPos);
\r
1445 s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
\r
1450 else if (Text.size() < Max || Max == 0)
\r
1452 // add new character
\r
1453 s = Text.subString(0, CursorPos);
\r
1455 s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
\r
1460 BlinkStartTime = os::Timer::getTime();
\r
1461 setTextMarkers(0, 0);
\r
1465 calculateScrollPos();
\r
1466 sendGuiEvent(EGET_EDITBOX_CHANGED);
\r
1469 // calculate autoscroll
\r
1470 void CGUIEditBox::calculateScrollPos()
\r
1475 IGUIFont* font = getActiveFont();
\r
1479 s32 cursLine = getLineFromPos(CursorPos);
\r
1480 if ( cursLine < 0 )
\r
1482 setTextRect(cursLine);
\r
1483 const bool hasBrokenText = MultiLine || WordWrap;
\r
1485 // Check horizonal scrolling
\r
1486 // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
\r
1488 // get cursor position
\r
1489 // get cursor area
\r
1490 irr::u32 cursorWidth = font->getDimension(CursorChar.c_str()).Width;
\r
1491 core::stringw *txtLine = hasBrokenText ? &BrokenText[cursLine] : &Text;
\r
1492 s32 cPos = hasBrokenText ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; // column
\r
1493 s32 cStart = font->getDimension(txtLine->subString(0, cPos).c_str()).Width; // pixels from text-start
\r
1494 s32 cEnd = cStart + cursorWidth;
\r
1495 s32 txtWidth = font->getDimension(txtLine->c_str()).Width;
\r
1497 if ( txtWidth < FrameRect.getWidth() )
\r
1499 // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
\r
1500 // This check just fixes the case where it was most noticable (text smaller than clipping area).
\r
1503 setTextRect(cursLine);
\r
1506 if ( CurrentTextRect.UpperLeftCorner.X+cStart < FrameRect.UpperLeftCorner.X )
\r
1508 // cursor to the left of the clipping area
\r
1509 HScrollPos -= FrameRect.UpperLeftCorner.X-(CurrentTextRect.UpperLeftCorner.X+cStart);
\r
1510 setTextRect(cursLine);
\r
1512 // TODO: should show more characters to the left when we're scrolling left
\r
1513 // and the cursor reaches the border.
\r
1515 else if ( CurrentTextRect.UpperLeftCorner.X+cEnd > FrameRect.LowerRightCorner.X)
\r
1517 // cursor to the right of the clipping area
\r
1518 HScrollPos += (CurrentTextRect.UpperLeftCorner.X+cEnd)-FrameRect.LowerRightCorner.X;
\r
1519 setTextRect(cursLine);
\r
1523 // calculate vertical scrolling
\r
1524 if (hasBrokenText)
\r
1526 irr::u32 lineHeight = font->getDimension(L"A").Height + font->getKerningHeight();
\r
1527 // only up to 1 line fits?
\r
1528 if ( lineHeight >= (irr::u32)FrameRect.getHeight() )
\r
1531 setTextRect(cursLine);
\r
1532 s32 unscrolledPos = CurrentTextRect.UpperLeftCorner.Y;
\r
1533 s32 pivot = FrameRect.UpperLeftCorner.Y;
\r
1536 case EGUIA_CENTER:
\r
1537 pivot += FrameRect.getHeight()/2;
\r
1538 unscrolledPos += lineHeight/2;
\r
1540 case EGUIA_LOWERRIGHT:
\r
1541 pivot += FrameRect.getHeight();
\r
1542 unscrolledPos += lineHeight;
\r
1547 VScrollPos = unscrolledPos-pivot;
\r
1548 setTextRect(cursLine);
\r
1552 // First 2 checks are necessary when people delete lines
\r
1554 if ( CurrentTextRect.UpperLeftCorner.Y > FrameRect.UpperLeftCorner.Y && VAlign != EGUIA_LOWERRIGHT)
\r
1556 // first line is leaving a gap on top
\r
1559 else if (VAlign != EGUIA_UPPERLEFT)
\r
1561 u32 lastLine = BrokenTextPositions.empty() ? 0 : BrokenTextPositions.size()-1;
\r
1562 setTextRect(lastLine);
\r
1563 if ( CurrentTextRect.LowerRightCorner.Y < FrameRect.LowerRightCorner.Y)
\r
1565 // last line is leaving a gap on bottom
\r
1566 VScrollPos -= FrameRect.LowerRightCorner.Y-CurrentTextRect.LowerRightCorner.Y;
\r
1570 setTextRect(cursLine);
\r
1571 if ( CurrentTextRect.UpperLeftCorner.Y < FrameRect.UpperLeftCorner.Y )
\r
1573 // text above valid area
\r
1574 VScrollPos -= FrameRect.UpperLeftCorner.Y-CurrentTextRect.UpperLeftCorner.Y;
\r
1575 setTextRect(cursLine);
\r
1577 else if ( CurrentTextRect.LowerRightCorner.Y > FrameRect.LowerRightCorner.Y)
\r
1579 // text below valid area
\r
1580 VScrollPos += CurrentTextRect.LowerRightCorner.Y-FrameRect.LowerRightCorner.Y;
\r
1581 setTextRect(cursLine);
\r
1587 void CGUIEditBox::calculateFrameRect()
\r
1589 FrameRect = AbsoluteRect;
\r
1590 IGUISkin *skin = 0;
\r
1592 skin = Environment->getSkin();
\r
1593 if (Border && skin)
\r
1595 FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
\r
1596 FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
\r
1597 FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
\r
1598 FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
\r
1602 //! set text markers
\r
1603 void CGUIEditBox::setTextMarkers(s32 begin, s32 end)
\r
1605 if ( begin != MarkBegin || end != MarkEnd )
\r
1607 MarkBegin = begin;
\r
1609 sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
\r
1613 //! send some gui event to parent
\r
1614 void CGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
\r
1619 e.EventType = EET_GUI_EVENT;
\r
1620 e.GUIEvent.Caller = this;
\r
1621 e.GUIEvent.Element = 0;
\r
1622 e.GUIEvent.EventType = type;
\r
1624 Parent->OnEvent(e);
\r
1628 //! Writes attributes of the element.
\r
1629 void CGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
\r
1631 // IGUIEditBox::serializeAttributes(out,options);
\r
1633 out->addBool ("Border", Border);
\r
1634 out->addBool ("Background", Background);
\r
1635 out->addBool ("OverrideColorEnabled", OverrideColorEnabled );
\r
1636 out->addColor ("OverrideColor", OverrideColor);
\r
1637 // out->addFont("OverrideFont", OverrideFont);
\r
1638 out->addInt ("MaxChars", Max);
\r
1639 out->addBool ("WordWrap", WordWrap);
\r
1640 out->addBool ("MultiLine", MultiLine);
\r
1641 out->addBool ("AutoScroll", AutoScroll);
\r
1642 out->addBool ("PasswordBox", PasswordBox);
\r
1643 core::stringw ch = L" ";
\r
1644 ch[0] = PasswordChar;
\r
1645 out->addString("PasswordChar", ch.c_str());
\r
1646 out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames);
\r
1647 out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames);
\r
1649 IGUIEditBox::serializeAttributes(out,options);
\r
1653 //! Reads attributes of the element
\r
1654 void CGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
\r
1656 IGUIEditBox::deserializeAttributes(in,options);
\r
1658 setDrawBorder( in->getAttributeAsBool("Border", Border) );
\r
1659 setDrawBackground( in->getAttributeAsBool("Background", Background) );
\r
1660 setOverrideColor(in->getAttributeAsColor("OverrideColor", OverrideColor));
\r
1661 enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled", OverrideColorEnabled));
\r
1662 setMax(in->getAttributeAsInt("MaxChars", Max));
\r
1663 setWordWrap(in->getAttributeAsBool("WordWrap", WordWrap));
\r
1664 setMultiLine(in->getAttributeAsBool("MultiLine", MultiLine));
\r
1665 setAutoScroll(in->getAttributeAsBool("AutoScroll", AutoScroll));
\r
1666 core::stringw ch = L" ";
\r
1667 ch[0] = PasswordChar;
\r
1668 ch = in->getAttributeAsStringW("PasswordChar", ch);
\r
1671 setPasswordBox(in->getAttributeAsBool("PasswordBox", PasswordBox));
\r
1673 setPasswordBox(in->getAttributeAsBool("PasswordBox", PasswordBox), ch[0]);
\r
1675 setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames, (s32)HAlign),
\r
1676 (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames, (s32)VAlign));
\r
1678 // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
\r
1682 } // end namespace gui
\r
1683 } // end namespace irr
\r
1685 #endif // _IRR_COMPILE_WITH_GUI_
\r