guiTextEditCtrl.cc 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 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 "console/consoleTypes.h"
  23. #include "console/console.h"
  24. #include "graphics/gColor.h"
  25. #include "graphics/dgl.h"
  26. #include "gui/guiCanvas.h"
  27. #include "gui/guiMLTextCtrl.h"
  28. #include "gui/guiTextEditCtrl.h"
  29. #include "gui/guiDefaultControlRender.h"
  30. #include "memory/frameAllocator.h"
  31. #include "string/unicode.h"
  32. #include <vector>
  33. #include <string>
  34. #include "guiTextEditCtrl_ScriptBinding.h"
  35. #pragma region GuiTextEditTextBlock
  36. GuiTextEditTextBlock::GuiTextEditTextBlock()
  37. {
  38. mGlobalBounds.set(0, 0, 0, 0);
  39. mTextOffsetX = 0;
  40. mTextScrollX = 0;
  41. mText = string();
  42. mLineStartIbeamValue = 0;
  43. }
  44. void GuiTextEditTextBlock::render(const RectI& bounds, string line, U32 ibeamStartValue, GuiControlProfile* profile, GuiControlState currentState, GuiTextEditSelection& selector, AlignmentType align, GFont* font)
  45. {
  46. mGlobalBounds.set(bounds.point, bounds.extent);
  47. mText.assign(line);
  48. mLineStartIbeamValue = ibeamStartValue;
  49. RectI clipRect = dglGetClipRect();
  50. if (mGlobalBounds.overlaps(clipRect))
  51. {
  52. const U32 selStart = selector.getSelStart();
  53. const U32 selEnd = selector.getSelEnd();
  54. U32 lengthOfPreBlockText = mClamp(selStart - mLineStartIbeamValue, 0, line.length());
  55. U32 lengthOfPostBlockText = mClamp(line.length() - (selEnd - mLineStartIbeamValue), 0, line.length());
  56. U32 lengthOfHighlightBlockText = mClamp(line.length() - (lengthOfPreBlockText + lengthOfPostBlockText), 0, line.length());
  57. processTextAlignment(line, font, align);
  58. Point2I movingStartPoint = getGlobalTextStart();
  59. movingStartPoint.x += renderTextSection(movingStartPoint, 0, lengthOfPreBlockText, profile, currentState, font);
  60. movingStartPoint.x += renderTextSection(movingStartPoint, lengthOfPreBlockText, lengthOfHighlightBlockText, profile, currentState, font, true);
  61. movingStartPoint.x += renderTextSection(movingStartPoint, lengthOfPreBlockText + lengthOfHighlightBlockText, lengthOfPostBlockText, profile, currentState, font);
  62. }
  63. Point2I textStartPoint = getGlobalTextStart();
  64. if (selector.renderIbeam(textStartPoint, mGlobalBounds.extent, line, mLineStartIbeamValue, mLineStartIbeamValue + line.length(), profile, font))
  65. {
  66. Point2I cursorCenter = selector.getCursorCenter();
  67. performScrollJumpX(cursorCenter.x, clipRect.point.x, clipRect.point.x + clipRect.extent.x);
  68. }
  69. }
  70. U32 GuiTextEditTextBlock::renderTextSection(const Point2I& startPoint, const U32 subStrStart, const U32 subStrLen, GuiControlProfile* profile, const GuiControlState currentState, GFont* font, bool isSelectedText)
  71. {
  72. if (subStrLen != 0)
  73. {
  74. string sectionText = mText.substr(subStrStart, subStrLen);
  75. U32 blockStrWidth = font->getStrWidth(sectionText.c_str());
  76. Point2I pointToDraw = Point2I(startPoint.x, startPoint.y);
  77. if (isSelectedText)
  78. {
  79. dglSetBitmapModulation(profile->mFontColorTextSL);
  80. RectI highlightRect = RectI(pointToDraw.x, pointToDraw.y, blockStrWidth, mGlobalBounds.extent.y);
  81. dglDrawRectFill(highlightRect, profile->mFillColorTextSL);
  82. }
  83. else
  84. {
  85. const ColorI& fontColor = profile->getFontColor(currentState);
  86. dglSetBitmapModulation(fontColor);
  87. }
  88. dglDrawText(font, pointToDraw, sectionText.c_str(), profile->mFontColors);
  89. return blockStrWidth;
  90. }
  91. return 0;
  92. }
  93. void GuiTextEditTextBlock::performScrollJumpX(const S32 targetX, const S32 areaStart, const S32 areaEnd)
  94. {
  95. S32 diff = 0;
  96. if (targetX < areaStart)
  97. {
  98. diff = targetX - areaStart;
  99. }
  100. else if (targetX > areaEnd)
  101. {
  102. diff = targetX - areaEnd;
  103. }
  104. mTextScrollX += diff;
  105. }
  106. U32 GuiTextEditTextBlock::calculateIbeamPositionInLine(const S32 targetX, GFont* font)
  107. {
  108. if (mText.length() == 0)
  109. return mLineStartIbeamValue;
  110. S32 curX = getGlobalTextStart().x;
  111. U32 result = mText.length();
  112. for (U32 count = 0; count < mText.length(); count++)
  113. {
  114. char c = mText[count];
  115. if (!font->isValidChar(c))
  116. continue;
  117. S32 backDiff = mAbs(curX - targetX);
  118. curX += font->getCharXIncrement(c);
  119. if (curX > targetX)
  120. {
  121. S32 forwardDiff = mAbs(curX - targetX);
  122. if (backDiff < forwardDiff)
  123. result = count;
  124. else
  125. result = count + 1;
  126. break;
  127. }
  128. }
  129. return result;
  130. }
  131. void GuiTextEditTextBlock::processScrollVelocity(const S32 delta, const S32 extentX, GFont* font)
  132. {
  133. U32 max = font->getStrWidth(mText.c_str()) - extentX;
  134. mTextScrollX = mClamp(mTextScrollX + delta, 0, max);
  135. }
  136. void GuiTextEditTextBlock::processTextAlignment(const string line, GFont* font, AlignmentType align)
  137. {
  138. if (align == AlignmentType::LeftAlign ||
  139. mGlobalBounds.extent.x < font->getStrWidth(line.c_str()))
  140. {
  141. mTextOffsetX = 0;
  142. }
  143. else if (align == AlignmentType::RightAlign)
  144. {
  145. mTextOffsetX = mGlobalBounds.extent.x - font->getStrWidth(line.c_str());
  146. }
  147. else if (align == AlignmentType::CenterAlign)
  148. {
  149. mTextOffsetX = (S32)mRound((mGlobalBounds.extent.x - font->getStrWidth(line.c_str())) / 2);
  150. }
  151. }
  152. #pragma endregion
  153. #pragma region GuiTextEditSelection
  154. GuiTextEditSelection::GuiTextEditSelection()
  155. {
  156. mBlockStart = 0;
  157. mBlockEnd = 0;
  158. mCursorPos = 0;
  159. mCursorOn = false;
  160. mNumFramesElapsed = 0;
  161. mCursorAtEOL = false;
  162. mIsFirstResponder = false;
  163. mGlobalUnadjustedCursorRect.set(0, 0, 0, 0);
  164. mCursorRendered = false;
  165. mNumFramesElapsed = 0;
  166. mTimeLastCursorFlipped = 0;
  167. mCursorOn = false;
  168. }
  169. bool GuiTextEditSelection::renderIbeam(const Point2I& startPoint, const Point2I& extent, const string line, const U32 start, const U32 end, GuiControlProfile* profile, GFont* font)
  170. {
  171. if (!mIsFirstResponder || !mCursorOn ||
  172. (mCursorAtEOL && mCursorPos == start && mCursorPos != 0) ||
  173. (!mCursorAtEOL && mCursorPos == end && mCursorPos != mTextLength) ||
  174. (mCursorPos < start || mCursorPos > end))
  175. {
  176. return false;
  177. }
  178. string blockText = line.substr(0, mCursorPos - start);
  179. U32 blockStrWidth = font->getStrWidth(blockText.c_str());
  180. RectI ibeamRect = RectI(startPoint.x + blockStrWidth - 1, startPoint.y, 2, extent.y);
  181. setCursorRect(ibeamRect);
  182. RectI clipRect = dglGetClipRect();
  183. if (clipRect.point.x > ibeamRect.point.x)
  184. {
  185. ibeamRect.point.x = clipRect.point.x;
  186. }
  187. else if ((clipRect.point.x + clipRect.extent.x) < (ibeamRect.point.x + ibeamRect.extent.x))
  188. {
  189. ibeamRect.point.x = (clipRect.point.x + clipRect.extent.x) - ibeamRect.extent.x;
  190. }
  191. dglDrawRectFill(ibeamRect, profile->mCursorColor);
  192. return true;
  193. }
  194. void GuiTextEditSelection::selectTo(const U32 target)
  195. {
  196. S32 safeTarget = mClamp(target, 0, mTextLength);
  197. if (mBlockStart == mBlockEnd)
  198. {
  199. mBlockAnchor = mBlockStart = mBlockEnd = mCursorPos;
  200. }
  201. if (safeTarget > mBlockAnchor)
  202. {
  203. mBlockStart = mBlockAnchor;
  204. mBlockEnd = mCursorPos = safeTarget;
  205. }
  206. else if (safeTarget < mBlockAnchor)
  207. {
  208. mBlockStart = mCursorPos = safeTarget;
  209. mBlockEnd = mBlockAnchor;
  210. }
  211. else
  212. {
  213. mCursorPos = mBlockStart = mBlockEnd = safeTarget;
  214. }
  215. }
  216. void GuiTextEditSelection::eraseSelection(string& fullText)
  217. {
  218. if (hasSelection())
  219. {
  220. fullText.erase(mBlockStart, mBlockEnd - mBlockStart);
  221. mCursorPos = mBlockStart;
  222. mTextLength = fullText.length();
  223. clearSelection();
  224. }
  225. }
  226. void GuiTextEditSelection::onPreRender(const U32 time)
  227. {
  228. if (mIsFirstResponder)
  229. {
  230. U32 timeElapsed = time - mTimeLastCursorFlipped;
  231. mNumFramesElapsed++;
  232. if ((timeElapsed > 400) && (mNumFramesElapsed > 3))
  233. {
  234. mCursorOn = !mCursorOn;
  235. mTimeLastCursorFlipped = time;
  236. mNumFramesElapsed = 0;
  237. }
  238. }
  239. }
  240. void GuiTextEditSelection::resetCursorBlink()
  241. {
  242. mCursorOn = true;
  243. mNumFramesElapsed = 0;
  244. mTimeLastCursorFlipped = Platform::getVirtualMilliseconds();
  245. }
  246. void GuiTextEditSelection::selectWholeWord(const string& text)
  247. {
  248. bool selectingSpace = (mCursorPos < text.length() && text[mCursorPos] == ' ');
  249. for (S32 i = mCursorPos; i >= 0; i--)
  250. {
  251. if (i == 0)
  252. {
  253. mBlockStart = 0;
  254. break;
  255. }
  256. if ((!selectingSpace && text[i - 1] == ' ') ||
  257. (selectingSpace && text[i - 1] != ' '))
  258. {
  259. mBlockStart = i;
  260. break;
  261. }
  262. }
  263. mBlockAnchor = mBlockStart;
  264. bool foundSpace = false;
  265. for (S32 j = mCursorPos; j <= (text.length() + 1); j++)
  266. {
  267. if (j == (text.length() + 1))
  268. {
  269. mBlockEnd = j;
  270. break;
  271. }
  272. if (!foundSpace && text[j] == ' ')
  273. {
  274. foundSpace = true;
  275. }
  276. else if (foundSpace && text[j] != ' ')
  277. {
  278. mBlockEnd = j;
  279. break;
  280. }
  281. }
  282. mCursorPos = mBlockEnd;
  283. mCursorAtEOL = true;
  284. }
  285. GuiTextEditSelection::GuiTextEditSelection(const GuiTextEditSelection& selector)
  286. {
  287. mBlockAnchor = selector.mBlockAnchor;
  288. mBlockStart = selector.mBlockStart;
  289. mBlockEnd = selector.mBlockEnd;
  290. mCursorPos = selector.mCursorPos;
  291. mCursorAtEOL = selector.mCursorAtEOL;
  292. mIsFirstResponder = selector.mIsFirstResponder;
  293. mGlobalUnadjustedCursorRect = selector.mGlobalUnadjustedCursorRect;
  294. mCursorRendered = selector.mCursorRendered;
  295. mTextLength = selector.mTextLength;
  296. mNumFramesElapsed = 0;
  297. mTimeLastCursorFlipped = Platform::getVirtualMilliseconds();
  298. mCursorOn = true;
  299. }
  300. void GuiTextEditSelection::stepCursorForward()
  301. {
  302. if (hasSelection())
  303. {
  304. setCursorPosition(mBlockEnd);
  305. }
  306. else
  307. {
  308. setCursorPosition(mCursorPos + 1);
  309. }
  310. }
  311. void GuiTextEditSelection::stepCursorBackward()
  312. {
  313. if (hasSelection())
  314. {
  315. setCursorPosition(mBlockStart);
  316. }
  317. else
  318. {
  319. setCursorPosition(mCursorPos - 1);
  320. }
  321. }
  322. #pragma endregion
  323. #pragma region GuiTextEditCtrl
  324. IMPLEMENT_CONOBJECT(GuiTextEditCtrl);
  325. U32 GuiTextEditCtrl::smNumAwake = 0;
  326. GuiTextEditCtrl::GuiTextEditCtrl()
  327. {
  328. mInsertOn = true;
  329. mMouseOver = false;
  330. mPasswordText = false;
  331. mReturnCausesTab = false;
  332. mSinkAllKeyEvents = false;
  333. mActive = true;
  334. mSelector = GuiTextEditSelection();
  335. mUndoSelector = GuiTextEditSelection();
  336. mUndoText = string();
  337. mTextOffsetY = 0;
  338. mReturnCommand = StringTable->EmptyString;
  339. mEscapeCommand = StringTable->EmptyString;
  340. mPasswordMask = StringTable->insert( "*" );
  341. mEditCursor = NULL;
  342. mMaxStrLen = MAX_STRING_LENGTH;
  343. mInputMode = AllText;
  344. mSuspendVerticalScrollJump = false;
  345. mTextBlockList = TextBlockList();
  346. mScrollVelocity = 0;
  347. }
  348. static EnumTable::Enums inputModeEnums[] =
  349. {
  350. { GuiTextEditCtrl::AllText, "AllText" },
  351. { GuiTextEditCtrl::Decimal, "Decimal" },
  352. { GuiTextEditCtrl::Number, "Number" },
  353. { GuiTextEditCtrl::Alpha, "Alpha" },
  354. { GuiTextEditCtrl::AlphaNumeric, "AlphaNumeric" }
  355. };
  356. static EnumTable gInputModeTable(5, &inputModeEnums[0]);
  357. void GuiTextEditCtrl::initPersistFields()
  358. {
  359. Parent::initPersistFields();
  360. addDepricatedField("validate");
  361. addDepricatedField("truncate");
  362. addDepricatedField("passwordMask");
  363. addDepricatedField("historySize");
  364. addDepricatedField("tabComplete");
  365. addGroup("Text Edit");
  366. addField("returnCommand", TypeString, Offset(mReturnCommand, GuiTextEditCtrl));
  367. addField("escapeCommand", TypeString, Offset(mEscapeCommand, GuiTextEditCtrl));
  368. addField("sinkAllKeyEvents", TypeBool, Offset(mSinkAllKeyEvents, GuiTextEditCtrl));
  369. addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl));
  370. addField("returnCausesTab", TypeBool, Offset(mReturnCausesTab, GuiTextEditCtrl));
  371. addProtectedField("maxLength", TypeS32, Offset(mMaxStrLen, GuiTextEditCtrl), &setMaxLengthProperty, &defaultProtectedGetFn, "The max number of characters that can be entered into the text edit box.");
  372. addProtectedField("inputMode", TypeEnum, Offset(mInputMode, GuiTextEditCtrl), &setInputMode, &getInputMode, &writeInputMode, 1, &gInputModeTable, "InputMode allows different characters to be entered.");
  373. addField("editCursor", TypeGuiCursor, Offset(mEditCursor, GuiTextEditCtrl));
  374. endGroup("Text Edit");
  375. }
  376. bool GuiTextEditCtrl::onAdd()
  377. {
  378. if ( ! Parent::onAdd() )
  379. return false;
  380. if( mText[0] )
  381. {
  382. setText(mText);
  383. }
  384. return true;
  385. }
  386. void GuiTextEditCtrl::onStaticModified(const char* slotName)
  387. {
  388. if(!dStricmp(slotName, "text"))
  389. setText(mText);
  390. }
  391. void GuiTextEditCtrl::inspectPostApply()
  392. {
  393. Parent::inspectPostApply();
  394. if (mTextID && *mTextID != 0)
  395. setTextID(mTextID);
  396. else
  397. setText(mText);
  398. }
  399. bool GuiTextEditCtrl::onWake()
  400. {
  401. if (! Parent::onWake())
  402. return false;
  403. if (mConsoleVariable[0])
  404. {
  405. const char *txt = Con::getVariable(mConsoleVariable);
  406. if (txt)
  407. {
  408. if (dStrlen(txt) > (U32)mMaxStrLen)
  409. {
  410. char* buf = new char[mMaxStrLen + 1];
  411. dStrncpy(buf, txt, mMaxStrLen);
  412. buf[mMaxStrLen] = 0;
  413. setScriptValue(buf);
  414. delete[] buf;
  415. }
  416. else
  417. setScriptValue(txt);
  418. }
  419. }
  420. // If this is the first awake text edit control, enable keyboard translation
  421. if (smNumAwake == 0)
  422. Platform::enableKeyboardTranslation();
  423. ++smNumAwake;
  424. mSuspendVerticalScrollJump = false;
  425. return true;
  426. }
  427. void GuiTextEditCtrl::onSleep()
  428. {
  429. Parent::onSleep();
  430. // If this is the last awake text edit control, disable keyboard translation
  431. --smNumAwake;
  432. if (smNumAwake == 0)
  433. Platform::disableKeyboardTranslation();
  434. }
  435. void GuiTextEditCtrl::execConsoleCallback()
  436. {
  437. setVariable(mTextBuffer.c_str());
  438. if ( mConsoleCommand[0] )
  439. {
  440. Con::evaluate( mConsoleCommand, false );
  441. }
  442. }
  443. const char* GuiTextEditCtrl::getText()
  444. {
  445. return mTextBuffer.c_str();
  446. }
  447. void GuiTextEditCtrl::setText( const UTF8 *txt )
  448. {
  449. setUpdate();
  450. enforceMaxLength();
  451. if (txt && txt[0] != 0)
  452. {
  453. Parent::setText(txt);
  454. mTextBuffer.assign(txt);
  455. }
  456. else
  457. mTextBuffer.clear();
  458. setVariable(mTextBuffer.c_str());
  459. }
  460. void GuiTextEditCtrl::setText( const UTF16* txt)
  461. {
  462. if(txt && txt[0] != 0)
  463. {
  464. UTF8* txt8 = convertUTF16toUTF8( txt );
  465. setText(txt8);
  466. delete[] txt8;
  467. }
  468. else
  469. {
  470. setText("");
  471. }
  472. }
  473. void GuiTextEditCtrl::enforceMaxLength()
  474. {
  475. int diff = mTextBuffer.length() - mMaxStrLen;
  476. if (diff > 0) {
  477. mTextBuffer.resize(mMaxStrLen);
  478. }
  479. }
  480. void GuiTextEditCtrl::setTextID(const char *id)
  481. {
  482. S32 n = Con::getIntVariable(id, -1);
  483. if (n != -1)
  484. {
  485. setTextID(n);
  486. }
  487. }
  488. void GuiTextEditCtrl::setTextID(S32 id)
  489. {
  490. const UTF8 *str = getGUIString(id);
  491. if (str)
  492. setText((const char*)str);
  493. }
  494. bool GuiTextEditCtrl::validate()
  495. {
  496. bool valid = true;
  497. if (isMethod("onValidate"))
  498. {
  499. valid = dAtob(Con::executef(this, 2, "onValidate"));
  500. }
  501. return valid;
  502. }
  503. const RectI GuiTextEditCtrl::getGlobalInnerRect()
  504. {
  505. Point2I offset = Point2I(mBounds.point.Zero);
  506. Point2I extent = Point2I(getExtent());
  507. RectI innerRect = getInnerRect(offset, extent, SelectedState, mProfile);
  508. Point2I globalCtrlOffset = localToGlobalCoord(innerRect.point);
  509. RectI globalInnerRect(globalCtrlOffset, innerRect.extent);
  510. return globalInnerRect;
  511. }
  512. S32 GuiTextEditCtrl::calculateIbeamPosition(const Point2I& globalMousePoint)
  513. {
  514. if (mTextBuffer.length() == 0)
  515. return 0;
  516. RectI globalInnerRect = getGlobalInnerRect();
  517. return calculateIbeamPosition(globalMousePoint, globalInnerRect);
  518. }
  519. S32 GuiTextEditCtrl::calculateIbeamPosition(const Point2I& globalMousePoint, const RectI& globalInnerRect)
  520. {
  521. if (mTextBuffer.length() == 0 || mTextBlockList.size() == 0)
  522. return 0;
  523. string textBuffer = applyPasswordMasking();
  524. GFont* font = mProfile->getFont(mFontSizeAdjust);
  525. if (!mTextWrap)
  526. {
  527. return mTextBlockList.front().calculateIbeamPositionInLine(globalMousePoint.x, font);
  528. }
  529. else
  530. {
  531. RectI firstLineBounds = mTextBlockList.front().getGlobalBounds();
  532. S32 textStartY = firstLineBounds.point.y;
  533. if (textStartY > globalMousePoint.y)
  534. return 0;
  535. U32 height = firstLineBounds.extent.y;
  536. if ((textStartY + (mTextBlockList.size() * height)) < globalMousePoint.y)
  537. return mTextBuffer.length();
  538. S32 curY = textStartY;
  539. for (auto block : mTextBlockList)
  540. {
  541. curY += height;
  542. if (curY > globalMousePoint.y)
  543. {
  544. U32 pos = block.calculateIbeamPositionInLine(globalMousePoint.x, font);
  545. mSelector.setCursorAtEOL(block.calculateCursorAtEOL(pos));
  546. return pos + block.getStartValue();
  547. }
  548. }
  549. mSelector.setCursorAtEOL(true);
  550. return mTextBlockList.back().calculateIbeamPositionInLine(globalMousePoint.x, font);
  551. }
  552. }
  553. void GuiTextEditCtrl::onTouchDown( const GuiEvent &event )
  554. {
  555. mScrollVelocity = 0;
  556. mSuspendVerticalScrollJump = false;
  557. if (!mVisible || !mAwake)
  558. return;
  559. mSelector.setTextLength(mTextBuffer.length());
  560. if(event.mouseClickCount > 2)
  561. {
  562. selectAllText();
  563. }
  564. else if(event.mouseClickCount > 1)
  565. {
  566. mSelector.selectWholeWord(mTextBuffer);
  567. }
  568. else
  569. {
  570. S32 newCursorPos = calculateIbeamPosition(event.mousePoint);
  571. if (event.modifier & SI_SHIFT)
  572. {
  573. modifySelectBlock(newCursorPos);
  574. }
  575. else
  576. {
  577. mSelector.setCursorPosition(newCursorPos);
  578. }
  579. }
  580. mouseLock();
  581. setFirstResponder();
  582. mSelector.resetCursorBlink();
  583. if( isMethod("onTouchDown") )
  584. {
  585. char buf[3][32];
  586. dSprintf(buf[0], 32, "%d", event.modifier);
  587. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  588. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  589. Con::executef(this, 4, "onTouchDown", buf[0], buf[1], buf[2]);
  590. }
  591. }
  592. void GuiTextEditCtrl::onTouchDragged( const GuiEvent &event )
  593. {
  594. if (!mVisible || !mAwake)
  595. return;
  596. mSuspendVerticalScrollJump = false;
  597. RectI globalInnerRect = getGlobalInnerRect();
  598. adjustScrollVelocity(event.mousePoint, globalInnerRect);
  599. if ((mTextWrap && event.mousePoint.y < globalInnerRect.point.y) ||
  600. (!mTextWrap && event.mousePoint.x < globalInnerRect.point.x))
  601. {
  602. modifySelectBlock(0);
  603. }
  604. else if ((mTextWrap && event.mousePoint.y > (globalInnerRect.point.y + globalInnerRect.extent.y)) ||
  605. (!mTextWrap && event.mousePoint.x > (globalInnerRect.point.x + globalInnerRect.extent.x)))
  606. {
  607. modifySelectBlock(mTextBuffer.length());
  608. }
  609. else
  610. {
  611. modifySelectBlock(calculateIbeamPosition(event.mousePoint, globalInnerRect));
  612. }
  613. // Notify Script.
  614. if( isMethod("onTouchDragged") )
  615. {
  616. char buf[3][32];
  617. dSprintf(buf[0], 32, "%d", event.modifier);
  618. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  619. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  620. Con::executef(this, 4, "onTouchDragged", buf[0], buf[1], buf[2]);
  621. }
  622. }
  623. void GuiTextEditCtrl::onTouchUp(const GuiEvent &event)
  624. {
  625. mScrollVelocity = 0;
  626. mSuspendVerticalScrollJump = false;
  627. mouseUnlock();
  628. if (!mVisible || !mAwake)
  629. return;
  630. // Notify Script.
  631. if( isMethod("onTouchUp") )
  632. {
  633. char buf[3][32];
  634. dSprintf(buf[0], 32, "%d", event.modifier);
  635. dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y);
  636. dSprintf(buf[2], 32, "%d", event.mouseClickCount);
  637. Con::executef(this, 4, "onTouchUp", buf[0], buf[1], buf[2]);
  638. }
  639. }
  640. bool GuiTextEditCtrl::onMouseWheelUp(const GuiEvent& event)
  641. {
  642. if (!mVisible || !mAwake)
  643. return true;
  644. if(mTextWrap && mTextOffsetY > 0)
  645. {
  646. mScrollVelocity = 0;
  647. mSuspendVerticalScrollJump = true;
  648. mTextOffsetY = getMax(mTextOffsetY - static_cast<S32>(mProfile->getFont(mFontSizeAdjust)->getHeight()), 0);
  649. return true;
  650. }
  651. GuiControl* parent = getParent();
  652. if (parent)
  653. return parent->onMouseWheelUp(event);
  654. else
  655. return false;
  656. }
  657. bool GuiTextEditCtrl::onMouseWheelDown(const GuiEvent& event)
  658. {
  659. if (!mVisible || !mAwake)
  660. return true;
  661. U32 blockHeight = mTextBlockList.size() * mProfile->getFont(mFontSizeAdjust)->getHeight();
  662. RectI innerRect = getGlobalInnerRect();
  663. S32 max = blockHeight - innerRect.extent.y;
  664. if (mTextWrap && innerRect.extent.y < blockHeight && mTextOffsetY < max)
  665. {
  666. mScrollVelocity = 0;
  667. mSuspendVerticalScrollJump = true;
  668. mTextOffsetY = getMin(mTextOffsetY + static_cast<S32>(mProfile->getFont(mFontSizeAdjust)->getHeight()), max);
  669. return true;
  670. }
  671. GuiControl* parent = getParent();
  672. if (parent)
  673. return parent->onMouseWheelDown(event);
  674. else
  675. return false;
  676. }
  677. void GuiTextEditCtrl::onTouchEnter(const GuiEvent& event)
  678. {
  679. if (!mActive)
  680. return;
  681. mMouseOver = true;
  682. Con::executef(this, 1, "onTouchEnter");
  683. //update
  684. setUpdate();
  685. }
  686. void GuiTextEditCtrl::onTouchLeave(const GuiEvent& event)
  687. {
  688. if (!mActive)
  689. return;
  690. mMouseOver = false;
  691. Con::executef(this, 1, "onTouchLeave");
  692. //update
  693. setUpdate();
  694. }
  695. void GuiTextEditCtrl::saveUndoState()
  696. {
  697. //save the current state
  698. mUndoText = mTextBuffer;
  699. mUndoSelector = mSelector;
  700. }
  701. void GuiTextEditCtrl::onCopy(bool andCut)
  702. {
  703. // Don't copy/cut password field!
  704. if(mPasswordText)
  705. return;
  706. if (mSelector.hasSelection())
  707. {
  708. //save the current state
  709. saveUndoState();
  710. //copy the text to the clipboard
  711. string subString = mSelector.getSelection(mTextBuffer);
  712. Platform::setClipboard(subString.c_str());
  713. //if we pressed the cut shortcut, we need to cut the selected text from the control...
  714. if (andCut)
  715. {
  716. mSelector.eraseSelection(mTextBuffer);
  717. }
  718. mSelector.clearSelection();
  719. }
  720. }
  721. void GuiTextEditCtrl::onPaste()
  722. {
  723. //first, make sure there's something in the clipboard to copy...
  724. string clipboard = Platform::getClipboard();
  725. if(clipboard.length() <= 0)
  726. return;
  727. //save the current state
  728. saveUndoState();
  729. //delete anything hilited
  730. if (mSelector.hasSelection())
  731. {
  732. mSelector.eraseSelection(mTextBuffer);
  733. mSelector.clearSelection();
  734. }
  735. U32 pos = mSelector.getCursorPos();
  736. mTextBuffer.insert(pos, clipboard);
  737. setText(mTextBuffer);
  738. pos += clipboard.length();
  739. mSelector.setCursorPosition(pos);
  740. execConsoleCallback();
  741. }
  742. void GuiTextEditCtrl::onUndo()
  743. {
  744. string tempText = mTextBuffer;
  745. GuiTextEditSelection tempSelector = mSelector;
  746. mTextBuffer = mUndoText;
  747. mSelector = mUndoSelector;
  748. mUndoText = tempText;
  749. mUndoSelector = tempSelector;
  750. }
  751. bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event)
  752. {
  753. if(! isActive())
  754. return false;
  755. mSuspendVerticalScrollJump = false;
  756. S32 stringLen = mTextBuffer.length();
  757. mSelector.setTextLength(stringLen);
  758. setUpdate();
  759. bool result = false;
  760. if (event.modifier & SI_SHIFT)
  761. {
  762. result = handleKeyDownWithShift(event);
  763. }
  764. else if (event.modifier & SI_CTRL)
  765. {
  766. //When holding the ctrl key, events must be handled here or passed up.
  767. return handleKeyDownWithCtrl(event);
  768. }
  769. else if (event.modifier & SI_ALT)
  770. {
  771. result = handleKeyDownWithAlt(event);
  772. #if (defined(TORQUE_OS_OSX) || defined(TORQUE_OS_IOS))
  773. //Likewise, the cmd key must be handled here or passed up.
  774. return result;
  775. #endif
  776. }
  777. if(result || (!result && handleKeyDownWithNoModifier(event)))
  778. {
  779. return true;
  780. }
  781. if (handleCharacterInput(event) || mSinkAllKeyEvents)
  782. {
  783. return true;
  784. }
  785. return Parent::onKeyDown( event );
  786. }
  787. bool GuiTextEditCtrl::tabNext()
  788. {
  789. if (isMethod("onTab"))
  790. Con::executef(this, 2, "onTab", "0");
  791. GuiCanvas *root = getRoot();
  792. if (root)
  793. {
  794. root->tabNext();
  795. return true;
  796. }
  797. return false;
  798. }
  799. bool GuiTextEditCtrl::tabPrev()
  800. {
  801. if (isMethod("onTab"))
  802. Con::executef(this, 2, "onTab", "1");
  803. GuiCanvas *root = getRoot();
  804. if (root)
  805. {
  806. root->tabPrev();
  807. return true;
  808. }
  809. return false;
  810. }
  811. void GuiTextEditCtrl::setFirstResponder()
  812. {
  813. mSelector.setFirstResponder(true);
  814. Parent::setFirstResponder();
  815. #if !defined(TORQUE_OS_IOS) && !defined(TORQUE_OS_ANDROID)
  816. Platform::enableKeyboardTranslation();
  817. #endif
  818. }
  819. void GuiTextEditCtrl::onLoseFirstResponder()
  820. {
  821. Platform::disableKeyboardTranslation();
  822. //execute the validate command
  823. bool valid = validate();
  824. if (valid)
  825. {
  826. execAltConsoleCallback();
  827. }
  828. if( isMethod( "onLoseFirstResponder" ) )
  829. Con::executef( this, 2, "onLoseFirstResponder", valid);
  830. mSelector.setFirstResponder(false);
  831. mTextOffsetY = 0;
  832. mScrollVelocity = 0;
  833. if (!mTextWrap && mTextBlockList.size() > 0)
  834. {
  835. mTextBlockList.front().resetScroll();
  836. }
  837. // Redraw the control:
  838. setUpdate();
  839. }
  840. void GuiTextEditCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
  841. {
  842. Parent::parentResized( oldParentExtent, newParentExtent );
  843. mTextOffsetY = 0;
  844. if (!mTextWrap && mTextBlockList.size() > 0)
  845. {
  846. mTextBlockList.front().resetScroll();
  847. }
  848. }
  849. GuiControlState GuiTextEditCtrl::getCurrentState()
  850. {
  851. if (!mActive)
  852. return GuiControlState::DisabledState;
  853. else if (isFirstResponder())
  854. return GuiControlState::SelectedState;
  855. else if (mMouseOver)
  856. return GuiControlState::HighlightState;
  857. else
  858. return GuiControlState::NormalState;
  859. }
  860. const ColorI& GuiTextEditCtrl::getCurrentFontColor()
  861. {
  862. auto currentState = getCurrentState();
  863. return mProfile->getFontColor(currentState);
  864. }
  865. void GuiTextEditCtrl::onPreRender()
  866. {
  867. mSelector.onPreRender(Platform::getVirtualMilliseconds());
  868. processScrollVelocity();
  869. }
  870. void GuiTextEditCtrl::onRender(Point2I offset, const RectI & updateRect)
  871. {
  872. GuiControlState currentState = getCurrentState();
  873. RectI ctrlRect = applyMargins(offset, mBounds.extent, currentState, mProfile);
  874. renderUniversalRect(ctrlRect, mProfile, currentState);
  875. //Render Text
  876. RectI fillRect = applyBorders(ctrlRect.point, ctrlRect.extent, NormalState, mProfile);
  877. RectI contentRect = applyPadding(fillRect.point, fillRect.extent, NormalState, mProfile);
  878. if (contentRect.isValidRect())
  879. {
  880. if (currentState != SelectedState)
  881. mSelector.clearSelection();
  882. string textBuffer = applyPasswordMasking();
  883. renderText(contentRect.point, contentRect.extent, textBuffer.c_str(), mProfile);
  884. //Render the childen
  885. renderChildControls(offset, contentRect, updateRect);
  886. }
  887. }
  888. void GuiTextEditCtrl::renderLineList(const Point2I& offset, const Point2I& extent, const S32 startOffsetY, const vector<string> lineList, GuiControlProfile* profile, const TextRotationOptions rot)
  889. {
  890. GFont* font = profile->getFont(mFontSizeAdjust);
  891. const S32 textHeight = font->getHeight();
  892. S32 totalWidth = extent.x;
  893. if (mTextBlockList.size() > lineList.size())
  894. {
  895. mTextBlockList.resize(lineList.size());
  896. }
  897. //Now print each line
  898. U32 ibeamPos = 0;
  899. S32 offsetY = startOffsetY - mTextOffsetY;
  900. for (U32 i = 0; i < lineList.size(); i++)
  901. {
  902. if(mTextBlockList.size() <= i)
  903. {
  904. mTextBlockList.push_back(GuiTextEditTextBlock());
  905. }
  906. Point2I start = Point2I(0, offsetY);
  907. Point2I blockExtent = Point2I(extent.x, textHeight);
  908. RectI blockBounds = RectI(start + offset + profile->mTextOffset, blockExtent);
  909. mTextBlockList[i].render(blockBounds, lineList[i], ibeamPos, mProfile, getCurrentState(), mSelector, getAlignmentType(), font);
  910. offsetY += textHeight;
  911. ibeamPos += lineList[i].length();
  912. }
  913. performScrollJumpY();
  914. }
  915. void GuiTextEditCtrl::processScrollVelocity()
  916. {
  917. if (mScrollVelocity == 0)
  918. {
  919. return;
  920. }
  921. U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastScrollProcess;
  922. S32 delta = mRound((F32)(timeElapsed * mScrollVelocity) / 1000);
  923. if (delta != 0)
  924. {
  925. RectI innerRect = getGlobalInnerRect();
  926. if (mTextWrap)
  927. {
  928. U32 max = (mTextBlockList.size() * mProfile->getFont(mFontSizeAdjust)->getHeight()) - innerRect.extent.y;
  929. mTextOffsetY = mClamp(mTextOffsetY + delta, 0, max);
  930. }
  931. else if(mTextBlockList.size() > 0)
  932. {
  933. mTextBlockList.front().processScrollVelocity(delta, innerRect.extent.x, mProfile->getFont(mFontSizeAdjust));
  934. }
  935. mTimeLastScrollProcess = Platform::getVirtualMilliseconds();
  936. S32 newCursorPos = calculateIbeamPosition(Canvas->getCursorPos());
  937. modifySelectBlock(newCursorPos);
  938. }
  939. }
  940. void GuiTextEditCtrl::performScrollJumpY()
  941. {
  942. if (mTextWrap && !mSuspendVerticalScrollJump && isFirstResponder() && mSelector.isCursorRendered())
  943. {
  944. RectI clipRect = dglGetClipRect();
  945. S32 areaStart = clipRect.point.y;
  946. S32 areaEnd = clipRect.point.y + clipRect.extent.y;
  947. RectI cursorRect = mSelector.getCursorRect();
  948. S32 lineTop = cursorRect.point.y;
  949. S32 lineBottom = lineTop + cursorRect.extent.y;
  950. S32 diff = 0;
  951. if (lineTop < areaStart)
  952. {
  953. diff = lineTop - areaStart;
  954. }
  955. else if (lineBottom > areaEnd)
  956. {
  957. diff = lineBottom - areaEnd;
  958. }
  959. mTextOffsetY += diff;
  960. }
  961. }
  962. void GuiTextEditCtrl::adjustScrollVelocity(const Point2I& globalMousePoint, const RectI& globalInnerRect)
  963. {
  964. RectI nonScrollArea = RectI(globalInnerRect);
  965. if (mTextWrap)
  966. {
  967. nonScrollArea.point.y += SCROLL_EDGE_SIZE;
  968. nonScrollArea.extent.y -= (2 * SCROLL_EDGE_SIZE);
  969. }
  970. else
  971. {
  972. nonScrollArea.point.x += SCROLL_EDGE_SIZE;
  973. nonScrollArea.extent.x -= (2 * SCROLL_EDGE_SIZE);
  974. }
  975. if (nonScrollArea.pointInRect(globalMousePoint))
  976. {
  977. mScrollVelocity = 0;
  978. }
  979. else if (globalInnerRect.pointInRect(globalMousePoint))
  980. {
  981. if (mScrollVelocity == 0)
  982. {
  983. mTimeLastScrollProcess = Platform::getVirtualMilliseconds();
  984. }
  985. mScrollVelocity = SCROLL_VELOCITY_PER_SEC;
  986. if ((mTextWrap && globalMousePoint.y < (globalInnerRect.point.y + SCROLL_EDGE_SIZE)) ||
  987. (!mTextWrap && globalMousePoint.x < (globalInnerRect.point.x + SCROLL_EDGE_SIZE)))
  988. {
  989. mScrollVelocity = -SCROLL_VELOCITY_PER_SEC;
  990. }
  991. }
  992. else
  993. {
  994. mScrollVelocity = 0;
  995. }
  996. }
  997. bool GuiTextEditCtrl::hasText()
  998. {
  999. return (mTextBuffer.length());
  1000. }
  1001. void GuiTextEditCtrl::keyDenied()
  1002. {
  1003. if (isMethod("onDenied"))
  1004. Con::executef(this, 1, "onDenied");
  1005. }
  1006. const char *GuiTextEditCtrl::getScriptValue()
  1007. {
  1008. return StringTable->insert(mTextBuffer.c_str());
  1009. }
  1010. void GuiTextEditCtrl::setScriptValue(const char *value)
  1011. {
  1012. mTextBuffer.assign(value);
  1013. mSelector.setTextLength(mTextBuffer.length());
  1014. }
  1015. GuiTextEditCtrl::InputMode GuiTextEditCtrl::getInputModeEnum(const char* label)
  1016. {
  1017. // Search for Mnemonic.
  1018. for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
  1019. {
  1020. if (dStricmp(inputModeEnums[i].label, label) == 0)
  1021. return (InputMode)inputModeEnums[i].index;
  1022. }
  1023. // Warn.
  1024. Con::warnf("GuiTextEditCtrl::getInputModeEnum() - Invalid mode of '%s'", label);
  1025. return (InputMode)-1;
  1026. }
  1027. const char* GuiTextEditCtrl::getInputModeDescription(const InputMode mode)
  1028. {
  1029. // Search for Mnemonic.
  1030. for (U32 i = 0; i < (sizeof(inputModeEnums) / sizeof(EnumTable::Enums)); i++)
  1031. {
  1032. if (inputModeEnums[i].index == mode)
  1033. return inputModeEnums[i].label;
  1034. }
  1035. // Warn.
  1036. Con::warnf("GuiTextEditCtrl::getInputModeDescription() - Invalid input mode.");
  1037. return StringTable->EmptyString;
  1038. }
  1039. //Returns true if valid, false if invalid
  1040. bool GuiTextEditCtrl::inputModeValidate(const U16 key, S32 cursorPos)
  1041. {
  1042. if (key == '-')
  1043. {
  1044. if (mInputMode == Alpha || mInputMode == AlphaNumeric)
  1045. {
  1046. return false;
  1047. }
  1048. else if (mInputMode == Decimal || mInputMode == Number)
  1049. {
  1050. //a minus sign only exists at the beginning, and only a single minus sign
  1051. if (cursorPos != 0 || (mInsertOn && mTextBuffer[0] == '-'))
  1052. {
  1053. return false;
  1054. }
  1055. }
  1056. }
  1057. else if (key >= '0' && key <= '9')
  1058. {
  1059. if (mInputMode == Alpha)
  1060. {
  1061. return false;
  1062. }
  1063. }
  1064. else if (key == '.')
  1065. {
  1066. if (mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
  1067. {
  1068. return false;
  1069. }
  1070. else if (mInputMode == Decimal)
  1071. {
  1072. if (!mInsertOn && mTextBuffer[cursorPos] == '.')
  1073. {
  1074. return true;
  1075. }
  1076. const char* dot = dStrchr(mTextBuffer.c_str(), '.');
  1077. if (dot != NULL)
  1078. {
  1079. return false;
  1080. }
  1081. }
  1082. }
  1083. else if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z'))
  1084. {
  1085. if (mInputMode == Decimal || mInputMode == Number)
  1086. {
  1087. return false;
  1088. }
  1089. }
  1090. else if (key == 32)
  1091. {
  1092. if (mInputMode == Decimal || mInputMode == Number)
  1093. {
  1094. return false;
  1095. }
  1096. }
  1097. else if (mInputMode == Decimal || mInputMode == Number || mInputMode == Alpha || mInputMode == AlphaNumeric)
  1098. {
  1099. //The remaining characters only go with AllText
  1100. return false;
  1101. }
  1102. //Looks like we have a valid character!
  1103. return true;
  1104. }
  1105. void GuiTextEditCtrl::setMaxLength(S32 max)
  1106. {
  1107. mMaxStrLen = getMax(1, getMin(max, MAX_STRING_LENGTH));
  1108. }
  1109. void GuiTextEditCtrl::setInputMode(const InputMode mode)
  1110. {
  1111. if(mInputMode == mode)
  1112. return;
  1113. //Time to set the mode
  1114. mInputMode = mode;
  1115. //now let's parse that buffer and get rid of invalid characters
  1116. if (mode != AllText)
  1117. {
  1118. bool oldInsert = mInsertOn;
  1119. mInsertOn = false;
  1120. for (S32 i = 0; i < MAX_STRING_LENGTH; i++)
  1121. {
  1122. const UTF16 character = mTextBuffer[i];
  1123. if (character == '\0')
  1124. {
  1125. //Done and done.
  1126. break;
  1127. }
  1128. if (!inputModeValidate(character, i))
  1129. {
  1130. //Bad Character! Let's remove it.
  1131. mTextBuffer.erase(i, 1);
  1132. //Step it back
  1133. i--;
  1134. }
  1135. }
  1136. mInsertOn = oldInsert;
  1137. }
  1138. }
  1139. string GuiTextEditCtrl::applyPasswordMasking()
  1140. {
  1141. if (mPasswordText)
  1142. {
  1143. string passwordCover = string();
  1144. passwordCover.resize(mTextBuffer.length(), mPasswordMask[0]);
  1145. return passwordCover;
  1146. }
  1147. return mTextBuffer;
  1148. }
  1149. bool GuiTextEditCtrl::handleKeyDownWithShift(const GuiEvent& event)
  1150. {
  1151. switch (event.keyCode)
  1152. {
  1153. case KEY_TAB:
  1154. return tabPrev();
  1155. case KEY_HOME:
  1156. mSelector.selectTo(0);
  1157. return true;
  1158. case KEY_END:
  1159. mSelector.selectTo(mTextBuffer.length());
  1160. return true;
  1161. case KEY_LEFT:
  1162. return handleShiftArrowKey(GuiDirection::Left);
  1163. case KEY_RIGHT:
  1164. return handleShiftArrowKey(GuiDirection::Right);
  1165. case KEY_UP:
  1166. return handleShiftArrowKey(GuiDirection::Up);
  1167. case KEY_DOWN:
  1168. return handleShiftArrowKey(GuiDirection::Down);
  1169. }
  1170. return false;
  1171. }
  1172. bool GuiTextEditCtrl::handleKeyDownWithCtrl(const GuiEvent& event)
  1173. {
  1174. switch (event.keyCode)
  1175. {
  1176. #if !(defined(TORQUE_OS_OSX) || defined(TORQUE_OS_IOS))
  1177. // windows style cut / copy / paste / undo keybinds
  1178. case KEY_C:
  1179. case KEY_X:
  1180. onCopy(event.keyCode == KEY_X);
  1181. return true;
  1182. case KEY_V:
  1183. onPaste();
  1184. return true;
  1185. case KEY_Z:
  1186. onUndo();
  1187. return true;
  1188. #endif
  1189. case KEY_DELETE:
  1190. case KEY_BACKSPACE:
  1191. selectAllText();
  1192. handleBackSpace();
  1193. return true;
  1194. }
  1195. return false;
  1196. }
  1197. bool GuiTextEditCtrl::handleKeyDownWithAlt(const GuiEvent& event)
  1198. {
  1199. #if (defined(TORQUE_OS_OSX) || defined(TORQUE_OS_IOS))
  1200. // Added Mac cut/copy/paste/undo keys
  1201. // Mac command key maps to alt in torque.
  1202. switch(event.keyCode)
  1203. {
  1204. case KEY_C:
  1205. case KEY_X:
  1206. onCopy( event.keyCode==KEY_X );
  1207. return true;
  1208. case KEY_V:
  1209. onPaste();
  1210. return true;
  1211. case KEY_Z:
  1212. onUndo();
  1213. return true;
  1214. }
  1215. #endif
  1216. return false;
  1217. }
  1218. bool GuiTextEditCtrl::handleKeyDownWithNoModifier(const GuiEvent& event)
  1219. {
  1220. switch (event.keyCode)
  1221. {
  1222. case KEY_TAB:
  1223. return tabNext();
  1224. case KEY_ESCAPE:
  1225. if (isMethod(mEscapeCommand))
  1226. {
  1227. Con::evaluate(mEscapeCommand);
  1228. return true;
  1229. }
  1230. return false;
  1231. case KEY_RETURN:
  1232. case KEY_NUMPADENTER:
  1233. if (!validate())
  1234. {
  1235. return true;
  1236. }
  1237. return handleEnterKey();
  1238. case KEY_LEFT:
  1239. return handleArrowKey(GuiDirection::Left);
  1240. case KEY_RIGHT:
  1241. return handleArrowKey(GuiDirection::Right);
  1242. case KEY_UP:
  1243. return handleArrowKey(GuiDirection::Up);
  1244. case KEY_DOWN:
  1245. return handleArrowKey(GuiDirection::Down);
  1246. case KEY_DELETE:
  1247. return handleDelete();
  1248. case KEY_BACKSPACE:
  1249. return handleBackSpace();
  1250. case KEY_INSERT:
  1251. mInsertOn = !mInsertOn;
  1252. return true;
  1253. case KEY_HOME:
  1254. mSelector.setCursorPosition(0);
  1255. return true;
  1256. case KEY_END:
  1257. mSelector.setCursorPosition(mTextBuffer.length());
  1258. return true;
  1259. }
  1260. return false;
  1261. }
  1262. bool GuiTextEditCtrl::handleCharacterInput(const GuiEvent& event)
  1263. {
  1264. if (!mProfile->getFont(mFontSizeAdjust))
  1265. return false;
  1266. if (mProfile->getFont(mFontSizeAdjust)->isValidChar(event.ascii))
  1267. {
  1268. // Get the character ready to add to a UTF8 string.
  1269. string characterToInsert = string(1, event.ascii);
  1270. //Stop characters that aren't allowed based on InputMode
  1271. if (!inputModeValidate(event.ascii, mSelector.getCursorPos()))
  1272. {
  1273. keyDenied();
  1274. return true;
  1275. }
  1276. saveUndoState();
  1277. if (mSelector.hasSelection())
  1278. {
  1279. mSelector.eraseSelection(mTextBuffer);
  1280. }
  1281. if (mTextBuffer.length() < mMaxStrLen || !mInsertOn)
  1282. {
  1283. if (!mInsertOn)
  1284. {
  1285. mTextBuffer.erase(mSelector.getCursorPos(), 1);
  1286. }
  1287. mTextBuffer.insert(mSelector.getCursorPos(), characterToInsert);
  1288. mSelector.setTextLength(mTextBuffer.length());
  1289. mSelector.stepCursorForward();
  1290. setText(mTextBuffer);
  1291. }
  1292. else
  1293. keyDenied();
  1294. execConsoleCallback();
  1295. return true;
  1296. }
  1297. return false;
  1298. }
  1299. bool GuiTextEditCtrl::handleBackSpace()
  1300. {
  1301. if (mTextBuffer.length() == 0 || (mSelector.getCursorPos() == 0 && !mSelector.hasSelection()))
  1302. return true;
  1303. saveUndoState();
  1304. if (mSelector.hasSelection())
  1305. {
  1306. mSelector.eraseSelection(mTextBuffer);
  1307. }
  1308. else
  1309. {
  1310. mSelector.stepCursorBackward();
  1311. mTextBuffer.erase(mSelector.getCursorPos(), 1);
  1312. }
  1313. mSelector.setTextLength(mTextBuffer.length());
  1314. setText(mTextBuffer);
  1315. execConsoleCallback();
  1316. return true;
  1317. }
  1318. bool GuiTextEditCtrl::handleDelete()
  1319. {
  1320. if (mTextBuffer.length() == mSelector.getCursorPos() && !mSelector.hasSelection())
  1321. return true;
  1322. saveUndoState();
  1323. if (mSelector.hasSelection())
  1324. {
  1325. mSelector.eraseSelection(mTextBuffer);
  1326. }
  1327. else
  1328. {
  1329. mTextBuffer.erase(mSelector.getCursorPos(), 1);
  1330. }
  1331. mSelector.setTextLength(mTextBuffer.length());
  1332. setText(mTextBuffer);
  1333. execConsoleCallback();
  1334. return true;
  1335. }
  1336. bool GuiTextEditCtrl::handleEnterKey()
  1337. {
  1338. if (isMethod("onReturn"))
  1339. Con::executef(this, 1, "onReturn");
  1340. if (mReturnCausesTab)
  1341. {
  1342. tabNext();
  1343. }
  1344. if (isMethod(mReturnCommand))
  1345. {
  1346. Con::evaluate(mReturnCommand);
  1347. }
  1348. return true;
  1349. }
  1350. bool GuiTextEditCtrl::handleArrowKey(GuiDirection direction)
  1351. {
  1352. if (direction == GuiDirection::Left)
  1353. {
  1354. mSelector.setCursorAtEOL(false);
  1355. mSelector.stepCursorBackward();
  1356. }
  1357. else if (direction == GuiDirection::Right)
  1358. {
  1359. mSelector.setCursorAtEOL(false);
  1360. mSelector.stepCursorForward();
  1361. }
  1362. else if (direction == GuiDirection::Up)
  1363. {
  1364. S32 newCursorPos = getLineAdjustedIbeamPosition(-mProfile->getFont(mFontSizeAdjust)->getHeight());
  1365. if (newCursorPos == mSelector.getCursorPos())
  1366. {
  1367. newCursorPos = 0;
  1368. }
  1369. mSelector.setCursorPosition(newCursorPos);
  1370. }
  1371. else if (direction == GuiDirection::Down)
  1372. {
  1373. S32 newCursorPos = getLineAdjustedIbeamPosition(mProfile->getFont(mFontSizeAdjust)->getHeight());
  1374. if (newCursorPos == mSelector.getCursorPos())
  1375. {
  1376. newCursorPos = mTextBuffer.length();
  1377. }
  1378. mSelector.setCursorPosition(newCursorPos);
  1379. }
  1380. setUpdate();
  1381. mSelector.resetCursorBlink();
  1382. return true;
  1383. }
  1384. bool GuiTextEditCtrl::handleShiftArrowKey(GuiDirection direction)
  1385. {
  1386. if (direction == GuiDirection::Left)
  1387. {
  1388. mSelector.setCursorAtEOL(false);
  1389. modifySelectBlock(mSelector.getCursorPos() - 1);
  1390. }
  1391. else if (direction == GuiDirection::Right)
  1392. {
  1393. mSelector.setCursorAtEOL(false);
  1394. modifySelectBlock(mSelector.getCursorPos() + 1);
  1395. }
  1396. else if (direction == GuiDirection::Up)
  1397. {
  1398. S32 newCursorPos = getLineAdjustedIbeamPosition(-mProfile->getFont(mFontSizeAdjust)->getHeight());
  1399. modifySelectBlock(newCursorPos);
  1400. }
  1401. else if (direction == GuiDirection::Down)
  1402. {
  1403. S32 newCursorPos = getLineAdjustedIbeamPosition(mProfile->getFont(mFontSizeAdjust)->getHeight());
  1404. modifySelectBlock(newCursorPos);
  1405. }
  1406. setUpdate();
  1407. mSelector.resetCursorBlink();
  1408. return true;
  1409. }
  1410. S32 GuiTextEditCtrl::getLineAdjustedIbeamPosition(S32 heightAdjustment)
  1411. {
  1412. Point2I centerPoint = mSelector.getCursorCenter();
  1413. centerPoint.y += heightAdjustment;
  1414. return calculateIbeamPosition(centerPoint);
  1415. }
  1416. void GuiTextEditCtrl::modifySelectBlock(const U32 target)
  1417. {
  1418. mSelector.setTextLength(mTextBuffer.length());
  1419. mSelector.selectTo(target);
  1420. setUpdate();
  1421. }
  1422. void GuiTextEditCtrl::selectAllText()
  1423. {
  1424. mSelector.setCursorPosition(0);
  1425. modifySelectBlock(mTextBuffer.length());
  1426. }
  1427. void GuiTextEditCtrl::getCursor(GuiCursor*& cursor, bool& showCursor, const GuiEvent& lastGuiEvent)
  1428. {
  1429. if (mEditCursor == NULL)
  1430. {
  1431. SimObject* obj;
  1432. obj = Sim::findObject("EditCursor");
  1433. mEditCursor = dynamic_cast<GuiCursor*>(obj);
  1434. }
  1435. if (mEditCursor != NULL)
  1436. {
  1437. cursor = mEditCursor;
  1438. }
  1439. }
  1440. #pragma endregion