guiTextEditCtrl.cpp 49 KB

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