guiTextEditCtrl.cpp 48 KB

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