2
0

guiTextEditCtrl.cpp 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 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 "platform/platform.h"
  23. #include "gui/controls/guiTextEditCtrl.h"
  24. #include "console/consoleTypes.h"
  25. #include "console/console.h"
  26. #include "gui/core/guiCanvas.h"
  27. #include "gui/controls/guiMLTextCtrl.h"
  28. #include "gui/core/guiDefaultControlRender.h"
  29. #include "gfx/gfxDevice.h"
  30. #include "gfx/gfxDrawUtil.h"
  31. #include "core/frameAllocator.h"
  32. #include "sfx/sfxTrack.h"
  33. #include "sfx/sfxTypes.h"
  34. #include "sfx/sfxSystem.h"
  35. #include "core/strings/unicode.h"
  36. #include "console/engineAPI.h"
  37. IMPLEMENT_CONOBJECT(GuiTextEditCtrl);
  38. ConsoleDocClass( GuiTextEditCtrl,
  39. "@brief A component that places a text entry box on the screen.\n\n"
  40. "Fonts and sizes are changed using profiles. The text value can be set or entered by a user.\n\n"
  41. "@tsexample\n"
  42. " new GuiTextEditCtrl(MessageHud_Edit)\n"
  43. " {\n"
  44. " text = \"Hello World\";\n"
  45. " validate = \"validateCommand();\"\n"
  46. " escapeCommand = \"escapeCommand();\";\n"
  47. " historySize = \"5\";\n"
  48. " tabComplete = \"true\";\n"
  49. " deniedSound = \"DeniedSoundProfile\";\n"
  50. " sinkAllKeyEvents = \"true\";\n"
  51. " password = \"true\";\n"
  52. " passwordMask = \"*\";\n"
  53. " //Properties not specific to this control have been omitted from this example.\n"
  54. " };\n"
  55. "@endtsexample\n\n"
  56. "@see GuiTextCtrl\n"
  57. "@see GuiControl\n\n"
  58. "@ingroup GuiControls\n"
  59. );
  60. IMPLEMENT_CALLBACK( GuiTextEditCtrl, onTabComplete, void, (const char* val),( val ),
  61. "@brief Called if tabComplete is true, and the 'tab' key is pressed.\n\n"
  62. "@param val Input to mimick the '1' sent by the actual tab key button press.\n"
  63. "@tsexample\n"
  64. "// Tab key has been pressed, causing the callback to occur.\n"
  65. "GuiTextEditCtrl::onTabComplete(%this,%val)\n"
  66. " {\n"
  67. " //Code to run when the onTabComplete callback occurs\n"
  68. " }\n"
  69. "@endtsexample\n\n"
  70. "@see GuiTextCtrl\n"
  71. "@see GuiControl\n\n"
  72. );
  73. IMPLEMENT_CALLBACK( GuiTextEditCtrl, onReturn, void, (),(),
  74. "@brief Called when the 'Return' or 'Enter' key is pressed.\n\n"
  75. "@tsexample\n"
  76. "// Return or Enter key was pressed, causing the callback to occur.\n"
  77. "GuiTextEditCtrl::onReturn(%this)\n"
  78. " {\n"
  79. " // Code to run when the onReturn callback occurs\n"
  80. " }\n"
  81. "@endtsexample\n\n"
  82. "@see GuiTextCtrl\n"
  83. "@see GuiControl\n\n"
  84. );
  85. IMPLEMENT_CALLBACK( GuiTextEditCtrl, onValidate, void, (),(),
  86. "@brief Called whenever the control is validated.\n\n"
  87. "@tsexample\n"
  88. "// The control gets validated, causing the callback to occur\n"
  89. "GuiTextEditCtrl::onValidated(%this)\n"
  90. " {\n"
  91. " // Code to run when the control is validated\n"
  92. " }\n"
  93. "@endtsexample\n\n"
  94. "@see GuiTextCtrl\n"
  95. "@see GuiControl\n\n"
  96. );
  97. GuiTextEditCtrl::GuiTextEditCtrl()
  98. {
  99. mInsertOn = true;
  100. mBlockStart = 0;
  101. mBlockEnd = 0;
  102. mCursorPos = 0;
  103. mCursorOn = false;
  104. mNumFramesElapsed = 0;
  105. mDragHit = false;
  106. mTabComplete = false;
  107. mScrollDir = 0;
  108. mUndoBlockStart = 0;
  109. mUndoBlockEnd = 0;
  110. mUndoCursorPos = 0;
  111. mPasswordText = false;
  112. mSinkAllKeyEvents = false;
  113. mActive = true;
  114. mTextValid = true;
  115. mTextOffsetReset = true;
  116. mHistoryDirty = false;
  117. mHistorySize = 0;
  118. mHistoryLast = -1;
  119. mHistoryIndex = 0;
  120. mHistoryBuf = NULL;
  121. mDoubleClickTimeMS = 50;
  122. mMouseUpTime = 0;
  123. mPlaceholderText = StringTable->EmptyString();
  124. #if defined(__MACOSX__)
  125. UTF8 bullet[4] = { UTF8(0xE2), UTF8(0x80), UTF8(0xA2), 0 };
  126. mPasswordMask = StringTable->insert( bullet );
  127. #else
  128. mPasswordMask = StringTable->insert( "*" );
  129. #endif
  130. Sim::findObject( "InputDeniedSound", mDeniedSound );
  131. mValidateCommand = "";
  132. }
  133. GuiTextEditCtrl::~GuiTextEditCtrl()
  134. {
  135. //delete the history buffer if it exists
  136. if (mHistoryBuf)
  137. {
  138. for (S32 i = 0; i < mHistorySize; i++)
  139. delete [] mHistoryBuf[i];
  140. delete [] mHistoryBuf;
  141. }
  142. }
  143. void GuiTextEditCtrl::initPersistFields()
  144. {
  145. addProtectedField("placeholderText", TypeCaseString, Offset(mPlaceholderText, GuiTextEditCtrl), setPlaceholderText, getPlaceholderText,
  146. "The text to show on the control.");
  147. addGroup( "Text Input" );
  148. addField("validate", TypeRealString,Offset(mValidateCommand, GuiTextEditCtrl), "Script command to be called when the first validater is lost.\n");
  149. addField("escapeCommand", TypeRealString,Offset(mEscapeCommand, GuiTextEditCtrl), "Script command to be called when the Escape key is pressed.\n");
  150. addField("historySize", TypeS32, Offset(mHistorySize, GuiTextEditCtrl), "How large of a history buffer to maintain.\n");
  151. addField("tabComplete", TypeBool, Offset(mTabComplete, GuiTextEditCtrl), "If true, when the 'tab' key is pressed, it will act as if the Enter key was pressed on the control.\n");
  152. addField("deniedSound", TypeSFXTrackName, Offset(mDeniedSound, GuiTextEditCtrl), "If the attempted text cannot be entered, this sound effect will be played.\n");
  153. addField("sinkAllKeyEvents", TypeBool, Offset(mSinkAllKeyEvents, GuiTextEditCtrl), "If true, every key event will act as if the Enter key was pressed.\n");
  154. addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl), "If true, all characters entered will be stored in the control, however will display as the character stored in passwordMask.\n");
  155. addField("passwordMask", TypeString, Offset(mPasswordMask, GuiTextEditCtrl), "If 'password' is true, this is the character that will be used to mask the characters in the control.\n");
  156. endGroup( "Text Input" );
  157. Parent::initPersistFields();
  158. }
  159. bool GuiTextEditCtrl::onAdd()
  160. {
  161. if ( ! Parent::onAdd() )
  162. return false;
  163. //create the history buffer
  164. if ( mHistorySize > 0 )
  165. {
  166. mHistoryBuf = new UTF16*[mHistorySize];
  167. for ( S32 i = 0; i < mHistorySize; i++ )
  168. {
  169. mHistoryBuf[i] = new UTF16[GuiTextCtrl::MAX_STRING_LENGTH + 1];
  170. mHistoryBuf[i][0] = '\0';
  171. }
  172. }
  173. if( mText && mText[0] )
  174. {
  175. setText(mText);
  176. }
  177. return true;
  178. }
  179. void GuiTextEditCtrl::onStaticModified(const char* slotName, const char* newValue)
  180. {
  181. if(!dStricmp(slotName, "text"))
  182. setText(mText);
  183. }
  184. void GuiTextEditCtrl::execConsoleCallback()
  185. {
  186. // Execute the console command!
  187. Parent::execConsoleCallback();
  188. // Update the console variable:
  189. if ( mConsoleVariable[0] )
  190. Con::setVariable(mConsoleVariable, mTextBuffer.getPtr8());
  191. }
  192. void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex )
  193. {
  194. if(!mHistorySize)
  195. return;
  196. const UTF16* txt = inTxt->getPtr();
  197. // Reject empty strings.
  198. if( !txt || !txt[ 0 ] )
  199. return;
  200. // see if it's already in
  201. if(mHistoryLast == -1 || String::compare(txt, mHistoryBuf[mHistoryLast]))
  202. {
  203. if(mHistoryLast == mHistorySize-1) // we're at the history limit... shuffle the pointers around:
  204. {
  205. UTF16 *first = mHistoryBuf[0];
  206. for(U32 i = 0; i < mHistorySize - 1; i++)
  207. mHistoryBuf[i] = mHistoryBuf[i+1];
  208. mHistoryBuf[mHistorySize-1] = first;
  209. if(mHistoryIndex > 0)
  210. mHistoryIndex--;
  211. }
  212. else
  213. mHistoryLast++;
  214. inTxt->getCopy(mHistoryBuf[mHistoryLast], GuiTextCtrl::MAX_STRING_LENGTH);
  215. mHistoryBuf[mHistoryLast][GuiTextCtrl::MAX_STRING_LENGTH] = '\0';
  216. }
  217. if(moveIndex)
  218. mHistoryIndex = mHistoryLast + 1;
  219. }
  220. void GuiTextEditCtrl::getText( char *dest )
  221. {
  222. if ( dest )
  223. mTextBuffer.getCopy8((UTF8*)dest, GuiTextCtrl::MAX_STRING_LENGTH+1);
  224. }
  225. void GuiTextEditCtrl::getRenderText(char *dest)
  226. {
  227. getText( dest );
  228. }
  229. void GuiTextEditCtrl::setText( const UTF8 *txt )
  230. {
  231. if(txt && txt[0] != 0)
  232. {
  233. Parent::setText(txt);
  234. mTextBuffer.set( txt );
  235. }
  236. else
  237. mTextBuffer.set( "" );
  238. mCursorPos = mTextBuffer.length();
  239. }
  240. void GuiTextEditCtrl::setText( const UTF16* txt)
  241. {
  242. if(txt && txt[0] != 0)
  243. {
  244. UTF8* txt8 = createUTF8string( txt );
  245. Parent::setText( txt8 );
  246. delete[] txt8;
  247. mTextBuffer.set( txt );
  248. }
  249. else
  250. {
  251. Parent::setText("");
  252. mTextBuffer.set("");
  253. }
  254. mCursorPos = mTextBuffer.length();
  255. }
  256. bool GuiTextEditCtrl::isAllTextSelected()
  257. {
  258. if( mBlockStart == 0 && mBlockEnd == mTextBuffer.length() )
  259. return true;
  260. else
  261. return false;
  262. }
  263. void GuiTextEditCtrl::selectAllText()
  264. {
  265. mBlockStart = 0;
  266. mBlockEnd = mTextBuffer.length();
  267. setUpdate();
  268. }
  269. void GuiTextEditCtrl::clearSelectedText()
  270. {
  271. mBlockStart = 0;
  272. mBlockEnd = 0;
  273. setUpdate();
  274. }
  275. void GuiTextEditCtrl::forceValidateText()
  276. {
  277. if( mValidateCommand.isNotEmpty() )
  278. evaluate( mValidateCommand );
  279. }
  280. void GuiTextEditCtrl::setCursorPos( const S32 newPos )
  281. {
  282. S32 charCount = mTextBuffer.length();
  283. S32 realPos = newPos > charCount ? charCount : newPos < 0 ? 0 : newPos;
  284. if ( realPos != mCursorPos )
  285. {
  286. mCursorPos = realPos;
  287. setUpdate();
  288. }
  289. }
  290. S32 GuiTextEditCtrl::calculateCursorPos( const Point2I &globalPos )
  291. {
  292. Point2I ctrlOffset = localToGlobalCoord( Point2I( 0, 0 ) );
  293. S32 charLength = 0;
  294. S32 curX;
  295. curX = globalPos.x - ctrlOffset.x;
  296. setUpdate();
  297. //if the cursor is too far to the left
  298. if ( curX < 0 )
  299. return -1;
  300. //if the cursor is too far to the right
  301. if ( curX >= ctrlOffset.x + getExtent().x )
  302. return -2;
  303. curX = globalPos.x - mTextOffset.x;
  304. S32 count=0;
  305. if(mTextBuffer.length() == 0)
  306. return 0;
  307. for(count=0; count<mTextBuffer.length(); count++)
  308. {
  309. UTF16 c = mTextBuffer.getChar(count);
  310. if(!mPasswordText && !mProfile->mFont->isValidChar(c))
  311. continue;
  312. if(mPasswordText)
  313. charLength += mProfile->mFont->getCharXIncrement( mPasswordMask[0] );
  314. else
  315. charLength += mProfile->mFont->getCharXIncrement( c );
  316. if ( charLength > curX )
  317. break;
  318. }
  319. return count;
  320. }
  321. void GuiTextEditCtrl::onMouseDown( const GuiEvent &event )
  322. {
  323. if(!isActive())
  324. return;
  325. mDragHit = false;
  326. // If we have a double click, select all text. Otherwise
  327. // act as before by clearing any selection.
  328. bool doubleClick = (event.mouseClickCount > 1 && Platform::getRealMilliseconds() - mMouseUpTime > mDoubleClickTimeMS);
  329. if(doubleClick)
  330. {
  331. selectAllText();
  332. } else
  333. {
  334. //undo any block function
  335. mBlockStart = 0;
  336. mBlockEnd = 0;
  337. }
  338. //find out where the cursor should be
  339. S32 pos = calculateCursorPos( event.mousePoint );
  340. // if the position is to the left
  341. if ( pos == -1 )
  342. mCursorPos = 0;
  343. else if ( pos == -2 ) //else if the position is to the right
  344. mCursorPos = mTextBuffer.length();
  345. else //else set the mCursorPos
  346. mCursorPos = pos;
  347. //save the mouseDragPos
  348. mMouseDragStart = mCursorPos;
  349. // lock the mouse
  350. mouseLock();
  351. //set the drag var
  352. mDragHit = true;
  353. //let the parent get the event
  354. setFirstResponder();
  355. }
  356. void GuiTextEditCtrl::onMouseDragged( const GuiEvent &event )
  357. {
  358. S32 pos = calculateCursorPos( event.mousePoint );
  359. // if the position is to the left
  360. if ( pos == -1 )
  361. mScrollDir = -1;
  362. else if ( pos == -2 ) // the position is to the right
  363. mScrollDir = 1;
  364. else // set the new cursor position
  365. {
  366. mScrollDir = 0;
  367. mCursorPos = pos;
  368. }
  369. // update the block:
  370. mBlockStart = getMin( mCursorPos, mMouseDragStart );
  371. mBlockEnd = getMax( mCursorPos, mMouseDragStart );
  372. if ( mBlockStart < 0 )
  373. mBlockStart = 0;
  374. if ( mBlockStart == mBlockEnd )
  375. mBlockStart = mBlockEnd = 0;
  376. //let the parent get the event
  377. Parent::onMouseDragged(event);
  378. }
  379. void GuiTextEditCtrl::onMouseUp(const GuiEvent &event)
  380. {
  381. TORQUE_UNUSED(event);
  382. mDragHit = false;
  383. mScrollDir = 0;
  384. mMouseUpTime = Platform::getRealMilliseconds();
  385. mouseUnlock();
  386. }
  387. void GuiTextEditCtrl::saveUndoState()
  388. {
  389. //save the current state
  390. mUndoText.set(&mTextBuffer);
  391. mUndoBlockStart = mBlockStart;
  392. mUndoBlockEnd = mBlockEnd;
  393. mUndoCursorPos = mCursorPos;
  394. }
  395. void GuiTextEditCtrl::onCopy(bool andCut)
  396. {
  397. // Don't copy/cut password field!
  398. if(mPasswordText)
  399. return;
  400. if (mBlockEnd > 0)
  401. {
  402. //save the current state
  403. saveUndoState();
  404. //copy the text to the clipboard
  405. UTF8* clipBuff = mTextBuffer.createSubstring8(mBlockStart, mBlockEnd - mBlockStart);
  406. Platform::setClipboard(clipBuff);
  407. delete[] clipBuff;
  408. //if we pressed the cut shortcut, we need to cut the selected text from the control...
  409. if (andCut)
  410. {
  411. mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
  412. mCursorPos = mBlockStart;
  413. }
  414. mBlockStart = 0;
  415. mBlockEnd = 0;
  416. }
  417. }
  418. void GuiTextEditCtrl::onPaste()
  419. {
  420. //first, make sure there's something in the clipboard to copy...
  421. const UTF8 *clipboard = Platform::getClipboard();
  422. if(dStrlen(clipboard) <= 0)
  423. return;
  424. //save the current state
  425. saveUndoState();
  426. //delete anything hilited
  427. if (mBlockEnd > 0)
  428. {
  429. mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart);
  430. mCursorPos = mBlockStart;
  431. mBlockStart = 0;
  432. mBlockEnd = 0;
  433. }
  434. // We'll be converting to UTF16, and maybe trimming the string,
  435. // so let's use a StringBuffer, for convinience.
  436. StringBuffer pasteText(clipboard);
  437. // Space left after we remove the highlighted text
  438. S32 stringLen = mTextBuffer.length();
  439. // Trim down to fit in a buffer of size mMaxStrLen
  440. S32 pasteLen = pasteText.length();
  441. if(stringLen + pasteLen > mMaxStrLen)
  442. {
  443. pasteLen = mMaxStrLen - stringLen;
  444. pasteText.cut(pasteLen, pasteText.length() - pasteLen);
  445. }
  446. if (mCursorPos == stringLen)
  447. {
  448. mTextBuffer.append(pasteText);
  449. }
  450. else
  451. {
  452. mTextBuffer.insert(mCursorPos, pasteText);
  453. }
  454. mCursorPos += pasteLen;
  455. }
  456. void GuiTextEditCtrl::onUndo()
  457. {
  458. StringBuffer tempBuffer;
  459. S32 tempBlockStart;
  460. S32 tempBlockEnd;
  461. S32 tempCursorPos;
  462. //save the current
  463. tempBuffer.set(&mTextBuffer);
  464. tempBlockStart = mBlockStart;
  465. tempBlockEnd = mBlockEnd;
  466. tempCursorPos = mCursorPos;
  467. //restore the prev
  468. mTextBuffer.set(&mUndoText);
  469. mBlockStart = mUndoBlockStart;
  470. mBlockEnd = mUndoBlockEnd;
  471. mCursorPos = mUndoCursorPos;
  472. //update the undo
  473. mUndoText.set(&tempBuffer);
  474. mUndoBlockStart = tempBlockStart;
  475. mUndoBlockEnd = tempBlockEnd;
  476. mUndoCursorPos = tempCursorPos;
  477. }
  478. bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
  479. {
  480. if ( !isActive() || !isAwake() )
  481. return false;
  482. S32 stringLen = mTextBuffer.length();
  483. setUpdate();
  484. // Ugly, but now I'm cool like MarkF.
  485. if(event.keyCode == KEY_BACKSPACE)
  486. goto dealWithBackspace;
  487. if ( event.modifier & SI_SHIFT )
  488. {
  489. // Added support for word jump selection.
  490. if ( event.modifier & SI_CTRL )
  491. {
  492. switch ( event.keyCode )
  493. {
  494. case KEY_LEFT:
  495. {
  496. S32 newpos = findPrevWord();
  497. if ( mBlockStart == mBlockEnd )
  498. {
  499. // There was not already a selection so start a new one.
  500. mBlockStart = newpos;
  501. mBlockEnd = mCursorPos;
  502. }
  503. else
  504. {
  505. // There was a selection already...
  506. // In this case the cursor MUST be at either the
  507. // start or end of that selection.
  508. if ( mCursorPos == mBlockStart )
  509. {
  510. // We are at the start block and traveling left so
  511. // just extend the start block farther left.
  512. mBlockStart = newpos;
  513. }
  514. else
  515. {
  516. // We are at the end block BUT traveling left
  517. // back towards the start block...
  518. if ( newpos > mBlockStart )
  519. {
  520. // We haven't overpassed the existing start block
  521. // so just trim back the end block.
  522. mBlockEnd = newpos;
  523. }
  524. else if ( newpos == mBlockStart )
  525. {
  526. // We are back at the start, so no more selection.
  527. mBlockEnd = mBlockStart = 0;
  528. }
  529. else
  530. {
  531. // Only other option, we just backtracked PAST
  532. // our original start block.
  533. // So the new position becomes the start block
  534. // and the old start block becomes the end block.
  535. mBlockEnd = mBlockStart;
  536. mBlockStart = newpos;
  537. }
  538. }
  539. }
  540. mCursorPos = newpos;
  541. return true;
  542. }
  543. case KEY_RIGHT:
  544. {
  545. S32 newpos = findNextWord();
  546. if ( mBlockStart == mBlockEnd )
  547. {
  548. // There was not already a selection so start a new one.
  549. mBlockStart = mCursorPos;
  550. mBlockEnd = newpos;
  551. }
  552. else
  553. {
  554. // There was a selection already...
  555. // In this case the cursor MUST be at either the
  556. // start or end of that selection.
  557. if ( mCursorPos == mBlockEnd )
  558. {
  559. // We are at the end block and traveling right so
  560. // just extend the end block farther right.
  561. mBlockEnd = newpos;
  562. }
  563. else
  564. {
  565. // We are at the start block BUT traveling right
  566. // back towards the end block...
  567. if ( newpos < mBlockEnd )
  568. {
  569. // We haven't overpassed the existing end block
  570. // so just trim back the start block.
  571. mBlockStart = newpos;
  572. }
  573. else if ( newpos == mBlockEnd )
  574. {
  575. // We are back at the end, so no more selection.
  576. mBlockEnd = mBlockStart = 0;
  577. }
  578. else
  579. {
  580. // Only other option, we just backtracked PAST
  581. // our original end block.
  582. // So the new position becomes the end block
  583. // and the old end block becomes the start block.
  584. mBlockStart = mBlockEnd;
  585. mBlockEnd = newpos;
  586. }
  587. }
  588. }
  589. mCursorPos = newpos;
  590. return true;
  591. }
  592. default:
  593. break;
  594. }
  595. }
  596. // End support for word jump selection.
  597. switch ( event.keyCode )
  598. {
  599. case KEY_TAB:
  600. if ( mTabComplete )
  601. {
  602. onTabComplete_callback("1");
  603. return true;
  604. }
  605. break; // We don't want to fall through if we don't handle the TAB here.
  606. case KEY_HOME:
  607. mBlockStart = 0;
  608. mBlockEnd = mCursorPos;
  609. mCursorPos = 0;
  610. return true;
  611. case KEY_END:
  612. mBlockStart = mCursorPos;
  613. mBlockEnd = stringLen;
  614. mCursorPos = stringLen;
  615. return true;
  616. case KEY_LEFT:
  617. if ((mCursorPos > 0) & (stringLen > 0))
  618. {
  619. //if we already have a selected block
  620. if (mCursorPos == mBlockEnd)
  621. {
  622. mCursorPos--;
  623. mBlockEnd--;
  624. if (mBlockEnd == mBlockStart)
  625. {
  626. mBlockStart = 0;
  627. mBlockEnd = 0;
  628. }
  629. }
  630. else {
  631. mCursorPos--;
  632. mBlockStart = mCursorPos;
  633. if (mBlockEnd == 0)
  634. {
  635. mBlockEnd = mCursorPos + 1;
  636. }
  637. }
  638. }
  639. return true;
  640. case KEY_RIGHT:
  641. if (mCursorPos < stringLen)
  642. {
  643. if ((mCursorPos == mBlockStart) && (mBlockEnd > 0))
  644. {
  645. mCursorPos++;
  646. mBlockStart++;
  647. if (mBlockStart == mBlockEnd)
  648. {
  649. mBlockStart = 0;
  650. mBlockEnd = 0;
  651. }
  652. }
  653. else
  654. {
  655. if (mBlockEnd == 0)
  656. {
  657. mBlockStart = mCursorPos;
  658. mBlockEnd = mCursorPos;
  659. }
  660. mCursorPos++;
  661. mBlockEnd++;
  662. }
  663. }
  664. return true;
  665. case KEY_RETURN:
  666. case KEY_NUMPADENTER:
  667. return dealWithEnter(false);
  668. default:
  669. break;
  670. }
  671. }
  672. else if (event.modifier & SI_CTRL)
  673. {
  674. switch(event.keyCode)
  675. {
  676. // Adding word jump navigation.
  677. case KEY_LEFT:
  678. {
  679. mCursorPos = findPrevWord();
  680. mBlockStart = 0;
  681. mBlockEnd = 0;
  682. return true;
  683. }
  684. case KEY_RIGHT:
  685. {
  686. mCursorPos = findNextWord();
  687. mBlockStart = 0;
  688. mBlockEnd = 0;
  689. return true;
  690. }
  691. // Select all
  692. case KEY_A:
  693. {
  694. selectAllText();
  695. return true;
  696. }
  697. // windows style cut / copy / paste / undo keybinds
  698. case KEY_C:
  699. case KEY_X:
  700. {
  701. // copy, and cut the text if we hit ctrl-x
  702. onCopy( event.keyCode==KEY_X );
  703. return true;
  704. }
  705. case KEY_V:
  706. {
  707. onPaste();
  708. // Execute the console command!
  709. execConsoleCallback();
  710. return true;
  711. }
  712. case KEY_Z:
  713. if (! mDragHit)
  714. {
  715. onUndo();
  716. return true;
  717. }
  718. case KEY_DELETE:
  719. case KEY_BACKSPACE:
  720. //save the current state
  721. saveUndoState();
  722. //delete everything in the field
  723. mTextBuffer.set("");
  724. mCursorPos = 0;
  725. mBlockStart = 0;
  726. mBlockEnd = 0;
  727. execConsoleCallback();
  728. return true;
  729. default:
  730. break;
  731. }
  732. }
  733. #if defined(TORQUE_OS_MAC)
  734. // mac style cut / copy / paste / undo keybinds
  735. else if (event.modifier & SI_ALT)
  736. {
  737. // Mac command key maps to alt in torque.
  738. // Added Mac cut/copy/paste/undo keys
  739. switch(event.keyCode)
  740. {
  741. // Select all
  742. case KEY_A:
  743. {
  744. selectAllText();
  745. return true;
  746. }
  747. case KEY_C:
  748. case KEY_X:
  749. {
  750. // copy, and cut the text if we hit cmd-x
  751. onCopy( event.keyCode==KEY_X );
  752. return true;
  753. }
  754. case KEY_V:
  755. {
  756. onPaste();
  757. // Execute the console command!
  758. execConsoleCallback();
  759. return true;
  760. }
  761. case KEY_Z:
  762. if (! mDragHit)
  763. {
  764. onUndo();
  765. return true;
  766. }
  767. default:
  768. break;
  769. }
  770. }
  771. #endif
  772. else
  773. {
  774. switch(event.keyCode)
  775. {
  776. case KEY_ESCAPE:
  777. if( mEscapeCommand.isNotEmpty() )
  778. {
  779. evaluate( mEscapeCommand );
  780. return( true );
  781. }
  782. return( Parent::onKeyDown( event ) );
  783. case KEY_RETURN:
  784. case KEY_NUMPADENTER:
  785. return dealWithEnter(true);
  786. case KEY_UP:
  787. {
  788. if( mHistorySize > 0 )
  789. {
  790. if(mHistoryDirty)
  791. {
  792. updateHistory(&mTextBuffer, false);
  793. mHistoryDirty = false;
  794. }
  795. mHistoryIndex--;
  796. if(mHistoryIndex >= 0 && mHistoryIndex <= mHistoryLast)
  797. setText(mHistoryBuf[mHistoryIndex]);
  798. else if(mHistoryIndex < 0)
  799. mHistoryIndex = 0;
  800. }
  801. return true;
  802. }
  803. case KEY_DOWN:
  804. {
  805. if( mHistorySize > 0 )
  806. {
  807. if(mHistoryDirty)
  808. {
  809. updateHistory(&mTextBuffer, false);
  810. mHistoryDirty = false;
  811. }
  812. mHistoryIndex++;
  813. if(mHistoryIndex > mHistoryLast)
  814. {
  815. mHistoryIndex = mHistoryLast + 1;
  816. setText("");
  817. }
  818. else
  819. setText(mHistoryBuf[mHistoryIndex]);
  820. }
  821. return true;
  822. }
  823. case KEY_LEFT:
  824. // If we have a selection put the cursor to the left side of it.
  825. if ( mBlockStart != mBlockEnd )
  826. {
  827. mCursorPos = mBlockStart;
  828. mBlockStart = mBlockEnd = 0;
  829. }
  830. else
  831. {
  832. mBlockStart = mBlockEnd = 0;
  833. mCursorPos = getMax( mCursorPos - 1, 0 );
  834. }
  835. return true;
  836. case KEY_RIGHT:
  837. // If we have a selection put the cursor to the right side of it.
  838. if ( mBlockStart != mBlockEnd )
  839. {
  840. mCursorPos = mBlockEnd;
  841. mBlockStart = mBlockEnd = 0;
  842. }
  843. else
  844. {
  845. mBlockStart = mBlockEnd = 0;
  846. mCursorPos = getMin( mCursorPos + 1, stringLen );
  847. }
  848. return true;
  849. case KEY_BACKSPACE:
  850. dealWithBackspace:
  851. //save the current state
  852. saveUndoState();
  853. if (mBlockEnd > 0)
  854. {
  855. mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart);
  856. mCursorPos = mBlockStart;
  857. mBlockStart = 0;
  858. mBlockEnd = 0;
  859. mHistoryDirty = true;
  860. // Execute the console command!
  861. execConsoleCallback();
  862. }
  863. else if (mCursorPos > 0)
  864. {
  865. mTextBuffer.cut(mCursorPos-1, 1);
  866. mCursorPos--;
  867. mHistoryDirty = true;
  868. // Execute the console command!
  869. execConsoleCallback();
  870. }
  871. return true;
  872. case KEY_DELETE:
  873. //save the current state
  874. saveUndoState();
  875. if (mBlockEnd > 0)
  876. {
  877. mHistoryDirty = true;
  878. mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart);
  879. mCursorPos = mBlockStart;
  880. mBlockStart = 0;
  881. mBlockEnd = 0;
  882. // Execute the console command!
  883. execConsoleCallback();
  884. }
  885. else if (mCursorPos < stringLen)
  886. {
  887. mHistoryDirty = true;
  888. mTextBuffer.cut(mCursorPos, 1);
  889. // Execute the console command!
  890. execConsoleCallback();
  891. }
  892. return true;
  893. case KEY_INSERT:
  894. mInsertOn = !mInsertOn;
  895. return true;
  896. case KEY_HOME:
  897. mBlockStart = 0;
  898. mBlockEnd = 0;
  899. mCursorPos = 0;
  900. return true;
  901. case KEY_END:
  902. mBlockStart = 0;
  903. mBlockEnd = 0;
  904. mCursorPos = stringLen;
  905. return true;
  906. default:
  907. break;
  908. }
  909. }
  910. switch ( event.keyCode )
  911. {
  912. case KEY_TAB:
  913. if ( mTabComplete )
  914. {
  915. onTabComplete_callback("0");
  916. return( true );
  917. }
  918. case KEY_UP:
  919. case KEY_DOWN:
  920. case KEY_ESCAPE:
  921. return Parent::onKeyDown( event );
  922. default:
  923. break;
  924. }
  925. // Handle character input events.
  926. if( mProfile->mFont->isValidChar( event.ascii ) )
  927. {
  928. handleCharInput( event.ascii );
  929. return true;
  930. }
  931. // Or eat it if that's appropriate.
  932. if( mSinkAllKeyEvents )
  933. return true;
  934. // Not handled - pass the event to it's parent.
  935. return Parent::onKeyDown( event );
  936. }
  937. bool GuiTextEditCtrl::dealWithEnter( bool clearResponder )
  938. {
  939. //first validate
  940. if (mProfile->mReturnTab)
  941. {
  942. onLoseFirstResponder();
  943. }
  944. updateHistory(&mTextBuffer, true);
  945. mHistoryDirty = false;
  946. //next exec the alt console command
  947. execAltConsoleCallback();
  948. // Notify of Return
  949. onReturn_callback();
  950. if (mProfile->mReturnTab)
  951. {
  952. GuiCanvas *root = getRoot();
  953. if (root)
  954. {
  955. root->tabNext();
  956. return true;
  957. }
  958. }
  959. if( clearResponder )
  960. clearFirstResponder();
  961. return true;
  962. }
  963. void GuiTextEditCtrl::setFirstResponder()
  964. {
  965. Parent::setFirstResponder();
  966. GuiCanvas *root = getRoot();
  967. if (root != NULL)
  968. {
  969. root->enableKeyboardTranslation();
  970. // If the native OS accelerator keys are not disabled
  971. // then some key events like Delete, ctrl+V, etc may
  972. // not make it down to us.
  973. root->setNativeAcceleratorsEnabled( false );
  974. }
  975. }
  976. void GuiTextEditCtrl::onLoseFirstResponder()
  977. {
  978. GuiCanvas *root = getRoot();
  979. if( root )
  980. {
  981. root->setNativeAcceleratorsEnabled( true );
  982. root->disableKeyboardTranslation();
  983. }
  984. updateHistory(&mTextBuffer, true);
  985. mHistoryDirty = false;
  986. //execute the validate command
  987. if( mValidateCommand.isNotEmpty() )
  988. evaluate( mValidateCommand.c_str() );
  989. onValidate_callback();
  990. // Redraw the control:
  991. setUpdate();
  992. // Lost Responder
  993. Parent::onLoseFirstResponder();
  994. }
  995. void GuiTextEditCtrl::onRender( Point2I offset, const RectI &updateRect )
  996. {
  997. RectI ctrlRect( offset, getExtent() );
  998. //if opaque, fill the update rect with the fill color
  999. if ( mProfile->mOpaque )
  1000. {
  1001. if ( !mTextValid )
  1002. GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorERR );
  1003. else if ( isFirstResponder() )
  1004. GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorHL );
  1005. else
  1006. GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColor );
  1007. }
  1008. //if there's a border, draw the border
  1009. if ( mProfile->mBorder )
  1010. {
  1011. renderBorder( ctrlRect, mProfile );
  1012. if ( !mTextValid )
  1013. GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorERR );
  1014. }
  1015. drawText( ctrlRect, isFirstResponder() );
  1016. }
  1017. void GuiTextEditCtrl::onPreRender()
  1018. {
  1019. if ( isFirstResponder() )
  1020. {
  1021. U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped;
  1022. mNumFramesElapsed++;
  1023. if ( ( timeElapsed > 500 ) && ( mNumFramesElapsed > 3 ) )
  1024. {
  1025. mCursorOn = !mCursorOn;
  1026. mTimeLastCursorFlipped = Platform::getVirtualMilliseconds();
  1027. mNumFramesElapsed = 0;
  1028. setUpdate();
  1029. }
  1030. //update the cursor if the text is scrolling
  1031. if ( mDragHit )
  1032. {
  1033. if ( ( mScrollDir < 0 ) && ( mCursorPos > 0 ) )
  1034. mCursorPos--;
  1035. else if ( ( mScrollDir > 0 ) && ( mCursorPos < (S32) mTextBuffer.length() ) )
  1036. mCursorPos++;
  1037. }
  1038. }
  1039. }
  1040. void GuiTextEditCtrl::drawText( const RectI &drawRect, bool isFocused )
  1041. {
  1042. StringBuffer textBuffer;
  1043. Point2I drawPoint = drawRect.point;
  1044. Point2I paddingLeftTop, paddingRightBottom;
  1045. // Or else just copy it over.
  1046. char *renderText = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH );
  1047. getRenderText( renderText );
  1048. // Apply password masking (make the masking char optional perhaps?)
  1049. if(mPasswordText)
  1050. {
  1051. const U32 renderLen = dStrlen( renderText );
  1052. for( U32 i = 0; i < renderLen; i++ )
  1053. textBuffer.append(mPasswordMask);
  1054. }
  1055. else
  1056. {
  1057. textBuffer.set( renderText );
  1058. }
  1059. bool usePlaceholder = false;
  1060. if (textBuffer.length() == 0 && !isFocused)
  1061. {
  1062. textBuffer.set(mPlaceholderText);
  1063. usePlaceholder = true;
  1064. }
  1065. // Just a little sanity.
  1066. if(mCursorPos > textBuffer.length())
  1067. mCursorPos = textBuffer.length();
  1068. paddingLeftTop.set(( mProfile->mTextOffset.x != 0 ? mProfile->mTextOffset.x : 3 ), mProfile->mTextOffset.y);
  1069. paddingRightBottom = paddingLeftTop;
  1070. // Center vertically:
  1071. drawPoint.y += ( ( drawRect.extent.y - paddingLeftTop.y - paddingRightBottom.y - S32( mProfile->mFont->getHeight() ) ) / 2 ) + paddingLeftTop.y;
  1072. // Align horizontally:
  1073. S32 textWidth = mProfile->mFont->getStrNWidth(textBuffer.getPtr(), textBuffer.length());
  1074. switch( mProfile->mAlignment )
  1075. {
  1076. case GuiControlProfile::RightJustify:
  1077. drawPoint.x += ( drawRect.extent.x - textWidth - paddingRightBottom.x );
  1078. break;
  1079. case GuiControlProfile::CenterJustify:
  1080. drawPoint.x += ( ( drawRect.extent.x - textWidth ) / 2 );
  1081. break;
  1082. default:
  1083. case GuiControlProfile::LeftJustify :
  1084. drawPoint.x += paddingLeftTop.x;
  1085. break;
  1086. }
  1087. ColorI fontColor = mActive ? mProfile->mFontColor : mProfile->mFontColorNA;
  1088. if (usePlaceholder)
  1089. fontColor = mProfile->mFontColorNA;
  1090. // now draw the text
  1091. Point2I cursorStart, cursorEnd;
  1092. mTextOffset.y = drawPoint.y;
  1093. mTextOffset.x = drawPoint.x;
  1094. if ( drawRect.extent.x - paddingLeftTop.x > textWidth )
  1095. mTextOffset.x = drawPoint.x;
  1096. else
  1097. {
  1098. // Alignment affects large text
  1099. if ( mProfile->mAlignment == GuiControlProfile::RightJustify
  1100. || mProfile->mAlignment == GuiControlProfile::CenterJustify )
  1101. {
  1102. if ( mTextOffset.x + textWidth < (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x)
  1103. mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth;
  1104. }
  1105. }
  1106. // calculate the cursor
  1107. if( isFocused && mActive )
  1108. {
  1109. // Where in the string are we?
  1110. S32 cursorOffset=0, charWidth=0;
  1111. UTF16 tempChar = textBuffer.getChar(mCursorPos);
  1112. // Alright, we want to terminate things momentarily.
  1113. if(mCursorPos > 0)
  1114. {
  1115. cursorOffset = mProfile->mFont->getStrNWidth(textBuffer.getPtr(), mCursorPos);
  1116. }
  1117. else
  1118. cursorOffset = 0;
  1119. if( tempChar && mProfile->mFont->isValidChar( tempChar ) )
  1120. charWidth = mProfile->mFont->getCharWidth( tempChar );
  1121. else
  1122. charWidth = paddingRightBottom.x;
  1123. if( mTextOffset.x + cursorOffset + 1 >= (drawRect.point.x + drawRect.extent.x) ) // +1 is for the cursor width
  1124. {
  1125. // Cursor somewhere beyond the textcontrol,
  1126. // skip forward roughly 25% of the total width (if possible)
  1127. S32 skipForward = drawRect.extent.x / 4 * 3;
  1128. if ( cursorOffset + skipForward > textWidth )
  1129. mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth;
  1130. else
  1131. {
  1132. //mTextOffset.x -= skipForward;
  1133. S32 mul = (S32)( mFloor( (cursorOffset-drawRect.extent.x) / skipForward ) );
  1134. mTextOffset.x -= skipForward * mul + drawRect.extent.x - 1; // -1 is for the cursor width
  1135. }
  1136. }
  1137. else if( mTextOffset.x + cursorOffset < drawRect.point.x + paddingLeftTop.x )
  1138. {
  1139. // Cursor somewhere before the textcontrol
  1140. // skip backward roughly 25% of the total width (if possible)
  1141. S32 skipBackward = drawRect.extent.x / 4 * 3;
  1142. if ( cursorOffset - skipBackward < 0 )
  1143. mTextOffset.x = drawRect.point.x + paddingLeftTop.x;
  1144. else
  1145. {
  1146. S32 mul = (S32)( mFloor( cursorOffset / skipBackward ) );
  1147. mTextOffset.x += drawRect.point.x - mTextOffset.x - skipBackward * mul;
  1148. }
  1149. }
  1150. cursorStart.x = mTextOffset.x + cursorOffset;
  1151. #ifdef TORQUE_OS_MAC
  1152. cursorStart.x += charWidth/2;
  1153. #endif
  1154. cursorEnd.x = cursorStart.x;
  1155. S32 cursorHeight = mProfile->mFont->getHeight();
  1156. if ( cursorHeight < drawRect.extent.y )
  1157. {
  1158. cursorStart.y = drawPoint.y;
  1159. cursorEnd.y = cursorStart.y + cursorHeight;
  1160. }
  1161. else
  1162. {
  1163. cursorStart.y = drawRect.point.y;
  1164. cursorEnd.y = cursorStart.y + drawRect.extent.y;
  1165. }
  1166. }
  1167. //draw the text
  1168. if ( !isFocused )
  1169. mBlockStart = mBlockEnd = 0;
  1170. //also verify the block start/end
  1171. if ((mBlockStart > textBuffer.length() || (mBlockEnd > textBuffer.length()) || (mBlockStart > mBlockEnd)))
  1172. mBlockStart = mBlockEnd = 0;
  1173. Point2I tempOffset = mTextOffset;
  1174. //draw the portion before the highlight
  1175. if ( mBlockStart > 0 )
  1176. {
  1177. GFX->getDrawUtil()->setBitmapModulation( fontColor );
  1178. const UTF16* preString2 = textBuffer.getPtr();
  1179. GFX->getDrawUtil()->drawText( mProfile->mFont, tempOffset, preString2, mProfile->mFontColors );
  1180. tempOffset.x += mProfile->mFont->getStrNWidth(preString2, mBlockStart);
  1181. }
  1182. //draw the highlighted portion
  1183. if ( mBlockEnd > 0 )
  1184. {
  1185. const UTF16* highlightBuff = textBuffer.getPtr() + mBlockStart;
  1186. U32 highlightBuffLen = mBlockEnd-mBlockStart;
  1187. S32 highlightWidth = mProfile->mFont->getStrNWidth(highlightBuff, highlightBuffLen);
  1188. GFX->getDrawUtil()->drawRectFill( Point2I( tempOffset.x, drawRect.point.y ),
  1189. Point2I( tempOffset.x + highlightWidth, drawRect.point.y + drawRect.extent.y - 1),
  1190. mProfile->mFontColorSEL );
  1191. GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColorHL );
  1192. GFX->getDrawUtil()->drawTextN( mProfile->mFont, tempOffset, highlightBuff, highlightBuffLen, mProfile->mFontColors );
  1193. tempOffset.x += highlightWidth;
  1194. }
  1195. //draw the portion after the highlight
  1196. if(mBlockEnd < textBuffer.length())
  1197. {
  1198. const UTF16* finalBuff = textBuffer.getPtr() + mBlockEnd;
  1199. U32 finalBuffLen = textBuffer.length() - mBlockEnd;
  1200. GFX->getDrawUtil()->setBitmapModulation( fontColor );
  1201. GFX->getDrawUtil()->drawTextN( mProfile->mFont, tempOffset, finalBuff, finalBuffLen, mProfile->mFontColors );
  1202. }
  1203. //draw the cursor
  1204. if ( isFocused && mCursorOn )
  1205. GFX->getDrawUtil()->drawLine( cursorStart, cursorEnd, mProfile->mCursorColor );
  1206. }
  1207. bool GuiTextEditCtrl::hasText()
  1208. {
  1209. return ( mTextBuffer.length() );
  1210. }
  1211. void GuiTextEditCtrl::invalidText(bool playSound)
  1212. {
  1213. mTextValid = false;
  1214. if ( playSound )
  1215. playDeniedSound();
  1216. }
  1217. void GuiTextEditCtrl::validText()
  1218. {
  1219. mTextValid = true;
  1220. }
  1221. bool GuiTextEditCtrl::isValidText()
  1222. {
  1223. return mTextValid;
  1224. }
  1225. void GuiTextEditCtrl::playDeniedSound()
  1226. {
  1227. if ( mDeniedSound )
  1228. SFX->playOnce( mDeniedSound );
  1229. }
  1230. const char *GuiTextEditCtrl::getScriptValue()
  1231. {
  1232. return StringTable->insert(mTextBuffer.getPtr8());
  1233. }
  1234. void GuiTextEditCtrl::setScriptValue(const char *value)
  1235. {
  1236. mTextBuffer.set(value);
  1237. mCursorPos = mTextBuffer.length();
  1238. }
  1239. void GuiTextEditCtrl::handleCharInput( U16 ascii )
  1240. {
  1241. S32 stringLen = mTextBuffer.length();
  1242. // Get the character ready to add to a UTF8 string.
  1243. UTF16 convertedChar[2] = { ascii, 0 };
  1244. //see if it's a number field
  1245. if ( mProfile->mNumbersOnly )
  1246. {
  1247. if (ascii == '-')
  1248. {
  1249. //a minus sign only exists at the beginning, and only a single minus sign
  1250. if (mCursorPos != 0 && !isAllTextSelected())
  1251. {
  1252. invalidText();
  1253. return;
  1254. }
  1255. if (mInsertOn && (mTextBuffer.getChar(0) == '-'))
  1256. {
  1257. invalidText();
  1258. return;
  1259. }
  1260. }
  1261. // BJTODO: This is probably not unicode safe.
  1262. else if (ascii != '.' && (ascii < '0' || ascii > '9'))
  1263. {
  1264. invalidText();
  1265. return;
  1266. }
  1267. else
  1268. validText();
  1269. }
  1270. //save the current state
  1271. saveUndoState();
  1272. bool alreadyCut = false;
  1273. //delete anything highlighted
  1274. if ( mBlockEnd > 0 )
  1275. {
  1276. mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart);
  1277. mCursorPos = mBlockStart;
  1278. mBlockStart = 0;
  1279. mBlockEnd = 0;
  1280. // We just changed the string length!
  1281. // Get its new value.
  1282. stringLen = mTextBuffer.length();
  1283. // If we already had text highlighted, we just want to cut that text.
  1284. // Don't cut the next character even if insert is not on.
  1285. alreadyCut = true;
  1286. }
  1287. if ( ( mInsertOn && ( stringLen < mMaxStrLen ) ) ||
  1288. ( !mInsertOn && ( mCursorPos < mMaxStrLen ) ) )
  1289. {
  1290. if ( mCursorPos == stringLen )
  1291. {
  1292. mTextBuffer.append(convertedChar);
  1293. mCursorPos++;
  1294. }
  1295. else
  1296. {
  1297. if ( mInsertOn || alreadyCut )
  1298. {
  1299. mTextBuffer.insert(mCursorPos, convertedChar);
  1300. mCursorPos++;
  1301. }
  1302. else
  1303. {
  1304. mTextBuffer.cut(mCursorPos, 1);
  1305. mTextBuffer.insert(mCursorPos, convertedChar);
  1306. mCursorPos++;
  1307. }
  1308. }
  1309. }
  1310. else
  1311. playDeniedSound();
  1312. //reset the history index
  1313. mHistoryDirty = true;
  1314. //execute the console command if it exists
  1315. execConsoleCallback();
  1316. }
  1317. S32 GuiTextEditCtrl::findPrevWord()
  1318. {
  1319. // First the first word to the left of the current cursor position
  1320. // and return the positional index of its starting character.
  1321. // We define the first character of a word as any non-whitespace
  1322. // character which has a non-alpha-numeric character to its immediate left.
  1323. const UTF8* text = mTextBuffer.getPtr8();
  1324. for ( S32 i = mCursorPos - 1; i > 0; i-- )
  1325. {
  1326. if ( !dIsspace( text[i] ) )
  1327. {
  1328. if ( !dIsalnum( text[i-1] ) )
  1329. {
  1330. return i;
  1331. }
  1332. }
  1333. }
  1334. return 0;
  1335. }
  1336. S32 GuiTextEditCtrl::findNextWord()
  1337. {
  1338. // First the first word to the right of the current cursor position
  1339. // and return the positional index of its starting character.
  1340. // We define the first character of a word as any non-whitespace
  1341. // character which has a non-alpha-numeric character to its immediate left.
  1342. const UTF8* text = mTextBuffer.getPtr8();
  1343. for ( S32 i = mCursorPos + 1; i < mTextBuffer.length(); i++ )
  1344. {
  1345. if ( !dIsspace( text[i] ) )
  1346. {
  1347. if ( !dIsalnum( text[i-1] ) )
  1348. {
  1349. return i;
  1350. }
  1351. }
  1352. }
  1353. return mTextBuffer.length();
  1354. }
  1355. DefineEngineMethod( GuiTextEditCtrl, getText, const char*, (),,
  1356. "@brief Acquires the current text displayed in this control.\n\n"
  1357. "@tsexample\n"
  1358. "// Acquire the value of the text control.\n"
  1359. "%text = %thisGuiTextEditCtrl.getText();\n"
  1360. "@endtsexample\n\n"
  1361. "@return The current text within the control.\n\n"
  1362. "@see GuiControl")
  1363. {
  1364. if( !object->hasText() )
  1365. return StringTable->EmptyString();
  1366. char *retBuffer = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH );
  1367. object->getText( retBuffer );
  1368. return retBuffer;
  1369. }
  1370. DefineEngineMethod( GuiTextEditCtrl, setText, void, (const char* text),,
  1371. "@brief Sets the text in the control.\n\n"
  1372. "@param text Text to place in the control.\n"
  1373. "@tsexample\n"
  1374. "// Define the text to display\n"
  1375. "%text = \"Text!\"\n\n"
  1376. "// Inform the GuiTextEditCtrl to display the defined text\n"
  1377. "%thisGuiTextEditCtrl.setText(%text);\n"
  1378. "@endtsexample\n\n"
  1379. "@see GuiControl")
  1380. {
  1381. object->setText( text );
  1382. }
  1383. DefineEngineMethod( GuiTextEditCtrl, getCursorPos, S32, (),,
  1384. "@brief Returns the current position of the text cursor in the control.\n\n"
  1385. "@tsexample\n"
  1386. "// Acquire the cursor position in the control\n"
  1387. "%position = %thisGuiTextEditCtrl.getCursorPost();\n"
  1388. "@endtsexample\n\n"
  1389. "@return Text cursor position within the control.\n\n"
  1390. "@see GuiControl")
  1391. {
  1392. return( object->getCursorPos() );
  1393. }
  1394. DefineEngineMethod( GuiTextEditCtrl, setCursorPos, void, (S32 position),,
  1395. "@brief Sets the text cursor at the defined position within the control.\n\n"
  1396. "@param position Text position to set the text cursor.\n"
  1397. "@tsexample\n"
  1398. "// Define the cursor position\n"
  1399. "%position = \"12\";\n\n"
  1400. "// Inform the GuiTextEditCtrl control to place the text cursor at the defined position\n"
  1401. "%thisGuiTextEditCtrl.setCursorPos(%position);\n"
  1402. "@endtsexample\n\n"
  1403. "@see GuiControl")
  1404. {
  1405. object->setCursorPos( position );
  1406. }
  1407. DefineEngineMethod( GuiTextEditCtrl, isAllTextSelected, bool, (),,
  1408. "@brief Checks to see if all text in the control has been selected.\n\n"
  1409. "@tsexample\n"
  1410. "// Check to see if all text has been selected or not.\n"
  1411. "%allSelected = %thisGuiTextEditCtrl.isAllTextSelected();\n"
  1412. "@endtsexample\n\n"
  1413. "@return True if all text in the control is selected, otherwise false.\n\n"
  1414. "@see GuiControl")
  1415. {
  1416. return object->isAllTextSelected();
  1417. }
  1418. DefineEngineMethod( GuiTextEditCtrl, selectAllText, void, (),,
  1419. "@brief Selects all text within the control.\n\n"
  1420. "@tsexample\n"
  1421. "// Inform the control to select all of its text.\n"
  1422. "%thisGuiTextEditCtrl.selectAllText();\n"
  1423. "@endtsexample\n\n"
  1424. "@see GuiControl")
  1425. {
  1426. object->selectAllText();
  1427. }
  1428. DefineEngineMethod( GuiTextEditCtrl, clearSelectedText, void, (),,
  1429. "@brief Unselects all selected text in the control.\n\n"
  1430. "@tsexample\n"
  1431. "// Inform the control to unselect all of its selected text\n"
  1432. "%thisGuiTextEditCtrl.clearSelectedText();\n"
  1433. "@endtsexample\n\n"
  1434. "@see GuiControl")
  1435. {
  1436. object->clearSelectedText();
  1437. }
  1438. DefineEngineMethod( GuiTextEditCtrl, forceValidateText, void, (),,
  1439. "@brief Force a validation to occur.\n\n"
  1440. "@tsexample\n"
  1441. "// Inform the control to force a validation of its text.\n"
  1442. "%thisGuiTextEditCtrl.forceValidateText();\n"
  1443. "@endtsexample\n\n"
  1444. "@see GuiControl")
  1445. {
  1446. object->forceValidateText();
  1447. }
  1448. DefineEngineMethod(GuiTextEditCtrl, invalidText, void, (bool playSound), (true),
  1449. "@brief Trigger the invalid sound and make the box red.nn"
  1450. "@param playSound Play the invalid text sound or not.n")
  1451. {
  1452. object->invalidText(playSound);
  1453. }
  1454. DefineEngineMethod(GuiTextEditCtrl, validText, void, (), ,
  1455. "@brief Restores the box to normal color.nn")
  1456. {
  1457. object->validText();
  1458. }
  1459. DefineEngineMethod(GuiTextEditCtrl, isValidText, bool, (), ,
  1460. "@brief Returns if the text is set to valid or not.n"
  1461. "@Return true if text is set to valid, false if not.nn")
  1462. {
  1463. return object->isValidText();
  1464. }