guiTextEditCtrl.cc 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "console/consoleTypes.h"
  23. #include "console/console.h"
  24. #include "graphics/gColor.h"
  25. #include "graphics/dgl.h"
  26. #include "gui/guiCanvas.h"
  27. #include "gui/guiMLTextCtrl.h"
  28. #include "gui/guiTextEditCtrl.h"
  29. #include "gui/guiDefaultControlRender.h"
  30. #include "memory/frameAllocator.h"
  31. #include "string/unicode.h"
  32. #include "guiTextEditCtrl_ScriptBinding.h"
  33. IMPLEMENT_CONOBJECT(GuiTextEditCtrl);
  34. U32 GuiTextEditCtrl::smNumAwake = 0;
  35. GuiTextEditCtrl::GuiTextEditCtrl()
  36. {
  37. mInsertOn = true;
  38. mBlockStart = 0;
  39. mBlockEnd = 0;
  40. mCursorPos = 0;
  41. mCursorOn = false;
  42. mNumFramesElapsed = 0;
  43. mDragHit = false;
  44. mScrollDir = 0;
  45. mUndoBlockStart = 0;
  46. mUndoBlockEnd = 0;
  47. mUndoCursorPos = 0;
  48. mPasswordText = false;
  49. mReturnCausesTab = false;
  50. mSinkAllKeyEvents = false;
  51. mActive = true;
  52. mTextOffsetReset = true;
  53. mHistoryDirty = false;
  54. mHistorySize = 0;
  55. mHistoryLast = -1;
  56. mHistoryIndex = 0;
  57. mHistoryBuf = NULL;
  58. mValidateCommand = StringTable->EmptyString;
  59. mEscapeCommand = StringTable->EmptyString;
  60. mPasswordMask = StringTable->insert( "*" );
  61. mEditCursor = NULL;
  62. mMaxStrLen = MAX_STRING_LENGTH;
  63. mInputMode = AllText;
  64. }
  65. GuiTextEditCtrl::~GuiTextEditCtrl()
  66. {
  67. //delete the history buffer if it exists
  68. if (mHistoryBuf)
  69. {
  70. for (S32 i = 0; i < mHistorySize; i++)
  71. delete [] mHistoryBuf[i];
  72. delete [] mHistoryBuf;
  73. }
  74. }
  75. static EnumTable::Enums inputModeEnums[] =
  76. {
  77. { GuiTextEditCtrl::AllText, "AllText" },
  78. { GuiTextEditCtrl::Decimal, "Decimal" },
  79. { GuiTextEditCtrl::Number, "Number" },
  80. { GuiTextEditCtrl::Alpha, "Alpha" },
  81. { GuiTextEditCtrl::AlphaNumeric, "AlphaNumeric" }
  82. };
  83. static EnumTable gInputModeTable(5, &inputModeEnums[0]);
  84. void GuiTextEditCtrl::initPersistFields()
  85. {
  86. Parent::initPersistFields();
  87. addDepricatedField("validate");
  88. addDepricatedField("truncate");
  89. addDepricatedField("passwordMask");
  90. addDepricatedField("historySize");
  91. addDepricatedField("tabComplete");
  92. addGroup("Text Edit");
  93. addField("validateCommand", TypeString, Offset(mValidateCommand, GuiTextEditCtrl));
  94. addField("escapeCommand", TypeString, Offset(mEscapeCommand, GuiTextEditCtrl));
  95. addField("sinkAllKeyEvents", TypeBool, Offset(mSinkAllKeyEvents, GuiTextEditCtrl));
  96. addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl));
  97. addField("returnCausesTab", TypeBool, Offset(mReturnCausesTab, GuiTextEditCtrl));
  98. addProtectedField("maxLength", TypeS32, Offset(mMaxStrLen, GuiTextEditCtrl), &setMaxLengthProperty, &defaultProtectedGetFn, "The max number of characters that can be entered into the text edit box.");
  99. addProtectedField("inputMode", TypeEnum, Offset(mInputMode, GuiTextEditCtrl), &setInputMode, &getInputMode, &writeInputMode, 1, &gInputModeTable, "InputMode allows different characters to be entered.");
  100. endGroup("Text Edit");
  101. }
  102. bool GuiTextEditCtrl::onAdd()
  103. {
  104. if ( ! Parent::onAdd() )
  105. return false;
  106. //create the history buffer
  107. if ( mHistorySize > 0 )
  108. {
  109. mHistoryBuf = new UTF16*[mHistorySize];
  110. for ( S32 i = 0; i < mHistorySize; i++ )
  111. {
  112. mHistoryBuf[i] = new UTF16[MAX_STRING_LENGTH + 1];
  113. mHistoryBuf[i][0] = '\0';
  114. }
  115. }
  116. if( mText[0] )
  117. {
  118. setText(mText);
  119. }
  120. return true;
  121. }
  122. void GuiTextEditCtrl::onStaticModified(const char* slotName)
  123. {
  124. if(!dStricmp(slotName, "text"))
  125. setText(mText);
  126. }
  127. void GuiTextEditCtrl::inspectPostApply()
  128. {
  129. Parent::inspectPostApply();
  130. if (mTextID && *mTextID != 0)
  131. setTextID(mTextID);
  132. else
  133. setText(mText);
  134. }
  135. bool GuiTextEditCtrl::onWake()
  136. {
  137. if (! Parent::onWake())
  138. return false;
  139. if (mConsoleVariable[0])
  140. {
  141. const char *txt = Con::getVariable(mConsoleVariable);
  142. if (txt)
  143. {
  144. if (dStrlen(txt) > (U32)mMaxStrLen)
  145. {
  146. char* buf = new char[mMaxStrLen + 1];
  147. dStrncpy(buf, txt, mMaxStrLen);
  148. buf[mMaxStrLen] = 0;
  149. setScriptValue(buf);
  150. delete[] buf;
  151. }
  152. else
  153. setScriptValue(txt);
  154. }
  155. }
  156. // If this is the first awake text edit control, enable keyboard translation
  157. if (smNumAwake == 0)
  158. Platform::enableKeyboardTranslation();
  159. ++smNumAwake;
  160. return true;
  161. }
  162. void GuiTextEditCtrl::onSleep()
  163. {
  164. Parent::onSleep();
  165. // If this is the last awake text edit control, disable keyboard translation
  166. --smNumAwake;
  167. if (smNumAwake == 0)
  168. Platform::disableKeyboardTranslation();
  169. }
  170. void GuiTextEditCtrl::execConsoleCallback()
  171. {
  172. // Execute the console command!
  173. if ( mConsoleCommand[0] )
  174. {
  175. char buf[16];
  176. dSprintf( buf, sizeof( buf ), "%d", getId() );
  177. Con::setVariable( "$ThisControl", buf );
  178. Con::evaluate( mConsoleCommand, false );
  179. }
  180. // Update the console variable:
  181. if ( mConsoleVariable[0] )
  182. Con::setVariable( mConsoleVariable, mTextBuffer.getPtr8() );
  183. }
  184. void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex )
  185. {
  186. const UTF16* txt = inTxt->getPtr();
  187. if(!txt)
  188. return;
  189. if(!mHistorySize)
  190. return;
  191. // see if it's already in
  192. if(mHistoryLast == -1 || dStrcmp(txt, mHistoryBuf[mHistoryLast]))
  193. {
  194. if(mHistoryLast == mHistorySize-1) // we're at the history limit... shuffle the pointers around:
  195. {
  196. UTF16 *first = mHistoryBuf[0];
  197. for(U32 i = 0; i < (U32)(mHistorySize - 1); i++)
  198. mHistoryBuf[i] = mHistoryBuf[i+1];
  199. mHistoryBuf[mHistorySize-1] = first;
  200. if(mHistoryIndex > 0)
  201. mHistoryIndex--;
  202. }
  203. else
  204. mHistoryLast++;
  205. inTxt->getCopy(mHistoryBuf[mHistoryLast], MAX_STRING_LENGTH);
  206. mHistoryBuf[mHistoryLast][MAX_STRING_LENGTH] = '\0';
  207. }
  208. if(moveIndex)
  209. mHistoryIndex = mHistoryLast + 1;
  210. }
  211. void GuiTextEditCtrl::getText( char *dest )
  212. {
  213. if ( dest )
  214. mTextBuffer.getCopy8((UTF8*)dest, MAX_STRING_LENGTH+1);
  215. }
  216. void GuiTextEditCtrl::setText( const UTF8 *txt )
  217. {
  218. //make sure we don't call this before onAdd();
  219. if (!mProfile)
  220. return;
  221. //Make sure we have a font
  222. mProfile->incRefCount();
  223. //If the font isn't found, we want to decrement the profile usage and return now or we may crash!
  224. if (mProfile->mFont.isNull())
  225. {
  226. //decrement the profile referrence
  227. mProfile->decRefCount();
  228. return;
  229. }
  230. setVariable((char*)mText);
  231. setUpdate();
  232. //decrement the profile referrence
  233. mProfile->decRefCount();
  234. if(txt && txt[0] != 0)
  235. {
  236. Parent::setText(txt);
  237. mTextBuffer.set( txt );
  238. }
  239. else
  240. mTextBuffer.set( "" );
  241. //respect the max size
  242. int diff = mTextBuffer.length() - mMaxStrLen;
  243. if( diff > 0 ) {
  244. mTextBuffer.cut( mMaxStrLen, diff );
  245. }
  246. mCursorPos = mTextBuffer.length();
  247. }
  248. void GuiTextEditCtrl::setText( const UTF16* txt)
  249. {
  250. if(txt && txt[0] != 0)
  251. {
  252. UTF8* txt8 = convertUTF16toUTF8( txt );
  253. Parent::setText( txt8 );
  254. setText(txt8);
  255. delete[] txt8;
  256. }
  257. else
  258. {
  259. Parent::setText("");
  260. setText("");
  261. }
  262. }
  263. void GuiTextEditCtrl::setTextID(const char *id)
  264. {
  265. S32 n = Con::getIntVariable(id, -1);
  266. if (n != -1)
  267. {
  268. setTextID(n);
  269. }
  270. }
  271. void GuiTextEditCtrl::setTextID(S32 id)
  272. {
  273. const UTF8 *str = getGUIString(id);
  274. if (str)
  275. setText((const char*)str);
  276. }
  277. void GuiTextEditCtrl::selectAllText()
  278. {
  279. mBlockStart = 0;
  280. mBlockEnd = mTextBuffer.length();
  281. setUpdate();
  282. }
  283. bool GuiTextEditCtrl::validate()
  284. {
  285. bool valid = true;
  286. if ( mValidateCommand[0] )
  287. {
  288. valid = Con::evaluate( mValidateCommand, false );
  289. }
  290. else if (isMethod("onValidate"))
  291. {
  292. valid = Con::executef(this, 2, "onValidate");
  293. }
  294. return valid;
  295. }
  296. void GuiTextEditCtrl::reallySetCursorPos( const S32 newPos )
  297. {
  298. S32 charCount = mTextBuffer.length();
  299. S32 realPos = newPos > charCount ? charCount : newPos < 0 ? 0 : newPos;
  300. if ( realPos != mCursorPos )
  301. {
  302. mCursorPos = realPos;
  303. setUpdate();
  304. }
  305. }
  306. S32 GuiTextEditCtrl::setCursorPos( const Point2I &offset )
  307. {
  308. Point2I ctrlOffset = localToGlobalCoord( Point2I( 0, 0 ) );
  309. S32 charLength = 0;
  310. S32 curX;
  311. curX = offset.x - ctrlOffset.x;
  312. setUpdate();
  313. //if the cursor is too far to the left
  314. if ( curX < 0 )
  315. return -1;
  316. //if the cursor is too far to the right
  317. if ( curX >= ctrlOffset.x + mBounds.extent.x )
  318. return -2;
  319. curX = offset.x - mTextOffset.x;
  320. S32 count=0;
  321. if(mTextBuffer.length() == 0)
  322. return 0;
  323. for(count=0; count< (S32)mTextBuffer.length(); count++)
  324. {
  325. UTF16 c = mTextBuffer.getChar(count);
  326. if(!mPasswordText && !mProfile->mFont->isValidChar(c))
  327. continue;
  328. if(mPasswordText)
  329. charLength += mProfile->mFont->getCharXIncrement( mPasswordMask[0] );
  330. else
  331. charLength += mProfile->mFont->getCharXIncrement( c );
  332. if ( charLength > curX )
  333. break;
  334. }
  335. return count;
  336. }
  337. void GuiTextEditCtrl::onTouchDown( const GuiEvent &event )
  338. {
  339. mDragHit = false;
  340. // If we have a double click, select all text. Otherwise
  341. // act as before by clearing any selection.
  342. bool doubleClick = (event.mouseClickCount > 1);
  343. if(doubleClick)
  344. {
  345. selectAllText();
  346. } else
  347. {
  348. //undo any block function
  349. mBlockStart = 0;
  350. mBlockEnd = 0;
  351. }
  352. //find out where the cursor should be
  353. S32 pos = setCursorPos( event.mousePoint );
  354. // if the position is to the left
  355. if ( pos == -1 )
  356. mCursorPos = 0;
  357. else if ( pos == -2 ) //else if the position is to the right
  358. mCursorPos = mTextBuffer.length();
  359. else //else set the mCursorPos
  360. mCursorPos = pos;
  361. //save the mouseDragPos
  362. mMouseDragStart = mCursorPos;
  363. // lock the mouse
  364. mouseLock();
  365. //set the drag var
  366. mDragHit = true;
  367. //let the parent get the event
  368. setFirstResponder();
  369. // Notify Script.
  370. if( isMethod("onTouchDown") )
  371. {
  372. char buf[3][32];
  373. dSprintf(buf[0], 32, "%d", event.modifier);
  374. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  375. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  376. Con::executef(this, 4, "onTouchDown", buf[0], buf[1], buf[2]);
  377. }
  378. }
  379. void GuiTextEditCtrl::onTouchDragged( const GuiEvent &event )
  380. {
  381. S32 pos = setCursorPos( event.mousePoint );
  382. // if the position is to the left
  383. if ( pos == -1 )
  384. mScrollDir = -1;
  385. else if ( pos == -2 ) // the position is to the right
  386. mScrollDir = 1;
  387. else // set the new cursor position
  388. {
  389. mScrollDir = 0;
  390. mCursorPos = pos;
  391. }
  392. // update the block:
  393. mBlockStart = getMin( mCursorPos, mMouseDragStart );
  394. mBlockEnd = getMax( mCursorPos, mMouseDragStart );
  395. if ( mBlockStart < 0 )
  396. mBlockStart = 0;
  397. if ( mBlockStart == mBlockEnd )
  398. mBlockStart = mBlockEnd = 0;
  399. //let the parent get the event
  400. Parent::onTouchDragged(event);
  401. // Notify Script.
  402. if( isMethod("onTouchDragged") )
  403. {
  404. char buf[3][32];
  405. dSprintf(buf[0], 32, "%d", event.modifier);
  406. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  407. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  408. Con::executef(this, 4, "onTouchDragged", buf[0], buf[1], buf[2]);
  409. }
  410. }
  411. void GuiTextEditCtrl::onTouchUp(const GuiEvent &event)
  412. {
  413. mDragHit = false;
  414. mScrollDir = 0;
  415. mouseUnlock();
  416. // Notify Script.
  417. if( isMethod("onTouchUp") )
  418. {
  419. char buf[3][32];
  420. dSprintf(buf[0], 32, "%d", event.modifier);
  421. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  422. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  423. Con::executef(this, 4, "onTouchUp", buf[0], buf[1], buf[2]);
  424. }
  425. }
  426. void GuiTextEditCtrl::saveUndoState()
  427. {
  428. //save the current state
  429. mUndoText.set(&mTextBuffer);
  430. mUndoBlockStart = mBlockStart;
  431. mUndoBlockEnd = mBlockEnd;
  432. mUndoCursorPos = mCursorPos;
  433. }
  434. void GuiTextEditCtrl::onCopy(bool andCut)
  435. {
  436. // Don't copy/cut password field!
  437. if(mPasswordText)
  438. return;
  439. if (mBlockEnd > 0)
  440. {
  441. //save the current state
  442. saveUndoState();
  443. //copy the text to the clipboard
  444. UTF8* clipBuff = mTextBuffer.createSubstring8(mBlockStart, mBlockEnd - mBlockStart);
  445. Platform::setClipboard(clipBuff);
  446. delete[] clipBuff;
  447. //if we pressed the cut shortcut, we need to cut the selected text from the control...
  448. if (andCut)
  449. {
  450. mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
  451. mCursorPos = mBlockStart;
  452. }
  453. mBlockStart = 0;
  454. mBlockEnd = 0;
  455. }
  456. }
  457. void GuiTextEditCtrl::onPaste()
  458. {
  459. //first, make sure there's something in the clipboard to copy...
  460. const UTF8 *clipboard = Platform::getClipboard();
  461. if(dStrlen(clipboard) <= 0)
  462. return;
  463. //save the current state
  464. saveUndoState();
  465. //delete anything hilited
  466. if (mBlockEnd > 0)
  467. {
  468. mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
  469. mCursorPos = mBlockStart;
  470. mBlockStart = 0;
  471. mBlockEnd = 0;
  472. }
  473. // We'll be converting to UTF16, and maybe trimming the string,
  474. // so let's use a StringBuffer, for convinience.
  475. StringBuffer pasteText(clipboard);
  476. // Space left after we remove the highlighted text
  477. S32 stringLen = mTextBuffer.length();
  478. // Trim down to fit in a buffer of size mMaxStrLen
  479. S32 pasteLen = pasteText.length();
  480. if(stringLen + pasteLen > mMaxStrLen)
  481. {
  482. pasteLen = mMaxStrLen - stringLen;
  483. pasteText.cut(pasteLen, pasteText.length() - pasteLen);
  484. }
  485. if (mCursorPos == stringLen)
  486. {
  487. mTextBuffer.append(pasteText);
  488. }
  489. else
  490. {
  491. mTextBuffer.insert(mCursorPos, pasteText);
  492. }
  493. mCursorPos += pasteLen;
  494. }
  495. void GuiTextEditCtrl::onUndo()
  496. {
  497. StringBuffer tempBuffer;
  498. S32 tempBlockStart;
  499. S32 tempBlockEnd;
  500. S32 tempCursorPos;
  501. //save the current
  502. tempBuffer.set(&mTextBuffer);
  503. tempBlockStart = mBlockStart;
  504. tempBlockEnd = mBlockEnd;
  505. tempCursorPos = mCursorPos;
  506. //restore the prev
  507. mTextBuffer.set(&mUndoText);
  508. mBlockStart = mUndoBlockStart;
  509. mBlockEnd = mUndoBlockEnd;
  510. mCursorPos = mUndoCursorPos;
  511. //update the undo
  512. mUndoText.set(&tempBuffer);
  513. mUndoBlockStart = tempBlockStart;
  514. mUndoBlockEnd = tempBlockEnd;
  515. mUndoCursorPos = tempCursorPos;
  516. }
  517. bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
  518. {
  519. if(! isActive())
  520. return false;
  521. S32 stringLen = mTextBuffer.length();
  522. setUpdate();
  523. if (event.keyCode == KEY_BACKSPACE)
  524. {
  525. handleBackSpace();
  526. return true;
  527. }
  528. if (event.modifier & SI_SHIFT)
  529. {
  530. switch (event.keyCode)
  531. {
  532. case KEY_TAB:
  533. if (isMethod("onTab"))
  534. Con::executef(this, 2, "onTab", "1");
  535. return tabPrev();
  536. case KEY_HOME:
  537. mBlockStart = 0;
  538. mBlockEnd = mCursorPos;
  539. mCursorPos = 0;
  540. return true;
  541. case KEY_END:
  542. mBlockStart = mCursorPos;
  543. mBlockEnd = stringLen;
  544. mCursorPos = stringLen;
  545. return true;
  546. case KEY_LEFT:
  547. if ((mCursorPos > 0) & (stringLen > 0))
  548. {
  549. //if we already have a selected block
  550. if (mCursorPos == mBlockEnd)
  551. {
  552. mCursorPos--;
  553. mBlockEnd--;
  554. if (mBlockEnd == mBlockStart)
  555. {
  556. mBlockStart = 0;
  557. mBlockEnd = 0;
  558. }
  559. }
  560. else {
  561. mCursorPos--;
  562. mBlockStart = mCursorPos;
  563. if (mBlockEnd == 0)
  564. {
  565. mBlockEnd = mCursorPos + 1;
  566. }
  567. }
  568. }
  569. return true;
  570. case KEY_RIGHT:
  571. if (mCursorPos < stringLen)
  572. {
  573. if ((mCursorPos == mBlockStart) && (mBlockEnd > 0))
  574. {
  575. mCursorPos++;
  576. mBlockStart++;
  577. if (mBlockStart == mBlockEnd)
  578. {
  579. mBlockStart = 0;
  580. mBlockEnd = 0;
  581. }
  582. }
  583. else
  584. {
  585. if (mBlockEnd == 0)
  586. {
  587. mBlockStart = mCursorPos;
  588. mBlockEnd = mCursorPos;
  589. }
  590. mCursorPos++;
  591. mBlockEnd++;
  592. }
  593. }
  594. return true;
  595. }
  596. }
  597. else if (event.modifier & SI_CTRL)
  598. {
  599. switch(event.keyCode)
  600. {
  601. // Added UNIX emacs key bindings - just a little hack here...
  602. // BJGTODO: Add vi bindings.
  603. // Ctrl-B - move one character back
  604. case KEY_B:
  605. {
  606. GuiEvent new_event;
  607. new_event.modifier = 0;
  608. new_event.keyCode = KEY_LEFT;
  609. return(onKeyDown(new_event));
  610. }
  611. // Ctrl-F - move one character forward
  612. case KEY_F:
  613. {
  614. GuiEvent new_event;
  615. new_event.modifier = 0;
  616. new_event.keyCode = KEY_RIGHT;
  617. return(onKeyDown(new_event));
  618. }
  619. // Ctrl-A - move to the beginning of the line
  620. case KEY_A:
  621. {
  622. GuiEvent new_event;
  623. new_event.modifier = 0;
  624. new_event.keyCode = KEY_HOME;
  625. return(onKeyDown(new_event));
  626. }
  627. // Ctrl-E - move to the end of the line
  628. case KEY_E:
  629. {
  630. GuiEvent new_event;
  631. new_event.modifier = 0;
  632. new_event.keyCode = KEY_END;
  633. return(onKeyDown(new_event));
  634. }
  635. // Ctrl-P - move backward in history
  636. case KEY_P:
  637. {
  638. GuiEvent new_event;
  639. new_event.modifier = 0;
  640. new_event.keyCode = KEY_UP;
  641. return(onKeyDown(new_event));
  642. }
  643. // Ctrl-N - move forward in history
  644. case KEY_N:
  645. {
  646. GuiEvent new_event;
  647. new_event.modifier = 0;
  648. new_event.keyCode = KEY_DOWN;
  649. return(onKeyDown(new_event));
  650. }
  651. // Ctrl-D - delete under cursor
  652. case KEY_D:
  653. {
  654. GuiEvent new_event;
  655. new_event.modifier = 0;
  656. new_event.keyCode = KEY_DELETE;
  657. return(onKeyDown(new_event));
  658. }
  659. case KEY_U:
  660. {
  661. GuiEvent new_event;
  662. new_event.modifier = SI_CTRL;
  663. new_event.keyCode = KEY_DELETE;
  664. return(onKeyDown(new_event));
  665. }
  666. // End added UNIX emacs key bindings
  667. #if !(defined(TORQUE_OS_OSX) || defined(TORQUE_OS_IOS))
  668. // windows style cut / copy / paste / undo keybinds
  669. case KEY_C:
  670. case KEY_X:
  671. {
  672. // copy, and cut the text if we hit ctrl-x
  673. onCopy( event.keyCode==KEY_X );
  674. return true;
  675. }
  676. case KEY_V:
  677. {
  678. onPaste();
  679. // Execute the console command!
  680. execConsoleCallback();
  681. return true;
  682. }
  683. case KEY_Z:
  684. if (! mDragHit)
  685. {
  686. onUndo();
  687. return true;
  688. }
  689. #endif
  690. case KEY_DELETE:
  691. case KEY_BACKSPACE:
  692. //save the current state
  693. saveUndoState();
  694. //delete everything in the field
  695. mTextBuffer.set("");
  696. mCursorPos = 0;
  697. mBlockStart = 0;
  698. mBlockEnd = 0;
  699. execConsoleCallback();
  700. return true;
  701. // [neo, 5/24/2007 - #2986]
  702. // We don't want to embed control characters in the text, so just return false
  703. // so that any that any other consumer can have a bash at the input.
  704. default:
  705. return false;
  706. }
  707. }
  708. #if (defined(TORQUE_OS_OSX) || defined(TORQUE_OS_IOS))
  709. // mac style cut / copy / paste / undo keybinds
  710. else if (event.modifier & SI_ALT)
  711. {
  712. // Added Mac cut/copy/paste/undo keys
  713. // Mac command key maps to alt in torque.
  714. switch(event.keyCode)
  715. {
  716. case KEY_C:
  717. case KEY_X:
  718. {
  719. // copy, and cut the text if we hit cmd-x
  720. onCopy( event.keyCode==KEY_X );
  721. return true;
  722. }
  723. case KEY_V:
  724. {
  725. onPaste();
  726. // Execute the console command!
  727. execConsoleCallback();
  728. return true;
  729. }
  730. case KEY_Z:
  731. if (! mDragHit)
  732. {
  733. onUndo();
  734. return true;
  735. }
  736. }
  737. }
  738. #endif
  739. else
  740. {
  741. switch(event.keyCode)
  742. {
  743. case KEY_ESCAPE:
  744. if ( mEscapeCommand[0] )
  745. {
  746. Con::evaluate( mEscapeCommand );
  747. return( true );
  748. }
  749. return( Parent::onKeyDown( event ) );
  750. case KEY_RETURN:
  751. case KEY_NUMPADENTER:
  752. //first validate
  753. if (!validate())
  754. {
  755. //The contents must be invalid. Stop here.
  756. return true;
  757. }
  758. updateHistory(&mTextBuffer, true);
  759. mHistoryDirty = false;
  760. //next exec the alt console command
  761. execAltConsoleCallback();
  762. // Notify of Return
  763. if ( isMethod("onReturn") )
  764. Con::executef( this, 1, "onReturn" );
  765. if (mReturnCausesTab)
  766. {
  767. tabNext();
  768. }
  769. return true;
  770. case KEY_UP:
  771. {
  772. if(mHistoryDirty)
  773. {
  774. updateHistory(&mTextBuffer, false);
  775. mHistoryDirty = false;
  776. }
  777. mHistoryIndex--;
  778. if(mHistoryIndex >= 0 && mHistoryIndex <= mHistoryLast)
  779. setText(mHistoryBuf[mHistoryIndex]);
  780. else if(mHistoryIndex < 0)
  781. mHistoryIndex = 0;
  782. return true;
  783. }
  784. case KEY_DOWN:
  785. if(mHistoryDirty)
  786. {
  787. updateHistory(&mTextBuffer, false);
  788. mHistoryDirty = false;
  789. }
  790. mHistoryIndex++;
  791. if(mHistoryIndex > mHistoryLast)
  792. {
  793. mHistoryIndex = mHistoryLast + 1;
  794. setText("");
  795. }
  796. else
  797. setText(mHistoryBuf[mHistoryIndex]);
  798. return true;
  799. case KEY_LEFT:
  800. mBlockStart = 0;
  801. mBlockEnd = 0;
  802. if (mCursorPos > 0)
  803. {
  804. mCursorPos--;
  805. }
  806. return true;
  807. case KEY_RIGHT:
  808. mBlockStart = 0;
  809. mBlockEnd = 0;
  810. if (mCursorPos < stringLen)
  811. {
  812. mCursorPos++;
  813. }
  814. return true;
  815. case KEY_BACKSPACE:
  816. //This should have been handled above, but it is here again for safety.
  817. handleBackSpace();
  818. return true;
  819. case KEY_DELETE:
  820. //save the current state
  821. saveUndoState();
  822. if (mBlockEnd > 0)
  823. {
  824. mHistoryDirty = true;
  825. mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart);
  826. mCursorPos = mBlockStart;
  827. mBlockStart = 0;
  828. mBlockEnd = 0;
  829. // Execute the console command!
  830. execConsoleCallback();
  831. }
  832. else if (mCursorPos < stringLen)
  833. {
  834. mHistoryDirty = true;
  835. mTextBuffer.cut(mCursorPos, 1);
  836. // Execute the console command!
  837. execConsoleCallback();
  838. }
  839. return true;
  840. case KEY_INSERT:
  841. mInsertOn = !mInsertOn;
  842. return true;
  843. case KEY_HOME:
  844. mBlockStart = 0;
  845. mBlockEnd = 0;
  846. mCursorPos = 0;
  847. return true;
  848. case KEY_END:
  849. mBlockStart = 0;
  850. mBlockEnd = 0;
  851. mCursorPos = stringLen;
  852. return true;
  853. }
  854. }
  855. switch ( event.keyCode )
  856. {
  857. case KEY_TAB:
  858. return tabNext();
  859. case KEY_UP:
  860. case KEY_DOWN:
  861. case KEY_ESCAPE:
  862. return Parent::onKeyDown( event );
  863. }
  864. if(mProfile->mFont.isNull())
  865. return false;
  866. if ( mProfile->mFont->isValidChar( event.ascii ) )
  867. {
  868. // Get the character ready to add to a UTF8 string.
  869. UTF16 convertedChar[2] = { event.ascii, 0 };
  870. //Stop characters that aren't allowed based on InputMode
  871. if (!inputModeValidate(event.ascii, mCursorPos))
  872. {
  873. keyDenied();
  874. return true;
  875. }
  876. //save the current state
  877. saveUndoState();
  878. //delete anything highlighted
  879. if ( mBlockEnd > 0 )
  880. {
  881. mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart);
  882. mCursorPos = mBlockStart;
  883. mBlockStart = 0;
  884. mBlockEnd = 0;
  885. }
  886. if ( ( mInsertOn && ( stringLen < mMaxStrLen ) ) ||
  887. ( !mInsertOn && ( mCursorPos < mMaxStrLen ) ) )
  888. {
  889. if ( mCursorPos == stringLen )
  890. {
  891. mTextBuffer.append(convertedChar);
  892. mCursorPos++;
  893. }
  894. else
  895. {
  896. if ( mInsertOn )
  897. {
  898. mTextBuffer.insert(mCursorPos, convertedChar);
  899. mCursorPos++;
  900. }
  901. else
  902. {
  903. mTextBuffer.cut(mCursorPos, 1);
  904. mTextBuffer.insert(mCursorPos, convertedChar);
  905. mCursorPos++;
  906. }
  907. }
  908. }
  909. else
  910. keyDenied();
  911. //reset the history index
  912. mHistoryDirty = true;
  913. //execute the console command if it exists
  914. execConsoleCallback();
  915. return true;
  916. }
  917. //not handled - pass the event to it's parent
  918. // Or eat it if that's appropriate.
  919. if (mSinkAllKeyEvents)
  920. return true;
  921. return Parent::onKeyDown( event );
  922. }
  923. bool GuiTextEditCtrl::tabNext()
  924. {
  925. if (isMethod("onTab"))
  926. Con::executef(this, 2, "onTab", "0");
  927. GuiCanvas *root = getRoot();
  928. if (root)
  929. {
  930. root->tabNext();
  931. return true;
  932. }
  933. return false;
  934. }
  935. bool GuiTextEditCtrl::tabPrev()
  936. {
  937. if (isMethod("onTab"))
  938. Con::executef(this, 2, "onTab", "1");
  939. GuiCanvas *root = getRoot();
  940. if (root)
  941. {
  942. root->tabPrev();
  943. return true;
  944. }
  945. return false;
  946. }
  947. void GuiTextEditCtrl::handleBackSpace()
  948. {
  949. //save the current state
  950. saveUndoState();
  951. if (mBlockEnd > 0)
  952. {
  953. mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
  954. mCursorPos = mBlockStart;
  955. mBlockStart = 0;
  956. mBlockEnd = 0;
  957. mHistoryDirty = true;
  958. }
  959. else if (mCursorPos > 0)
  960. {
  961. mTextBuffer.cut(mCursorPos - 1, 1);
  962. mCursorPos--;
  963. mHistoryDirty = true;
  964. }
  965. // Execute the console command!
  966. execConsoleCallback();
  967. }
  968. void GuiTextEditCtrl::setFirstResponder()
  969. {
  970. Parent::setFirstResponder();
  971. #if !defined(TORQUE_OS_IOS) && !defined(TORQUE_OS_ANDROID)
  972. Platform::enableKeyboardTranslation();
  973. #endif
  974. }
  975. void GuiTextEditCtrl::onLoseFirstResponder()
  976. {
  977. Platform::disableKeyboardTranslation();
  978. //first, update the history
  979. updateHistory( &mTextBuffer, true );
  980. //execute the validate command
  981. bool valid = validate();
  982. if (valid)
  983. {
  984. execAltConsoleCallback();
  985. }
  986. if( isMethod( "onLoseFirstResponder" ) )
  987. Con::executef( this, 2, "onLoseFirstResponder", valid);
  988. // Redraw the control:
  989. setUpdate();
  990. }
  991. void GuiTextEditCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
  992. {
  993. Parent::parentResized( oldParentExtent, newParentExtent );
  994. mTextOffsetReset = true;
  995. }
  996. void GuiTextEditCtrl::onRender(Point2I offset, const RectI & updateRect)
  997. {
  998. //Notice that there's no Highlight state. The HL colors are used for selected text - not hover.
  999. //The selected state is used when the box has the focus and can be typed in.
  1000. GuiControlState currentState = NormalState;
  1001. if (!mActive)
  1002. {
  1003. currentState = DisabledState;
  1004. }
  1005. else if (isFirstResponder())
  1006. {
  1007. currentState = SelectedState;
  1008. }
  1009. RectI ctrlRect = applyMargins(offset, mBounds.extent, currentState, mProfile);
  1010. if (!ctrlRect.isValidRect())
  1011. {
  1012. return;
  1013. }
  1014. renderUniversalRect(ctrlRect, mProfile, currentState);
  1015. //Render Text
  1016. dglSetBitmapModulation(mProfile->mFontColor);
  1017. RectI fillRect = applyBorders(ctrlRect.point, ctrlRect.extent, NormalState, mProfile);
  1018. RectI contentRect = applyPadding(fillRect.point, fillRect.extent, NormalState, mProfile);
  1019. if (contentRect.isValidRect())
  1020. {
  1021. drawText(contentRect, currentState);
  1022. //Render the childen
  1023. renderChildControls(offset, contentRect, updateRect);
  1024. }
  1025. }
  1026. void GuiTextEditCtrl::onPreRender()
  1027. {
  1028. if ( isFirstResponder() )
  1029. {
  1030. U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped;
  1031. mNumFramesElapsed++;
  1032. if ( ( timeElapsed > 500 ) && ( mNumFramesElapsed > 3 ) )
  1033. {
  1034. mCursorOn = !mCursorOn;
  1035. mTimeLastCursorFlipped = Platform::getVirtualMilliseconds();
  1036. mNumFramesElapsed = 0;
  1037. setUpdate();
  1038. }
  1039. //update the cursor if the text is scrolling
  1040. if ( mDragHit )
  1041. {
  1042. if ( ( mScrollDir < 0 ) && ( mCursorPos > 0 ) )
  1043. mCursorPos--;
  1044. else if ( ( mScrollDir > 0 ) && ( mCursorPos < (S32) mTextBuffer.length() ) )
  1045. mCursorPos++;
  1046. }
  1047. }
  1048. }
  1049. void GuiTextEditCtrl::drawText( const RectI &drawRect, GuiControlState currentState )
  1050. {
  1051. RectI old = dglGetClipRect();
  1052. RectI clipRect = RectI(drawRect.point, drawRect.extent);
  1053. if (clipRect.intersect(old))
  1054. {
  1055. dglSetClipRect(clipRect);
  1056. // Just a little sanity.
  1057. if(mCursorPos > (S32)mTextBuffer.length())
  1058. mCursorPos = (S32)mTextBuffer.length();
  1059. if(mCursorPos < 0)
  1060. mCursorPos = 0;
  1061. StringBuffer textBuffer;
  1062. Point2I drawPoint = drawRect.point;
  1063. // Apply password masking (make the masking char optional perhaps?)
  1064. if(mPasswordText)
  1065. {
  1066. for(U32 i = 0; i<mTextBuffer.length(); i++)
  1067. textBuffer.append(mPasswordMask);
  1068. }
  1069. else
  1070. {
  1071. // Or else just copy it over.
  1072. textBuffer.set(&mTextBuffer);
  1073. }
  1074. // Center vertically:
  1075. S32 h = mProfile->mFont->getHeight();
  1076. drawPoint.y += ( ( drawRect.extent.y - h ) / 2 );
  1077. // Align horizontally:
  1078. S32 textWidth = mProfile->mFont->getStrNWidth(textBuffer.getPtr(), textBuffer.length());
  1079. if ( drawRect.extent.x > textWidth )
  1080. {
  1081. switch( mProfile->mAlignment )
  1082. {
  1083. case GuiControlProfile::RightAlign:
  1084. drawPoint.x += ( drawRect.extent.x - textWidth );
  1085. break;
  1086. case GuiControlProfile::CenterAlign:
  1087. drawPoint.x += ( ( drawRect.extent.x - textWidth ) / 2 );
  1088. break;
  1089. }
  1090. }
  1091. ColorI fontColor = mActive ? mProfile->mFontColor : mProfile->mFontColorNA;
  1092. // now draw the text
  1093. Point2I cursorStart, cursorEnd;
  1094. mTextOffset = drawPoint;
  1095. if ( mTextOffsetReset )
  1096. {
  1097. mTextOffset.x = drawPoint.x;
  1098. mTextOffsetReset = false;
  1099. }
  1100. if ( drawRect.extent.x > textWidth )
  1101. mTextOffset.x = drawPoint.x;
  1102. else
  1103. {
  1104. // Alignment affects large text
  1105. if ( mProfile->mAlignment == GuiControlProfile::RightAlign
  1106. || mProfile->mAlignment == GuiControlProfile::CenterAlign )
  1107. {
  1108. if ( mTextOffset.x + textWidth < (drawRect.point.x + drawRect.extent.x))
  1109. mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - textWidth;
  1110. }
  1111. }
  1112. // calculate the cursor
  1113. if ( currentState == SelectedState )
  1114. {
  1115. // Where in the string are we?
  1116. S32 cursorOffset=0, charWidth=0;
  1117. UTF16 tempChar = mTextBuffer.getChar(mCursorPos);
  1118. // Alright, we want to terminate things momentarily.
  1119. if(mCursorPos > 0)
  1120. {
  1121. cursorOffset = mProfile->mFont->getStrNWidth(textBuffer.getPtr(), mCursorPos);
  1122. }
  1123. else
  1124. cursorOffset = 0;
  1125. if ( tempChar )
  1126. charWidth = mProfile->mFont->getCharWidth( tempChar );
  1127. else
  1128. charWidth = 0;
  1129. if( mTextOffset.x + cursorOffset + charWidth >= (drawRect.point.x + drawRect.extent.x))
  1130. {
  1131. // Cursor somewhere beyond the textcontrol,
  1132. // skip forward roughly 25% of the total width (if possible)
  1133. S32 skipForward = drawRect.extent.x / 4;
  1134. if ( cursorOffset + skipForward > textWidth )
  1135. mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - textWidth;
  1136. else
  1137. mTextOffset.x -= skipForward;
  1138. }
  1139. else if( mTextOffset.x + cursorOffset < drawRect.point.x)
  1140. {
  1141. // Cursor somewhere before the textcontrol
  1142. // skip backward roughly 25% of the total width (if possible)
  1143. S32 skipBackward = drawRect.extent.x / 4;
  1144. if ( cursorOffset - skipBackward < 0 )
  1145. mTextOffset.x = drawRect.point.x;
  1146. else
  1147. mTextOffset.x += skipBackward;
  1148. }
  1149. cursorStart.x = mTextOffset.x + cursorOffset;
  1150. cursorEnd.x = cursorStart.x;
  1151. S32 cursorHeight = mProfile->mFont->getHeight();
  1152. if ( cursorHeight < drawRect.extent.y )
  1153. {
  1154. cursorStart.y = drawPoint.y;
  1155. cursorEnd.y = cursorStart.y + cursorHeight;
  1156. }
  1157. else
  1158. {
  1159. cursorStart.y = drawRect.point.y;
  1160. cursorEnd.y = cursorStart.y + drawRect.extent.y;
  1161. }
  1162. }
  1163. //draw the text
  1164. if ( currentState != SelectedState )
  1165. mBlockStart = mBlockEnd = 0;
  1166. //also verify the block start/end
  1167. if ((mBlockStart > (S32)textBuffer.length() || (mBlockEnd > (S32)textBuffer.length()) || (mBlockStart > mBlockEnd)))
  1168. mBlockStart = mBlockEnd = 0;
  1169. Point2I tempOffset = mTextOffset;
  1170. //draw the portion before the highlight
  1171. if ( mBlockStart > 0 )
  1172. {
  1173. dglSetBitmapModulation( fontColor );
  1174. const UTF16* preString2 = textBuffer.getPtr();
  1175. dglDrawTextN( mProfile->mFont, tempOffset, preString2, mBlockStart, mProfile->mFontColors);
  1176. tempOffset.x += mProfile->mFont->getStrNWidth(preString2, mBlockStart);
  1177. }
  1178. //draw the hilighted portion
  1179. if ( mBlockEnd > 0 )
  1180. {
  1181. const UTF16* highlightBuff = textBuffer.getPtr() + mBlockStart;
  1182. U32 highlightBuffLen = mBlockEnd-mBlockStart;
  1183. S32 highlightWidth = mProfile->mFont->getStrNWidth(highlightBuff, highlightBuffLen);
  1184. dglDrawRectFill( Point2I( tempOffset.x, drawRect.point.y + 1 ),
  1185. Point2I( tempOffset.x + highlightWidth, drawRect.point.y + drawRect.extent.y - 1),
  1186. mProfile->mFillColorHL );
  1187. dglSetBitmapModulation( mProfile->mFontColorHL );
  1188. dglDrawTextN( mProfile->mFont, tempOffset, highlightBuff, highlightBuffLen, mProfile->mFontColors );
  1189. tempOffset.x += highlightWidth;
  1190. }
  1191. //draw the portion after the highlite
  1192. if(mBlockEnd < (S32)mTextBuffer.length())
  1193. {
  1194. const UTF16* finalBuff = textBuffer.getPtr() + mBlockEnd;
  1195. U32 finalBuffLen = mTextBuffer.length() - mBlockEnd;
  1196. dglSetBitmapModulation( fontColor );
  1197. dglDrawTextN( mProfile->mFont, tempOffset, finalBuff, finalBuffLen, mProfile->mFontColors );
  1198. }
  1199. //draw the cursor
  1200. if (currentState == SelectedState && mCursorOn )
  1201. dglDrawLine( cursorStart, cursorEnd, mProfile->mCursorColor );
  1202. dglSetClipRect(old);
  1203. }
  1204. }
  1205. bool GuiTextEditCtrl::hasText()
  1206. {
  1207. return (mTextBuffer.length());
  1208. }
  1209. void GuiTextEditCtrl::keyDenied()
  1210. {
  1211. if (isMethod("onDenied"))
  1212. Con::executef(this, 1, "onDenied");
  1213. }
  1214. const char *GuiTextEditCtrl::getScriptValue()
  1215. {
  1216. return StringTable->insert(mTextBuffer.getPtr8());
  1217. }
  1218. void GuiTextEditCtrl::setScriptValue(const char *value)
  1219. {
  1220. mTextBuffer.set(value);
  1221. mCursorPos = getMin((S32)(mTextBuffer.length() - 1), 0);
  1222. }
  1223. StringBuffer GuiTextEditCtrl::truncate(StringBuffer buffer, StringBuffer terminationString, S32 width)
  1224. {
  1225. // Check if the buffer width exceeds the specified width
  1226. S32 bufferWidth = textBufferWidth(buffer);
  1227. // If not, just return the unmodified buffer
  1228. if (bufferWidth <= width)
  1229. return buffer;
  1230. // Get the width of the termination string
  1231. S32 terminationWidth = textBufferWidth(terminationString) + 6; // add an extra bit of space at the end
  1232. // Calculate the new target width with space allowed for the termination string
  1233. S32 targetWidth = width - terminationWidth;
  1234. // If the target width is zero or less, just replace the entire buffer with the termination string
  1235. if (targetWidth <= 0)
  1236. return terminationString;
  1237. // Step backwards in the buffer until we find the character that fits within the target width
  1238. S32 currentWidth = 0;
  1239. S32 count = 0;
  1240. for (S32 i = 0; i < (S32)buffer.length(); i++)
  1241. {
  1242. if (currentWidth >= targetWidth)
  1243. break;
  1244. UTF16 c = buffer.getChar(i);
  1245. currentWidth += mProfile->mFont->getCharXIncrement(c);
  1246. count++;
  1247. }
  1248. // Get the substring
  1249. StringBuffer retBuffer = buffer.substring(0, count - 2);
  1250. // Append terminating string
  1251. retBuffer.append(terminationString);
  1252. return retBuffer;
  1253. }
  1254. S32 GuiTextEditCtrl::textBufferWidth(StringBuffer buffer)
  1255. {
  1256. S32 charLength = 0;
  1257. for (S32 count = 0; count < (S32)buffer.length(); count++)
  1258. {
  1259. UTF16 c = buffer.getChar(count);
  1260. if (!mProfile->mFont->isValidChar(c))
  1261. continue;
  1262. charLength += mProfile->mFont->getCharXIncrement(c);
  1263. }
  1264. return charLength;
  1265. }
  1266. GuiTextEditCtrl::InputMode GuiTextEditCtrl::getInputModeEnum(const char* label)
  1267. {
  1268. // Search for Mnemonic.
  1269. for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
  1270. {
  1271. if (dStricmp(inputModeEnums[i].label, label) == 0)
  1272. return (InputMode)inputModeEnums[i].index;
  1273. }
  1274. // Warn.
  1275. Con::warnf("GuiTextEditCtrl::getInputModeEnum() - Invalid mode of '%s'", label);
  1276. return (InputMode)-1;
  1277. }
  1278. const char* GuiTextEditCtrl::getInputModeDescription(const InputMode mode)
  1279. {
  1280. // Search for Mnemonic.
  1281. for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
  1282. {
  1283. if (inputModeEnums[i].index == mode)
  1284. return inputModeEnums[i].label;
  1285. }
  1286. // Warn.
  1287. Con::warnf("GuiTextEditCtrl::getInputModeDescription() - Invalid input mode.");
  1288. return StringTable->EmptyString;
  1289. }
  1290. //Returns true if valid, false if invalid
  1291. bool GuiTextEditCtrl::inputModeValidate(const U16 key, S32 cursorPos)
  1292. {
  1293. if (key == '-')
  1294. {
  1295. if (mInputMode == Alpha || mInputMode == AlphaNumeric)
  1296. {
  1297. return false;
  1298. }
  1299. else if (mInputMode == Decimal || mInputMode == Number)
  1300. {
  1301. //a minus sign only exists at the beginning, and only a single minus sign
  1302. if (cursorPos != 0 || (mInsertOn && mTextBuffer.getChar(0) == '-'))
  1303. {
  1304. return false;
  1305. }
  1306. }
  1307. }
  1308. else if (key >= '0' && key <= '9')
  1309. {
  1310. if (mInputMode == Alpha)
  1311. {
  1312. return false;
  1313. }
  1314. }
  1315. else if (key == '.')
  1316. {
  1317. if (mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
  1318. {
  1319. return false;
  1320. }
  1321. else if (mInputMode == Decimal)
  1322. {
  1323. if (!mInsertOn && mTextBuffer.getChar(cursorPos) == '.')
  1324. {
  1325. return true;
  1326. }
  1327. const char* dot = dStrchr(mTextBuffer.getPtr8(), '.');
  1328. if (dot != NULL)
  1329. {
  1330. return false;
  1331. }
  1332. }
  1333. }
  1334. else if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z'))
  1335. {
  1336. if (mInputMode == Decimal || mInputMode == Number)
  1337. {
  1338. return false;
  1339. }
  1340. }
  1341. else if (key == 32)
  1342. {
  1343. if (mInputMode == Decimal || mInputMode == Number)
  1344. {
  1345. return false;
  1346. }
  1347. }
  1348. else if (mInputMode == Decimal || mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
  1349. {
  1350. //The remaining characters only go with AllText
  1351. return false;
  1352. }
  1353. //Looks like we have a valid character!
  1354. return true;
  1355. }
  1356. void GuiTextEditCtrl::setMaxLength(S32 max)
  1357. {
  1358. mMaxStrLen = getMax(1, getMin(max, MAX_STRING_LENGTH));
  1359. }
  1360. void GuiTextEditCtrl::setInputMode(const InputMode mode)
  1361. {
  1362. if(mInputMode == mode)
  1363. return;
  1364. //Let's start by clearing the history.
  1365. mHistoryIndex = 0;
  1366. mHistoryLast = -1;
  1367. //Time to set the mode
  1368. mInputMode = mode;
  1369. //now let's parse that buffer and get rid of invalid characters
  1370. if (mode != AllText)
  1371. {
  1372. bool oldInsert = mInsertOn;
  1373. mInsertOn = false;
  1374. for (S32 i = 0; i < MAX_STRING_LENGTH; i++)
  1375. {
  1376. const UTF16 character = mTextBuffer.getChar(i);
  1377. if (character == '\0')
  1378. {
  1379. //Done and done.
  1380. break;
  1381. }
  1382. if (!inputModeValidate(character, i))
  1383. {
  1384. //Bad Character! Let's remove it.
  1385. mTextBuffer.cut(i, 1);
  1386. //Step it back
  1387. i--;
  1388. }
  1389. }
  1390. mInsertOn = oldInsert;
  1391. }
  1392. }