guiTextEditCtrl.cc 42 KB

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