guiMLTextCtrl.cpp 70 KB

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