guiTextEditCtrl.cpp 49 KB

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