guiTextEditCtrl.cc 43 KB

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