guiMLTextCtrl.cc 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166
  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 "gui/guiMLTextCtrl.h"
  23. #include "gui/containers/guiScrollCtrl.h"
  24. #include "graphics/dgl.h"
  25. #include "console/consoleTypes.h"
  26. #include "gui/guiCanvas.h"
  27. #include "memory/frameAllocator.h"
  28. #include "string/unicode.h"
  29. IMPLEMENT_CONOBJECT(GuiMLTextCtrl);
  30. const U32 GuiMLTextCtrl::csmTextBufferGrowthSize = 1024;
  31. ConsoleMethod( GuiMLTextCtrl, setText, void, 3, 3, "(text) Use the setText method to change the current text content of the control to text. This replaces all old content.\n"
  32. "@param text The new contents for this control.\n"
  33. "@return No return value.\n"
  34. "@sa addText, getText")
  35. {
  36. object->setText(argv[2], dStrlen(argv[2]));
  37. }
  38. ConsoleMethod( GuiMLTextCtrl, getText, const char*, 2, 2, "() Use the getText method to return the current text contents of the control, including all formatting characters.\n"
  39. "@return Returns the entire text contents of the control or indicating no contents.\n"
  40. "@sa addText")
  41. {
  42. return( object->getTextContent() );
  43. }
  44. ConsoleMethod( GuiMLTextCtrl, addText, void, 4, 4, "( text , reformat ) Use the addText method to add new text to the control. You may optionally request that the control be reformatted.\n"
  45. "@param text Text to add to control.\n"
  46. "@param reformat A boolean value that when set to true forces the control to re-evaluate the entire contents and to redisplay it.\n"
  47. "@return No return value.\n"
  48. "@sa getText, setText, forceReflow")
  49. {
  50. object->addText(argv[2], dStrlen(argv[2]), dAtob(argv[3]));
  51. }
  52. ConsoleMethod( GuiMLTextCtrl, setCursorPosition, bool, 3, 3, "(newPos) Use the setCursorPosition method to offset the cursor by newPos characters into the current text contents of the control.\n"
  53. "@param newPos An integer value indicating the character position at which to place the cursor.\n"
  54. "@return No return value.\n"
  55. "@sa scrollToTag, scrollToTop")
  56. {
  57. return object->setCursorPosition(dAtoi(argv[2]));
  58. }
  59. ConsoleMethod( GuiMLTextCtrl, scrollToTag, void, 3, 3, "( tagID ) Use the scrollToTag method to scroll to the first instance of a tag if it exists.\n"
  60. "@param tagID A tag number to search for. These tags are specified by embedding TorqueML <tag:tag_number> entries in text.\n"
  61. "@return No return value.\n"
  62. "@sa scrollToTop, setCursorPosition")
  63. {
  64. object->scrollToTag( dAtoi( argv[2] ) );
  65. }
  66. ConsoleMethod( GuiMLTextCtrl, scrollToTop, void, 2, 2, "() Use the scrollToTop method to scroll to the top of the text.\n"
  67. "@return No return value.\n"
  68. "@sa scrollToTag, setCursorPosition, scrollToBottom")
  69. {
  70. object->scrollToTop();
  71. }
  72. ConsoleMethod( GuiMLTextCtrl, scrollToBottom, void, 2, 2, "() Use the scrollToBottom method to scroll to the bottom of the text.\n"
  73. "@return No return value.\n"
  74. "@sa scrollToTag, setCursorPosition, scrollToTop")
  75. {
  76. object->scrollToBottom();
  77. }
  78. ConsoleFunction( StripMLControlChars, const char*, 2, 2, "( sourceString ) Use the stripMLControlChars function to remove all Torque Markup-Language (ML) symbols from sourceString.\n"
  79. "This may not remove <br> correctly, so check before you trust this function.\n"
  80. "@param sourceString The string to be modified.\n"
  81. "@return Returns a copy of sourceString with all the ML symbols removed, or the original string if no ML symbols were present.\n"
  82. "@sa stripChars")
  83. {
  84. return GuiMLTextCtrl::stripControlChars(argv[1]);
  85. }
  86. ConsoleMethod(GuiMLTextCtrl,forceReflow,void,2,2,"() Use the forceReflow method to force the text control to re-evaluate the entire contents and to redisplay it, possibly resizing the control.\n"
  87. "@return No return value.\n"
  88. "@sa addText")
  89. {
  90. if(!object->isAwake())
  91. Con::errorf("GuiMLTextCtrl::forceReflow can only be called on visible controls.");
  92. else
  93. object->reflow();
  94. }
  95. //--------------------------------------------------------------------------
  96. GuiMLTextCtrl::GuiMLTextCtrl()
  97. {
  98. mIsEditCtrl = false;
  99. mCursorPosition = 0;
  100. mMaxBufferSize = -1;
  101. mInitialText = StringTable->EmptyString;
  102. mSelectionActive = false;
  103. mSelectionStart = 0;
  104. mSelectionEnd = 0;
  105. mLineSpacingPixels = 2;
  106. mAllowColorChars = false;
  107. mBitmapList = 0;
  108. mBitmapRefList = 0;
  109. mFontList = 0;
  110. mDirty = true;
  111. mLineList = 0;
  112. mTagList = 0;
  113. mHitURL = 0;
  114. mActive = true;
  115. mAlpha = 1.0;
  116. }
  117. //--------------------------------------------------------------------------
  118. GuiMLTextCtrl::~GuiMLTextCtrl()
  119. {
  120. mCursorPosition = 0;
  121. mSelectionActive = false;
  122. mSelectionStart = 0;
  123. mSelectionEnd = 0;
  124. freeResources();
  125. }
  126. //--------------------------------------------------------------------------
  127. void GuiMLTextCtrl::initPersistFields()
  128. {
  129. Parent::initPersistFields();
  130. addField("lineSpacing", TypeS32, Offset(mLineSpacingPixels, GuiMLTextCtrl));
  131. addField("allowColorChars", TypeBool, Offset(mAllowColorChars, GuiMLTextCtrl));
  132. addField("maxChars", TypeS32, Offset(mMaxBufferSize, GuiMLTextCtrl));
  133. addField("deniedSound", TypeAudioAssetPtr, Offset(mDeniedSound, GuiMLTextCtrl));
  134. addField("text", TypeCaseString, Offset( mInitialText, GuiMLTextCtrl ) );
  135. }
  136. ConsoleMethod(GuiMLTextCtrl, setAlpha, void, 3, 3, "( alpha ) Use the setAlpha method to set alpha of this control to between [0.0 , 1.0].\n"
  137. "@param alpha A floating point value between 0.0 and 1.0 indicating the control's new alpha setting.\n"
  138. "@return No return value")
  139. {
  140. object->setAlpha(dAtof(argv[2]));
  141. }
  142. //--------------------------------------------------------------------------
  143. bool GuiMLTextCtrl::onAdd()
  144. {
  145. if(!Parent::onAdd())
  146. return false;
  147. if (!mTextBuffer.length() && mInitialText[0] != 0)
  148. setText(mInitialText, dStrlen(mInitialText)+1);
  149. return true;
  150. }
  151. //--------------------------------------------------------------------------
  152. bool GuiMLTextCtrl::onWake()
  153. {
  154. if (Parent::onWake() == false)
  155. return false;
  156. mDirty = true;
  157. return true;
  158. }
  159. //--------------------------------------------------------------------------
  160. void GuiMLTextCtrl::onPreRender()
  161. {
  162. if(mDirty)
  163. reflow();
  164. }
  165. //--------------------------------------------------------------------------
  166. void GuiMLTextCtrl::drawAtomText(bool sel, U32 start, U32 end, Atom *atom, Line *line, Point2I offset)
  167. {
  168. GFont *font = atom->style->font->fontRes;
  169. U32 xOff = 0;
  170. if(start != atom->textStart)
  171. {
  172. const UTF16* buff = mTextBuffer.getPtr() + atom->textStart;
  173. xOff += font->getStrNWidth(buff, start - atom->textStart);
  174. }
  175. Point2I drawPoint(offset.x + atom->xStart + xOff, offset.y + atom->yStart);
  176. ColorI color;
  177. if(atom->url)
  178. {
  179. if(atom->url->mouseDown)
  180. color = atom->style->linkColorHL;
  181. else
  182. color = atom->style->linkColor;
  183. }
  184. else
  185. color = atom->style->color;
  186. const UTF16* tmp = mTextBuffer.getPtr() + start;
  187. U32 tmpLen = end-start;
  188. if(!sel)
  189. {
  190. if(atom->style->shadowOffset.x || atom->style->shadowOffset.y)
  191. {
  192. ColorI shadowColor = atom->style->shadowColor;
  193. shadowColor.alpha = (U8)(shadowColor.alpha * mAlpha);
  194. dglSetBitmapModulation(shadowColor);
  195. dglDrawTextN(font, drawPoint + atom->style->shadowOffset, tmp, tmpLen, mAllowColorChars ? mProfile->mFontColors : NULL);
  196. }
  197. color.alpha = (U8)(color.alpha * mAlpha);
  198. dglSetBitmapModulation(color);
  199. dglDrawTextN(font, drawPoint, tmp, end-start, mAllowColorChars ? mProfile->mFontColors : NULL);
  200. //if the atom was "clipped", see if we need to draw a "..." at the end
  201. if (atom->isClipped)
  202. {
  203. Point2I p2 = drawPoint;
  204. p2.x += font->getStrNWidthPrecise(tmp, tmpLen);
  205. dglDrawTextN(font, p2, "...", 3, mAllowColorChars ? mProfile->mFontColors : NULL);
  206. }
  207. }
  208. else
  209. {
  210. RectI rect;
  211. rect.point.x = drawPoint.x;
  212. rect.point.y = line->y + offset.y;
  213. rect.extent.x = font->getStrNWidth(tmp, tmpLen) + 1;
  214. rect.extent.y = line->height + 1;
  215. dglDrawRectFill(rect, mProfile->mFillColorHL);
  216. dglSetBitmapModulation( mProfile->mFontColorHL ); // over-ride atom color:
  217. dglDrawTextN(font, drawPoint, tmp, tmpLen, mAllowColorChars ? mProfile->mFontColors : NULL);
  218. //if the atom was "clipped", see if we need to draw a "..." at the end
  219. if (atom->isClipped)
  220. {
  221. Point2I p2 = drawPoint;
  222. p2.x += font->getStrNWidthPrecise(tmp, end - atom->textStart);
  223. dglDrawTextN(font, p2, "...", 3, mAllowColorChars ? mProfile->mFontColors : NULL);
  224. }
  225. }
  226. if(atom->url && !atom->url->noUnderline)
  227. {
  228. drawPoint.y += atom->baseLine + 2;
  229. Point2I p2 = drawPoint;
  230. p2.x += font->getStrNWidthPrecise(tmp, end - atom->textStart);
  231. dglDrawLine(drawPoint, p2, color);
  232. }
  233. }
  234. //--------------------------------------------------------------------------
  235. void GuiMLTextCtrl::onRender(Point2I offset, const RectI& updateRect)
  236. {
  237. Parent::onRender(offset, updateRect);
  238. // draw all the bitmaps
  239. for(BitmapRef *walk = mBitmapRefList; walk; walk = walk->next)
  240. {
  241. RectI screenBounds = *walk;
  242. screenBounds.point += offset;
  243. if(!screenBounds.overlaps(updateRect))
  244. continue;
  245. dglClearBitmapModulation();
  246. dglDrawBitmap(walk->bitmap->bitmapHandle, screenBounds.point);
  247. //dglDrawRectFill(screenBounds, mProfile->mFillColor);
  248. }
  249. // draw all the text and dividerStyles
  250. for(Line *lwalk = mLineList; lwalk; lwalk = lwalk->next)
  251. {
  252. RectI lineRect(offset.x, offset.y + lwalk->y, mBounds.extent.x, lwalk->height);
  253. if(!lineRect.overlaps(updateRect))
  254. continue;
  255. if(lwalk->divStyle)
  256. dglDrawRectFill(lineRect, mProfile->mFillColorHL);
  257. for(Atom *awalk = lwalk->atomList; awalk; awalk = awalk->next)
  258. {
  259. if(!mSelectionActive || mSelectionEnd < awalk->textStart || mSelectionStart >= awalk->textStart + awalk->len)
  260. drawAtomText(false, awalk->textStart, awalk->textStart + awalk->len, awalk, lwalk, offset);
  261. else
  262. {
  263. U32 selectionStart = getMax(awalk->textStart, mSelectionStart);
  264. U32 selectionEnd = getMin(awalk->textStart + awalk->len, mSelectionEnd + 1);
  265. // draw some unselected text
  266. if(selectionStart > awalk->textStart)
  267. drawAtomText(false, awalk->textStart, selectionStart, awalk, lwalk, offset);
  268. // draw the selection
  269. drawAtomText(true, selectionStart, selectionEnd, awalk, lwalk, offset);
  270. if(selectionEnd < awalk->textStart + awalk->len)
  271. drawAtomText(false, selectionEnd, awalk->textStart + awalk->len, awalk, lwalk, offset);
  272. }
  273. }
  274. }
  275. dglClearBitmapModulation();
  276. }
  277. //--------------------------------------------------------------------------
  278. void GuiMLTextCtrl::freeLineBuffers()
  279. {
  280. mViewChunker.freeBlocks();
  281. mLineList = NULL;
  282. mBitmapRefList = NULL;
  283. mTagList = NULL;
  284. mHitURL = 0;
  285. mDirty = true;
  286. }
  287. //--------------------------------------------------------------------------
  288. void GuiMLTextCtrl::freeResources()
  289. {
  290. for(Font* walk = mFontList; walk; walk = walk->next)
  291. {
  292. delete[] walk->faceName;
  293. walk->fontRes = 0;
  294. }
  295. for(Bitmap* bwalk = mBitmapList; bwalk; bwalk = bwalk->next)
  296. bwalk->bitmapHandle = 0;
  297. mFontList = NULL;
  298. mBitmapList = NULL;
  299. mResourceChunker.freeBlocks();
  300. mDirty = true;
  301. freeLineBuffers();
  302. }
  303. //--------------------------------------------------------------------------
  304. void GuiMLTextCtrl::onSleep()
  305. {
  306. freeResources();
  307. Parent::onSleep();
  308. }
  309. //--------------------------------------------------------------------------
  310. void GuiMLTextCtrl::inspectPostApply()
  311. {
  312. Parent::inspectPostApply();
  313. if (mInitialText[0] != 0)
  314. setText(mInitialText, dStrlen(mInitialText));
  315. if ((S32)mLineSpacingPixels < 0)
  316. mLineSpacingPixels = 0;
  317. }
  318. //--------------------------------------------------------------------------
  319. void GuiMLTextCtrl::resize( const Point2I& newPosition, const Point2I& newExtent )
  320. {
  321. Parent::resize( newPosition, newExtent );
  322. //Con::executef( this, 3, "onResize", Con::getIntArg( newExtent.x ), Con::getIntArg( newExtent.y ) );
  323. }
  324. //--------------------------------------------------------------------------
  325. void GuiMLTextCtrl::parentResized(const Point2I& oldParentExtent,
  326. const Point2I& newParentExtent)
  327. {
  328. Parent::parentResized(oldParentExtent, newParentExtent);
  329. mDirty = true;
  330. }
  331. //--------------------------------------------------------------------------
  332. U32 GuiMLTextCtrl::getNumChars() const
  333. {
  334. return mTextBuffer.length();
  335. }
  336. //--------------------------------------------------------------------------
  337. U32 GuiMLTextCtrl::getText(char* pBuffer, const U32 bufferSize) const
  338. {
  339. mTextBuffer.getCopy8(pBuffer, bufferSize);
  340. return getMin(mTextBuffer.length(), bufferSize);
  341. }
  342. //--------------------------------------------------------------------------
  343. const char* GuiMLTextCtrl::getTextContent()
  344. {
  345. if ( mTextBuffer.length() > 0 )
  346. {
  347. UTF8* returnString = Con::getReturnBuffer( mTextBuffer.getUTF8BufferSizeEstimate() );
  348. mTextBuffer.getCopy8(returnString, mTextBuffer.getUTF8BufferSizeEstimate() );
  349. return returnString;
  350. }
  351. return( "" );
  352. }
  353. //--------------------------------------------------------------------------
  354. const char *GuiMLTextCtrl::getScriptValue()
  355. {
  356. return getTextContent();
  357. }
  358. //--------------------------------------------------------------------------
  359. void GuiMLTextCtrl::setScriptValue(const char *newText)
  360. {
  361. setText(newText, dStrlen(newText));
  362. }
  363. //--------------------------------------------------------------------------
  364. void GuiMLTextCtrl::setText(const char* textBuffer, const U32 numChars)
  365. {
  366. U32 chars = numChars;
  367. if(numChars >= (U32)mMaxBufferSize)
  368. chars = mMaxBufferSize;
  369. // leaving this usage because we StringBuffer.set((UTF8*)) cannot take a numChars arg.
  370. // perhaps it should? -paxorr
  371. FrameTemp<UTF8> tmp(chars+1);
  372. dStrncpy(tmp, textBuffer, chars);
  373. tmp[chars] = 0;
  374. mTextBuffer.set(tmp);
  375. //after setting text, always set the cursor to the beginning
  376. setCursorPosition(0);
  377. clearSelection();
  378. mDirty = true;
  379. scrollToTop();
  380. }
  381. //--------------------------------------------------------------------------
  382. void GuiMLTextCtrl::addText(const char* textBuffer, const U32 numChars, bool reformat)
  383. {
  384. if(numChars >= (U32)mMaxBufferSize)
  385. return;
  386. FrameTemp<UTF8> tmp(numChars+1);
  387. dStrncpy(tmp, textBuffer, numChars);
  388. tmp[numChars] = 0;
  389. mTextBuffer.append(tmp);
  390. //after setting text, always set the cursor to the beginning
  391. if (reformat)
  392. {
  393. setCursorPosition(0);
  394. clearSelection();
  395. mDirty = true;
  396. scrollToTop();
  397. }
  398. }
  399. //--------------------------------------------------------------------------
  400. bool GuiMLTextCtrl::setCursorPosition(const S32 newPosition)
  401. {
  402. if (newPosition < 0)
  403. {
  404. mCursorPosition = 0;
  405. return true;
  406. }
  407. else if ((U32)newPosition >= mTextBuffer.length())
  408. {
  409. mCursorPosition = mTextBuffer.length();
  410. return true;
  411. }
  412. else
  413. {
  414. mCursorPosition = newPosition;
  415. return false;
  416. }
  417. }
  418. //--------------------------------------------------------------------------
  419. void GuiMLTextCtrl::ensureCursorOnScreen()
  420. {
  421. // If our parent isn't a scroll control, or we're not the only control
  422. // in the content region, bail...
  423. GuiControl* pParent = getParent();
  424. GuiScrollCtrl *sc = dynamic_cast<GuiScrollCtrl*>(pParent);
  425. if(!sc)
  426. return;
  427. // Ok. Now we know that our parent is a scroll control. Let's find the
  428. // top of the cursor, and it's bottom. We can then scroll the parent control
  429. // if appropriate...
  430. Point2I cursorTopP, cursorBottomP;
  431. ColorI color;
  432. getCursorPositionAndColor(cursorTopP, cursorBottomP, color);
  433. sc->scrollRectVisible(RectI(cursorTopP.x, cursorTopP.y, 1, cursorBottomP.y - cursorTopP.y));
  434. }
  435. //--------------------------------------
  436. void GuiMLTextCtrl::getCursorPositionAndColor(Point2I &cursorTop, Point2I &cursorBottom, ColorI &color)
  437. {
  438. S32 x = 0;
  439. S32 y = 0;
  440. S32 height = mProfile->getFont(mFontSizeAdjust)->getHeight();
  441. color = mProfile->mCursorColor;
  442. for(Line *walk = mLineList; walk; walk = walk->next)
  443. {
  444. if ((mCursorPosition <= walk->textStart + walk->len) || (walk->next == NULL))
  445. {
  446. // it's in the atoms on this line...
  447. y = walk->y;
  448. height = walk->height;
  449. for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next)
  450. {
  451. if(mCursorPosition < awalk->textStart)
  452. {
  453. x = awalk->xStart;
  454. goto done;
  455. }
  456. if(mCursorPosition > awalk->textStart + awalk->len)
  457. {
  458. x = awalk->xStart + awalk->width;
  459. continue;
  460. }
  461. // it's in the text block...
  462. x = awalk->xStart;
  463. //
  464. // [neo, 5/7/2007]: cannot use const as GFont::getStrNWidth() is not declared as const
  465. //
  466. //const GFont *font = awalk->style->font->fontRes;
  467. GFont *font = awalk->style->font->fontRes;
  468. const UTF16* buff = mTextBuffer.getPtr() + awalk->textStart;
  469. x += font->getStrNWidth(buff, mCursorPosition - awalk->textStart - 1);
  470. color = awalk->style->color;
  471. goto done;
  472. }
  473. //if it's within this walk's width, but we didn't find an atom, leave the cursor at the beginning of the line...
  474. goto done;
  475. }
  476. }
  477. done:
  478. cursorTop.set(x, y);
  479. cursorBottom.set(x, y + height);
  480. }
  481. //--------------------------------------------------------------------------
  482. // Keyboard events...
  483. bool GuiMLTextCtrl::onKeyDown(const GuiEvent& event)
  484. {
  485. //only cut/copy work with this control...
  486. if (event.modifier & SI_CTRL)
  487. {
  488. switch(event.keyCode)
  489. {
  490. //copy
  491. case KEY_C:
  492. {
  493. //make sure we actually have something selected
  494. if (mSelectionActive)
  495. {
  496. copyToClipboard(mSelectionStart, mSelectionEnd);
  497. setUpdate();
  498. }
  499. return true;
  500. }
  501. }
  502. }
  503. // Otherwise, let the parent have the event...
  504. return Parent::onKeyDown(event);
  505. }
  506. //--------------------------------------------------------------------------
  507. // Mousing events...
  508. void GuiMLTextCtrl::onMouseDown(const GuiEvent& event)
  509. {
  510. if(!mActive)
  511. return;
  512. Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint));
  513. if(hitAtom && !mIsEditCtrl)
  514. {
  515. mouseLock();
  516. mHitURL = hitAtom->url;
  517. if (mHitURL)
  518. mHitURL->mouseDown = true;
  519. }
  520. setFirstResponder();
  521. mouseLock();
  522. mSelectionActive = false;
  523. mSelectionAnchor = getTextPosition(globalToLocalCoord(event.mousePoint));
  524. mSelectionAnchorDropped = event.mousePoint;
  525. if (mSelectionAnchor < 0)
  526. mSelectionAnchor = 0;
  527. else if ((U32)mSelectionAnchor >= mTextBuffer.length())
  528. mSelectionAnchor = mTextBuffer.length();
  529. mVertMoveAnchorValid = false;
  530. setUpdate();
  531. }
  532. //--------------------------------------------------------------------------
  533. void GuiMLTextCtrl::onMouseDragged(const GuiEvent& event)
  534. {
  535. if (!mActive || (Canvas->getMouseLockedControl() != this))
  536. return;
  537. Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint));
  538. bool down = false;
  539. //note mHitURL can't be set unless this is (!mIsEditCtrl)
  540. if(hitAtom && hitAtom->url == mHitURL)
  541. down = true;
  542. if(mHitURL && down != mHitURL->mouseDown)
  543. mHitURL->mouseDown = down;
  544. if (!mHitURL)
  545. {
  546. S32 newSelection = 0;
  547. newSelection = getTextPosition(globalToLocalCoord(event.mousePoint));
  548. if (newSelection < 0)
  549. newSelection = 0;
  550. else if ((U32)newSelection > mTextBuffer.length())
  551. newSelection = mTextBuffer.length();
  552. if (newSelection == mSelectionAnchor)
  553. {
  554. mSelectionActive = false;
  555. }
  556. else if (newSelection > mSelectionAnchor)
  557. {
  558. mSelectionActive = true;
  559. mSelectionStart = mSelectionAnchor;
  560. mSelectionEnd = newSelection - 1;
  561. }
  562. else
  563. {
  564. mSelectionStart = newSelection;
  565. mSelectionEnd = mSelectionAnchor - 1;
  566. mSelectionActive = true;
  567. }
  568. setCursorPosition(newSelection);
  569. mDirty = true;
  570. }
  571. setUpdate();
  572. }
  573. //--------------------------------------------------------------------------
  574. void GuiMLTextCtrl::onMouseUp(const GuiEvent& event)
  575. {
  576. if (!mActive || (Canvas->getMouseLockedControl() != this))
  577. return;
  578. mouseUnlock();
  579. //see if we've clicked on a URL
  580. Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint));
  581. if (mHitURL && hitAtom && hitAtom->url == mHitURL && mHitURL->mouseDown)
  582. {
  583. mHitURL->mouseDown = false;
  584. // Convert URL from UTF16 to UTF8.
  585. UTF8* url = mTextBuffer.createSubstring8(mHitURL->textStart, mHitURL->len);
  586. Con::executef(this, 2, "onURL", url);
  587. delete[] url;
  588. mHitURL = NULL;
  589. setUpdate();
  590. return;
  591. }
  592. //else, update our selection
  593. else
  594. {
  595. if ((event.mousePoint - mSelectionAnchorDropped).len() < 3)
  596. mSelectionActive = false;
  597. setCursorPosition(getTextPosition(globalToLocalCoord(event.mousePoint)));
  598. mVertMoveAnchorValid = false;
  599. setUpdate();
  600. }
  601. }
  602. //--------------------------------------------------------------------------
  603. void GuiMLTextCtrl::insertChars(const char* inputChars,
  604. const U32 numInputChars,
  605. const U32 position)
  606. {
  607. AssertFatal(isSelectionActive() == false, "GuiMLTextCtrl::insertChars: don't use this function when there's a selection active!");
  608. AssertFatal(position <= mTextBuffer.length(), "GuiMLTextCtrl::insertChars: can't insert outside of current text!");
  609. //make sure the text will fit...
  610. S32 numCharsToInsert = numInputChars;
  611. if ((U32)mMaxBufferSize > 0 && mTextBuffer.length() + numInputChars + 1 > (U32)mMaxBufferSize)
  612. numCharsToInsert = mMaxBufferSize - mTextBuffer.length() - 1;
  613. if (numCharsToInsert <= 0)
  614. {
  615. // Play the "Denied" sound:
  616. if ( numInputChars > 0 && mDeniedSound.notNull() )
  617. {
  618. AUDIOHANDLE handle = alxCreateSource( mDeniedSound );
  619. alxPlay( handle );
  620. }
  621. return;
  622. }
  623. mTextBuffer.insert(position, inputChars );
  624. if (mCursorPosition >= position)
  625. {
  626. // Cursor was at or after the inserted text, move forward...
  627. mCursorPosition += numCharsToInsert;
  628. }
  629. AssertFatal(mCursorPosition <= mTextBuffer.length(), "GuiMLTextCtrl::insertChars: bad cursor position");
  630. mDirty = true;
  631. }
  632. //--------------------------------------------------------------------------
  633. void GuiMLTextCtrl::deleteChars(const U32 rangeStart,
  634. const U32 rangeEnd)
  635. {
  636. AssertFatal(isSelectionActive() == false, "GuiMLTextCtrl::deleteChars: don't use this function when there's a selection active");
  637. AssertFatal(rangeStart <= mTextBuffer.length() && rangeEnd <= mTextBuffer.length(),
  638. avar("GuiMLTextCtrl::deleteChars: can't delete outside of current text (%d, %d, %d)",
  639. rangeStart, rangeEnd, mTextBuffer.length()));
  640. AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::deleteChars: invalid delete range");
  641. // Currently deleting text doesn't resize the text buffer, perhaps this should
  642. // change?
  643. mTextBuffer.cut(rangeStart, rangeEnd - rangeStart);
  644. if (mCursorPosition <= rangeStart)
  645. {
  646. // Cursor placed before deleted text, ignore
  647. }
  648. else if (mCursorPosition > rangeStart && mCursorPosition <= rangeEnd)
  649. {
  650. // Cursor inside deleted text, set to start of range
  651. mCursorPosition = rangeStart;
  652. }
  653. else
  654. {
  655. // Cursor after deleted text, decrement by number of chars deleted
  656. mCursorPosition -= (rangeEnd - rangeStart) + 1;
  657. }
  658. AssertFatal(mCursorPosition <= mTextBuffer.length(), "GuiMLTextCtrl::deleteChars: bad cursor position");
  659. mDirty = true;
  660. }
  661. //--------------------------------------------------------------------------
  662. void GuiMLTextCtrl::copyToClipboard(const U32 rangeStart, const U32 rangeEnd)
  663. {
  664. AssertFatal(rangeStart < mTextBuffer.length() && rangeEnd < mTextBuffer.length(),
  665. avar("GuiMLTextCtrl::copyToClipboard: can't copy outside of current text (%d, %d, %d)",
  666. rangeStart, rangeEnd, mTextBuffer.length()));
  667. AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::copyToClipboard: invalid copy range");
  668. //copy the selection to the clipboard
  669. UTF8* selection = mTextBuffer.createSubstring8(rangeStart, rangeEnd-rangeStart);
  670. Platform::setClipboard(selection);
  671. delete[] selection;
  672. }
  673. //--------------------------------------------------------------------------
  674. bool GuiMLTextCtrl::isSelectionActive() const
  675. {
  676. return mSelectionActive;
  677. }
  678. //--------------------------------------------------------------------------
  679. void GuiMLTextCtrl::clearSelection()
  680. {
  681. mSelectionActive = false;
  682. mSelectionStart = 0;
  683. mSelectionEnd = 0;
  684. }
  685. //--------------------------------------------------------------------------
  686. void GuiMLTextCtrl::scrollToTag( U32 id )
  687. {
  688. // If the parent control is not a GuiScrollContentCtrl, then this call is invalid:
  689. GuiScrollCtrl *pappy = dynamic_cast<GuiScrollCtrl*>(getParent());
  690. if ( !pappy )
  691. return;
  692. // Find the indicated tag:
  693. LineTag* tag = NULL;
  694. for ( tag = mTagList; tag; tag = tag->next )
  695. {
  696. if ( tag->id == id )
  697. break;
  698. }
  699. if ( !tag )
  700. {
  701. Con::warnf( ConsoleLogEntry::General, "GuiMLTextCtrl::scrollToTag - tag id %d not found!", id );
  702. return;
  703. }
  704. pappy->scrollRectVisible(RectI(0, tag->y, 1, 1));
  705. }
  706. //--------------------------------------------------------------------------
  707. void GuiMLTextCtrl::scrollToTop()
  708. {
  709. // If the parent control is not a GuiScrollContentCtrl, then this call is invalid:
  710. GuiScrollCtrl *pappy = dynamic_cast<GuiScrollCtrl*>(getParent());
  711. if ( !pappy )
  712. return;
  713. pappy->scrollRectVisible(RectI(0,0,0,0));
  714. }
  715. //--------------------------------------------------------------------------
  716. void GuiMLTextCtrl::scrollToBottom()
  717. {
  718. // If the parent control is not a GuiScrollContentCtrl, then this call is invalid:
  719. GuiScrollCtrl *pappy = dynamic_cast<GuiScrollCtrl*>(getParent());
  720. if ( !pappy )
  721. return;
  722. // Figure bounds for the bottom left corner
  723. RectI cornerBounds (0, getPosition().y + getExtent().y, 1, 1);
  724. pappy->scrollRectVisible(cornerBounds);
  725. }
  726. //--------------------------------------------------------------------------
  727. GuiMLTextCtrl::Atom *GuiMLTextCtrl::findHitAtom(const Point2I localCoords)
  728. {
  729. AssertFatal(mAwake, "Can't get the text position of a sleeping control.");
  730. if(mDirty)
  731. reflow();
  732. for(Line *walk = mLineList; walk; walk = walk->next)
  733. {
  734. if((U32)localCoords.y < walk->y)
  735. return NULL;
  736. if(localCoords.y >= (S32)walk->y && localCoords.y < (S32)(walk->y + walk->height))
  737. {
  738. for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next)
  739. {
  740. if(localCoords.x < (S32)awalk->xStart)
  741. return NULL;
  742. if(localCoords.x >= (S32)(awalk->xStart + awalk->width))
  743. continue;
  744. return awalk;
  745. }
  746. }
  747. }
  748. return NULL;
  749. }
  750. //--------------------------------------------------------------------------
  751. S32 GuiMLTextCtrl::getTextPosition(const Point2I& localCoords)
  752. {
  753. AssertFatal(mAwake, "Can't get the text position of a sleeping control.");
  754. if(mDirty)
  755. reflow();
  756. for(Line *walk = mLineList; walk; walk = walk->next)
  757. {
  758. if((S32)localCoords.y < (S32)walk->y)
  759. return walk->textStart;
  760. if(localCoords.y >= (S32)walk->y && localCoords.y < (S32)(walk->y + walk->height))
  761. {
  762. for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next)
  763. {
  764. if(localCoords.x < (S32)awalk->xStart)
  765. return awalk->textStart;
  766. if(localCoords.x >= (S32)(awalk->xStart + awalk->width))
  767. continue;
  768. // it's in the text block...
  769. //
  770. // [neo, 5/7/2007]: cannot use const as getBreakPos() is not declared as const
  771. //
  772. //const GFont *font = awalk->style->font->fontRes;
  773. GFont *font = awalk->style->font->fontRes;
  774. const UTF16 *tmp16 = mTextBuffer.getPtr() + awalk->textStart;
  775. U32 bp = font->getBreakPos(tmp16, awalk->len, localCoords.x - awalk->xStart, false);
  776. return awalk->textStart + bp;
  777. }
  778. return walk->textStart + walk->len;
  779. }
  780. }
  781. return mTextBuffer.length() - 1;
  782. }
  783. //--------------------------------------------------------------------------
  784. GuiMLTextCtrl::Font *GuiMLTextCtrl::allocFont(const char *faceName, U32 faceNameLen, U32 size)
  785. {
  786. // check if it's in the font list currently:
  787. for(Font *walk = mFontList; walk; walk = walk->next)
  788. if(faceNameLen == walk->faceNameLen &&
  789. !dStrncmp(walk->faceName, faceName, faceNameLen) &&
  790. size == walk->size)
  791. return walk;
  792. // Create!
  793. Font *ret;
  794. ret = constructInPlace((Font *) mResourceChunker.alloc(sizeof(Font)));
  795. ret->faceName = new char[faceNameLen+1];
  796. dStrncpy(ret->faceName, faceName, faceNameLen);
  797. ret->faceName[faceNameLen] = '\0';
  798. ret->faceNameLen = faceNameLen;
  799. ret->size = size;
  800. ret->next = mFontList;
  801. ret->fontRes = GFont::create(ret->faceName, size, mProfile->mFontDirectory);
  802. if(bool(ret->fontRes))
  803. {
  804. ret->next = mFontList;
  805. mFontList = ret;
  806. return ret;
  807. }
  808. return NULL;
  809. }
  810. //--------------------------------------------------------------------------
  811. GuiMLTextCtrl::Bitmap *GuiMLTextCtrl::allocBitmap(const char *bitmapName, U32 bitmapNameLen)
  812. {
  813. for(Bitmap *walk = mBitmapList; walk; walk = walk->next)
  814. {
  815. if (bitmapNameLen == walk->bitmapNameLen && !dStrncmp(walk->bitmapName, bitmapName, bitmapNameLen))
  816. return walk;
  817. }
  818. Bitmap *ret = constructInPlace((Bitmap *) mResourceChunker.alloc(sizeof(Bitmap)));
  819. ret->bitmapName = bitmapName;
  820. ret->bitmapNameLen = bitmapNameLen;
  821. char nameBuffer[256];
  822. AssertFatal(sizeof(nameBuffer) >= bitmapNameLen, "GuiMLTextCtrl::allocBitmap() - bitmap name too long");
  823. dStrncpy(nameBuffer, bitmapName, bitmapNameLen);
  824. nameBuffer[bitmapNameLen] = 0;
  825. ret->bitmapHandle = TextureHandle(nameBuffer, TextureHandle::BitmapTexture);
  826. if(bool(ret->bitmapHandle))
  827. {
  828. ret->next = mBitmapList;
  829. mBitmapList = ret;
  830. return ret;
  831. }
  832. return NULL;
  833. }
  834. //--------------------------------------------------------------------------
  835. GuiMLTextCtrl::LineTag *GuiMLTextCtrl::allocLineTag(U32 id)
  836. {
  837. for ( LineTag* walk = mTagList; walk; walk = walk->next )
  838. {
  839. if ( walk->id == id )
  840. {
  841. Con::warnf( ConsoleLogEntry::General, "GuiMLTextCtrl - can't add duplicate line tags!" );
  842. return( NULL );
  843. }
  844. }
  845. LineTag* newTag = (LineTag*) mViewChunker.alloc( sizeof( LineTag ) );
  846. newTag->id = id;
  847. newTag->y = mCurY;
  848. newTag->next = mTagList;
  849. mTagList = newTag;
  850. return( newTag );
  851. }
  852. //--------------------------------------------------------------------------
  853. void GuiMLTextCtrl::emitNewLine(U32 textStart)
  854. {
  855. //clear any clipping
  856. mCurClipX = 0;
  857. Line *l = (Line *) mViewChunker.alloc(sizeof(Line));
  858. l->height = mCurStyle->font->fontRes->getHeight();
  859. l->y = mCurY;
  860. l->textStart = mLineStart;
  861. l->len = textStart - l->textStart;
  862. mLineStart = textStart;
  863. l->atomList = mLineAtoms;
  864. l->next = 0;
  865. l->divStyle = mCurDiv;
  866. *mLineInsert = l;
  867. mLineInsert = &(l->next);
  868. mCurX = mCurLMargin;
  869. mCurTabStop = 0;
  870. if(mLineAtoms)
  871. {
  872. // scan through the atoms in the line, get the largest height
  873. U32 maxBaseLine = 0;
  874. U32 maxDescent = 0;
  875. Atom* walk;
  876. for(walk = mLineAtoms; walk; walk = walk->next)
  877. {
  878. if(walk->baseLine > maxBaseLine)
  879. maxBaseLine = walk->baseLine;
  880. if(walk->descent > maxDescent)
  881. maxDescent = walk->descent;
  882. if(!walk->next)
  883. {
  884. l->len = walk->textStart + walk->len - l->textStart;
  885. mLineStart = walk->textStart + walk->len;
  886. }
  887. }
  888. l->height = maxBaseLine + maxDescent + mLineSpacingPixels;
  889. for(walk = mLineAtoms; walk; walk = walk->next)
  890. walk->yStart = mCurY + maxBaseLine - walk->baseLine;
  891. }
  892. mCurY += l->height;
  893. mLineAtoms = NULL;
  894. mLineAtomPtr = &mLineAtoms;
  895. // clear out the blocker list
  896. BitmapRef **blockList = &mBlockList;
  897. while(*blockList)
  898. {
  899. BitmapRef *blk = *blockList;
  900. if(blk->point.y + blk->extent.y <= (S32)mCurY)
  901. *blockList = blk->nextBlocker;
  902. else
  903. blockList = &(blk->nextBlocker);
  904. }
  905. if(mCurY > mMaxY)
  906. mMaxY = mCurY;
  907. }
  908. //--------------------------------------------------------------------------
  909. void GuiMLTextCtrl::emitBitmapToken(GuiMLTextCtrl::Bitmap *bmp, U32 textStart, bool bitmapBreak)
  910. {
  911. if(mCurRMargin <= mCurLMargin)
  912. return;
  913. if(mCurRMargin - mCurLMargin < bmp->bitmapHandle.getWidth())
  914. return;
  915. BitmapRef *ref = (BitmapRef *) mViewChunker.alloc(sizeof(BitmapRef));
  916. ref->bitmap = bmp;
  917. ref->next = mBitmapRefList;
  918. mBitmapRefList = ref;
  919. // now we gotta insert it into the blocker list and figure out where it's spos to go...
  920. ref->extent.x = bmp->bitmapHandle.getWidth();
  921. ref->extent.y = bmp->bitmapHandle.getHeight();
  922. // find the first space in the blocker list that will fit this thats > curLMargin
  923. while(bitmapBreak && mBlockList != &mSentinel)
  924. emitNewLine(textStart);
  925. for(;;)
  926. {
  927. // loop til we find a line that fits...
  928. // we'll have to emitLine repeatedly to clear out the block lists...
  929. BitmapRef **walk = &mBlockList;
  930. U32 minx = mCurX;
  931. U32 maxx = mCurRMargin;
  932. while(*walk)
  933. {
  934. BitmapRef *blk = *walk;
  935. if(blk->point.x > (S32)minx)
  936. {
  937. U32 right = maxx;
  938. if(blk->point.x < (S32)right)
  939. right = blk->point.x;
  940. U32 width = right - minx;
  941. if(right > minx && width >= (U32)ref->extent.x) // we've found the spot...
  942. {
  943. // insert it:
  944. U32 x = minx;
  945. if(mCurJustify == CenterAlign)
  946. x += (width - ref->extent.x) >> 1;
  947. else if(mCurJustify == RightAlign)
  948. x += width - ref->extent.x;
  949. ref->point.x = x;
  950. ref->point.y = mCurY;
  951. ref->nextBlocker = blk;
  952. *walk = ref;
  953. if(ref->point.y + ref->extent.y > (S32)mMaxY)
  954. mMaxY = ref->point.y + ref->extent.y;
  955. return;
  956. }
  957. }
  958. if((S32)minx < blk->point.x + blk->extent.x)
  959. minx = blk->point.x + blk->extent.x;
  960. // move on to the next blocker...
  961. walk = &(blk->nextBlocker);
  962. }
  963. // go to the next line...
  964. emitNewLine(textStart);
  965. }
  966. }
  967. //--------------------------------------------------------------------------
  968. void GuiMLTextCtrl::emitTextToken(U32 textStart, U32 len)
  969. {
  970. if(mCurRMargin <= mCurLMargin)
  971. return;
  972. GFont *font = mCurStyle->font->fontRes;
  973. Atom *a = (Atom *) mViewChunker.alloc(sizeof(Atom));
  974. a->url = mCurURL;
  975. a->style = mCurStyle;
  976. mCurStyle->used = true;
  977. a->baseLine = font->getBaseline();
  978. a->descent = font->getDescent();
  979. a->textStart = textStart;
  980. a->len = len;
  981. a->isClipped = false;
  982. a->next = NULL;
  983. *mEmitAtomPtr = a;
  984. mEmitAtomPtr = &(a->next);
  985. }
  986. //--------------------------------------------------------------------------
  987. void GuiMLTextCtrl::processEmitAtoms()
  988. {
  989. Atom *atomList = mEmitAtoms;
  990. mEmitAtoms = NULL;
  991. mEmitAtomPtr = &mEmitAtoms;
  992. bool bailout = false;
  993. while(atomList)
  994. {
  995. // split the tokenlist by space
  996. // first find the first space that the text can go into:
  997. BitmapRef *br = mBlockList;
  998. //bool bailout = false; // Scoping error here? Moved up one scope. -pw
  999. Atom *list = atomList;
  1000. while(br && atomList)
  1001. {
  1002. // if the blocker is before the current x, ignore it.
  1003. if(br->point.x + br->extent.x <= (S32)mCurX)
  1004. {
  1005. br = br->nextBlocker;
  1006. continue;
  1007. }
  1008. // if cur x is in the middle of the blocker
  1009. // advance cur x to right edge of blocker.
  1010. if((S32)mCurX >= br->point.x)
  1011. {
  1012. mCurX = br->point.x + br->extent.x;
  1013. br = br->nextBlocker;
  1014. continue;
  1015. }
  1016. // get the remaining width
  1017. U32 right = br->point.x;
  1018. if(right > mCurRMargin)
  1019. right = mCurRMargin;
  1020. //if we're clipping text, readjust
  1021. if (mCurClipX > 0 && right > mCurClipX)
  1022. right = mCurClipX;
  1023. // if there's no room, break to the next line...
  1024. if(right <= mCurX)
  1025. break;
  1026. // we've got some space:
  1027. U32 width = right - mCurX;
  1028. atomList = splitAtomListEmit(atomList, width);
  1029. if(atomList) // there's more, so advance cur x
  1030. {
  1031. mCurX = br->point.x + br->extent.x;
  1032. br = br->nextBlocker;
  1033. }
  1034. }
  1035. if(mBlockList == &mSentinel && atomList == list)
  1036. {
  1037. if(bailout)
  1038. return;
  1039. else
  1040. bailout = true;
  1041. }
  1042. // is there more to process for the next line?
  1043. if(atomList)
  1044. emitNewLine(mScanPos);
  1045. }
  1046. }
  1047. //--------------------------------------------------------------------------
  1048. GuiMLTextCtrl::Atom *GuiMLTextCtrl::splitAtomListEmit(Atom *list, U32 width)
  1049. {
  1050. U32 totalWidth = 0;
  1051. Atom *emitList = 0;
  1052. Atom **emitPtr = &emitList;
  1053. bool adjustClipAtom = false;
  1054. Atom *clipAtom = NULL;
  1055. bool emitted = false;
  1056. while(list)
  1057. {
  1058. GFont *font = list->style->font->fontRes;
  1059. U32 breakPos;
  1060. const UTF16 *tmp16 = mTextBuffer.getPtr() + list->textStart;
  1061. //if we're clipping the text, we don't break within an atom, we adjust the atom to only render
  1062. //the portion of text that does fit, and to ignore the rest...
  1063. if (mCurClipX > 0)
  1064. {
  1065. //find out how many character's fit within the given width
  1066. breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth, false);
  1067. //if there isn't room for even the first character...
  1068. if (breakPos == 0)
  1069. {
  1070. //set the atom's len and width to prevent it from being drawn
  1071. list->len = 0;
  1072. list->width = 0;
  1073. adjustClipAtom = true;
  1074. }
  1075. //if our text doesn't fit within the clip region, add a "..."
  1076. else if (breakPos != list->len)
  1077. {
  1078. U32 etcWidth = font->getStrNWidthPrecise("...", 3);
  1079. breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth - etcWidth, false);
  1080. //again, if there isn't even room for a single character before the "...."
  1081. if (breakPos == 0)
  1082. {
  1083. //set the atom's len and width to prevent it from being drawn
  1084. list->len = 0;
  1085. list->width = 0;
  1086. adjustClipAtom = true;
  1087. }
  1088. else
  1089. {
  1090. //set the char len to the break pos, and the rest of the characters in this atom will be ignored
  1091. list->len = breakPos;
  1092. list->width = width - totalWidth;
  1093. //mark this one as clipped
  1094. list->isClipped = true;
  1095. clipAtom = NULL;
  1096. }
  1097. }
  1098. //otherwise no need to treat this atom any differently..
  1099. else
  1100. {
  1101. //set the atom width == to the string length
  1102. list->width = font->getStrNWidthPrecise(tmp16, breakPos);
  1103. //set the pointer to the last atom that fit within the clip region
  1104. clipAtom = list;
  1105. }
  1106. }
  1107. else
  1108. {
  1109. breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth, true);
  1110. if(breakPos == 0 || (breakPos < list->len && mTextBuffer.getChar(list->textStart + breakPos - 1)!= ' ' && emitted))
  1111. break;
  1112. //set the atom width == to the string length
  1113. list->width = font->getStrNWidthPrecise(tmp16, breakPos);
  1114. }
  1115. //update the total width
  1116. totalWidth += list->width;
  1117. // see if this is the last atom that will fit:
  1118. Atom *emit = list;
  1119. *emitPtr = emit;
  1120. emitPtr = &(emit->next);
  1121. emitted = true;
  1122. //if we're clipping, don't split the atom, otherwise, see if it needs to be split
  1123. if(!list->isClipped && breakPos != list->len)
  1124. {
  1125. Atom *a = (Atom *) mViewChunker.alloc(sizeof(Atom));
  1126. a->url = list->url;
  1127. a->textStart = list->textStart + breakPos;
  1128. a->len = list->len - breakPos;
  1129. a->next = list->next;
  1130. a->baseLine = list->baseLine;
  1131. a->descent = list->descent;
  1132. a->style = list->style;
  1133. a->isClipped = false;
  1134. list = a;
  1135. emit->len = breakPos;
  1136. break;
  1137. }
  1138. list = list->next;
  1139. if(totalWidth > width)
  1140. break;
  1141. }
  1142. //if we had to completely clip an atom(s), the last (partially) visible atom should be modified to include a "..."
  1143. if (adjustClipAtom && clipAtom)
  1144. {
  1145. GFont *font = clipAtom->style->font->fontRes;
  1146. U32 breakPos;
  1147. U32 etcWidth = font->getStrNWidthPrecise("...", 3);
  1148. const UTF16 *tmp16 = mTextBuffer.getPtr() + clipAtom->textStart;
  1149. breakPos = font->getBreakPos(tmp16, clipAtom->len, clipAtom->width - etcWidth, false);
  1150. if (breakPos != 0)
  1151. {
  1152. clipAtom->isClipped = true;
  1153. clipAtom->len = breakPos;
  1154. }
  1155. }
  1156. // terminate the emit list:
  1157. *emitPtr = 0;
  1158. // now emit it:
  1159. // going from mCurX to mCurX + width:
  1160. if(mCurJustify == CenterAlign)
  1161. {
  1162. if ( width > totalWidth )
  1163. mCurX += (width - totalWidth) >> 1;
  1164. }
  1165. else if(mCurJustify == RightAlign)
  1166. {
  1167. if ( width > totalWidth )
  1168. mCurX += width - totalWidth;
  1169. }
  1170. while(emitList)
  1171. {
  1172. emitList->xStart = mCurX;
  1173. mCurX += emitList->width;
  1174. Atom *temp = emitList->next;
  1175. *mLineAtomPtr = emitList;
  1176. emitList->next = 0;
  1177. mLineAtomPtr = &(emitList->next);
  1178. emitList = temp;
  1179. }
  1180. return list;
  1181. }
  1182. //--------------------------------------------------------------------------
  1183. static bool scanforchar(const char *str, U32 &idx, char c)
  1184. {
  1185. U32 startidx = idx;
  1186. while(str[idx] != c && str[idx] && str[idx] != ':' && str[idx] != '>' && str[idx] != '\n')
  1187. idx++;
  1188. return str[idx] == c && startidx != idx;
  1189. }
  1190. //--------------------------------------------------------------------------
  1191. static S32 getHexVal(char c)
  1192. {
  1193. if(c >= '0' && c <= '9')
  1194. return c - '0';
  1195. else if(c >= 'A' && c <= 'Z')
  1196. return c - 'A' + 10;
  1197. else if(c >= 'a' && c <= 'z')
  1198. return c - 'a' + 10;
  1199. return -1;
  1200. }
  1201. //--------------------------------------------------------------------------
  1202. GuiMLTextCtrl::Style *GuiMLTextCtrl::allocStyle(GuiMLTextCtrl::Style *style)
  1203. {
  1204. Style *ret = (Style *) mViewChunker.alloc(sizeof(Style));
  1205. ret->used = false;
  1206. if(style)
  1207. {
  1208. ret->font = style->font;
  1209. ret->color = style->color;
  1210. ret->linkColor = style->linkColor;
  1211. ret->linkColorHL = style->linkColorHL;
  1212. ret->shadowColor = style->shadowColor;
  1213. ret->shadowOffset = style->shadowOffset;
  1214. ret->next = style->next;
  1215. }
  1216. else
  1217. {
  1218. ret->font = 0;
  1219. ret->next = 0;
  1220. }
  1221. return ret;
  1222. }
  1223. //--------------------------------------------------------------------------
  1224. void GuiMLTextCtrl::reflow()
  1225. {
  1226. AssertFatal(mAwake, "Can't reflow a sleeping control.");
  1227. freeLineBuffers();
  1228. mDirty = false;
  1229. mScanPos = 0;
  1230. mLineList = NULL;
  1231. mLineInsert = &mLineList;
  1232. mCurStyle = allocStyle(NULL);
  1233. mCurStyle->font = allocFont((char *) mProfile->mFontType, dStrlen(mProfile->mFontType), mProfile->mFontSize);
  1234. if(!mCurStyle->font)
  1235. return;
  1236. mCurStyle->color = mProfile->mFontColor;
  1237. mCurStyle->shadowColor = mProfile->mFontColor;
  1238. mCurStyle->shadowOffset.set(0,0);
  1239. mCurStyle->linkColor = mProfile->mFontColors[GuiControlProfile::ColorUser0];
  1240. mCurStyle->linkColorHL = mProfile->mFontColors[GuiControlProfile::ColorUser1];
  1241. U32 width = mBounds.extent.x;
  1242. mCurLMargin = 0;
  1243. mCurRMargin = width;
  1244. mCurJustify = LeftAlign;
  1245. mCurDiv = 0;
  1246. mCurY = 0;
  1247. mCurX = 0;
  1248. mCurClipX = 0;
  1249. mLineAtoms = NULL;
  1250. mLineAtomPtr = &mLineAtoms;
  1251. mSentinel.point.x = width;
  1252. mSentinel.point.y = 0;
  1253. mSentinel.extent.x = 0;
  1254. mSentinel.extent.y = 0x7FFFFF;
  1255. mSentinel.nextBlocker = NULL;
  1256. mLineStart = 0;
  1257. mEmitAtoms = 0;
  1258. mMaxY = 0;
  1259. mEmitAtomPtr = &mEmitAtoms;
  1260. mBlockList = &mSentinel;
  1261. Font *nextFont;
  1262. LineTag *nextTag;
  1263. mTabStops = 0;
  1264. mCurTabStop = 0;
  1265. mTabStopCount = 0;
  1266. mCurURL = 0;
  1267. Style *newStyle;
  1268. U32 textStart;
  1269. U32 len;
  1270. U32 idx;
  1271. U32 sizidx;
  1272. for(;;)
  1273. {
  1274. UTF16 curChar = mTextBuffer.getChar(mScanPos);
  1275. if(!curChar)
  1276. break;
  1277. if(curChar == '\n')
  1278. {
  1279. textStart = mScanPos;
  1280. len = 1;
  1281. mScanPos++;
  1282. processEmitAtoms();
  1283. emitNewLine(textStart);
  1284. mCurDiv = 0;
  1285. continue;
  1286. }
  1287. if(curChar == '\t')
  1288. {
  1289. textStart = mScanPos;
  1290. len = 1;
  1291. mScanPos++;
  1292. processEmitAtoms();
  1293. if(mTabStopCount)
  1294. {
  1295. if(mCurTabStop < mTabStopCount)
  1296. {
  1297. if(mCurX < mTabStops[mCurTabStop])
  1298. mCurX = mTabStops[mCurTabStop];
  1299. }
  1300. mCurTabStop++;
  1301. }
  1302. continue;
  1303. }
  1304. if(curChar == '<')
  1305. {
  1306. // it's probably some kind of tag:
  1307. // Get a pointer into the utf8 version of the buffer,
  1308. // because we're still scanning text in in utf8 mode.
  1309. const UTF8 *str = mTextBuffer.getPtr8();
  1310. str = getNthCodepoint(str, mScanPos);
  1311. // And go!
  1312. if(!dStrnicmp(str + 1, "br>", 3))
  1313. {
  1314. mScanPos += 4;
  1315. len = 4;
  1316. textStart = mScanPos + 4;
  1317. processEmitAtoms();
  1318. emitNewLine(textStart);
  1319. mCurDiv = 0;
  1320. continue;
  1321. }
  1322. if(!dStrnicmp(str + 1, "font:", 5))
  1323. {
  1324. // scan for the second colon...
  1325. // at each level it should drop out to the text case below...
  1326. idx = 6;
  1327. if(!scanforchar(str, idx, ':'))
  1328. goto textemit;
  1329. sizidx = idx + 1;
  1330. if(!scanforchar(str, sizidx, '>'))
  1331. goto textemit;
  1332. U32 size = dAtoi(str + idx + 1);
  1333. if(!size || size > 64)
  1334. goto textemit;
  1335. textStart = mScanPos + 6;
  1336. len = idx - 6;
  1337. mScanPos += sizidx + 1;
  1338. nextFont = allocFont(str + 6, len, size);
  1339. if(nextFont)
  1340. {
  1341. if(mCurStyle->used)
  1342. mCurStyle = allocStyle(mCurStyle);
  1343. mCurStyle->font = nextFont;
  1344. }
  1345. continue;
  1346. }
  1347. if ( !dStrnicmp( str + 1, "tag:", 4 ) )
  1348. {
  1349. idx = 5;
  1350. if ( !scanforchar( str, idx, '>' ) )
  1351. goto textemit;
  1352. U32 tagId = dAtoi( str + 5 );
  1353. nextTag = allocLineTag( tagId );
  1354. mScanPos += idx + 1;
  1355. continue;
  1356. }
  1357. if(!dStrnicmp(str +1, "color:", 6))
  1358. {
  1359. idx = 7;
  1360. if(!scanforchar(str, idx, '>'))
  1361. goto textemit;
  1362. if(idx != 13 && idx != 15)
  1363. goto textemit;
  1364. ColorI color;
  1365. color.red = getHexVal(str[7]) * 16 + getHexVal(str[8]);
  1366. color.green = getHexVal(str[9]) * 16 + getHexVal(str[10]);
  1367. color.blue = getHexVal(str[11]) * 16 + getHexVal(str[12]);
  1368. if(idx == 15)
  1369. color.alpha = getHexVal(str[13]) * 16 + getHexVal(str[14]);
  1370. else
  1371. color.alpha = 255;
  1372. mScanPos += idx + 1;
  1373. if(mCurStyle->used)
  1374. mCurStyle = allocStyle(mCurStyle);
  1375. mCurStyle->color = color;
  1376. continue;
  1377. }
  1378. if(!dStrnicmp(str +1, "shadowcolor:", 12))
  1379. {
  1380. idx = 13;
  1381. if(!scanforchar(str, idx, '>'))
  1382. goto textemit;
  1383. if(idx != 19 && idx != 21)
  1384. goto textemit;
  1385. ColorI color;
  1386. color.red = getHexVal(str[13]) * 16 + getHexVal(str[14]);
  1387. color.green = getHexVal(str[15]) * 16 + getHexVal(str[16]);
  1388. color.blue = getHexVal(str[17]) * 16 + getHexVal(str[18]);
  1389. if(idx == 21)
  1390. color.alpha = getHexVal(str[19]) * 16 + getHexVal(str[20]);
  1391. else
  1392. color.alpha = 255;
  1393. mScanPos += idx + 1;
  1394. if(mCurStyle->used)
  1395. mCurStyle = allocStyle(mCurStyle);
  1396. mCurStyle->shadowColor = color;
  1397. continue;
  1398. }
  1399. if(!dStrnicmp(str +1, "linkcolor:", 10))
  1400. {
  1401. idx = 11;
  1402. if(!scanforchar(str, idx, '>'))
  1403. goto textemit;
  1404. if(idx != 17 && idx != 19)
  1405. goto textemit;
  1406. ColorI color;
  1407. color.red = getHexVal(str[11]) * 16 + getHexVal(str[12]);
  1408. color.green = getHexVal(str[13]) * 16 + getHexVal(str[14]);
  1409. color.blue = getHexVal(str[15]) * 16 + getHexVal(str[16]);
  1410. if(idx == 19)
  1411. color.alpha = getHexVal(str[17]) * 16 + getHexVal(str[18]);
  1412. else
  1413. color.alpha = 255;
  1414. mScanPos += idx + 1;
  1415. if(mCurStyle->used)
  1416. mCurStyle = allocStyle(mCurStyle);
  1417. mCurStyle->linkColor = color;
  1418. continue;
  1419. }
  1420. if(!dStrnicmp(str +1, "linkcolorhl:", 12))
  1421. {
  1422. idx = 13;
  1423. if(!scanforchar(str, idx, '>'))
  1424. goto textemit;
  1425. if(idx != 19 && idx != 21)
  1426. goto textemit;
  1427. ColorI color;
  1428. color.red = getHexVal(str[13]) * 16 + getHexVal(str[14]);
  1429. color.green = getHexVal(str[15]) * 16 + getHexVal(str[16]);
  1430. color.blue = getHexVal(str[17]) * 16 + getHexVal(str[18]);
  1431. if(idx == 21)
  1432. color.alpha = getHexVal(str[19]) * 16 + getHexVal(str[20]);
  1433. else
  1434. color.alpha = 255;
  1435. mScanPos += idx + 1;
  1436. if(mCurStyle->used)
  1437. mCurStyle = allocStyle(mCurStyle);
  1438. mCurStyle->linkColorHL = color;
  1439. continue;
  1440. }
  1441. if(!dStrnicmp(str +1, "shadow:", 7))
  1442. {
  1443. idx = 8;
  1444. if(!scanforchar(str, idx, ':'))
  1445. goto textemit;
  1446. U32 yidx = idx + 1;
  1447. if(!scanforchar(str, yidx, '>'))
  1448. goto textemit;
  1449. mScanPos += yidx + 1;
  1450. Point2I offset;
  1451. offset.x = dAtoi(str + 8);
  1452. offset.y = dAtoi(str + idx + 1);
  1453. if(mCurStyle->used)
  1454. mCurStyle = allocStyle(mCurStyle);
  1455. mCurStyle->shadowOffset = offset;
  1456. continue;
  1457. }
  1458. if(!dStrnicmp(str +1, "bitmap", 6))
  1459. {
  1460. S32 start = 8;
  1461. bool bitBrk = false;
  1462. if(str[7] == 'k' && str[8] == ':')
  1463. {
  1464. bitBrk = true;
  1465. start = 9;
  1466. }
  1467. else if(str[7] != ':')
  1468. goto textemit;
  1469. idx = start;
  1470. if(!scanforchar(str, idx, '>'))
  1471. goto textemit;
  1472. textStart = mScanPos + start;
  1473. len = idx - start;
  1474. mScanPos += idx + 1;
  1475. processEmitAtoms();
  1476. Bitmap *bmp;
  1477. bmp = allocBitmap(str + 8, len);
  1478. if(bmp)
  1479. emitBitmapToken(bmp, textStart, bitBrk);
  1480. continue;
  1481. }
  1482. if(!dStrnicmp(str +1, "spush>", 6))
  1483. {
  1484. mScanPos += 7;
  1485. newStyle = allocStyle(mCurStyle); // copy out all the attributes...
  1486. newStyle->next = mCurStyle;
  1487. mCurStyle = newStyle;
  1488. continue;
  1489. }
  1490. if(!dStrnicmp(str +1, "spop>", 5))
  1491. {
  1492. mScanPos += 6;
  1493. if(mCurStyle->next)
  1494. mCurStyle = mCurStyle->next;
  1495. continue;
  1496. }
  1497. if(!dStrnicmp(str +1, "sbreak>", 7))
  1498. {
  1499. mScanPos += 8;
  1500. processEmitAtoms();
  1501. while(mBlockList != &mSentinel)
  1502. emitNewLine(mScanPos);
  1503. continue;
  1504. }
  1505. if(!dStrnicmp(str +1, "just:left>", 10))
  1506. {
  1507. processEmitAtoms();
  1508. mCurJustify = LeftAlign;
  1509. mScanPos += 11;
  1510. continue;
  1511. }
  1512. if(!dStrnicmp(str +1, "just:right>", 11))
  1513. {
  1514. processEmitAtoms();
  1515. mCurJustify = RightAlign;
  1516. mScanPos += 12;
  1517. continue;
  1518. }
  1519. if(!dStrnicmp(str +1, "just:center>", 12))
  1520. {
  1521. processEmitAtoms();
  1522. mCurJustify = CenterAlign;
  1523. mScanPos += 13;
  1524. continue;
  1525. }
  1526. if(!dStrnicmp(str +1, "a:", 2))
  1527. {
  1528. idx = 3;
  1529. if(!scanforchar(str, idx, '>'))
  1530. goto textemit;
  1531. mCurURL = (URL *) mViewChunker.alloc(sizeof(URL));
  1532. mCurURL->mouseDown = false;
  1533. mCurURL->textStart = mScanPos + 3;
  1534. mCurURL->len = idx - 3;
  1535. mCurURL->noUnderline = false;
  1536. //if the URL is a "gamelink", don't underline...
  1537. if (!dStrnicmp(str + 3, "gamelink", 8))
  1538. mCurURL->noUnderline = true;
  1539. mScanPos += idx + 1;
  1540. continue;
  1541. }
  1542. if(!dStrnicmp(str+1, "/a>", 3))
  1543. {
  1544. mCurURL = NULL;
  1545. mScanPos += 4;
  1546. continue;
  1547. }
  1548. U32 margin;
  1549. if(!dStrnicmp(str + 1, "lmargin%:", 9))
  1550. {
  1551. idx = 10;
  1552. if(!scanforchar(str, idx, '>'))
  1553. goto textemit;
  1554. margin = (mBounds.extent.x * dAtoi(str + 10)) / 100;
  1555. mScanPos += idx + 1;
  1556. goto setleftmargin;
  1557. }
  1558. if(!dStrnicmp(str + 1, "lmargin:", 8))
  1559. {
  1560. idx = 9;
  1561. if(!scanforchar(str, idx, '>'))
  1562. goto textemit;
  1563. margin = dAtoi(str + 9);
  1564. mScanPos += idx + 1;
  1565. setleftmargin:
  1566. processEmitAtoms();
  1567. U32 oldLMargin;
  1568. oldLMargin = mCurLMargin;
  1569. mCurLMargin = margin;
  1570. if(mCurLMargin >= width)
  1571. mCurLMargin = width - 1;
  1572. if(mCurX == oldLMargin)
  1573. mCurX = mCurLMargin;
  1574. if(mCurX < mCurLMargin)
  1575. mCurX = mCurLMargin;
  1576. continue;
  1577. }
  1578. if(!dStrnicmp(str + 1, "rmargin%:", 9))
  1579. {
  1580. idx = 10;
  1581. if(!scanforchar(str, idx, '>'))
  1582. goto textemit;
  1583. margin = (mBounds.extent.x * dAtoi(str + 10)) / 100;
  1584. mScanPos += idx + 1;
  1585. goto setrightmargin;
  1586. }
  1587. if(!dStrnicmp(str + 1, "rmargin:", 8))
  1588. {
  1589. idx = 9;
  1590. if(!scanforchar(str, idx, '>'))
  1591. goto textemit;
  1592. margin = dAtoi(str + 9);
  1593. mScanPos += idx + 1;
  1594. setrightmargin:
  1595. processEmitAtoms();
  1596. mCurRMargin = margin;
  1597. if(mCurLMargin >= width)
  1598. mCurLMargin = width;
  1599. if (mCurClipX > mCurRMargin)
  1600. mCurClipX = mCurRMargin;
  1601. continue;
  1602. }
  1603. if(!dStrnicmp(str + 1, "clip:", 5))
  1604. {
  1605. U32 clipWidth = 0;
  1606. idx = 6;
  1607. if(!scanforchar(str, idx, '>'))
  1608. goto textemit;
  1609. clipWidth = dAtoi(str + 6);
  1610. mScanPos += idx + 1;
  1611. processEmitAtoms();
  1612. if (clipWidth > 0)
  1613. mCurClipX = mCurX + clipWidth;
  1614. else
  1615. mCurClipX = 0;
  1616. if(mCurClipX > mCurRMargin)
  1617. mCurClipX = mCurRMargin;
  1618. continue;
  1619. }
  1620. if(!dStrnicmp(str + 1, "/clip>", 6))
  1621. {
  1622. processEmitAtoms();
  1623. mCurClipX = 0;
  1624. mScanPos += 7;
  1625. continue;
  1626. }
  1627. if(!dStrnicmp(str + 1, "div:", 4))
  1628. {
  1629. idx = 5;
  1630. if(!scanforchar(str, idx, '>'))
  1631. goto textemit;
  1632. mScanPos += idx + 1;
  1633. mCurDiv = dAtoi(str + 5);
  1634. continue;
  1635. }
  1636. if(!dStrnicmp(str + 1, "tab:", 4))
  1637. {
  1638. idx = 5;
  1639. if(!scanforchar(str, idx, '>'))
  1640. goto textemit;
  1641. // scan for tab stops...
  1642. mTabStopCount = 1;
  1643. idx = 5;
  1644. while(scanforchar(str, idx, ','))
  1645. {
  1646. idx++;
  1647. mTabStopCount++;
  1648. }
  1649. idx = 5;
  1650. mTabStops = (U32 *) mViewChunker.alloc(sizeof(U32) * mTabStopCount);
  1651. mTabStops[0] = dAtoi(str + idx);
  1652. U32 i = 1;
  1653. while(scanforchar(str, idx, ','))
  1654. {
  1655. idx++;
  1656. mTabStops[i] = dAtoi(str + idx);
  1657. i++;
  1658. }
  1659. mScanPos += idx + 1;
  1660. continue;
  1661. }
  1662. }
  1663. // default case:
  1664. textemit:
  1665. textStart = mScanPos;
  1666. idx = 1;
  1667. while(mTextBuffer.getChar(mScanPos+idx) != '\t' && mTextBuffer.getChar(mScanPos+idx) != '<' && mTextBuffer.getChar(mScanPos+idx) != '\n' && mTextBuffer.getChar(mScanPos+idx))
  1668. idx++;
  1669. len = idx;
  1670. mScanPos += idx;
  1671. emitTextToken(textStart, len);
  1672. }
  1673. processEmitAtoms();
  1674. emitNewLine(mScanPos);
  1675. resize(mBounds.point, Point2I(mBounds.extent.x, mMaxY));
  1676. Con::executef( this, 3, "onResize", Con::getIntArg( mBounds.extent.x ), Con::getIntArg( mMaxY ) );
  1677. //make sure the cursor is still visible - this handles if we're a child of a scroll ctrl...
  1678. ensureCursorOnScreen();
  1679. }
  1680. //-----------------------------------------------------------------------------
  1681. char* GuiMLTextCtrl::stripControlChars(const char *inString)
  1682. {
  1683. if (! bool(inString))
  1684. return NULL;
  1685. U32 maxBufLength = 64;
  1686. char *strippedBuffer = Con::getReturnBuffer(maxBufLength);
  1687. char *stripBufPtr = &strippedBuffer[0];
  1688. const char *bufPtr = (char *) inString;
  1689. U32 idx, sizidx;
  1690. for(;;)
  1691. {
  1692. //if we've reached the end of the string, or run out of room in the stripped Buffer...
  1693. if(*bufPtr == '\0' || (U32(stripBufPtr - strippedBuffer) >= maxBufLength - 1))
  1694. break;
  1695. if (*bufPtr == '\n')
  1696. {
  1697. U32 walked;
  1698. oneUTF8toUTF32(bufPtr,&walked);
  1699. bufPtr += walked;
  1700. continue;
  1701. }
  1702. if(*bufPtr == '\t')
  1703. {
  1704. U32 walked;
  1705. oneUTF8toUTF32(bufPtr,&walked);
  1706. bufPtr += walked;
  1707. continue;
  1708. }
  1709. if(*bufPtr < 0x20 && *bufPtr >= 0)
  1710. {
  1711. U32 walked;
  1712. oneUTF8toUTF32(bufPtr,&walked);
  1713. bufPtr += walked;
  1714. continue;
  1715. }
  1716. if(*bufPtr == '<')
  1717. {
  1718. // it's probably some kind of tag:
  1719. if(!dStrnicmp(bufPtr + 1, "font:", 5))
  1720. {
  1721. // scan for the second colon...
  1722. // at each level it should drop out to the text case below...
  1723. idx = 6;
  1724. if(!scanforchar((char*)bufPtr, idx, ':'))
  1725. goto textemit;
  1726. sizidx = idx + 1;
  1727. if(!scanforchar((char*)bufPtr, sizidx, '>'))
  1728. goto textemit;
  1729. bufPtr += sizidx + 1;
  1730. continue;
  1731. }
  1732. if (!dStrnicmp(bufPtr + 1, "tag:", 4 ))
  1733. {
  1734. idx = 5;
  1735. if ( !scanforchar((char*)bufPtr, idx, '>' ))
  1736. goto textemit;
  1737. bufPtr += idx + 1;
  1738. continue;
  1739. }
  1740. if(!dStrnicmp(bufPtr + 1, "color:", 6))
  1741. {
  1742. idx = 7;
  1743. if(!scanforchar((char*)bufPtr, idx, '>'))
  1744. goto textemit;
  1745. if(idx != 13)
  1746. goto textemit;
  1747. bufPtr += 14;
  1748. continue;
  1749. }
  1750. if(!dStrnicmp(bufPtr +1, "bitmap:", 7))
  1751. {
  1752. idx = 8;
  1753. if(!scanforchar((char*)bufPtr, idx, '>'))
  1754. goto textemit;
  1755. bufPtr += idx + 1;
  1756. continue;
  1757. }
  1758. if(!dStrnicmp(bufPtr +1, "spush>", 6))
  1759. {
  1760. bufPtr += 7;
  1761. continue;
  1762. }
  1763. if(!dStrnicmp(bufPtr +1, "spop>", 5))
  1764. {
  1765. bufPtr += 6;
  1766. continue;
  1767. }
  1768. if(!dStrnicmp(bufPtr +1, "sbreak>", 7))
  1769. {
  1770. bufPtr += 8;
  1771. continue;
  1772. }
  1773. if(!dStrnicmp(bufPtr +1, "just:left>", 10))
  1774. {
  1775. bufPtr += 11;
  1776. continue;
  1777. }
  1778. if(!dStrnicmp(bufPtr +1, "just:right>", 11))
  1779. {
  1780. bufPtr += 12;
  1781. continue;
  1782. }
  1783. if(!dStrnicmp(bufPtr +1, "just:center>", 12))
  1784. {
  1785. bufPtr += 13;
  1786. continue;
  1787. }
  1788. if(!dStrnicmp(bufPtr +1, "a:", 2))
  1789. {
  1790. idx = 3;
  1791. if(!scanforchar((char*)bufPtr, idx, '>'))
  1792. goto textemit;
  1793. bufPtr += idx + 1;
  1794. continue;
  1795. }
  1796. if(!dStrnicmp(bufPtr+1, "/a>", 3))
  1797. {
  1798. bufPtr += 4;
  1799. continue;
  1800. }
  1801. if(!dStrnicmp(bufPtr + 1, "lmargin%:", 9))
  1802. {
  1803. idx = 10;
  1804. if(!scanforchar((char*)bufPtr, idx, '>'))
  1805. goto textemit;
  1806. bufPtr += idx + 1;
  1807. goto setleftmargin;
  1808. }
  1809. if(!dStrnicmp(bufPtr + 1, "lmargin:", 8))
  1810. {
  1811. idx = 9;
  1812. if(!scanforchar((char*)bufPtr, idx, '>'))
  1813. goto textemit;
  1814. bufPtr += idx + 1;
  1815. setleftmargin:
  1816. continue;
  1817. }
  1818. if(!dStrnicmp(bufPtr + 1, "rmargin%:", 9))
  1819. {
  1820. idx = 10;
  1821. if(!scanforchar((char*)bufPtr, idx, '>'))
  1822. goto textemit;
  1823. bufPtr += idx + 1;
  1824. goto setrightmargin;
  1825. }
  1826. if(!dStrnicmp(bufPtr + 1, "rmargin:", 8))
  1827. {
  1828. idx = 9;
  1829. if(!scanforchar((char*)bufPtr, idx, '>'))
  1830. goto textemit;
  1831. bufPtr += idx + 1;
  1832. setrightmargin:
  1833. continue;
  1834. }
  1835. if(!dStrnicmp(bufPtr + 1, "clip:", 5))
  1836. {
  1837. idx = 6;
  1838. if(!scanforchar((char*)bufPtr, idx, '>'))
  1839. goto textemit;
  1840. bufPtr += idx + 1;
  1841. continue;
  1842. }
  1843. if(!dStrnicmp(bufPtr + 1, "/clip>", 6))
  1844. {
  1845. bufPtr += 7;
  1846. continue;
  1847. }
  1848. if(!dStrnicmp(bufPtr + 1, "div:", 4))
  1849. {
  1850. idx = 5;
  1851. if(!scanforchar((char*)bufPtr, idx, '>'))
  1852. goto textemit;
  1853. bufPtr += idx + 1;
  1854. continue;
  1855. }
  1856. if(!dStrnicmp(bufPtr + 1, "tab:", 4))
  1857. {
  1858. idx = 5;
  1859. if(!scanforchar((char*)bufPtr, idx, '>'))
  1860. goto textemit;
  1861. bufPtr += idx + 1;
  1862. continue;
  1863. }
  1864. }
  1865. // default case:
  1866. textemit:
  1867. *stripBufPtr++ = *bufPtr++;
  1868. while(*bufPtr != '\t' && *bufPtr != '<' && *bufPtr != '\n' && (*bufPtr >= 0x20 || *bufPtr < 0))
  1869. *stripBufPtr++ = *bufPtr++;
  1870. }
  1871. //we're finished - terminate the string
  1872. *stripBufPtr = '\0';
  1873. return strippedBuffer;
  1874. }