2023-10-03 20:37:00 +02:00
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
# include "CGUIEditBox.h"
# include "IGUISkin.h"
# include "IGUIEnvironment.h"
# include "IGUIFont.h"
# include "IVideoDriver.h"
# include "rect.h"
# include "os.h"
# include "Keycodes.h"
/*
todo :
optional scrollbars
ctrl + left / right to select word
double click / ctrl click : word select + drag to select whole words , triple click to select line
optional ? dragging selected text
numerical
*/
namespace irr
{
namespace gui
{
//! constructor
2024-03-20 19:35:52 +01:00
CGUIEditBox : : CGUIEditBox ( const wchar_t * text , bool border ,
IGUIEnvironment * environment , IGUIElement * parent , s32 id ,
const core : : rect < s32 > & rectangle ) :
IGUIEditBox ( environment , parent , id , rectangle ) ,
OverwriteMode ( false ) , MouseMarking ( false ) ,
Border ( border ) , Background ( true ) , OverrideColorEnabled ( false ) , MarkBegin ( 0 ) , MarkEnd ( 0 ) ,
OverrideColor ( video : : SColor ( 101 , 255 , 255 , 255 ) ) , OverrideFont ( 0 ) , LastBreakFont ( 0 ) ,
Operator ( 0 ) , BlinkStartTime ( 0 ) , CursorBlinkTime ( 350 ) , CursorChar ( L " _ " ) , CursorPos ( 0 ) , HScrollPos ( 0 ) , VScrollPos ( 0 ) , Max ( 0 ) ,
WordWrap ( false ) , MultiLine ( false ) , AutoScroll ( true ) , PasswordBox ( false ) ,
PasswordChar ( L ' * ' ) , HAlign ( EGUIA_UPPERLEFT ) , VAlign ( EGUIA_CENTER ) ,
CurrentTextRect ( 0 , 0 , 1 , 1 ) , FrameRect ( rectangle )
2023-10-03 20:37:00 +02:00
{
2024-03-20 19:35:52 +01:00
# ifdef _DEBUG
2023-10-03 20:37:00 +02:00
setDebugName ( " CGUIEditBox " ) ;
2024-03-20 19:35:52 +01:00
# endif
2023-10-03 20:37:00 +02:00
Text = text ;
if ( Environment )
Operator = Environment - > getOSOperator ( ) ;
if ( Operator )
Operator - > grab ( ) ;
// this element can be tabbed to
setTabStop ( true ) ;
setTabOrder ( - 1 ) ;
calculateFrameRect ( ) ;
breakText ( ) ;
calculateScrollPos ( ) ;
}
//! destructor
CGUIEditBox : : ~ CGUIEditBox ( )
{
if ( OverrideFont )
OverrideFont - > drop ( ) ;
if ( Operator )
Operator - > drop ( ) ;
}
//! Sets another skin independent font.
2024-03-20 19:35:52 +01:00
void CGUIEditBox : : setOverrideFont ( IGUIFont * font )
2023-10-03 20:37:00 +02:00
{
if ( OverrideFont = = font )
return ;
if ( OverrideFont )
OverrideFont - > drop ( ) ;
OverrideFont = font ;
if ( OverrideFont )
OverrideFont - > grab ( ) ;
breakText ( ) ;
}
//! Gets the override font (if any)
2024-03-20 19:35:52 +01:00
IGUIFont * CGUIEditBox : : getOverrideFont ( ) const
2023-10-03 20:37:00 +02:00
{
return OverrideFont ;
}
//! Get the font which is used right now for drawing
2024-03-20 19:35:52 +01:00
IGUIFont * CGUIEditBox : : getActiveFont ( ) const
2023-10-03 20:37:00 +02:00
{
2024-03-20 19:35:52 +01:00
if ( OverrideFont )
2023-10-03 20:37:00 +02:00
return OverrideFont ;
2024-03-20 19:35:52 +01:00
IGUISkin * skin = Environment - > getSkin ( ) ;
2023-10-03 20:37:00 +02:00
if ( skin )
return skin - > getFont ( ) ;
return 0 ;
}
//! Sets another color for the text.
void CGUIEditBox : : setOverrideColor ( video : : SColor color )
{
OverrideColor = color ;
OverrideColorEnabled = true ;
}
video : : SColor CGUIEditBox : : getOverrideColor ( ) const
{
return OverrideColor ;
}
//! Turns the border on or off
void CGUIEditBox : : setDrawBorder ( bool border )
{
Border = border ;
}
//! Checks if border drawing is enabled
bool CGUIEditBox : : isDrawBorderEnabled ( ) const
{
return Border ;
}
//! Sets whether to draw the background
void CGUIEditBox : : setDrawBackground ( bool draw )
{
Background = draw ;
}
//! Checks if background drawing is enabled
bool CGUIEditBox : : isDrawBackgroundEnabled ( ) const
{
return Background ;
}
//! Sets if the text should use the override color or the color in the gui skin.
void CGUIEditBox : : enableOverrideColor ( bool enable )
{
OverrideColorEnabled = enable ;
}
bool CGUIEditBox : : isOverrideColorEnabled ( ) const
{
return OverrideColorEnabled ;
}
//! Enables or disables word wrap
void CGUIEditBox : : setWordWrap ( bool enable )
{
WordWrap = enable ;
breakText ( ) ;
}
void CGUIEditBox : : updateAbsolutePosition ( )
{
core : : rect < s32 > oldAbsoluteRect ( AbsoluteRect ) ;
IGUIElement : : updateAbsolutePosition ( ) ;
2024-03-20 19:35:52 +01:00
if ( oldAbsoluteRect ! = AbsoluteRect ) {
2023-10-03 20:37:00 +02:00
calculateFrameRect ( ) ;
breakText ( ) ;
calculateScrollPos ( ) ;
}
}
//! Checks if word wrap is enabled
bool CGUIEditBox : : isWordWrapEnabled ( ) const
{
return WordWrap ;
}
//! Enables or disables newlines.
void CGUIEditBox : : setMultiLine ( bool enable )
{
MultiLine = enable ;
breakText ( ) ;
}
//! Checks if multi line editing is enabled
bool CGUIEditBox : : isMultiLineEnabled ( ) const
{
return MultiLine ;
}
void CGUIEditBox : : setPasswordBox ( bool passwordBox , wchar_t passwordChar )
{
PasswordBox = passwordBox ;
2024-03-20 19:35:52 +01:00
if ( PasswordBox ) {
2023-10-03 20:37:00 +02:00
PasswordChar = passwordChar ;
setMultiLine ( false ) ;
setWordWrap ( false ) ;
BrokenText . clear ( ) ;
}
}
bool CGUIEditBox : : isPasswordBox ( ) const
{
return PasswordBox ;
}
//! Sets text justification
void CGUIEditBox : : setTextAlignment ( EGUI_ALIGNMENT horizontal , EGUI_ALIGNMENT vertical )
{
HAlign = horizontal ;
VAlign = vertical ;
}
//! called if an event happened.
2024-03-20 19:35:52 +01:00
bool CGUIEditBox : : OnEvent ( const SEvent & event )
2023-10-03 20:37:00 +02:00
{
2024-03-20 19:35:52 +01:00
if ( isEnabled ( ) ) {
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
switch ( event . EventType ) {
2023-10-03 20:37:00 +02:00
case EET_GUI_EVENT :
2024-03-20 19:35:52 +01:00
if ( event . GUIEvent . EventType = = EGET_ELEMENT_FOCUS_LOST ) {
if ( event . GUIEvent . Caller = = this ) {
2023-10-03 20:37:00 +02:00
MouseMarking = false ;
2024-03-20 19:35:52 +01:00
setTextMarkers ( 0 , 0 ) ;
2023-10-03 20:37:00 +02:00
}
}
break ;
case EET_KEY_INPUT_EVENT :
if ( processKey ( event ) )
return true ;
break ;
case EET_MOUSE_INPUT_EVENT :
if ( processMouse ( event ) )
return true ;
break ;
case EET_STRING_INPUT_EVENT :
inputString ( * event . StringInput . Str ) ;
return true ;
break ;
default :
break ;
}
}
return IGUIElement : : OnEvent ( event ) ;
}
2024-03-20 19:35:52 +01:00
bool CGUIEditBox : : processKey ( const SEvent & event )
2023-10-03 20:37:00 +02:00
{
if ( ! event . KeyInput . PressedDown )
return false ;
bool textChanged = false ;
s32 newMarkBegin = MarkBegin ;
s32 newMarkEnd = MarkEnd ;
// control shortcut handling
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Control ) {
2023-10-03 20:37:00 +02:00
// german backlash '\' entered with control + '?'
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Char = = ' \\ ' ) {
2023-10-03 20:37:00 +02:00
inputChar ( event . KeyInput . Char ) ;
return true ;
}
2024-03-20 19:35:52 +01:00
switch ( event . KeyInput . Key ) {
2023-10-03 20:37:00 +02:00
case KEY_KEY_A :
// select all
newMarkBegin = 0 ;
newMarkEnd = Text . size ( ) ;
break ;
case KEY_KEY_C :
// copy to clipboard
2024-03-20 19:35:52 +01:00
if ( ! PasswordBox & & Operator & & MarkBegin ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
core : : stringc s ;
2023-10-05 17:55:55 +02:00
wStringToUTF8 ( s , Text . subString ( realmbgn , realmend - realmbgn ) ) ;
2023-10-03 20:37:00 +02:00
Operator - > copyToClipboard ( s . c_str ( ) ) ;
}
break ;
case KEY_KEY_X :
// cut to the clipboard
2024-03-20 19:35:52 +01:00
if ( ! PasswordBox & & Operator & & MarkBegin ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
// copy
core : : stringc sc ;
2023-10-05 17:55:55 +02:00
wStringToUTF8 ( sc , Text . subString ( realmbgn , realmend - realmbgn ) ) ;
2023-10-03 20:37:00 +02:00
Operator - > copyToClipboard ( sc . c_str ( ) ) ;
2024-03-20 19:35:52 +01:00
if ( isEnabled ( ) ) {
2023-10-03 20:37:00 +02:00
// delete
core : : stringw s ;
s = Text . subString ( 0 , realmbgn ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( realmend , Text . size ( ) - realmend ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
CursorPos = realmbgn ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
textChanged = true ;
}
}
break ;
case KEY_KEY_V :
2024-03-20 19:35:52 +01:00
if ( ! isEnabled ( ) )
2023-10-03 20:37:00 +02:00
break ;
// paste from the clipboard
2024-03-20 19:35:52 +01:00
if ( Operator ) {
2023-10-03 20:37:00 +02:00
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
// add the string
const c8 * p = Operator - > getTextFromClipboard ( ) ;
2024-03-20 19:35:52 +01:00
if ( p ) {
2023-10-03 20:37:00 +02:00
irr : : core : : stringw widep ;
2023-10-05 17:55:55 +02:00
core : : utf8ToWString ( widep , p ) ;
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
if ( MarkBegin = = MarkEnd ) {
2023-10-03 20:37:00 +02:00
// insert text
core : : stringw s = Text . subString ( 0 , CursorPos ) ;
s . append ( widep ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( CursorPos , Text . size ( ) - CursorPos ) ) ;
2023-10-03 20:37:00 +02:00
2023-10-04 20:10:58 +02:00
if ( ! Max | | s . size ( ) < = Max ) { // thx to Fish FH for fix
2023-10-03 20:37:00 +02:00
Text = s ;
s = widep ;
CursorPos + = s . size ( ) ;
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// replace text
core : : stringw s = Text . subString ( 0 , realmbgn ) ;
s . append ( widep ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( realmend , Text . size ( ) - realmend ) ) ;
2023-10-03 20:37:00 +02:00
2023-10-04 20:10:58 +02:00
if ( ! Max | | s . size ( ) < = Max ) { // thx to Fish FH for fix
2023-10-03 20:37:00 +02:00
Text = s ;
s = widep ;
CursorPos = realmbgn + s . size ( ) ;
}
}
}
newMarkBegin = 0 ;
newMarkEnd = 0 ;
textChanged = true ;
}
break ;
case KEY_HOME :
// move/highlight to start of text
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
2023-10-03 20:37:00 +02:00
newMarkEnd = CursorPos ;
newMarkBegin = 0 ;
CursorPos = 0 ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
CursorPos = 0 ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
break ;
case KEY_END :
// move/highlight to end of text
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
2023-10-03 20:37:00 +02:00
newMarkBegin = CursorPos ;
newMarkEnd = Text . size ( ) ;
CursorPos = 0 ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
CursorPos = Text . size ( ) ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
break ;
default :
return false ;
}
}
// 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
2024-03-20 19:35:52 +01:00
else if ( event . KeyInput . Char = = 0 ) {
switch ( event . KeyInput . Key ) {
case KEY_END : {
s32 p = Text . size ( ) ;
if ( WordWrap | | MultiLine ) {
p = getLineFromPos ( CursorPos ) ;
p = BrokenTextPositions [ p ] + ( s32 ) BrokenText [ p ] . size ( ) ;
if ( p > 0 & & ( Text [ p - 1 ] = = L ' \r ' | | Text [ p - 1 ] = = L ' \n ' ) )
p - = 1 ;
}
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
if ( MarkBegin = = MarkEnd )
newMarkBegin = CursorPos ;
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
newMarkEnd = p ;
} else {
newMarkBegin = 0 ;
newMarkEnd = 0 ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
CursorPos = p ;
BlinkStartTime = os : : Timer : : getTime ( ) ;
} break ;
case KEY_HOME : {
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
s32 p = 0 ;
if ( WordWrap | | MultiLine ) {
p = getLineFromPos ( CursorPos ) ;
p = BrokenTextPositions [ p ] ;
}
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
if ( MarkBegin = = MarkEnd )
newMarkBegin = CursorPos ;
newMarkEnd = p ;
} else {
newMarkBegin = 0 ;
newMarkEnd = 0 ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
CursorPos = p ;
BlinkStartTime = os : : Timer : : getTime ( ) ;
} break ;
2023-10-03 20:37:00 +02:00
case KEY_LEFT :
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
if ( CursorPos > 0 ) {
2023-10-03 20:37:00 +02:00
if ( MarkBegin = = MarkEnd )
newMarkBegin = CursorPos ;
2024-03-20 19:35:52 +01:00
newMarkEnd = CursorPos - 1 ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
2024-03-20 19:35:52 +01:00
if ( CursorPos > 0 )
CursorPos - - ;
2023-10-03 20:37:00 +02:00
BlinkStartTime = os : : Timer : : getTime ( ) ;
break ;
case KEY_RIGHT :
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
if ( Text . size ( ) > ( u32 ) CursorPos ) {
2023-10-03 20:37:00 +02:00
if ( MarkBegin = = MarkEnd )
newMarkBegin = CursorPos ;
2024-03-20 19:35:52 +01:00
newMarkEnd = CursorPos + 1 ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
2024-03-20 19:35:52 +01:00
if ( Text . size ( ) > ( u32 ) CursorPos )
CursorPos + + ;
2023-10-03 20:37:00 +02:00
BlinkStartTime = os : : Timer : : getTime ( ) ;
break ;
case KEY_UP :
2024-03-20 19:35:52 +01:00
if ( MultiLine | | ( WordWrap & & BrokenText . size ( ) > 1 ) ) {
2023-10-03 20:37:00 +02:00
s32 lineNo = getLineFromPos ( CursorPos ) ;
s32 mb = ( MarkBegin = = MarkEnd ) ? CursorPos : ( MarkBegin > MarkEnd ? MarkBegin : MarkEnd ) ;
2024-03-20 19:35:52 +01:00
if ( lineNo > 0 ) {
2023-10-03 20:37:00 +02:00
s32 cp = CursorPos - BrokenTextPositions [ lineNo ] ;
2024-03-20 19:35:52 +01:00
if ( ( s32 ) BrokenText [ lineNo - 1 ] . size ( ) < cp )
CursorPos = BrokenTextPositions [ lineNo - 1 ] + core : : max_ ( ( u32 ) 1 , BrokenText [ lineNo - 1 ] . size ( ) ) - 1 ;
2023-10-03 20:37:00 +02:00
else
2024-03-20 19:35:52 +01:00
CursorPos = BrokenTextPositions [ lineNo - 1 ] + cp ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
2023-10-03 20:37:00 +02:00
newMarkBegin = mb ;
newMarkEnd = CursorPos ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
return false ;
}
break ;
case KEY_DOWN :
2024-03-20 19:35:52 +01:00
if ( MultiLine | | ( WordWrap & & BrokenText . size ( ) > 1 ) ) {
2023-10-03 20:37:00 +02:00
s32 lineNo = getLineFromPos ( CursorPos ) ;
s32 mb = ( MarkBegin = = MarkEnd ) ? CursorPos : ( MarkBegin < MarkEnd ? MarkBegin : MarkEnd ) ;
2024-03-20 19:35:52 +01:00
if ( lineNo < ( s32 ) BrokenText . size ( ) - 1 ) {
2023-10-03 20:37:00 +02:00
s32 cp = CursorPos - BrokenTextPositions [ lineNo ] ;
2024-03-20 19:35:52 +01:00
if ( ( s32 ) BrokenText [ lineNo + 1 ] . size ( ) < cp )
CursorPos = BrokenTextPositions [ lineNo + 1 ] + core : : max_ ( ( u32 ) 1 , BrokenText [ lineNo + 1 ] . size ( ) ) - 1 ;
2023-10-03 20:37:00 +02:00
else
2024-03-20 19:35:52 +01:00
CursorPos = BrokenTextPositions [ lineNo + 1 ] + cp ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Shift ) {
2023-10-03 20:37:00 +02:00
newMarkBegin = mb ;
newMarkEnd = CursorPos ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
newMarkBegin = 0 ;
newMarkEnd = 0 ;
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
return false ;
}
break ;
case KEY_INSERT :
2024-03-20 19:35:52 +01:00
if ( ! isEnabled ( ) )
2023-10-03 20:37:00 +02:00
break ;
OverwriteMode = ! OverwriteMode ;
break ;
case KEY_DELETE :
2024-03-20 19:35:52 +01:00
if ( ! isEnabled ( ) )
2023-10-03 20:37:00 +02:00
break ;
2024-03-20 19:35:52 +01:00
if ( keyDelete ( ) ) {
2023-10-03 20:37:00 +02:00
BlinkStartTime = os : : Timer : : getTime ( ) ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
textChanged = true ;
}
break ;
default :
return false ;
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// default keyboard handling
2024-03-20 19:35:52 +01:00
switch ( event . KeyInput . Key ) {
2023-10-03 20:37:00 +02:00
case KEY_RETURN :
2024-03-20 19:35:52 +01:00
if ( MultiLine ) {
2023-10-03 20:37:00 +02:00
inputChar ( L ' \n ' ) ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
calculateScrollPos ( ) ;
2024-03-20 19:35:52 +01:00
sendGuiEvent ( EGET_EDITBOX_ENTER ) ;
2023-10-03 20:37:00 +02:00
}
return true ;
case KEY_BACK :
2024-03-20 19:35:52 +01:00
if ( ! isEnabled ( ) )
2023-10-03 20:37:00 +02:00
break ;
2024-03-20 19:35:52 +01:00
if ( Text . size ( ) ) {
2023-10-03 20:37:00 +02:00
core : : stringw s ;
2024-03-20 19:35:52 +01:00
if ( MarkBegin ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
// delete marked text
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
s = Text . subString ( 0 , realmbgn ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( realmend , Text . size ( ) - realmend ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
CursorPos = realmbgn ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// delete text behind cursor
2024-03-20 19:35:52 +01:00
if ( CursorPos > 0 )
s = Text . subString ( 0 , CursorPos - 1 ) ;
2023-10-03 20:37:00 +02:00
else
s = L " " ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( CursorPos , Text . size ( ) - CursorPos ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
- - CursorPos ;
}
if ( CursorPos < 0 )
CursorPos = 0 ;
BlinkStartTime = os : : Timer : : getTime ( ) ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
textChanged = true ;
}
break ;
case KEY_DELETE :
// At least on X11 we get a char with 127 when the delete key is pressed.
// 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).
// We get a keykode != 127 when delete key on numlock is pressed with numlock on.
2024-03-20 19:35:52 +01:00
if ( event . KeyInput . Char = = 127 ) {
if ( ! isEnabled ( ) )
2023-10-03 20:37:00 +02:00
break ;
2024-03-20 19:35:52 +01:00
if ( keyDelete ( ) ) {
2023-10-03 20:37:00 +02:00
BlinkStartTime = os : : Timer : : getTime ( ) ;
newMarkBegin = 0 ;
newMarkEnd = 0 ;
textChanged = true ;
}
break ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
inputChar ( event . KeyInput . Char ) ;
return true ;
}
case KEY_ESCAPE :
case KEY_TAB :
case KEY_SHIFT :
case KEY_F1 :
case KEY_F2 :
case KEY_F3 :
case KEY_F4 :
case KEY_F5 :
case KEY_F6 :
case KEY_F7 :
case KEY_F8 :
case KEY_F9 :
case KEY_F10 :
case KEY_F11 :
case KEY_F12 :
case KEY_F13 :
case KEY_F14 :
case KEY_F15 :
case KEY_F16 :
case KEY_F17 :
case KEY_F18 :
case KEY_F19 :
case KEY_F20 :
case KEY_F21 :
case KEY_F22 :
case KEY_F23 :
case KEY_F24 :
// ignore these keys
return false ;
default :
inputChar ( event . KeyInput . Char ) ;
return true ;
}
}
// Set new text markers
2024-03-20 19:35:52 +01:00
setTextMarkers ( newMarkBegin , newMarkEnd ) ;
2023-10-03 20:37:00 +02:00
// break the text if it has changed
2024-03-20 19:35:52 +01:00
if ( textChanged ) {
2023-10-03 20:37:00 +02:00
breakText ( ) ;
calculateScrollPos ( ) ;
sendGuiEvent ( EGET_EDITBOX_CHANGED ) ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
calculateScrollPos ( ) ;
}
return true ;
}
bool CGUIEditBox : : keyDelete ( )
{
2024-03-20 19:35:52 +01:00
if ( Text . size ( ) ! = 0 ) {
2023-10-03 20:37:00 +02:00
core : : stringw s ;
2024-03-20 19:35:52 +01:00
if ( MarkBegin ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
// delete marked text
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
s = Text . subString ( 0 , realmbgn ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( realmend , Text . size ( ) - realmend ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
CursorPos = realmbgn ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// delete text before cursor
s = Text . subString ( 0 , CursorPos ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( CursorPos + 1 , Text . size ( ) - CursorPos - 1 ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
}
if ( CursorPos > ( s32 ) Text . size ( ) )
CursorPos = ( s32 ) Text . size ( ) ;
return true ;
}
return false ;
}
//! draws the element and its children
void CGUIEditBox : : draw ( )
{
if ( ! IsVisible )
return ;
const bool focus = Environment - > hasFocus ( this ) ;
2024-03-20 19:35:52 +01:00
IGUISkin * skin = Environment - > getSkin ( ) ;
2023-10-03 20:37:00 +02:00
if ( ! skin )
return ;
EGUI_DEFAULT_COLOR bgCol = EGDC_GRAY_EDITABLE ;
2024-03-20 19:35:52 +01:00
if ( isEnabled ( ) )
2023-10-03 20:37:00 +02:00
bgCol = focus ? EGDC_FOCUSED_EDITABLE : EGDC_EDITABLE ;
2024-03-20 19:35:52 +01:00
if ( ! Border & & Background ) {
2023-10-03 20:37:00 +02:00
skin - > draw2DRectangle ( this , skin - > getColor ( bgCol ) , AbsoluteRect , & AbsoluteClippingRect ) ;
}
2024-03-20 19:35:52 +01:00
if ( Border ) {
2023-10-03 20:37:00 +02:00
// draw the border
skin - > draw3DSunkenPane ( this , skin - > getColor ( bgCol ) , false , Background , AbsoluteRect , & AbsoluteClippingRect ) ;
calculateFrameRect ( ) ;
}
core : : rect < s32 > localClipRect = FrameRect ;
localClipRect . clipAgainst ( AbsoluteClippingRect ) ;
// draw the text
2024-03-20 19:35:52 +01:00
IGUIFont * font = getActiveFont ( ) ;
2023-10-03 20:37:00 +02:00
s32 cursorLine = 0 ;
s32 charcursorpos = 0 ;
2024-03-20 19:35:52 +01:00
if ( font ) {
if ( LastBreakFont ! = font ) {
2023-10-03 20:37:00 +02:00
breakText ( ) ;
}
// calculate cursor pos
core : : stringw * txtLine = & Text ;
s32 startPos = 0 ;
core : : stringw s , s2 ;
// get mark position
const bool ml = ( ! PasswordBox & & ( WordWrap | | MultiLine ) ) ;
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
const s32 hlineStart = ml ? getLineFromPos ( realmbgn ) : 0 ;
const s32 hlineCount = ml ? getLineFromPos ( realmend ) - hlineStart + 1 : 1 ;
const s32 lineCount = ml ? BrokenText . size ( ) : 1 ;
// Save the override color information.
// Then, alter it if the edit box is disabled.
const bool prevOver = OverrideColorEnabled ;
const video : : SColor prevColor = OverrideColor ;
2024-03-20 19:35:52 +01:00
if ( Text . size ( ) ) {
if ( ! isEnabled ( ) & & ! OverrideColorEnabled ) {
2023-10-03 20:37:00 +02:00
OverrideColorEnabled = true ;
OverrideColor = skin - > getColor ( EGDC_GRAY_TEXT ) ;
}
2024-03-20 19:35:52 +01:00
for ( s32 i = 0 ; i < lineCount ; + + i ) {
2023-10-03 20:37:00 +02:00
setTextRect ( i ) ;
// clipping test - don't draw anything outside the visible area
core : : rect < s32 > c = localClipRect ;
c . clipAgainst ( CurrentTextRect ) ;
if ( ! c . isValid ( ) )
continue ;
// get current line
2024-03-20 19:35:52 +01:00
if ( PasswordBox ) {
if ( BrokenText . size ( ) ! = 1 ) {
2023-10-03 20:37:00 +02:00
BrokenText . clear ( ) ;
BrokenText . push_back ( core : : stringw ( ) ) ;
}
2024-03-20 19:35:52 +01:00
if ( BrokenText [ 0 ] . size ( ) ! = Text . size ( ) ) {
2023-10-03 20:37:00 +02:00
BrokenText [ 0 ] = Text ;
2024-03-20 19:35:52 +01:00
for ( u32 q = 0 ; q < Text . size ( ) ; + + q ) {
BrokenText [ 0 ] [ q ] = PasswordChar ;
2023-10-03 20:37:00 +02:00
}
}
txtLine = & BrokenText [ 0 ] ;
startPos = 0 ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
txtLine = ml ? & BrokenText [ i ] : & Text ;
startPos = ml ? BrokenTextPositions [ i ] : 0 ;
}
// draw normal text
font - > draw ( txtLine - > c_str ( ) , CurrentTextRect ,
2024-03-20 19:35:52 +01:00
OverrideColorEnabled ? OverrideColor : skin - > getColor ( EGDC_BUTTON_TEXT ) ,
false , true , & localClipRect ) ;
2023-10-03 20:37:00 +02:00
// draw mark and marked text
2024-03-20 19:35:52 +01:00
if ( focus & & MarkBegin ! = MarkEnd & & i > = hlineStart & & i < hlineStart + hlineCount ) {
2023-10-03 20:37:00 +02:00
s32 mbegin = 0 , mend = 0 ;
s32 lineStartPos = 0 , lineEndPos = txtLine - > size ( ) ;
2024-03-20 19:35:52 +01:00
if ( i = = hlineStart ) {
2023-10-03 20:37:00 +02:00
// highlight start is on this line
s = txtLine - > subString ( 0 , realmbgn - startPos ) ;
mbegin = font - > getDimension ( s . c_str ( ) ) . Width ;
// deal with kerning
mbegin + = font - > getKerningWidth (
2024-03-20 19:35:52 +01:00
& ( ( * txtLine ) [ realmbgn - startPos ] ) ,
realmbgn - startPos > 0 ? & ( ( * txtLine ) [ realmbgn - startPos - 1 ] ) : 0 ) ;
2023-10-03 20:37:00 +02:00
lineStartPos = realmbgn - startPos ;
}
2024-03-20 19:35:52 +01:00
if ( i = = hlineStart + hlineCount - 1 ) {
2023-10-03 20:37:00 +02:00
// highlight end is on this line
s2 = txtLine - > subString ( 0 , realmend - startPos ) ;
mend = font - > getDimension ( s2 . c_str ( ) ) . Width ;
lineEndPos = ( s32 ) s2 . size ( ) ;
2024-03-20 19:35:52 +01:00
} else
2023-10-03 20:37:00 +02:00
mend = font - > getDimension ( txtLine - > c_str ( ) ) . Width ;
CurrentTextRect . UpperLeftCorner . X + = mbegin ;
CurrentTextRect . LowerRightCorner . X = CurrentTextRect . UpperLeftCorner . X + mend - mbegin ;
// draw mark
skin - > draw2DRectangle ( this , skin - > getColor ( EGDC_HIGH_LIGHT ) , CurrentTextRect , & localClipRect ) ;
// draw marked text
s = txtLine - > subString ( lineStartPos , lineEndPos - lineStartPos ) ;
if ( s . size ( ) )
font - > draw ( s . c_str ( ) , CurrentTextRect ,
2024-03-20 19:35:52 +01:00
OverrideColorEnabled ? OverrideColor : skin - > getColor ( EGDC_HIGH_LIGHT_TEXT ) ,
false , true , & localClipRect ) ;
2023-10-03 20:37:00 +02:00
}
}
// Return the override color information to its previous settings.
OverrideColorEnabled = prevOver ;
OverrideColor = prevColor ;
}
// draw cursor
2024-03-20 19:35:52 +01:00
if ( isEnabled ( ) ) {
if ( WordWrap | | MultiLine ) {
2023-10-03 20:37:00 +02:00
cursorLine = getLineFromPos ( CursorPos ) ;
txtLine = & BrokenText [ cursorLine ] ;
startPos = BrokenTextPositions [ cursorLine ] ;
}
2024-03-20 19:35:52 +01:00
s = txtLine - > subString ( 0 , CursorPos - startPos ) ;
2023-10-03 20:37:00 +02:00
charcursorpos = font - > getDimension ( s . c_str ( ) ) . Width +
2024-03-20 19:35:52 +01:00
font - > getKerningWidth ( CursorChar . c_str ( ) , CursorPos - startPos > 0 ? & ( ( * txtLine ) [ CursorPos - startPos - 1 ] ) : 0 ) ;
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
if ( focus & & ( CursorBlinkTime = = 0 | | ( os : : Timer : : getTime ( ) - BlinkStartTime ) % ( 2 * CursorBlinkTime ) < CursorBlinkTime ) ) {
2023-10-03 20:37:00 +02:00
setTextRect ( cursorLine ) ;
CurrentTextRect . UpperLeftCorner . X + = charcursorpos ;
2024-03-20 19:35:52 +01:00
if ( OverwriteMode ) {
core : : stringw character = Text . subString ( CursorPos , 1 ) ;
2023-10-03 20:37:00 +02:00
s32 mend = font - > getDimension ( character . c_str ( ) ) . Width ;
2024-03-20 19:35:52 +01:00
// Make sure the cursor box has at least some width to it
if ( mend < = 0 )
2023-10-03 20:37:00 +02:00
mend = font - > getDimension ( CursorChar . c_str ( ) ) . Width ;
CurrentTextRect . LowerRightCorner . X = CurrentTextRect . UpperLeftCorner . X + mend ;
skin - > draw2DRectangle ( this , skin - > getColor ( EGDC_HIGH_LIGHT ) , CurrentTextRect , & localClipRect ) ;
font - > draw ( character . c_str ( ) , CurrentTextRect ,
2024-03-20 19:35:52 +01:00
OverrideColorEnabled ? OverrideColor : skin - > getColor ( EGDC_HIGH_LIGHT_TEXT ) ,
false , true , & localClipRect ) ;
} else {
2023-10-03 20:37:00 +02:00
font - > draw ( CursorChar , CurrentTextRect ,
2024-03-20 19:35:52 +01:00
OverrideColorEnabled ? OverrideColor : skin - > getColor ( EGDC_BUTTON_TEXT ) ,
false , true , & localClipRect ) ;
2023-10-03 20:37:00 +02:00
}
}
}
}
// draw children
IGUIElement : : draw ( ) ;
}
//! Sets the new caption of this element.
2024-03-20 19:35:52 +01:00
void CGUIEditBox : : setText ( const wchar_t * text )
2023-10-03 20:37:00 +02:00
{
Text = text ;
if ( u32 ( CursorPos ) > Text . size ( ) )
CursorPos = Text . size ( ) ;
HScrollPos = 0 ;
breakText ( ) ;
}
//! Enables or disables automatic scrolling with cursor position
//! \param enable: If set to true, the text will move around with the cursor position
void CGUIEditBox : : setAutoScroll ( bool enable )
{
AutoScroll = enable ;
}
//! Checks to see if automatic scrolling is enabled
//! \return true if automatic scrolling is enabled, false if not
bool CGUIEditBox : : isAutoScrollEnabled ( ) const
{
return AutoScroll ;
}
//! Gets the area of the text in the edit box
//! \return Returns the size in pixels of the text
core : : dimension2du CGUIEditBox : : getTextDimension ( )
{
core : : rect < s32 > ret ;
setTextRect ( 0 ) ;
ret = CurrentTextRect ;
2024-03-20 19:35:52 +01:00
for ( u32 i = 1 ; i < BrokenText . size ( ) ; + + i ) {
2023-10-03 20:37:00 +02:00
setTextRect ( i ) ;
ret . addInternalPoint ( CurrentTextRect . UpperLeftCorner ) ;
ret . addInternalPoint ( CurrentTextRect . LowerRightCorner ) ;
}
return core : : dimension2du ( ret . getSize ( ) ) ;
}
//! Sets the maximum amount of characters which may be entered in the box.
//! \param max: Maximum amount of characters. If 0, the character amount is
//! infinity.
void CGUIEditBox : : setMax ( u32 max )
{
Max = max ;
if ( Text . size ( ) > Max & & Max ! = 0 )
Text = Text . subString ( 0 , Max ) ;
}
//! Returns maximum amount of characters, previously set by setMax();
u32 CGUIEditBox : : getMax ( ) const
{
return Max ;
}
//! Set the character used for the cursor.
/** By default it's "_" */
void CGUIEditBox : : setCursorChar ( const wchar_t cursorChar )
{
CursorChar [ 0 ] = cursorChar ;
}
//! Get the character used for the cursor.
wchar_t CGUIEditBox : : getCursorChar ( ) const
{
return CursorChar [ 0 ] ;
}
//! Set the blinktime for the cursor. 2x blinktime is one full cycle.
void CGUIEditBox : : setCursorBlinkTime ( irr : : u32 timeMs )
{
CursorBlinkTime = timeMs ;
}
//! Get the cursor blinktime
irr : : u32 CGUIEditBox : : getCursorBlinkTime ( ) const
{
return CursorBlinkTime ;
}
2024-03-20 19:35:52 +01:00
bool CGUIEditBox : : processMouse ( const SEvent & event )
2023-10-03 20:37:00 +02:00
{
2024-03-20 19:35:52 +01:00
switch ( event . MouseInput . Event ) {
2023-10-03 20:37:00 +02:00
case irr : : EMIE_LMOUSE_LEFT_UP :
2024-03-20 19:35:52 +01:00
if ( Environment - > hasFocus ( this ) ) {
2023-10-03 20:37:00 +02:00
CursorPos = getCursorPos ( event . MouseInput . X , event . MouseInput . Y ) ;
2024-03-20 19:35:52 +01:00
if ( MouseMarking ) {
setTextMarkers ( MarkBegin , CursorPos ) ;
2023-10-03 20:37:00 +02:00
}
MouseMarking = false ;
calculateScrollPos ( ) ;
return true ;
}
break ;
2024-03-20 19:35:52 +01:00
case irr : : EMIE_MOUSE_MOVED : {
if ( MouseMarking ) {
CursorPos = getCursorPos ( event . MouseInput . X , event . MouseInput . Y ) ;
setTextMarkers ( MarkBegin , CursorPos ) ;
calculateScrollPos ( ) ;
return true ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
} break ;
2023-10-03 20:37:00 +02:00
case EMIE_LMOUSE_PRESSED_DOWN :
2023-10-04 20:10:58 +02:00
if ( ! Environment - > hasFocus ( this ) ) { // can happen when events are manually send to the element
2023-10-03 20:37:00 +02:00
BlinkStartTime = os : : Timer : : getTime ( ) ;
MouseMarking = true ;
CursorPos = getCursorPos ( event . MouseInput . X , event . MouseInput . Y ) ;
2024-03-20 19:35:52 +01:00
setTextMarkers ( CursorPos , CursorPos ) ;
2023-10-03 20:37:00 +02:00
calculateScrollPos ( ) ;
return true ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
if ( ! AbsoluteClippingRect . isPointInside (
2024-03-20 19:35:52 +01:00
core : : position2d < s32 > ( event . MouseInput . X , event . MouseInput . Y ) ) ) {
2023-10-03 20:37:00 +02:00
return false ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// move cursor
CursorPos = getCursorPos ( event . MouseInput . X , event . MouseInput . Y ) ;
s32 newMarkBegin = MarkBegin ;
if ( ! MouseMarking )
newMarkBegin = CursorPos ;
MouseMarking = true ;
2024-03-20 19:35:52 +01:00
setTextMarkers ( newMarkBegin , CursorPos ) ;
2023-10-03 20:37:00 +02:00
calculateScrollPos ( ) ;
return true ;
}
}
case EMIE_MMOUSE_PRESSED_DOWN : {
if ( ! AbsoluteClippingRect . isPointInside ( core : : position2d < s32 > (
event . MouseInput . X , event . MouseInput . Y ) ) )
return false ;
if ( ! Environment - > hasFocus ( this ) ) {
BlinkStartTime = os : : Timer : : getTime ( ) ;
}
// move cursor and disable marking
CursorPos = getCursorPos ( event . MouseInput . X , event . MouseInput . Y ) ;
MouseMarking = false ;
setTextMarkers ( CursorPos , CursorPos ) ;
// paste from the primary selection
inputString ( [ & ] {
irr : : core : : stringw inserted_text ;
if ( ! Operator )
return inserted_text ;
const c8 * inserted_text_utf8 = Operator - > getTextFromPrimarySelection ( ) ;
if ( ! inserted_text_utf8 )
return inserted_text ;
2023-10-05 17:55:55 +02:00
core : : utf8ToWString ( inserted_text , inserted_text_utf8 ) ;
2023-10-03 20:37:00 +02:00
return inserted_text ;
} ( ) ) ;
return true ;
}
default :
break ;
}
return false ;
}
s32 CGUIEditBox : : getCursorPos ( s32 x , s32 y )
{
2024-03-20 19:35:52 +01:00
IGUIFont * font = getActiveFont ( ) ;
2023-10-03 20:37:00 +02:00
const u32 lineCount = ( WordWrap | | MultiLine ) ? BrokenText . size ( ) : 1 ;
2024-03-20 19:35:52 +01:00
core : : stringw * txtLine = 0 ;
s32 startPos = 0 ;
x + = 3 ;
2023-10-03 20:37:00 +02:00
2024-03-20 19:35:52 +01:00
for ( u32 i = 0 ; i < lineCount ; + + i ) {
2023-10-03 20:37:00 +02:00
setTextRect ( i ) ;
if ( i = = 0 & & y < CurrentTextRect . UpperLeftCorner . Y )
y = CurrentTextRect . UpperLeftCorner . Y ;
2024-03-20 19:35:52 +01:00
if ( i = = lineCount - 1 & & y > CurrentTextRect . LowerRightCorner . Y )
2023-10-03 20:37:00 +02:00
y = CurrentTextRect . LowerRightCorner . Y ;
// is it inside this region?
2024-03-20 19:35:52 +01:00
if ( y > = CurrentTextRect . UpperLeftCorner . Y & & y < = CurrentTextRect . LowerRightCorner . Y ) {
2023-10-03 20:37:00 +02:00
// we've found the clicked line
txtLine = ( WordWrap | | MultiLine ) ? & BrokenText [ i ] : & Text ;
startPos = ( WordWrap | | MultiLine ) ? BrokenTextPositions [ i ] : 0 ;
break ;
}
}
if ( x < CurrentTextRect . UpperLeftCorner . X )
x = CurrentTextRect . UpperLeftCorner . X ;
2024-03-20 19:35:52 +01:00
if ( ! txtLine )
2023-10-03 20:37:00 +02:00
return 0 ;
s32 idx = font - > getCharacterFromPos ( txtLine - > c_str ( ) , x - CurrentTextRect . UpperLeftCorner . X ) ;
// click was on or left of the line
if ( idx ! = - 1 )
return idx + startPos ;
// click was off the right edge of the line, go to end.
return txtLine - > size ( ) + startPos ;
}
//! Breaks the single text line.
void CGUIEditBox : : breakText ( )
{
if ( ( ! WordWrap & & ! MultiLine ) )
return ;
BrokenText . clear ( ) ; // need to reallocate :/
BrokenTextPositions . set_used ( 0 ) ;
2024-03-20 19:35:52 +01:00
IGUIFont * font = getActiveFont ( ) ;
2023-10-03 20:37:00 +02:00
if ( ! font )
return ;
LastBreakFont = font ;
core : : stringw line ;
core : : stringw word ;
core : : stringw whitespace ;
s32 lastLineStart = 0 ;
s32 size = Text . size ( ) ;
s32 length = 0 ;
s32 elWidth = RelativeRect . getWidth ( ) - 6 ;
wchar_t c ;
2024-03-20 19:35:52 +01:00
for ( s32 i = 0 ; i < size ; + + i ) {
2023-10-03 20:37:00 +02:00
c = Text [ i ] ;
bool lineBreak = false ;
2023-10-04 20:10:58 +02:00
if ( c = = L ' \r ' ) { // Mac or Windows breaks
2023-10-03 20:37:00 +02:00
lineBreak = true ;
c = 0 ;
2023-10-04 20:10:58 +02:00
if ( Text [ i + 1 ] = = L ' \n ' ) { // Windows breaks
2023-10-03 20:37:00 +02:00
// TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
// Instead rework the cursor positioning to be able to handle this (but not in stable release
// branch as users might already expect this behavior).
2024-03-20 19:35:52 +01:00
Text . erase ( i + 1 ) ;
2023-10-03 20:37:00 +02:00
- - size ;
2024-03-20 19:35:52 +01:00
if ( CursorPos > i )
2023-10-03 20:37:00 +02:00
- - CursorPos ;
}
2023-10-04 20:10:58 +02:00
} else if ( c = = L ' \n ' ) { // Unix breaks
2023-10-03 20:37:00 +02:00
lineBreak = true ;
c = 0 ;
}
// don't break if we're not a multi-line edit box
if ( ! MultiLine )
lineBreak = false ;
2024-03-20 19:35:52 +01:00
if ( c = = L ' ' | | c = = 0 | | i = = ( size - 1 ) ) {
2023-10-03 20:37:00 +02:00
// here comes the next whitespace, look if
// we can break the last word to the next line
// We also break whitespace, otherwise cursor would vanish beside the right border.
s32 whitelgth = font - > getDimension ( whitespace . c_str ( ) ) . Width ;
s32 worldlgth = font - > getDimension ( word . c_str ( ) ) . Width ;
2024-03-20 19:35:52 +01:00
if ( WordWrap & & length + worldlgth + whitelgth > elWidth & & line . size ( ) > 0 ) {
2023-10-03 20:37:00 +02:00
// break to next line
length = worldlgth ;
BrokenText . push_back ( line ) ;
BrokenTextPositions . push_back ( lastLineStart ) ;
lastLineStart = i - ( s32 ) word . size ( ) ;
line = word ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// add word to line
line + = whitespace ;
line + = word ;
length + = whitelgth + worldlgth ;
}
word = L " " ;
whitespace = L " " ;
2024-03-20 19:35:52 +01:00
if ( c )
2023-10-03 20:37:00 +02:00
whitespace + = c ;
// compute line break
2024-03-20 19:35:52 +01:00
if ( lineBreak ) {
2023-10-03 20:37:00 +02:00
line + = whitespace ;
line + = word ;
BrokenText . push_back ( line ) ;
BrokenTextPositions . push_back ( lastLineStart ) ;
2024-03-20 19:35:52 +01:00
lastLineStart = i + 1 ;
2023-10-03 20:37:00 +02:00
line = L " " ;
word = L " " ;
whitespace = L " " ;
length = 0 ;
}
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// yippee this is a word..
word + = c ;
}
}
line + = whitespace ;
line + = word ;
BrokenText . push_back ( line ) ;
BrokenTextPositions . push_back ( lastLineStart ) ;
}
// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
// but HAlign according to line-width (pixels) and not by row.
// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
void CGUIEditBox : : setTextRect ( s32 line )
{
2024-03-20 19:35:52 +01:00
if ( line < 0 )
2023-10-03 20:37:00 +02:00
return ;
2024-03-20 19:35:52 +01:00
IGUIFont * font = getActiveFont ( ) ;
2023-10-03 20:37:00 +02:00
if ( ! font )
return ;
core : : dimension2du d ;
// get text dimension
const u32 lineCount = ( WordWrap | | MultiLine ) ? BrokenText . size ( ) : 1 ;
2024-03-20 19:35:52 +01:00
if ( WordWrap | | MultiLine ) {
2023-10-03 20:37:00 +02:00
d = font - > getDimension ( BrokenText [ line ] . c_str ( ) ) ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
d = font - > getDimension ( Text . c_str ( ) ) ;
d . Height = AbsoluteRect . getHeight ( ) ;
}
d . Height + = font - > getKerningHeight ( ) ;
// justification
2024-03-20 19:35:52 +01:00
switch ( HAlign ) {
2023-10-03 20:37:00 +02:00
case EGUIA_CENTER :
// align to h centre
2024-03-20 19:35:52 +01:00
CurrentTextRect . UpperLeftCorner . X = ( FrameRect . getWidth ( ) / 2 ) - ( d . Width / 2 ) ;
CurrentTextRect . LowerRightCorner . X = ( FrameRect . getWidth ( ) / 2 ) + ( d . Width / 2 ) ;
2023-10-03 20:37:00 +02:00
break ;
case EGUIA_LOWERRIGHT :
// align to right edge
CurrentTextRect . UpperLeftCorner . X = FrameRect . getWidth ( ) - d . Width ;
CurrentTextRect . LowerRightCorner . X = FrameRect . getWidth ( ) ;
break ;
default :
// align to left edge
CurrentTextRect . UpperLeftCorner . X = 0 ;
CurrentTextRect . LowerRightCorner . X = d . Width ;
}
2024-03-20 19:35:52 +01:00
switch ( VAlign ) {
2023-10-03 20:37:00 +02:00
case EGUIA_CENTER :
// align to v centre
CurrentTextRect . UpperLeftCorner . Y =
2024-03-20 19:35:52 +01:00
( FrameRect . getHeight ( ) / 2 ) - ( lineCount * d . Height ) / 2 + d . Height * line ;
2023-10-03 20:37:00 +02:00
break ;
case EGUIA_LOWERRIGHT :
// align to bottom edge
CurrentTextRect . UpperLeftCorner . Y =
2024-03-20 19:35:52 +01:00
FrameRect . getHeight ( ) - lineCount * d . Height + d . Height * line ;
2023-10-03 20:37:00 +02:00
break ;
default :
// align to top edge
2024-03-20 19:35:52 +01:00
CurrentTextRect . UpperLeftCorner . Y = d . Height * line ;
2023-10-03 20:37:00 +02:00
break ;
}
2024-03-20 19:35:52 +01:00
CurrentTextRect . UpperLeftCorner . X - = HScrollPos ;
2023-10-03 20:37:00 +02:00
CurrentTextRect . LowerRightCorner . X - = HScrollPos ;
2024-03-20 19:35:52 +01:00
CurrentTextRect . UpperLeftCorner . Y - = VScrollPos ;
2023-10-03 20:37:00 +02:00
CurrentTextRect . LowerRightCorner . Y = CurrentTextRect . UpperLeftCorner . Y + d . Height ;
CurrentTextRect + = FrameRect . UpperLeftCorner ;
}
s32 CGUIEditBox : : getLineFromPos ( s32 pos )
{
if ( ! WordWrap & & ! MultiLine )
return 0 ;
2024-03-20 19:35:52 +01:00
s32 i = 0 ;
while ( i < ( s32 ) BrokenTextPositions . size ( ) ) {
2023-10-03 20:37:00 +02:00
if ( BrokenTextPositions [ i ] > pos )
2024-03-20 19:35:52 +01:00
return i - 1 ;
2023-10-03 20:37:00 +02:00
+ + i ;
}
return ( s32 ) BrokenTextPositions . size ( ) - 1 ;
}
void CGUIEditBox : : inputChar ( wchar_t c )
{
if ( c = = 0 )
return ;
core : : stringw s ( & c , 1 ) ;
inputString ( s ) ;
}
void CGUIEditBox : : inputString ( const core : : stringw & str )
{
if ( ! isEnabled ( ) )
return ;
core : : stringw s ;
u32 len = str . size ( ) ;
2024-03-20 19:35:52 +01:00
if ( MarkBegin ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
// replace marked text
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
s = Text . subString ( 0 , realmbgn ) ;
s . append ( str ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( realmend , Text . size ( ) - realmend ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
2024-03-20 19:35:52 +01:00
CursorPos = realmbgn + len ;
} else if ( OverwriteMode ) {
// check to see if we are at the end of the text
if ( ( u32 ) CursorPos + len < Text . size ( ) ) {
2023-10-03 20:37:00 +02:00
bool isEOL = false ;
s32 EOLPos ;
2024-03-20 19:35:52 +01:00
for ( u32 i = CursorPos ; i < CursorPos + len & & i < Max ; i + + ) {
if ( Text [ i ] = = L ' \n ' | | Text [ i ] = = L ' \r ' ) {
2023-10-03 20:37:00 +02:00
isEOL = true ;
EOLPos = i ;
break ;
}
}
2024-03-20 19:35:52 +01:00
if ( ! isEOL | | Text . size ( ) + len < = Max | | Max = = 0 ) {
2023-10-03 20:37:00 +02:00
s = Text . subString ( 0 , CursorPos ) ;
s . append ( str ) ;
2024-03-20 19:35:52 +01:00
if ( isEOL ) {
// just keep appending to the current line
// This follows the behavior of other gui libraries behaviors
s . append ( Text . subString ( EOLPos , Text . size ( ) - EOLPos ) ) ;
} else {
// replace the next character
s . append ( Text . subString ( CursorPos + len , Text . size ( ) - CursorPos - len ) ) ;
2023-10-03 20:37:00 +02:00
}
Text = s ;
2024-03-20 19:35:52 +01:00
CursorPos + = len ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
} else if ( Text . size ( ) + len < = Max | | Max = = 0 ) {
2023-10-03 20:37:00 +02:00
// add new character because we are at the end of the string
s = Text . subString ( 0 , CursorPos ) ;
s . append ( str ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( CursorPos + len , Text . size ( ) - CursorPos - len ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
2024-03-20 19:35:52 +01:00
CursorPos + = len ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
} else if ( Text . size ( ) + len < = Max | | Max = = 0 ) {
2023-10-03 20:37:00 +02:00
// add new character
s = Text . subString ( 0 , CursorPos ) ;
s . append ( str ) ;
2024-03-20 19:35:52 +01:00
s . append ( Text . subString ( CursorPos , Text . size ( ) - CursorPos ) ) ;
2023-10-03 20:37:00 +02:00
Text = s ;
2024-03-20 19:35:52 +01:00
CursorPos + = len ;
2023-10-03 20:37:00 +02:00
}
BlinkStartTime = os : : Timer : : getTime ( ) ;
setTextMarkers ( 0 , 0 ) ;
breakText ( ) ;
calculateScrollPos ( ) ;
sendGuiEvent ( EGET_EDITBOX_CHANGED ) ;
}
// calculate autoscroll
void CGUIEditBox : : calculateScrollPos ( )
{
if ( ! AutoScroll )
return ;
2024-03-20 19:35:52 +01:00
IGUIFont * font = getActiveFont ( ) ;
2023-10-03 20:37:00 +02:00
if ( ! font )
return ;
s32 cursLine = getLineFromPos ( CursorPos ) ;
2024-03-20 19:35:52 +01:00
if ( cursLine < 0 )
2023-10-03 20:37:00 +02:00
return ;
setTextRect ( cursLine ) ;
const bool hasBrokenText = MultiLine | | WordWrap ;
// Check horizonal scrolling
// NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
{
// get cursor position
// get cursor area
irr : : u32 cursorWidth = font - > getDimension ( CursorChar . c_str ( ) ) . Width ;
core : : stringw * txtLine = hasBrokenText ? & BrokenText [ cursLine ] : & Text ;
2024-03-20 19:35:52 +01:00
s32 cPos = hasBrokenText ? CursorPos - BrokenTextPositions [ cursLine ] : CursorPos ; // column
2024-03-21 17:30:28 +01:00
s32 cStart = font - > getDimension ( txtLine - > subString ( 0 , cPos ) . c_str ( ) ) . Width ; // pixels from text-start
2023-10-03 20:37:00 +02:00
s32 cEnd = cStart + cursorWidth ;
s32 txtWidth = font - > getDimension ( txtLine - > c_str ( ) ) . Width ;
2024-03-20 19:35:52 +01:00
if ( txtWidth < FrameRect . getWidth ( ) ) {
2023-10-03 20:37:00 +02:00
// TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
// This check just fixes the case where it was most noticable (text smaller than clipping area).
HScrollPos = 0 ;
setTextRect ( cursLine ) ;
}
2024-03-20 19:35:52 +01:00
if ( CurrentTextRect . UpperLeftCorner . X + cStart < FrameRect . UpperLeftCorner . X ) {
2023-10-03 20:37:00 +02:00
// cursor to the left of the clipping area
2024-03-20 19:35:52 +01:00
HScrollPos - = FrameRect . UpperLeftCorner . X - ( CurrentTextRect . UpperLeftCorner . X + cStart ) ;
2023-10-03 20:37:00 +02:00
setTextRect ( cursLine ) ;
// TODO: should show more characters to the left when we're scrolling left
// and the cursor reaches the border.
2024-03-20 19:35:52 +01:00
} else if ( CurrentTextRect . UpperLeftCorner . X + cEnd > FrameRect . LowerRightCorner . X ) {
2023-10-03 20:37:00 +02:00
// cursor to the right of the clipping area
2024-03-20 19:35:52 +01:00
HScrollPos + = ( CurrentTextRect . UpperLeftCorner . X + cEnd ) - FrameRect . LowerRightCorner . X ;
2023-10-03 20:37:00 +02:00
setTextRect ( cursLine ) ;
}
}
// calculate vertical scrolling
2024-03-20 19:35:52 +01:00
if ( hasBrokenText ) {
2023-10-03 20:37:00 +02:00
irr : : u32 lineHeight = font - > getDimension ( L " A " ) . Height + font - > getKerningHeight ( ) ;
// only up to 1 line fits?
2024-03-20 19:35:52 +01:00
if ( lineHeight > = ( irr : : u32 ) FrameRect . getHeight ( ) ) {
2023-10-03 20:37:00 +02:00
VScrollPos = 0 ;
setTextRect ( cursLine ) ;
s32 unscrolledPos = CurrentTextRect . UpperLeftCorner . Y ;
s32 pivot = FrameRect . UpperLeftCorner . Y ;
2024-03-20 19:35:52 +01:00
switch ( VAlign ) {
case EGUIA_CENTER :
pivot + = FrameRect . getHeight ( ) / 2 ;
unscrolledPos + = lineHeight / 2 ;
break ;
case EGUIA_LOWERRIGHT :
pivot + = FrameRect . getHeight ( ) ;
unscrolledPos + = lineHeight ;
break ;
default :
break ;
2023-10-03 20:37:00 +02:00
}
2024-03-20 19:35:52 +01:00
VScrollPos = unscrolledPos - pivot ;
2023-10-03 20:37:00 +02:00
setTextRect ( cursLine ) ;
2024-03-20 19:35:52 +01:00
} else {
2023-10-03 20:37:00 +02:00
// First 2 checks are necessary when people delete lines
setTextRect ( 0 ) ;
2024-03-20 19:35:52 +01:00
if ( CurrentTextRect . UpperLeftCorner . Y > FrameRect . UpperLeftCorner . Y & & VAlign ! = EGUIA_LOWERRIGHT ) {
2023-10-03 20:37:00 +02:00
// first line is leaving a gap on top
VScrollPos = 0 ;
2024-03-20 19:35:52 +01:00
} else if ( VAlign ! = EGUIA_UPPERLEFT ) {
u32 lastLine = BrokenTextPositions . empty ( ) ? 0 : BrokenTextPositions . size ( ) - 1 ;
2023-10-03 20:37:00 +02:00
setTextRect ( lastLine ) ;
2024-03-20 19:35:52 +01:00
if ( CurrentTextRect . LowerRightCorner . Y < FrameRect . LowerRightCorner . Y ) {
2023-10-03 20:37:00 +02:00
// last line is leaving a gap on bottom
2024-03-20 19:35:52 +01:00
VScrollPos - = FrameRect . LowerRightCorner . Y - CurrentTextRect . LowerRightCorner . Y ;
2023-10-03 20:37:00 +02:00
}
}
setTextRect ( cursLine ) ;
2024-03-20 19:35:52 +01:00
if ( CurrentTextRect . UpperLeftCorner . Y < FrameRect . UpperLeftCorner . Y ) {
2023-10-03 20:37:00 +02:00
// text above valid area
2024-03-20 19:35:52 +01:00
VScrollPos - = FrameRect . UpperLeftCorner . Y - CurrentTextRect . UpperLeftCorner . Y ;
2023-10-03 20:37:00 +02:00
setTextRect ( cursLine ) ;
2024-03-20 19:35:52 +01:00
} else if ( CurrentTextRect . LowerRightCorner . Y > FrameRect . LowerRightCorner . Y ) {
2023-10-03 20:37:00 +02:00
// text below valid area
2024-03-20 19:35:52 +01:00
VScrollPos + = CurrentTextRect . LowerRightCorner . Y - FrameRect . LowerRightCorner . Y ;
2023-10-03 20:37:00 +02:00
setTextRect ( cursLine ) ;
}
}
}
}
void CGUIEditBox : : calculateFrameRect ( )
{
FrameRect = AbsoluteRect ;
IGUISkin * skin = 0 ;
if ( Environment )
skin = Environment - > getSkin ( ) ;
2024-03-20 19:35:52 +01:00
if ( Border & & skin ) {
FrameRect . UpperLeftCorner . X + = skin - > getSize ( EGDS_TEXT_DISTANCE_X ) + 1 ;
FrameRect . UpperLeftCorner . Y + = skin - > getSize ( EGDS_TEXT_DISTANCE_Y ) + 1 ;
FrameRect . LowerRightCorner . X - = skin - > getSize ( EGDS_TEXT_DISTANCE_X ) + 1 ;
FrameRect . LowerRightCorner . Y - = skin - > getSize ( EGDS_TEXT_DISTANCE_Y ) + 1 ;
2023-10-03 20:37:00 +02:00
}
}
//! set text markers
void CGUIEditBox : : setTextMarkers ( s32 begin , s32 end )
{
2024-03-20 19:35:52 +01:00
if ( begin ! = MarkBegin | | end ! = MarkEnd ) {
2023-10-03 20:37:00 +02:00
MarkBegin = begin ;
MarkEnd = end ;
if ( ! PasswordBox & & Operator & & MarkBegin ! = MarkEnd ) {
// copy to primary selection
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd ;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin ;
core : : stringc s ;
2023-10-05 17:55:55 +02:00
wStringToUTF8 ( s , Text . subString ( realmbgn , realmend - realmbgn ) ) ;
2023-10-03 20:37:00 +02:00
Operator - > copyToPrimarySelection ( s . c_str ( ) ) ;
}
sendGuiEvent ( EGET_EDITBOX_MARKING_CHANGED ) ;
}
}
//! send some gui event to parent
void CGUIEditBox : : sendGuiEvent ( EGUI_EVENT_TYPE type )
{
2024-03-20 19:35:52 +01:00
if ( Parent ) {
2023-10-03 20:37:00 +02:00
SEvent e ;
e . EventType = EET_GUI_EVENT ;
e . GUIEvent . Caller = this ;
e . GUIEvent . Element = 0 ;
e . GUIEvent . EventType = type ;
Parent - > OnEvent ( e ) ;
}
}
//! Returns whether the element takes input from the IME
bool CGUIEditBox : : acceptsIME ( )
{
return isEnabled ( ) ;
}
} // end namespace gui
} // end namespace irr