Browse Source

Updates to GuiTextEditCtrl

This includes some serious updates to the GuiTextEditCtrl. The biggest change is that controls  now have an InputMode that can be used to require certain types of characters like plain text, a number, or a decimal value. There's also just a lot of clean up. There was an actual goto statement in there! It's gone now. I also removed a number of features that might have been cool 30 years ago, but seem a little out of place now. Script functions are now in their own file too.
Peter Robinson 4 years ago
parent
commit
35899e3eda

+ 2 - 13
editor/EditorConsole/EditorConsole.cs

@@ -34,11 +34,6 @@ function EditorConsole::create(%this)
 		AltCommand="EditorConsole.eval();";
 		MaxLength="255";
 		active = "1";
-		HistorySize="40";
-		password="0";
-		TabComplete="0";
-		SinkAllKeyEvents="1";
-		UseSiblingScroller="1";
 	};
 	ThemeManager.setProfile(%this.consoleEntry, "textEditProfile");
 	%this.guiPage.add(%this.consoleEntry);
@@ -108,15 +103,9 @@ function EditorConsole::eval(%this)
 {
 	%text = trim(%this.consoleEntry.getValue());
 
-    if(strpos(%text, "(") == -1)
+    if(strpos(%text, "(") == -1 && strpos(%text, "=") == -1 && strpos(%text, " ") == -1 && strpos(%text, "{") == -1 && strpos(%text, "}") == -1)
     {
-        if(strpos(%text, "=") == -1 && strpos(%text, " ") == -1)
-        {
-            if(strpos(%text, "{") == -1 && strpos(%text, "}") == -1)
-            {
-                %text = %text @ "()";
-            }
-        }
+        %text = %text @ "()";
     }
 
     %pos = strlen(%text) - 1;

+ 0 - 1
editor/ProjectManager/ProjectManager.cs

@@ -26,7 +26,6 @@ function ProjectManager::create(%this)
 
 	%this.comingSoon = new GuiControl()
 	{
-		Profile = ThemeManager.activeTheme.panelProfile;
 		HorizSizing="center";
 		VertSizing="center";
 		Position="412 324";

+ 1 - 0
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj

@@ -1186,6 +1186,7 @@
     <ClInclude Include="..\..\source\gui\guiProgressCtrl_ScriptBinding.h" />
     <ClInclude Include="..\..\source\gui\guiSliderCtrl.h" />
     <ClInclude Include="..\..\source\gui\guiTextEditCtrl.h" />
+    <ClInclude Include="..\..\source\gui\guiTextEditCtrl_ScriptBinding.h" />
     <ClInclude Include="..\..\source\gui\guiTextEditSliderCtrl.h" />
     <ClInclude Include="..\..\source\gui\guiTextListCtrl.h" />
     <ClInclude Include="..\..\source\gui\guiTreeViewCtrl.h" />

+ 3 - 0
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj.filters

@@ -3352,6 +3352,9 @@
     <ClInclude Include="..\..\source\gui\editor\guiMenuBarCtrl_ScriptBinding.h">
       <Filter>gui\editor</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\source\gui\guiTextEditCtrl_ScriptBinding.h">
+      <Filter>gui</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Torque 2D.rc" />

+ 1 - 4
engine/source/gui/editor/guiInspector.cc

@@ -537,7 +537,6 @@ GuiControl* GuiInspectorField::constructEditControl(S32 width)
    char szBuffer[512];
    dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() );
    retCtrl->setField("AltCommand", szBuffer );
-   retCtrl->setField("Validate", szBuffer );
    retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 30));
 
    return retCtrl;
@@ -1126,12 +1125,11 @@ void GuiInspectorDynamicField::renameField( StringTableEntry newFieldName )
    // Assign our dynamic field pointer (where we retrieve field information from) to our new field pointer
    mDynField = newEntry;
 
-   // Lastly we need to reassign our Command and AltCommand fields for our value edit control
+   // Lastly we need to reassign our AltCommand fields for our value edit control
    char szBuffer[512];
    dSprintf( szBuffer, 512, "%d.%s = %d.getText();",mTarget->getId(), getFieldName(), mEdit->getId() );
    mEdit->setExtent(Point2I((getExtent().x / 2) - 20, 30));
    mEdit->setField("AltCommand", szBuffer );
-   mEdit->setField("Validate", szBuffer );
 }
 
 ConsoleMethod( GuiInspectorDynamicField, renameField, void, 3,3, "field.renameField(newDynamicFieldName);" )
@@ -1178,7 +1176,6 @@ GuiControl* GuiInspectorDynamicField::constructRenameControl()
    dynamic_cast<GuiTextEditCtrl*>(retCtrl)->setText( getFieldName() );
    retCtrl->setExtent(Point2I((getExtent().x / 2) - 20, 30));
    retCtrl->setField("AltCommand", szBuffer );
-   retCtrl->setField("Validate", szBuffer);
    addObject( retCtrl );
 
 

+ 0 - 1
engine/source/gui/editor/guiInspectorTypes.cc

@@ -295,7 +295,6 @@ GuiControl* GuiInspectorTypeFileName::constructEditControl(S32 width)
    char szBuffer[512];
    dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(),retCtrl->getId() );
    retCtrl->setField("AltCommand", szBuffer );
-   retCtrl->setField("Validate", szBuffer );
    retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - (mGroup->mInspector->mControlOffset.x + 34), 30));
 
    mBrowseButton = new GuiButtonCtrl();

+ 5 - 3
engine/source/gui/guiConsoleEditCtrl.cc

@@ -34,6 +34,8 @@ GuiConsoleEditCtrl::GuiConsoleEditCtrl()
    mSinkAllKeyEvents = true;
    mSiblingScroller = NULL;
    mUseSiblingScroller = true;
+   mHistorySize = 40;
+   mReturnCausesTab = false;
 }
 
 void GuiConsoleEditCtrl::initPersistFields()
@@ -52,13 +54,13 @@ bool GuiConsoleEditCtrl::onKeyDown(const GuiEvent &event)
    if (event.keyCode == KEY_TAB) 
    {
       // Get a buffer that can hold the completed text...
-      FrameTemp<UTF8> tmpBuff(GuiTextCtrl::MAX_STRING_LENGTH);
+      FrameTemp<UTF8> tmpBuff(GuiTextEditCtrl::MAX_STRING_LENGTH);
       // And copy the text to be completed into it.
-      mTextBuffer.getCopy8(tmpBuff, GuiTextCtrl::MAX_STRING_LENGTH);
+      mTextBuffer.getCopy8(tmpBuff, GuiTextEditCtrl::MAX_STRING_LENGTH);
 
       // perform the completion
       bool forward = event.modifier & SI_SHIFT;
-      mCursorPos = Con::tabComplete(tmpBuff, mCursorPos, GuiTextCtrl::MAX_STRING_LENGTH, forward);
+      mCursorPos = Con::tabComplete(tmpBuff, mCursorPos, GuiTextEditCtrl::MAX_STRING_LENGTH, forward);
 
       // place results in our buffer.
       mTextBuffer.set(tmpBuff);

+ 1 - 1
engine/source/gui/guiControl_ScriptBinding.h

@@ -117,7 +117,7 @@ ConsoleMethodWithDocs( GuiControl, setActive, ConsoleVoid, 3, 3, ( isActive ))
 }
 
 /*! Use the isActive method to determine if this control is active.
-    An inactive control may visible, but will not accept inputs. It will also normally re-shade or re-skin itself to reflect its inactive state
+    An inactive control may be visible, but will not accept inputs. It will also normally re-shade or re-skin itself to reflect its inactive state
     @return Returns true if this control is active.
 */
 ConsoleMethodWithDocs( GuiControl, isActive, ConsoleBool, 2, 2, ())

+ 259 - 165
engine/source/gui/guiTextEditCtrl.cc

@@ -31,6 +31,8 @@
 #include "memory/frameAllocator.h"
 #include "string/unicode.h"
 
+#include "guiTextEditCtrl_ScriptBinding.h"
+
 IMPLEMENT_CONOBJECT(GuiTextEditCtrl);
 
 U32 GuiTextEditCtrl::smNumAwake = 0;
@@ -45,7 +47,6 @@ GuiTextEditCtrl::GuiTextEditCtrl()
    mNumFramesElapsed = 0;
 
    mDragHit = false;
-   mTabComplete = false;
    mScrollDir = 0;
 
    mUndoBlockStart = 0;
@@ -53,6 +54,7 @@ GuiTextEditCtrl::GuiTextEditCtrl()
    mUndoCursorPos = 0;
    mPasswordText = false;
 
+   mReturnCausesTab = false;
    mSinkAllKeyEvents = false;
 
    mActive = true;
@@ -70,8 +72,9 @@ GuiTextEditCtrl::GuiTextEditCtrl()
    mPasswordMask = StringTable->insert( "*" );
 
    mEditCursor = NULL;
-   mMaxStrLen = GuiTextEditCtrl::MAX_STRING_LENGTH;
-   mTruncateWhenUnfocused = false;
+   mMaxStrLen = MAX_STRING_LENGTH;
+
+   mInputMode = AllText;
 }
 
 GuiTextEditCtrl::~GuiTextEditCtrl()
@@ -86,20 +89,35 @@ GuiTextEditCtrl::~GuiTextEditCtrl()
    }
 }
 
+static EnumTable::Enums inputModeEnums[] =
+{
+	{ GuiTextEditCtrl::AllText, "AllText" },
+	{ GuiTextEditCtrl::Decimal, "Decimal" },
+	{ GuiTextEditCtrl::Number, "Number" },
+	{ GuiTextEditCtrl::Alpha, "Alpha" },
+	{ GuiTextEditCtrl::AlphaNumeric, "AlphaNumeric" }
+};
+static EnumTable gInputModeTable(5, &inputModeEnums[0]);
+
 void GuiTextEditCtrl::initPersistFields()
 {
    Parent::initPersistFields();
 
-   addField("validate",          TypeString,    Offset(mValidateCommand,   GuiTextEditCtrl));
+   addDepricatedField("validate");
+   addDepricatedField("truncate");
+   addDepricatedField("passwordMask");
+   addDepricatedField("historySize");
+   addDepricatedField("tabComplete");
+
+   addGroup("Text Edit");
+   addField("validateCommand",   TypeString,    Offset(mValidateCommand,   GuiTextEditCtrl));
    addField("escapeCommand",     TypeString,    Offset(mEscapeCommand,     GuiTextEditCtrl));
-   addField("historySize",       TypeS32,       Offset(mHistorySize,       GuiTextEditCtrl));
-   addField("tabComplete",       TypeBool,      Offset(mTabComplete,       GuiTextEditCtrl));     
-   addField("deniedSound",       TypeAudioAssetPtr, Offset(mDeniedSound, GuiTextEditCtrl));
    addField("sinkAllKeyEvents",  TypeBool,      Offset(mSinkAllKeyEvents,  GuiTextEditCtrl));
-   addField("password",          TypeBool,      Offset(mPasswordText,      GuiTextEditCtrl));
-   addField("passwordMask",      TypeString,    Offset(mPasswordMask,      GuiTextEditCtrl));
-   addField("maxLength",		TypeS32, Offset(mMaxStrLen, GuiTextEditCtrl));
-   addField("truncate",			TypeBool, Offset(mTruncateWhenUnfocused, GuiTextEditCtrl));
+   addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl));
+   addField("returnCausesTab", TypeBool, Offset(mReturnCausesTab, GuiTextEditCtrl));
+   addProtectedField("maxLength", TypeS32,		Offset(mMaxStrLen,			GuiTextEditCtrl), &setMaxLengthProperty, &defaultProtectedGetFn, "The max number of characters that can be entered into the text edit box.");
+   addProtectedField("inputMode", TypeEnum,		Offset(mInputMode,			GuiTextEditCtrl), &setInputMode, &getInputMode, &writeInputMode, 1, &gInputModeTable, "InputMode allows different characters to be entered.");	
+   endGroup("Text Edit");
 }
 
 bool GuiTextEditCtrl::onAdd()
@@ -113,7 +131,7 @@ bool GuiTextEditCtrl::onAdd()
       mHistoryBuf = new UTF16*[mHistorySize];
       for ( S32 i = 0; i < mHistorySize; i++ )
       {
-         mHistoryBuf[i] = new UTF16[GuiTextCtrl::MAX_STRING_LENGTH + 1];
+         mHistoryBuf[i] = new UTF16[MAX_STRING_LENGTH + 1];
          mHistoryBuf[i][0] = '\0';
       }
    }
@@ -223,8 +241,8 @@ void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex )
       else
          mHistoryLast++;
 
-      inTxt->getCopy(mHistoryBuf[mHistoryLast], GuiTextCtrl::MAX_STRING_LENGTH);
-      mHistoryBuf[mHistoryLast][GuiTextCtrl::MAX_STRING_LENGTH] = '\0';
+      inTxt->getCopy(mHistoryBuf[mHistoryLast], MAX_STRING_LENGTH);
+      mHistoryBuf[mHistoryLast][MAX_STRING_LENGTH] = '\0';
    }
 
    if(moveIndex)
@@ -234,7 +252,7 @@ void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex )
 void GuiTextEditCtrl::getText( char *dest )
 {
    if ( dest )
-      mTextBuffer.getCopy8((UTF8*)dest, GuiTextCtrl::MAX_STRING_LENGTH+1);
+      mTextBuffer.getCopy8((UTF8*)dest, MAX_STRING_LENGTH+1);
 }  
  
 void GuiTextEditCtrl::setText( const UTF8 *txt )
@@ -314,15 +332,18 @@ void GuiTextEditCtrl::selectAllText()
    setUpdate();
 }
 
-void GuiTextEditCtrl::forceValidateText()
+bool GuiTextEditCtrl::validate()
 {
-   if ( mValidateCommand[0] )
-   {
-      char buf[16];
-      dSprintf(buf, sizeof(buf), "%d", getId());
-      Con::setVariable("$ThisControl", buf);
-      Con::evaluate( mValidateCommand, false );
-   }
+	bool valid = true;
+	if ( mValidateCommand[0] )
+	{
+		valid = Con::evaluate( mValidateCommand, false );
+	}
+	else if (isMethod("onValidate"))
+	{
+		valid = Con::executef(this, 2, "onValidate");
+	}
+	return valid;
 }
 
 void GuiTextEditCtrl::reallySetCursorPos( const S32 newPos )
@@ -603,21 +624,20 @@ 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.keyCode == KEY_BACKSPACE)
+   {
+	   handleBackSpace();
+	   return true;
+   }
 
    if (event.modifier & SI_SHIFT)
     {
         switch (event.keyCode)
         {
             case KEY_TAB:
-               if ( mTabComplete )
-               {
-                  Con::executef( this, 2, "onTabComplete", "1" );
-                  return( true );
-               }
-               break; //*** DAW: We don't want to fall through if we don't handle the TAB here.
+				if (isMethod("onTab"))
+					Con::executef(this, 2, "onTab", "1");
+               return tabPrev();
 
             case KEY_HOME:
                mBlockStart = 0;
@@ -861,19 +881,10 @@ bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
          case KEY_RETURN:
          case KEY_NUMPADENTER:
             //first validate
-            if (mProfile->mReturnTab)
+            if (!validate())
             {
-               //execute the validate command
-               if ( mValidateCommand[0] )
-               {
-                  char buf[16];
-                  dSprintf(buf, sizeof(buf), "%d", getId());
-                  Con::setVariable("$ThisControl", buf);
-                  Con::evaluate( mValidateCommand, false );
-               }
-               
-               if( isMethod( "onValidate" ) )
-                  Con::executef( this, 2, "onValidate" );
+				//The contents must be invalid. Stop here.
+				return true;
             }
 
             updateHistory(&mTextBuffer, true);
@@ -884,16 +895,11 @@ bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
 
             // Notify of Return
             if ( isMethod("onReturn") )
-               Con::executef( this, 2, "onReturn" );
+               Con::executef( this, 1, "onReturn" );
 
-            if (mProfile->mReturnTab)
+            if (mReturnCausesTab)
             {
-               GuiCanvas *root = getRoot();
-               if (root)
-               {
-                  root->tabNext();
-                  return true;
-               }
+               tabNext();
             }
             return true;
 
@@ -950,31 +956,8 @@ bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
             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();
-            }
+			//This should have been handled above, but it is here again for safety.
+            handleBackSpace();
             return true;
 
          case KEY_DELETE:
@@ -1025,11 +1008,7 @@ dealWithBackspace:
    switch ( event.keyCode )
    {
       case KEY_TAB:
-         if ( mTabComplete )
-         {
-            Con::executef( this, 2, "onTabComplete", "0" );
-            return( true );
-         }
+		  return tabNext();
       case KEY_UP:
       case KEY_DOWN:
       case KEY_ESCAPE:
@@ -1044,31 +1023,12 @@ dealWithBackspace:
       // Get the character ready to add to a UTF8 string.
       UTF16 convertedChar[2] = { event.ascii, 0 };
 
-      //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;
-         }
-      }
+		//Stop characters that aren't allowed based on InputMode
+		if (!inputModeValidate(event.ascii, mCursorPos))
+		{
+			keyDenied();
+			return true;
+		}
 
       //save the current state
       saveUndoState();
@@ -1106,7 +1066,7 @@ dealWithBackspace:
          }
       }
       else
-         playDeniedSound();
+		  keyDenied();
 
       //reset the history index
       mHistoryDirty = true;
@@ -1126,6 +1086,58 @@ dealWithBackspace:
    return Parent::onKeyDown( event );
 }
 
+bool GuiTextEditCtrl::tabNext()
+{
+	if (isMethod("onTab"))
+		Con::executef(this, 2, "onTab", "0");
+
+	GuiCanvas *root = getRoot();
+	if (root)
+	{
+		root->tabNext();
+		return true;
+	}
+	return false;
+}
+
+bool GuiTextEditCtrl::tabPrev()
+{
+	if (isMethod("onTab"))
+		Con::executef(this, 2, "onTab", "1");
+
+	GuiCanvas *root = getRoot();
+	if (root)
+	{
+		root->tabPrev();
+		return true;
+	}
+	return false;
+}
+
+void GuiTextEditCtrl::handleBackSpace()
+{
+	//save the current state
+	saveUndoState();
+
+	if (mBlockEnd > 0)
+	{
+		mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
+		mCursorPos = mBlockStart;
+		mBlockStart = 0;
+		mBlockEnd = 0;
+		mHistoryDirty = true;
+	}
+	else if (mCursorPos > 0)
+	{
+		mTextBuffer.cut(mCursorPos - 1, 1);
+		mCursorPos--;
+		mHistoryDirty = true;
+	}
+
+	// Execute the console command!
+	execConsoleCallback();
+}
+
 void GuiTextEditCtrl::setFirstResponder()
 {
    Parent::setFirstResponder();
@@ -1143,19 +1155,15 @@ void GuiTextEditCtrl::onLoseFirstResponder()
    updateHistory( &mTextBuffer, true );
 
    //execute the validate command
-   if ( mValidateCommand[0] )
+   bool valid = validate();
+
+   if (valid)
    {
-      char buf[16];
-      dSprintf(buf, sizeof(buf), "%d", getId());
-      Con::setVariable("$ThisControl", buf);
-      Con::evaluate( mValidateCommand, false );
+	   execAltConsoleCallback();
    }
 
-   if( isMethod( "onValidate" ) )
-      Con::executef( this, 2, "onValidate" );
-
    if( isMethod( "onLoseFirstResponder" ) )
-      Con::executef( this, 2, "onLoseFirstResponder" );
+      Con::executef( this, 2, "onLoseFirstResponder", valid);
 
    // Redraw the control:
    setUpdate();
@@ -1402,26 +1410,11 @@ void GuiTextEditCtrl::drawText( const RectI &drawRect, GuiControlState currentSt
 	   //draw the portion after the highlite
 	   if(mBlockEnd < (S32)mTextBuffer.length())
 	   {
-		   // Special handling if we are truncating when the ctrl is unfocused
-		   if (currentState != SelectedState && mTruncateWhenUnfocused)
-		   {
-			  StringBuffer terminationString = "...";
-			  S32 width = mBounds.extent.x;
-			  StringBuffer truncatedBuffer = truncate(textBuffer, terminationString, width);
-			  const UTF16* truncatedBufferPtr = truncatedBuffer.getPtr();
-			  U32 finalBuffLen = truncatedBuffer.length();
-
-			  dglSetBitmapModulation( fontColor );
-			  dglDrawTextN( mProfile->mFont, tempOffset, truncatedBufferPtr, finalBuffLen, mProfile->mFontColors );
-		   }
-		   else
-		   {
-			  const UTF16* finalBuff = textBuffer.getPtr() + mBlockEnd;
-			  U32 finalBuffLen = mTextBuffer.length() - mBlockEnd;
+			const UTF16* finalBuff = textBuffer.getPtr() + mBlockEnd;
+			U32 finalBuffLen = mTextBuffer.length() - mBlockEnd;
 
-			  dglSetBitmapModulation( fontColor );
-			  dglDrawTextN( mProfile->mFont, tempOffset, finalBuff, finalBuffLen, mProfile->mFontColors );
-		   }
+			dglSetBitmapModulation( fontColor );
+			dglDrawTextN( mProfile->mFont, tempOffset, finalBuff, finalBuffLen, mProfile->mFontColors );
 	   }
 
 	   //draw the cursor
@@ -1437,13 +1430,10 @@ bool GuiTextEditCtrl::hasText()
    return (mTextBuffer.length());
 }
 
-void GuiTextEditCtrl::playDeniedSound()
+void GuiTextEditCtrl::keyDenied()
 {
-    if ( mDeniedSound.notNull() )
-   {
-      AUDIOHANDLE handle = alxCreateSource( mDeniedSound );
-      alxPlay( handle );
-   }
+	if (isMethod("onDenied"))
+		Con::executef(this, 1, "onDenied");
 }
 
 const char *GuiTextEditCtrl::getScriptValue()
@@ -1515,41 +1505,145 @@ S32 GuiTextEditCtrl::textBufferWidth(StringBuffer buffer)
 	return charLength;
 }
 
-ConsoleMethod( GuiTextEditCtrl, getText, const char*, 2, 2, "() Get the contents of the textedit control\n"
-              "@return Returns the current textedit buffer.")
+GuiTextEditCtrl::InputMode GuiTextEditCtrl::getInputModeEnum(const char* label)
 {
-   if( !object->hasText() )
-      return StringTable->EmptyString;
+	// Search for Mnemonic.
+	for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
+	{
+		if (dStricmp(inputModeEnums[i].label, label) == 0)
+			return (InputMode)inputModeEnums[i].index;
+	}
 
-   char *retBuffer = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH );
-   object->getText( retBuffer );
+	// Warn.
+	Con::warnf("GuiTextEditCtrl::getInputModeEnum() - Invalid mode of '%s'", label);
 
-   return retBuffer;
+	return (InputMode)-1;
 }
 
-
-ConsoleMethod( GuiTextEditCtrl, getCursorPos, S32, 2, 2, "() Use the getCursorPos method to get the current position of the text cursor in the control.\n"
-                                                                "@return Returns the current position of the text cursor in the control, where 0 is at the beginning of the line, 1 is after the first letter, and so on.\n"
-                                                                "@sa setCursorPos")
+const char* GuiTextEditCtrl::getInputModeDescription(const InputMode mode)
 {
-   return( object->getCursorPos() );
+	// Search for Mnemonic.
+	for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
+	{
+		if (inputModeEnums[i].index == mode)
+			return inputModeEnums[i].label;
+	}
+
+	// Warn.
+	Con::warnf("GuiTextEditCtrl::getInputModeDescription() - Invalid input mode.");
+
+	return StringTable->EmptyString;
 }
 
-ConsoleMethod( GuiTextEditCtrl, setCursorPos, void, 3, 3, "( newPos ) Use the setCursorPos method to set the current position of text cursor to newPos.\n"
-                                                                "If the requested position is beyond the end of text, the cursor will be placed after the last letter. If the value is less than zero, the cursor will be placed at the front of the entry.\n"
-                                                                "@param newPos The new position to place the cursor at, where 0 is at the beginning of the line, 1 is after the first letter, and so on.\n"
-                                                                "@return No return value.\n"
-                                                                "@sa getCursorPos")
+//Returns true if valid, false if invalid
+bool GuiTextEditCtrl::inputModeValidate(const U16 key, S32 cursorPos)
 {
-   object->reallySetCursorPos( dAtoi( argv[2] ) );
+	if (key == '-')
+	{
+		if (mInputMode == Alpha || mInputMode == AlphaNumeric)
+		{
+			return false;
+		}
+		else if (mInputMode == Decimal || mInputMode == Number)
+		{
+			//a minus sign only exists at the beginning, and only a single minus sign
+			if (cursorPos != 0 || (mInsertOn && mTextBuffer.getChar(0) == '-'))
+			{
+				return false;
+			}
+		}
+	}
+	else if (key >= '0' && key <= '9')
+	{
+		if (mInputMode == Alpha)
+		{
+			return false;
+		}
+	}
+	else if (key == '.')
+	{
+		if (mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
+		{
+			return false;
+		}
+		else if (mInputMode == Decimal)
+		{
+			if (!mInsertOn && mTextBuffer.getChar(cursorPos) == '.')
+			{
+				return true;
+			}
+
+			const char* dot = dStrchr(mTextBuffer.getPtr8(), '.');
+			if (dot != NULL)
+			{
+				return false;
+			}
+		}
+	}
+	else if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z'))
+	{
+		if (mInputMode == Decimal || mInputMode == Number)
+		{
+			return false;
+		}
+	}
+	else if (key == 32)
+	{
+		if (mInputMode == Decimal || mInputMode == Number)
+		{
+			return false;
+		}
+	}
+	else if (mInputMode == Decimal || mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
+	{
+		//The remaining characters only go with AllText
+		return false;
+	}
+
+	//Looks like we have a valid character!
+	return true;
 }
 
-ConsoleMethod( GuiTextEditCtrl, selectAllText, void, 2, 2, "textEditCtrl.selectAllText()" )
+void GuiTextEditCtrl::setMaxLength(S32 max)
 {
-   object->selectAllText();
+	mMaxStrLen = getMax(1, getMin(max, MAX_STRING_LENGTH));
 }
 
-ConsoleMethod( GuiTextEditCtrl, forceValidateText, void, 2, 2, "textEditCtrl.forceValidateText()" )
+void GuiTextEditCtrl::setInputMode(const InputMode mode)
 {
-   object->forceValidateText();
-}
+	if(mInputMode == mode)
+		return;
+
+	//Let's start by clearing the history.
+	mHistoryIndex = 0;
+	mHistoryLast = -1;
+
+	//Time to set the mode
+	mInputMode = mode;
+
+	//now let's parse that buffer and get rid of invalid characters
+	if (mode != AllText)
+	{
+		bool oldInsert = mInsertOn;
+		mInsertOn = false;
+		for (S32 i = 0; i < MAX_STRING_LENGTH; i++)
+		{
+			const UTF16 character = mTextBuffer.getChar(i);
+			if (character == '\0')
+			{
+				//Done and done.
+				break;
+			}
+			
+			if (!inputModeValidate(character, i))
+			{
+				//Bad Character! Let's remove it.
+				mTextBuffer.cut(i, 1);
+
+				//Step it back
+				i--;
+			}
+		}
+		mInsertOn = oldInsert;
+	}
+}

+ 65 - 9
engine/source/gui/guiTextEditCtrl.h

@@ -39,18 +39,30 @@ private:
 
 protected:
 
-	S32 mMaxStrLen;   // max string len, must be less then or equal to 255
-	bool     mTruncateWhenUnfocused;
+	S32 mMaxStrLen; 
    StringBuffer mTextBuffer;
 
    StringTableEntry mValidateCommand;
    StringTableEntry mEscapeCommand;
-   AssetPtr<AudioAsset>  mDeniedSound;
+
+public:
+   //input mode
+   enum InputMode
+   {
+		AllText = 0,
+		Decimal,
+		Number,
+		Alpha,
+		AlphaNumeric
+   };
+
+protected:
+   InputMode mInputMode;
+   bool mReturnCausesTab;
 
    // for animating the cursor
    S32      mNumFramesElapsed;
    U32      mTimeLastCursorFlipped;
-   ColorI   mCursorColor;
    bool     mCursorOn;
 
    //Edit Cursor
@@ -61,7 +73,6 @@ protected:
    Point2I  mTextOffset;
    bool     mTextOffsetReset;
    bool     mDragHit;
-   bool     mTabComplete;
    S32      mScrollDir;
 
    //undo members
@@ -80,6 +91,7 @@ protected:
    S32                  mHistoryLast;
    S32                  mHistoryIndex;
    S32                  mHistorySize;
+
    bool                 mPasswordText;
    StringTableEntry     mPasswordMask;
 
@@ -104,8 +116,8 @@ public:
 
    /// Get the contents of the control.
    ///
-   /// dest should be of size GuiTextCtrl::MAX_STRING_LENGTH+1.
-   void getText(char *dest);
+   /// dest should be of size GuiTextEditCtrl::MAX_STRING_LENGTH+1.
+   virtual void getText(char *dest);
 
    virtual void setText(const UTF8* txt);
    virtual void setText(const UTF16* txt);
@@ -115,7 +127,7 @@ public:
    void  reallySetCursorPos( const S32 newPos );
    
    void selectAllText();
-   void forceValidateText();
+   bool validate();
    const char *getScriptValue();
    void setScriptValue(const char *value);
 
@@ -140,10 +152,54 @@ public:
    void onRender(Point2I offset, const RectI &updateRect);
    virtual void drawText( const RectI &drawRect, GuiControlState currentState );
 	
-	void playDeniedSound();
+	bool inputModeValidate(const U16 key, S32 cursorPos);
+	void keyDenied();
 	void execConsoleCallback();
 
+	inline void setValidateCommand(const char *newCmd) { mValidateCommand = newCmd ? StringTable->insert(newCmd) : StringTable->EmptyString; }
+	inline const char * getValidateCommand() { return mValidateCommand; }
+
+	inline void setEscapeCommand(const char *newCmd) { mEscapeCommand = newCmd ? StringTable->insert(newCmd) : StringTable->EmptyString; }
+	inline const char * getEscapeCommand() { return mEscapeCommand; }
+
+	inline void setReturnCausesTab(bool setting) { mReturnCausesTab = setting; }
+	inline bool getReturnCausesTab() { return mReturnCausesTab; }
+
+	inline void setSinkAllKeyEvents(bool setting) { mSinkAllKeyEvents = setting; }
+	inline bool getSinkAllKeyEvents() { return mSinkAllKeyEvents; }
+
+	inline void setIsPassword(bool setting) { mPasswordText = setting; }
+	inline bool getIsPassword() { return mPasswordText; }
+
+	void setMaxLength(S32 max);
+	inline S32 getMaxLength() { return mMaxStrLen; }
+	static bool setMaxLengthProperty(void* obj, const char* data) { static_cast<GuiTextEditCtrl*>(obj)->setMaxLength(dAtoi(data)); return true; }
+
+	void setInputMode(const InputMode mode);
+	inline InputMode getInputMode() { return mInputMode; }
+	static bool setInputMode(void* obj, const char* data)
+	{
+		// Fetch body type.
+		const InputMode mode = getInputModeEnum(data);
+
+		// Check for error.
+		if (mode < AllText || mode > AlphaNumeric)
+			return false;
+
+		static_cast<GuiTextEditCtrl*>(obj)->setInputMode(mode);
+		return true;
+	}
+	static const char* getInputMode(void* obj, const char* data) { return getInputModeDescription(static_cast<GuiTextEditCtrl*>(obj)->getInputMode()); }
+	static bool writeInputMode(void* obj, StringTableEntry pFieldName) { return static_cast<GuiTextEditCtrl*>(obj)->getInputMode() != AllText; }
+	static InputMode getInputModeEnum(const char* label);
+	static const char* getInputModeDescription(const InputMode mode);
+
 	enum Constants { MAX_STRING_LENGTH = 1024 };
+
+private:
+	bool tabNext();
+	bool tabPrev();
+	void handleBackSpace();
 };
 
 #endif //_GUI_TEXTEDIT_CTRL_H

+ 171 - 0
engine/source/gui/guiTextEditCtrl_ScriptBinding.h

@@ -0,0 +1,171 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+ConsoleMethodGroupBeginWithDocs(GuiTextEditCtrl, GuiControl)
+
+
+/*! Get the contents of the textedit control.
+	@return The current text in the textedit box.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getText, ConsoleString, 2, 2, "()")
+{
+	if (!object->hasText())
+		return StringTable->EmptyString;
+
+	char *retBuffer = Con::getReturnBuffer(GuiTextEditCtrl::MAX_STRING_LENGTH);
+	object->getText(retBuffer);
+
+	return retBuffer;
+}
+
+/*! Gets the current position of the text cursor in the control.
+	@return The current position of the text cursor in the control, where 0 is at the beginning of the line, 1 is after the first letter, and so on.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getCursorPos, ConsoleInt, 2, 2, "()")
+{
+	return(object->getCursorPos());
+}
+
+/*! Sets the current position of the text cursor in the control.
+	@param newPos The new position to place the cursor at, where 0 is at the beginning of the line, 1 is after the first letter, and so on.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setCursorPos, ConsoleVoid, 3, 3, "( newPos )")
+{
+	object->reallySetCursorPos(dAtoi(argv[2]));
+}
+
+/*! Selects all the text in the control.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, selectAllText, ConsoleVoid, 2, 2, "()")
+{
+	object->selectAllText();
+}
+
+/*! Calls the validation function for the control. This is depreciated. Please use validate() instead.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, forceValidateText, ConsoleVoid, 2, 2, "()")
+{
+	Con::warnf("GuiTextEditCtrl::forceValidateText() is depreciated. Please use GuiTextEditCtrl::validate() instead.");
+	object->validate();
+}
+
+/*! Calls the validation function for the control.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, validate, ConsoleVoid, 2, 2, "()")
+{
+	object->validate();
+}
+
+/*! Sets the returnCausesTab flag. If true, the pressing enter will attempt to tab to the next control. False will keep the focus in this control.
+	@param setting True to turn on the flag. False otherwise.
+	@return No return value
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setReturnCausesTab, ConsoleVoid, 3, 3, (setting))
+{
+	object->setReturnCausesTab(dAtob(argv[2]));
+}
+
+/*! Returns if the returnCausesTab flag is on.
+	@return Returns the state of the returnCausesTab flag.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getReturnCausesTab, ConsoleBool, 2, 2, ())
+{
+	return object->getReturnCausesTab();
+}
+
+/*! Sets the sinkAllKeyEvents flag. If true, the tab key will act like the enter key.
+	@param setting True to turn on the flag. False otherwise.
+	@return No return value
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setSinkAllKeyEvents, ConsoleVoid, 3, 3, (setting))
+{
+	object->setSinkAllKeyEvents(dAtob(argv[2]));
+}
+
+/*! Returns if the sinkAllKeyEvents flag is on.
+	@return Returns the state of the sinkAllKeyEvents flag.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getSinkAllKeyEvents, ConsoleBool, 2, 2, ())
+{
+	return object->getSinkAllKeyEvents();
+}
+
+/*! Sets the password flag. If true, the tab key will act like the enter key.
+	@param setting True to turn on the flag. False otherwise.
+	@return No return value
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setIsPassword, ConsoleVoid, 3, 3, (setting))
+{
+	object->setIsPassword(dAtob(argv[2]));
+}
+
+/*! Returns if the password flag is on.
+	@return Returns the state of the password flag.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getIsPassword, ConsoleBool, 2, 2, ())
+{
+	return object->getIsPassword();
+}
+
+/*! Sets the maxLength which is the max number of characters that can be entered in the text edit box.
+	@param max An integer value between 1 and 1024.
+	@return No return value
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setMaxLength, ConsoleVoid, 3, 3, (max))
+{
+	object->setMaxLength(dAtoi(argv[2]));
+}
+
+/*! Returns if the current maxLength.
+	@return Returns the maxLength.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getMaxLength, ConsoleBool, 2, 2, ())
+{
+	return object->getMaxLength();
+}
+
+/*! Sets the InputMode. Possible values include AllText, Decimal, Number, Alpha, and AlphaNumeric.
+	@param mode The InputMode to use.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, setInputMode, ConsoleVoid, 3, 3, (mode))
+{
+	// Fetch body type.
+	const GuiTextEditCtrl::InputMode mode = GuiTextEditCtrl::getInputModeEnum(argv[2]);
+
+	// Set body type.
+	object->setInputMode(mode);
+}
+
+/*! Gets the text edit InputMode.
+	@return The InputMode.
+*/
+ConsoleMethodWithDocs(GuiTextEditCtrl, getInputMode, ConsoleString, 2, 2, ())
+{
+	return GuiTextEditCtrl::getInputModeDescription(object->getInputMode());
+}
+
+ConsoleMethodGroupEndWithDocs(GuiTextEditCtrl)

+ 0 - 6
engine/source/gui/guiTypes.cc

@@ -335,8 +335,6 @@ GuiControlProfile::GuiControlProfile(void) :
 	
 	mAlignment     = LeftAlign;
 	mVAlignment    = MiddleVAlign;
-	mReturnTab     = false;
-	mNumbersOnly   = false;
    mProfileForChildrenName = NULL;
 	mProfileForChildren = NULL;
 
@@ -378,8 +376,6 @@ GuiControlProfile::GuiControlProfile(void) :
 
       //used by GuiTextCtrl
       mAlignment = def->mAlignment;
-      mReturnTab = def->mReturnTab;
-      mNumbersOnly = def->mNumbersOnly;
       mCursorColor = def->mCursorColor;
 
       // Child profile
@@ -431,8 +427,6 @@ void GuiControlProfile::initPersistFields()
    addField("align", TypeEnum, Offset(mAlignment, GuiControlProfile), 1, &gAlignTable);
    addField("vAlign", TypeEnum, Offset(mVAlignment, GuiControlProfile), 1, &gVAlignTable);
    addField("textOffset",    TypePoint2I,    Offset(mTextOffset, GuiControlProfile));
-   addField("returnTab",     TypeBool,       Offset(mReturnTab, GuiControlProfile));
-   addField("numbersOnly",   TypeBool,       Offset(mNumbersOnly, GuiControlProfile));
    addField("cursorColor",   TypeColorI,     Offset(mCursorColor, GuiControlProfile));
 
    addField("bitmap",        TypeFilename,   Offset(mBitmapName, GuiControlProfile));

+ 1 - 3
engine/source/gui/guiTypes.h

@@ -228,9 +228,7 @@ public:
 	   MiddleVAlign
    };
    VertAlignmentType mVAlignment;				   ///< Vertical text alignment
-
-   bool mReturnTab;                                ///< Used in GuiTextEditCtrl to specify if a tab-event should be simulated when return is pressed.
-   bool mNumbersOnly;                              ///< For text controls, true if this should only accept numerical data
+                             
    bool mMouseOverSelected;                        ///< True if this object should be "selected" while the mouse is over it
    ColorI mCursorColor;                            ///< Color for the blinking cursor in text fields (for example)