//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "console/consoleTypes.h" #include "console/console.h" #include "dgl/dgl.h" #include "gui/core/guiCanvas.h" #include "gui/controls/guiMLTextCtrl.h" #include "gui/controls/guiTextEditCtrl.h" #include "gui/core/guiDefaultControlRender.h" #include "core/frameAllocator.h" IMPLEMENT_CONOBJECT(GuiTextEditCtrl); U32 GuiTextEditCtrl::smNumAwake = 0; GuiTextEditCtrl::GuiTextEditCtrl() { mInsertOn = true; mBlockStart = 0; mBlockEnd = 0; mCursorPos = 0; mCursorOn = false; mNumFramesElapsed = 0; mDragHit = false; mTabComplete = false; mScrollDir = 0; mUndoBlockStart = 0; mUndoBlockEnd = 0; mUndoCursorPos = 0; mPasswordText = false; mSinkAllKeyEvents = false; mActive = true; mTextOffsetReset = true; mHistoryDirty = false; mHistorySize = 0; mHistoryLast = -1; mHistoryIndex = 0; mHistoryBuf = NULL; mValidateCommand = StringTable->insert(""); mEscapeCommand = StringTable->insert( "" ); mPasswordMask = StringTable->insert( "*" ); mDeniedSound = dynamic_cast( Sim::findObject( "InputDeniedSound" ) ); mEditCursor = NULL; } GuiTextEditCtrl::~GuiTextEditCtrl() { //delete the history buffer if it exists if (mHistoryBuf) { for (S32 i = 0; i < mHistorySize; i++) delete [] mHistoryBuf[i]; delete [] mHistoryBuf; } } void GuiTextEditCtrl::initPersistFields() { Parent::initPersistFields(); addField("validate", TypeString, Offset(mValidateCommand, GuiTextEditCtrl)); addField("escapeCommand", TypeString, Offset(mEscapeCommand, GuiTextEditCtrl)); addField("historySize", TypeS32, Offset(mHistorySize, GuiTextEditCtrl)); addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl)); addField("tabComplete", TypeBool, Offset(mTabComplete, GuiTextEditCtrl)); addField("deniedSound", TypeAudioProfilePtr, Offset(mDeniedSound, GuiTextEditCtrl)); addField("sinkAllKeyEvents", TypeBool, Offset(mSinkAllKeyEvents, GuiTextEditCtrl)); addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl)); addField("passwordMask", TypeString, Offset(mPasswordMask, GuiTextEditCtrl)); } bool GuiTextEditCtrl::onAdd() { if ( ! Parent::onAdd() ) return false; //create the history buffer if ( mHistorySize > 0 ) { mHistoryBuf = new UTF8*[mHistorySize]; for ( S32 i = 0; i < mHistorySize; i++ ) { mHistoryBuf[i] = new UTF8[GuiTextCtrl::MAX_STRING_LENGTH + 1]; mHistoryBuf[i][0] = '\0'; } } setText(mText); return true; } void GuiTextEditCtrl::onStaticModified(const char* slotName) { if(!dStricmp(slotName, "text")) setText(mText); } bool GuiTextEditCtrl::onWake() { if (! Parent::onWake()) return false; // If this is the first awake text edit control, enable keyboard translation if (smNumAwake == 0) Platform::enableKeyboardTranslation(); ++smNumAwake; return true; } void GuiTextEditCtrl::onSleep() { Parent::onSleep(); // If this is the last awake text edit control, disable keyboard translation --smNumAwake; if (smNumAwake == 0) Platform::disableKeyboardTranslation(); } void GuiTextEditCtrl::execConsoleCallback() { // Execute the console command! if ( mConsoleCommand[0] ) { char buf[16]; dSprintf( buf, sizeof( buf ), "%d", getId() ); Con::setVariable( "$ThisControl", buf ); Con::evaluate( mConsoleCommand, false ); } // Update the console variable: UTF8 buffer[1024]; mTextBuffer.get(buffer, 1024); if ( mConsoleVariable[0] ) Con::setVariable( mConsoleVariable, (char*)buffer ); } void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex ) { FrameTemp textString(inTxt->length()*3 + 1); inTxt->get(textString, inTxt->length()*3 + 1); UTF8 *txt = textString; if(!txt) return; if(!mHistorySize) return; // see if it's already in if(mHistoryLast == -1 || dStrcmp((const UTF8*)txt, (const UTF8*)mHistoryBuf[mHistoryLast])) { if(mHistoryLast == mHistorySize-1) // we're at the history limit... shuffle the pointers around: { UTF8 *first = mHistoryBuf[0]; for(U32 i = 0; i < mHistorySize - 1; i++) mHistoryBuf[i] = mHistoryBuf[i+1]; mHistoryBuf[mHistorySize-1] = first; if(mHistoryIndex > 0) mHistoryIndex--; } else mHistoryLast++; inTxt->get(mHistoryBuf[mHistoryLast], GuiTextCtrl::MAX_STRING_LENGTH); mHistoryBuf[mHistoryLast][GuiTextCtrl::MAX_STRING_LENGTH] = '\0'; } if(moveIndex) mHistoryIndex = mHistoryLast + 1; } void GuiTextEditCtrl::getText( char *dest ) { if ( dest ) mTextBuffer.get((UTF8*)dest, GuiTextCtrl::MAX_STRING_LENGTH+1); } void GuiTextEditCtrl::setText( const char *txt ) { Parent::setText( txt ); if(txt && txt[0] != 0) mTextBuffer.set( txt ); else mTextBuffer.set( "" ); mCursorPos = mTextBuffer.length(); } void GuiTextEditCtrl::reallySetCursorPos( const S32 newPos ) { S32 charCount = mTextBuffer.length(); S32 realPos = newPos > charCount ? charCount : newPos < 0 ? 0 : newPos; if ( realPos != mCursorPos ) { mCursorPos = realPos; setUpdate(); } } S32 GuiTextEditCtrl::setCursorPos( const Point2I &offset ) { Point2I ctrlOffset = localToGlobalCoord( Point2I( 0, 0 ) ); S32 charCount = mTextBuffer.length(); S32 charLength = 0; S32 curX; curX = offset.x - ctrlOffset.x; setUpdate(); //if the cursor is too far to the left if ( curX < 0 ) return -1; //if the cursor is too far to the right if ( curX >= ctrlOffset.x + mBounds.extent.x ) return -2; curX = offset.x - mTextOffset.x; S32 count=0; if(mTextBuffer.length()) { for(count=0; countgetCharXIncrement( mTextBuffer.getChar(count) ); if ( charLength > curX ) break; } } return count; } void GuiTextEditCtrl::onMouseDown( const GuiEvent &event ) { mDragHit = false; //undo any block function mBlockStart = 0; mBlockEnd = 0; //find out where the cursor should be S32 pos = setCursorPos( event.mousePoint ); // if the position is to the left if ( pos == -1 ) mCursorPos = 0; else if ( pos == -2 ) //else if the position is to the right mCursorPos = mTextBuffer.length(); else //else set the mCursorPos mCursorPos = pos; //save the mouseDragPos mMouseDragStart = mCursorPos; // lock the mouse mouseLock(); //set the drag var mDragHit = true; //let the parent get the event setFirstResponder(); } void GuiTextEditCtrl::onMouseDragged( const GuiEvent &event ) { S32 pos = setCursorPos( event.mousePoint ); // if the position is to the left if ( pos == -1 ) mScrollDir = -1; else if ( pos == -2 ) // the position is to the right mScrollDir = 1; else // set the new cursor position { mScrollDir = 0; mCursorPos = pos; } // update the block: mBlockStart = getMin( mCursorPos, mMouseDragStart ); mBlockEnd = getMax( mCursorPos, mMouseDragStart ); if ( mBlockStart < 0 ) mBlockStart = 0; if ( mBlockStart == mBlockEnd ) mBlockStart = mBlockEnd = 0; //let the parent get the event Parent::onMouseDragged(event); } void GuiTextEditCtrl::onMouseUp(const GuiEvent &event) { event; mDragHit = false; mScrollDir = 0; mouseUnlock(); } void GuiTextEditCtrl::saveUndoState() { //save the current state mUndoText.set(&mTextBuffer); mUndoBlockStart = mBlockStart; mUndoBlockEnd = mBlockEnd; mUndoCursorPos = mCursorPos; } bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event) { S32 stringLen = mTextBuffer.length(); setUpdate(); // Ugly, but now I'm cool like MarkF. if(event.keyCode == KEY_BACKSPACE) goto dealWithBackspace; if (event.modifier & SI_SHIFT) { switch (event.keyCode) { case KEY_TAB: if ( mTabComplete ) { Con::executef( this, 2, "onTabComplete", "1" ); return( true ); } case KEY_HOME: mBlockStart = 0; mBlockEnd = mCursorPos; mCursorPos = 0; return true; case KEY_END: mBlockStart = mCursorPos; mBlockEnd = stringLen; mCursorPos = stringLen; return true; case KEY_LEFT: if ((mCursorPos > 0) & (stringLen > 0)) { //if we already have a selected block if (mCursorPos == mBlockEnd) { mCursorPos--; mBlockEnd--; if (mBlockEnd == mBlockStart) { mBlockStart = 0; mBlockEnd = 0; } } else { mCursorPos--; mBlockStart = mCursorPos; if (mBlockEnd == 0) { mBlockEnd = mCursorPos + 1; } } } return true; case KEY_RIGHT: if (mCursorPos < stringLen) { if ((mCursorPos == mBlockStart) && (mBlockEnd > 0)) { mCursorPos++; mBlockStart++; if (mBlockStart == mBlockEnd) { mBlockStart = 0; mBlockEnd = 0; } } else { if (mBlockEnd == 0) { mBlockStart = mCursorPos; mBlockEnd = mCursorPos; } mCursorPos++; mBlockEnd++; } } return true; } } else if (event.modifier & SI_CTRL) { switch(event.keyCode) { // Added UNIX emacs key bindings - just a little hack here... // BJGTODO: Add vi bindings. // Ctrl-B - move one character back case KEY_B: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_LEFT; return(onKeyDown(new_event)); } // Ctrl-F - move one character forward case KEY_F: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_RIGHT; return(onKeyDown(new_event)); } // Ctrl-A - move to the beginning of the line case KEY_A: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_HOME; return(onKeyDown(new_event)); } // Ctrl-E - move to the end of the line case KEY_E: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_END; return(onKeyDown(new_event)); } // Ctrl-P - move backward in history case KEY_P: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_UP; return(onKeyDown(new_event)); } // Ctrl-N - move forward in history case KEY_N: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_DOWN; return(onKeyDown(new_event)); } // Ctrl-D - delete under cursor case KEY_D: { GuiEvent new_event; new_event.modifier = 0; new_event.keyCode = KEY_DELETE; return(onKeyDown(new_event)); } case KEY_U: { GuiEvent new_event; new_event.modifier = SI_CTRL; new_event.keyCode = KEY_DELETE; return(onKeyDown(new_event)); } // End added UNIX emacs key bindings case KEY_C: case KEY_X: // Don't copy/cut password field! if(mPasswordText) return true; UTF8 buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; if (mBlockEnd > 0) { //save the current state saveUndoState(); //copy the text to the clipboard StringBuffer tmp = mTextBuffer.substring(mBlockStart, mBlockEnd - mBlockStart); FrameTemp clipBuff((mBlockEnd-mBlockStart) * 3 + 1); tmp.get(clipBuff, (mBlockEnd-mBlockStart) * 3 + 1); Platform::setClipboard((char*)(UTF8*)clipBuff); //if we pressed ctrl-x, we need to cut the selected text from the control... if (event.keyCode == KEY_X) { mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart); mCursorPos = mBlockStart; } mBlockStart = 0; mBlockEnd = 0; } return true; case KEY_V: { UTF8 buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; //first, make sure there's something in the clipboard to copy... const char *temp = (const char*)Platform::getClipboard(); UTF8 *clipBuf = (UTF8*)GuiMLTextCtrl::stripControlChars(temp); if (dStrlen(clipBuf) <= 0) return true; //save the current state saveUndoState(); //delete anything hilited if (mBlockEnd > 0) { mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart); mCursorPos = mBlockStart; mBlockStart = 0; mBlockEnd = 0; } StringBuffer tmp(clipBuf); S32 pasteLen = tmp.length(); if ((stringLen + pasteLen) > mMaxStrLen) { pasteLen = mMaxStrLen - stringLen; // Trim down to be pasteLen long. tmp.cut(pasteLen, tmp.length() - pasteLen); } if (mCursorPos == stringLen) { mTextBuffer.append(tmp); } else { mTextBuffer.insert(mCursorPos, tmp); } mCursorPos += tmp.length(); return true; } case KEY_Z: if (! mDragHit) { StringBuffer tempBuffer; S32 tempBlockStart; S32 tempBlockEnd; S32 tempCursorPos; //save the current tempBuffer.set(&mTextBuffer); tempBlockStart = mBlockStart; tempBlockEnd = mBlockEnd; tempCursorPos = mCursorPos; //restore the prev mTextBuffer.set(&mUndoText); mBlockStart = mUndoBlockStart; mBlockEnd = mUndoBlockEnd; mCursorPos = mUndoCursorPos; //update the undo mUndoText.set(&tempBuffer); mUndoBlockStart = tempBlockStart; mUndoBlockEnd = tempBlockEnd; mUndoCursorPos = tempCursorPos; return true; } case KEY_DELETE: case KEY_BACKSPACE: //save the current state saveUndoState(); //delete everything in the field mTextBuffer.set(""); mCursorPos = 0; mBlockStart = 0; mBlockEnd = 0; execConsoleCallback(); return true; } } else { switch(event.keyCode) { case KEY_ESCAPE: if ( mEscapeCommand[0] ) { Con::evaluate( mEscapeCommand ); return( true ); } return( Parent::onKeyDown( event ) ); case KEY_RETURN: case KEY_NUMPADENTER: //first validate if (mProfile->mReturnTab) { onLoseFirstResponder(); } updateHistory(&mTextBuffer, true); mHistoryDirty = false; //next exec the alt console command if ( mAltConsoleCommand[0] ) { char buf[16]; dSprintf( buf, sizeof( buf ), "%d", getId() ); Con::setVariable( "$ThisControl", buf ); Con::evaluate( mAltConsoleCommand, false ); } if (mProfile->mReturnTab) { GuiCanvas *root = getRoot(); if (root) { root->tabNext(); return true; } } return true; case KEY_UP: { if(mHistoryDirty) { updateHistory(&mTextBuffer, false); mHistoryDirty = false; } mHistoryIndex--; if(mHistoryIndex >= 0 && mHistoryIndex <= mHistoryLast) setText((char*)mHistoryBuf[mHistoryIndex]); else if(mHistoryIndex < 0) mHistoryIndex = 0; return true; } case KEY_DOWN: if(mHistoryDirty) { updateHistory(&mTextBuffer, false); mHistoryDirty = false; } mHistoryIndex++; if(mHistoryIndex > mHistoryLast) { mHistoryIndex = mHistoryLast + 1; setText(""); } else setText((char*)mHistoryBuf[mHistoryIndex]); return true; case KEY_LEFT: mBlockStart = 0; mBlockEnd = 0; if (mCursorPos > 0) { mCursorPos--; } return true; case KEY_RIGHT: mBlockStart = 0; mBlockEnd = 0; if (mCursorPos < stringLen) { mCursorPos++; } return true; case KEY_BACKSPACE: dealWithBackspace: //save the current state saveUndoState(); if (mBlockEnd > 0) { mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); mCursorPos = mBlockStart; mBlockStart = 0; mBlockEnd = 0; mHistoryDirty = true; // Execute the console command! execConsoleCallback(); } else if (mCursorPos > 0) { mTextBuffer.cut(mCursorPos-1, 1); mCursorPos--; mHistoryDirty = true; // Execute the console command! execConsoleCallback(); } return true; case KEY_DELETE: //save the current state saveUndoState(); if (mBlockEnd > 0) { mHistoryDirty = true; mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); mCursorPos = mBlockStart; mBlockStart = 0; mBlockEnd = 0; // Execute the console command! execConsoleCallback(); } else if (mCursorPos < stringLen) { mHistoryDirty = true; mTextBuffer.cut(mCursorPos, 1); // Execute the console command! execConsoleCallback(); } return true; case KEY_INSERT: mInsertOn = !mInsertOn; return true; case KEY_HOME: mBlockStart = 0; mBlockEnd = 0; mCursorPos = 0; return true; case KEY_END: mBlockStart = 0; mBlockEnd = 0; mCursorPos = stringLen; return true; } } switch ( event.keyCode ) { case KEY_TAB: if ( mTabComplete ) { Con::executef( this, 2, "onTabComplete", "0" ); return( true ); } case KEY_UP: case KEY_DOWN: case KEY_ESCAPE: return Parent::onKeyDown( event ); } if ( mFont->isValidChar( event.ascii ) ) { // Get the character ready to add to a UTF8 string. UTF16 conv[2] = { event.ascii, 0 }; StringBuffer convertedChar(conv); #if 0 { UTF8 pony[10]; convertedChar.get(pony, 10); Con::printf("Got key '%s' (0x%x)", pony, event.ascii ); } #endif //see if it's a number field if ( mProfile->mNumbersOnly ) { if (event.ascii == '-') { //a minus sign only exists at the beginning, and only a single minus sign if ( mCursorPos != 0 ) { playDeniedSound(); return true; } if ( mInsertOn && ( mTextBuffer.getChar(0) == '-' ) ) { playDeniedSound(); return true; } } // BJTODO: This is probably not unicode safe. else if ( event.ascii < '0' || event.ascii > '9' ) { playDeniedSound(); return true; } } //save the current state saveUndoState(); //delete anything highlighted if ( mBlockEnd > 0 ) { mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); mCursorPos = mBlockStart; mBlockStart = 0; mBlockEnd = 0; } if ( ( mInsertOn && ( stringLen < mMaxStrLen ) ) || ( !mInsertOn && ( mCursorPos < mMaxStrLen ) ) ) { if ( mCursorPos == stringLen ) { mTextBuffer.append(convertedChar); mCursorPos++; } else { if ( mInsertOn ) { mTextBuffer.insert(mCursorPos, convertedChar); mCursorPos++; } else { mTextBuffer.cut(mCursorPos, 1); mTextBuffer.insert(mCursorPos, convertedChar); mCursorPos++; } } } else playDeniedSound(); //reset the history index mHistoryDirty = true; //execute the console command if it exists execConsoleCallback(); return true; } //not handled - pass the event to it's parent // Or eat it if that's appropriate. if (mSinkAllKeyEvents) return true; return Parent::onKeyDown( event ); } void GuiTextEditCtrl::setFirstResponder() { Parent::setFirstResponder(); Platform::enableKeyboardTranslation(); } void GuiTextEditCtrl::onLoseFirstResponder() { Platform::disableKeyboardTranslation(); //first, update the history updateHistory( &mTextBuffer, true ); //execute the validate command if ( mValidateCommand[0] ) Con::evaluate( mValidateCommand, false ); // Redraw the control: setUpdate(); } void GuiTextEditCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent) { Parent::parentResized( oldParentExtent, newParentExtent ); mTextOffsetReset = true; } void GuiTextEditCtrl::onRender(Point2I offset, const RectI & /*updateRect*/) { RectI ctrlRect( offset, mBounds.extent ); //if opaque, fill the update rect with the fill color if ( mProfile->mOpaque ) dglDrawRectFill( ctrlRect, mProfile->mFillColor ); //if there's a border, draw the border if ( mProfile->mBorder ) renderBorder( ctrlRect, mProfile ); drawText( ctrlRect, isFirstResponder() ); } void GuiTextEditCtrl::onPreRender() { if ( isFirstResponder() ) { U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; mNumFramesElapsed++; if ( ( timeElapsed > 500 ) && ( mNumFramesElapsed > 3 ) ) { mCursorOn = !mCursorOn; mTimeLastCursorFlipped = Platform::getVirtualMilliseconds(); mNumFramesElapsed = 0; setUpdate(); } //update the cursor if the text is scrolling if ( mDragHit ) { if ( ( mScrollDir < 0 ) && ( mCursorPos > 0 ) ) mCursorPos--; else if ( ( mScrollDir > 0 ) && ( mCursorPos < (S32) mTextBuffer.length() ) ) mCursorPos++; } } } void GuiTextEditCtrl::drawText( const RectI &drawRect, bool isFocused ) { StringBuffer textBuffer; Point2I drawPoint = drawRect.point; Point2I paddingLeftTop, paddingRightBottom; // Just a little sanity. if(mCursorPos > mTextBuffer.length()) mCursorPos = mTextBuffer.length(); // Apply password masking (make the masking char optional perhaps?) if(mPasswordText) { for(U32 i = 0; imTextOffset.x != 0 ? mProfile->mTextOffset.x : 3 ), mProfile->mTextOffset.y); paddingRightBottom = paddingLeftTop; // Center vertically: drawPoint.y += ( ( drawRect.extent.y - paddingLeftTop.y - paddingRightBottom.y - mFont->getHeight() ) / 2 ) + paddingLeftTop.y; // Align horizontally: S32 textWidth; { FrameTemp widthTemp(textBuffer.length()*3+1); textBuffer.get(widthTemp, textBuffer.length()*3+1); textWidth = mFont->getStrWidth( widthTemp ); } if ( drawRect.extent.x - paddingLeftTop.x > textWidth ) { switch( mProfile->mAlignment ) { case GuiControlProfile::RightJustify: drawPoint.x += ( drawRect.extent.x - textWidth - paddingRightBottom.x ); break; case GuiControlProfile::CenterJustify: drawPoint.x += ( ( drawRect.extent.x - textWidth ) / 2 ); break; default: case GuiControlProfile::LeftJustify : drawPoint.x += paddingLeftTop.x; break; } } else drawPoint.x += paddingLeftTop.x; ColorI fontColor = mActive ? mProfile->mFontColor : mProfile->mFontColorNA; // now draw the text Point2I cursorStart, cursorEnd; mTextOffset.y = drawPoint.y; if ( mTextOffsetReset ) { mTextOffset.x = drawPoint.x; mTextOffsetReset = false; } if ( drawRect.extent.x - paddingLeftTop.x > textWidth ) mTextOffset.x = drawPoint.x; else { // Alignment affects large text if ( mProfile->mAlignment == GuiControlProfile::RightJustify || mProfile->mAlignment == GuiControlProfile::CenterJustify ) { if ( mTextOffset.x + textWidth < (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x) mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth; } } // calculate the cursor if ( isFocused ) { // Where in the string are we? StringBuffer preCursor = mTextBuffer.substring(0, mCursorPos); S32 cursorOffset=0, charWidth=0; UTF16 tempChar = mTextBuffer.getChar(mCursorPos); // Alright, we want to terminate things momentarily. if(mCursorPos > 0) { FrameTemp cursorTemp(preCursor.length()*3+1); preCursor.get(cursorTemp, preCursor.length()*3+1); cursorOffset = mFont->getStrWidth(cursorTemp); } else cursorOffset = 0; if ( tempChar ) charWidth = mFont->getCharWidth( tempChar ); else charWidth = paddingRightBottom.x; if( mTextOffset.x + cursorOffset + charWidth >= (drawRect.point.x + drawRect.extent.x) - paddingLeftTop.x ) { // Cursor somewhere beyond the textcontrol, // skip forward roughly 25% of the total width (if possible) S32 skipForward = drawRect.extent.x / 4; if ( cursorOffset + skipForward > textWidth ) mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth; else mTextOffset.x -= skipForward; } else if( mTextOffset.x + cursorOffset < drawRect.point.x + paddingLeftTop.x ) { // Cursor somewhere before the textcontrol // skip backward roughly 25% of the total width (if possible) S32 skipBackward = drawRect.extent.x / 4; if ( cursorOffset - skipBackward < 0 ) mTextOffset.x = drawRect.point.x + paddingLeftTop.x; else mTextOffset.x += skipBackward; } cursorStart.x = mTextOffset.x + cursorOffset; cursorEnd.x = cursorStart.x; S32 cursorHeight = mFont->getHeight(); if ( cursorHeight < drawRect.extent.y ) { cursorStart.y = drawPoint.y; cursorEnd.y = cursorStart.y + cursorHeight; } else { cursorStart.y = drawRect.point.y; cursorEnd.y = cursorStart.y + drawRect.extent.y; } } //draw the text if ( !isFocused ) mBlockStart = mBlockEnd = 0; //also verify the block start/end if ((mBlockStart > textBuffer.length() || (mBlockEnd > textBuffer.length()) || (mBlockStart > mBlockEnd))) mBlockStart = mBlockEnd = 0; UTF16 temp; Point2I tempOffset = mTextOffset; //draw the portion before the highlight if ( mBlockStart > 0 ) { StringBuffer preBuffer = textBuffer.substring(0, mBlockStart); FrameTemp preString(preBuffer.length()*3+1); preBuffer.get(preString, preBuffer.length()*3+1); dglSetBitmapModulation( fontColor ); dglDrawText( mFont, tempOffset, preString, mProfile->mFontColors ); tempOffset.x += mFont->getStrWidth( (const UTF8*)preString ); } //draw the hilighted portion if ( mBlockEnd > 0 ) { StringBuffer highlightBuff = mTextBuffer.substring(mBlockStart, mBlockEnd-mBlockStart); FrameTemp highlightTemp(highlightBuff.length()*3 + 1); highlightBuff.get(highlightTemp, highlightBuff.length()*3 + 1); S32 highlightWidth = mFont->getStrWidth( highlightTemp ); dglDrawRectFill( Point2I( tempOffset.x, drawRect.point.y ), Point2I( tempOffset.x + highlightWidth, drawRect.point.y + drawRect.extent.y - 1), mProfile->mFillColorHL ); dglSetBitmapModulation( mProfile->mFontColorHL ); dglDrawText( mFont, tempOffset, highlightTemp, mProfile->mFontColors ); tempOffset.x += highlightWidth; } //draw the portion after the highlite if(mBlockEnd < mTextBuffer.length()) { StringBuffer finalBuff = mTextBuffer.substring(mBlockEnd, mTextBuffer.length() - mBlockEnd); FrameTemp finalTemp(finalBuff.length()*3 + 1); finalBuff.get(finalTemp, finalBuff.length()*3 + 1); dglSetBitmapModulation( fontColor ); dglDrawText( mFont, tempOffset, finalTemp, mProfile->mFontColors ); } //draw the cursor if ( isFocused && mCursorOn ) dglDrawLine( cursorStart, cursorEnd, mProfile->mCursorColor ); } bool GuiTextEditCtrl::hasText() { return (mTextBuffer.length()); } void GuiTextEditCtrl::playDeniedSound() { if ( mDeniedSound ) { AUDIOHANDLE handle = alxCreateSource( mDeniedSound ); alxPlay( handle ); } } bool GuiTextEditCtrl::initCursors() { if ( mEditCursor == NULL ) { SimObject *obj; obj = Sim::findObject("TextEditCursor"); mEditCursor = dynamic_cast(obj); return( mEditCursor != NULL ); } else return(true); } void GuiTextEditCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) { showCursor = true; if( initCursors() ) { Point2I mousePos = lastGuiEvent.mousePoint; RectI winRect = mBounds; winRect.point = localToGlobalCoord( winRect.point ); if( winRect.pointInRect( mousePos ) ) cursor = mEditCursor; else cursor = NULL; } } const char *GuiTextEditCtrl::getScriptValue() { FrameTemp temp(mTextBuffer.length() * 3 + 1); mTextBuffer.get(temp, mTextBuffer.length() * 3 + 1); return StringTable->insert((const char*)(UTF8*)temp); } void GuiTextEditCtrl::setScriptValue(const char *value) { mTextBuffer.set(value); mCursorPos = mTextBuffer.length() - 1; } ConsoleMethod( GuiTextEditCtrl, getText, const char*, 2, 2, "textEditCtrl.getText()" ) { argc; argv; if( !object->hasText() ) return StringTable->insert(""); char *retBuffer = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH ); object->getText( retBuffer ); return retBuffer; } ConsoleMethod( GuiTextEditCtrl, getCursorPos, S32, 2, 2, "textEditCtrl.getCursorPos()" ) { argc; argv; return( object->getCursorPos() ); } ConsoleMethod( GuiTextEditCtrl, setCursorPos, void, 3, 3, "textEditCtrl.setCursorPos( newPos )" ) { argc; object->reallySetCursorPos( dAtoi( argv[2] ) ); }