guiTextEditCtrl.cpp 47 KB

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