guiTextEditCtrl.cpp 49 KB

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