Torque3D Documentation / _generateds / guiMessageVectorCtrl.cpp

guiMessageVectorCtrl.cpp

Engine/source/gui/game/guiMessageVectorCtrl.cpp

More...

Classes:

Public Functions

ConsoleDocClass(GuiMessageVectorCtrl , "@brief A chat HUD <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that displays messages from a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector.\n\n</a>" "This renders messages from a <a href="/coding/class/classmessagevector/">MessageVector</a>; the important thing " "here is that the <a href="/coding/class/classmessagevector/">MessageVector</a> holds all the messages we care " " about, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a0e48c1f64b558d03d870367324920354">while</a> we can destroy and create these GUI controls as " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">needed.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Declare ChatHud, which is what will display the actual chat from a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classguimessagevectorctrl/">GuiMessageVectorCtrl</a>(ChatHud) {\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " profile=\"ChatHudMessageProfile\";\n" "   horizSizing = \"width\";\n" "   vertSizing = \"height\";\n" "   position = \"1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   extent = \"252 16\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   minExtent = \"8 8\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   visible = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   helpTag = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lineSpacing = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lineContinuedIndex = \"10\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   matchColor = \"0 0 255 255\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   maxColorIndex = \"5\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "// All messages are stored in this HudMessageVector, the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">actual\n</a>" "//MainChatHud only displays the contents of this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classmessagevector/">MessageVector</a>(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "//Attach the <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the chat <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n</a>" "chatHud.attach(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" " @see <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> more details on how this is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">used\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiUtil\n</a>" )
DefineEngineMethod(GuiMessageVectorCtrl , attach , bool , (MessageVector *item) , "@brief Push a line onto the back of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">list.\n\n</a>" "@param item The GUI element being pushed into the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// All messages are stored in this HudMessageVector, the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">actual\n</a>" "//MainChatHud only displays the contents of this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classmessagevector/">MessageVector</a>(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "//Attach the <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the chat <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n</a>" "chatHud.attach(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" " @return Value" )
DefineEngineMethod(GuiMessageVectorCtrl , detach , void , () , "@brief Stop listing messages from the <a href="/coding/class/classmessagevector/">MessageVector</a> previously attached to, <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">any.\n\n</a>" "Detailed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">description\n\n</a>" " @param param <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Description\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Deatch the <a href="/coding/class/classmessagevector/">MessageVector</a> from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">HudMessageVector\n</a>" "//HudMessageVector will no longer render the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">text\n</a>" "chatHud.detach();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" )

Detailed Description

Public Functions

ConsoleDocClass(GuiMessageVectorCtrl , "@brief A chat HUD <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that displays messages from a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector.\n\n</a>" "This renders messages from a <a href="/coding/class/classmessagevector/">MessageVector</a>; the important thing " "here is that the <a href="/coding/class/classmessagevector/">MessageVector</a> holds all the messages we care " " about, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a0e48c1f64b558d03d870367324920354">while</a> we can destroy and create these GUI controls as " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">needed.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Declare ChatHud, which is what will display the actual chat from a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classguimessagevectorctrl/">GuiMessageVectorCtrl</a>(ChatHud) {\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " profile=\"ChatHudMessageProfile\";\n" "   horizSizing = \"width\";\n" "   vertSizing = \"height\";\n" "   position = \"1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   extent = \"252 16\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   minExtent = \"8 8\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   visible = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   helpTag = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lineSpacing = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lineContinuedIndex = \"10\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   matchColor = \"0 0 255 255\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   maxColorIndex = \"5\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "// All messages are stored in this HudMessageVector, the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">actual\n</a>" "//MainChatHud only displays the contents of this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classmessagevector/">MessageVector</a>(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "//Attach the <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the chat <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n</a>" "chatHud.attach(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" " @see <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> more details on how this is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">used\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiUtil\n</a>" )

DefineEngineMethod(GuiMessageVectorCtrl , attach , bool , (MessageVector *item) , "@brief Push a line onto the back of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">list.\n\n</a>" "@param item The GUI element being pushed into the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// All messages are stored in this HudMessageVector, the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">actual\n</a>" "//MainChatHud only displays the contents of this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classmessagevector/">MessageVector</a>(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "//Attach the <a href="/coding/class/classmessagevector/">MessageVector</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the chat <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">control\n</a>" "chatHud.attach(HudMessageVector);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" " @return Value" )

DefineEngineMethod(GuiMessageVectorCtrl , detach , void , () , "@brief Stop listing messages from the <a href="/coding/class/classmessagevector/">MessageVector</a> previously attached to, <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">any.\n\n</a>" "Detailed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">description\n\n</a>" " @param param <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Description\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Deatch the <a href="/coding/class/classmessagevector/">MessageVector</a> from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">HudMessageVector\n</a>" "//HudMessageVector will no longer render the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">text\n</a>" "chatHud.detach();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" )

IMPLEMENT_CONOBJECT(GuiMessageVectorCtrl )

sMVCtrlCallback(void * spectatorKey, const MessageVector::MessageCode code, const U32 argument)

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "gui/game/guiMessageVectorCtrl.h"
 26
 27#include "gui/utility/messageVector.h"
 28#include "console/consoleTypes.h"
 29#include "gui/containers/guiScrollCtrl.h"
 30#include "gfx/gfxDevice.h"
 31#include "gfx/gfxDrawUtil.h"
 32#include "console/engineAPI.h"
 33
 34IMPLEMENT_CONOBJECT(GuiMessageVectorCtrl);
 35
 36ConsoleDocClass( GuiMessageVectorCtrl,
 37   "@brief A chat HUD control that displays messages from a MessageVector.\n\n"
 38
 39   "This renders messages from a MessageVector; the important thing "
 40   "here is that the MessageVector holds all the messages we care "
 41   "about, while we can destroy and create these GUI controls as "
 42   "needed.\n\n"
 43
 44   "@tsexample\n"
 45   "// Declare ChatHud, which is what will display the actual chat from a MessageVector\n"
 46   "new GuiMessageVectorCtrl(ChatHud) {\n"
 47    "   profile = \"ChatHudMessageProfile\";\n"
 48    "   horizSizing = \"width\";\n"
 49    "   vertSizing = \"height\";\n"
 50    "   position = \"1 1\";\n"
 51    "   extent = \"252 16\";\n"
 52    "   minExtent = \"8 8\";\n"
 53    "   visible = \"1\";\n"
 54    "   helpTag = \"0\";\n"
 55    "   lineSpacing = \"0\";\n"
 56    "   lineContinuedIndex = \"10\";\n"
 57    "   matchColor = \"0 0 255 255\";\n"
 58    "   maxColorIndex = \"5\";\n"
 59    "};\n\n"
 60   "// All messages are stored in this HudMessageVector, the actual\n"
 61   "// MainChatHud only displays the contents of this vector.\n"
 62   "new MessageVector(HudMessageVector);\n\n"
 63   "// Attach the MessageVector to the chat control\n"
 64   "chatHud.attach(HudMessageVector);\n"
 65   "@endtsexample\n\n"
 66
 67   "@see MessageVector for more details on how this is used\n"
 68
 69   "@ingroup GuiUtil\n");
 70
 71
 72//-------------------------------------- Console functions
 73DefineEngineMethod( GuiMessageVectorCtrl, attach, bool, ( MessageVector* item),,
 74      "@brief Push a line onto the back of the list.\n\n"
 75
 76      "@param item The GUI element being pushed into the control\n\n"
 77
 78      "@tsexample\n"
 79      "// All messages are stored in this HudMessageVector, the actual\n"
 80      "// MainChatHud only displays the contents of this vector.\n"
 81      "new MessageVector(HudMessageVector);\n\n"
 82      "// Attach the MessageVector to the chat control\n"
 83      "chatHud.attach(HudMessageVector);\n"
 84      "@endtsexample\n\n"
 85
 86      "@return Value")
 87{
 88   if (item == NULL)
 89   {
 90      Con::errorf(ConsoleLogEntry::General, "Could not find MessageVector: %s", item);
 91      return false;
 92   }
 93   
 94   return object->attach(item);
 95}
 96
 97//ConsoleMethod(GuiMessageVectorCtrl, attach, bool, 3, 3, "(MessageVector item)"
 98//              "Make this gui control display messages from the specified MessageVector")
 99//{
100//   MessageVector* pMV = NULL;
101//   Sim::findObject(argv[2], pMV);
102//   if (pMV == NULL) {
103//      Con::errorf(ConsoleLogEntry::General, "Could not find MessageVector: %s", argv[2]);
104//      return false;
105//   }
106//
107//   return object->attach(pMV);
108//}
109
110DefineEngineMethod( GuiMessageVectorCtrl, detach, void, (),,
111               "@brief Stop listing messages from the MessageVector previously attached to, if any.\n\n"
112
113               "Detailed description\n\n"
114
115               "@param param Description\n\n"
116
117               "@tsexample\n"
118               "// Deatch the MessageVector from HudMessageVector\n"
119               "// HudMessageVector will no longer render the text\n"
120               "chatHud.detach();\n"
121               "@endtsexample\n\n")
122{
123   if (object->isAttached() == false)
124   {
125      Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl: double detach");
126      return;
127   }
128
129   object->detach();
130}
131
132//ConsoleMethod(GuiMessageVectorCtrl, detach, void, 2, 2, "()"
133//              "Stop listing messages from the MessageVector previously attached to, if any.")
134//{
135//   if (object->isAttached() == false) {
136//      Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl: double detach");
137//      return;
138//   }
139//
140//   object->detach();
141//}
142
143struct TempLineBreak
144{
145   S32 start;
146   S32 end;
147};
148
149//--------------------------------------------------------------------------
150// Callback for messageVector
151void sMVCtrlCallback(void *                           spectatorKey,
152                     const MessageVector::MessageCode code,
153                     const U32                        argument)
154{
155   GuiMessageVectorCtrl* pMVC = reinterpret_cast<GuiMessageVectorCtrl*>(spectatorKey);
156   pMVC->callbackRouter(code, argument);
157}
158
159
160//--------------------------------------------------------------------------
161GuiMessageVectorCtrl::GuiMessageVectorCtrl()
162{
163   VECTOR_SET_ASSOCIATION(mLineWrappings);
164   VECTOR_SET_ASSOCIATION(mSpecialMarkers);
165   VECTOR_SET_ASSOCIATION(mLineElements);
166
167   mMessageVector = NULL;
168   mLineSpacingPixels = 0;
169   mLineContinuationIndent = 10;
170
171   mMouseDown      = false;
172   mMouseSpecialLine = -1;
173   mMouseSpecialRef  = -1;
174
175   for (U32 i = 0; i < 16; i++)
176      mAllowedMatches[i] = "";
177   mSpecialColor.set(0, 0, 255);
178
179   mMaxColorIndex = 9;
180}
181
182
183//--------------------------------------------------------------------------
184GuiMessageVectorCtrl::~GuiMessageVectorCtrl()
185{
186   AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!");
187   AssertFatal(mSpecialMarkers.size() == 0, "Error, special markers not properly cleared!");
188   AssertFatal(mLineElements.size() == 0, "Error, line elements not properly cleared!");
189}
190
191
192//--------------------------------------------------------------------------
193void GuiMessageVectorCtrl::initPersistFields()
194{
195   addField("lineSpacing",        TypeS32,    Offset(mLineSpacingPixels,      GuiMessageVectorCtrl));
196   addField("lineContinuedIndex", TypeS32,    Offset(mLineContinuationIndent, GuiMessageVectorCtrl));
197   addField("allowedMatches",     TypeString, Offset(mAllowedMatches,         GuiMessageVectorCtrl), 16);
198   addField("matchColor",         TypeColorI, Offset(mSpecialColor,           GuiMessageVectorCtrl));
199   addField("maxColorIndex",      TypeS32,    Offset(mMaxColorIndex,          GuiMessageVectorCtrl));
200   Parent::initPersistFields();
201}
202
203
204bool GuiMessageVectorCtrl::onAdd()
205{
206   return Parent::onAdd();
207}
208
209
210void GuiMessageVectorCtrl::onRemove()
211{
212   Parent::onRemove();
213}
214
215
216//--------------------------------------------------------------------------
217bool GuiMessageVectorCtrl::isAttached() const
218{
219   return (mMessageVector != NULL);
220}
221
222
223//--------------------------------------------------------------------------
224bool GuiMessageVectorCtrl::attach(MessageVector* newAttachment)
225{
226   AssertFatal(newAttachment, "No attachment!");
227   if (newAttachment == NULL || !isAwake())
228      return false;
229
230   if (isAttached()) {
231      Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::attach: overriding attachment");
232      detach();
233   }
234   AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!");
235
236   mMessageVector = newAttachment;
237   mMessageVector->registerSpectator(sMVCtrlCallback, this);
238
239   return true;
240}
241
242//--------------------------------------------------------------------------
243void GuiMessageVectorCtrl::detach()
244{
245   if (isAttached() == false) {
246      Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::detach: not attached!");
247      return;
248   }
249
250   mMessageVector->unregisterSpectator(this);
251   mMessageVector = NULL;
252   AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!");
253}
254
255
256//--------------------------------------------------------------------------
257void GuiMessageVectorCtrl::lineInserted(const U32 arg)
258{
259   AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!");
260
261   GuiScrollCtrl* pScroll = dynamic_cast<GuiScrollCtrl*>(getParent());
262   bool fullyScrolled = pScroll->isScrolledToBottom();
263
264   mSpecialMarkers.insert(arg);
265   createSpecialMarkers(mSpecialMarkers[arg], mMessageVector->getLine(arg).message);
266
267   mLineWrappings.insert(arg);
268   createLineWrapping(mLineWrappings[arg], mMessageVector->getLine(arg).message);
269
270   mLineElements.insert(arg);
271   createLineElement(mLineElements[arg], mLineWrappings[arg], mSpecialMarkers[arg]);
272
273   U32 numLines = 0;
274   for (U32 i = 0; i < mLineWrappings.size(); i++) {
275      // We need to rebuild the physicalLineStart markers at the same time as
276      //  we find out how many of them are left...
277      mLineElements[i].physicalLineStart = numLines;
278
279      numLines += mLineWrappings[i].numLines;
280   }
281
282   Point2I newExtent = getExtent();
283   newExtent.y = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1));
284   setExtent(newExtent);
285   if(fullyScrolled)
286      pScroll->scrollTo(0, 0x7FFFFFFF);
287}
288
289
290void GuiMessageVectorCtrl::lineDeleted(const U32 arg)
291{
292   AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!");
293   AssertFatal(arg < mLineWrappings.size(), "Error, out of bounds line deleted!");
294
295   // It's a somewhat involved process to delete the lineelements...
296   LineElement& rElement = mLineElements[arg];
297
298   TextElement* walk = rElement.headLineElements;
299   while (walk != NULL) {
300      TextElement* lineWalk = walk->nextInLine;
301      while (lineWalk != NULL) {
302         TextElement* temp = lineWalk;
303         lineWalk = lineWalk->nextPhysicalLine;
304         delete temp;
305      }
306
307      TextElement* temp = walk;
308      walk = walk->nextPhysicalLine;
309      delete temp;
310   }
311   rElement.headLineElements = NULL;
312   mLineElements.erase(arg);
313
314   delete [] mLineWrappings[arg].startEndPairs;
315   mLineWrappings.erase(arg);
316
317   delete [] mSpecialMarkers[arg].specials;
318   mSpecialMarkers.erase(arg);
319
320   U32 numLines = 0;
321   for (U32 i = 0; i < mLineWrappings.size(); i++) {
322      // We need to rebuild the physicalLineStart markers at the same time as
323      //  we find out how many of them are left...
324      mLineElements[i].physicalLineStart = numLines;
325
326      numLines += mLineWrappings[i].numLines;
327   }
328
329   U32 newHeight = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1));
330   resize(getPosition(), Point2I(getWidth(), newHeight));
331}
332
333
334void GuiMessageVectorCtrl::vectorDeleted()
335{
336   AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!");
337   AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared out!");
338
339   mMessageVector = NULL;
340   U32 newHeight = mProfile->mFont->getHeight() + mLineSpacingPixels;
341   resize(getPosition(), Point2I(getWidth(), newHeight));
342}
343
344
345void GuiMessageVectorCtrl::callbackRouter(const MessageVector::MessageCode code,
346                                          const U32                        arg)
347{
348   switch (code) {
349      case MessageVector::LineInserted:
350         lineInserted(arg);
351         break;
352      case MessageVector::LineDeleted:
353         lineDeleted(arg);
354         break;
355      case MessageVector::VectorDeletion:
356         vectorDeleted();
357         break;
358   }
359}
360
361
362//--------------------------------------------------------------------------
363void GuiMessageVectorCtrl::createSpecialMarkers(SpecialMarkers& rSpecial, const char* string)
364{
365   // The first thing we need to do is create a version of the string with no uppercase
366   //  chars for matching...
367
368   String pLCCopyStr = String::ToLower( string );
369   const char* pLCCopy = pLCCopyStr.c_str();
370
371   Vector<TempLineBreak> tempSpecials(__FILE__, __LINE__);
372   Vector<S32>           tempTypes(__FILE__, __LINE__);
373
374   const char* pCurr = pLCCopy;
375   while (pCurr[0] != '\0') {
376      const char* pMinMatch = &pLCCopy[dStrlen(string)];
377      U32 minMatchType = 0xFFFFFFFF;
378      AssertFatal(pMinMatch[0] == '\0', "Error, bad positioning of sentry...");
379
380      // Find the earliest match
381      for (U32 i = 0; i < 16; i++) {
382         if (mAllowedMatches[i][0] == '\0')
383            continue;
384
385         const char* pMatch = dStrstr(pCurr, mAllowedMatches[i]);
386         if (pMatch != NULL && pMatch < pMinMatch) {
387            pMinMatch = pMatch;
388            minMatchType = i;
389         }
390      }
391
392      if (pMinMatch[0] != '\0') {
393         AssertFatal(minMatchType != 0xFFFFFFFF, "Hm, that's bad");
394         // Found a match => now find the end
395         U32 start = pMinMatch - pLCCopy;
396         U32 j;
397         for (j = 1; pLCCopy[start + j] != '\0'; j++) {
398            if (pLCCopy[start + j] == '\n' ||
399                pLCCopy[start + j] == ' '  ||
400                pLCCopy[start + j] == '\t')
401               break;
402         }
403         AssertFatal(j > 0, "Error, j must be > 0 at this point!");
404         U32 end = start + j - 1;
405
406         tempSpecials.increment();
407         tempSpecials.last().start = start;
408         tempSpecials.last().end   = end;
409         tempTypes.push_back(minMatchType);
410
411         pCurr  = &pLCCopy[end + 1];
412      } else {
413         // No match.  This will cause the while loop to terminate...
414         pCurr = pMinMatch;
415      }
416   }
417
418   if ((rSpecial.numSpecials = tempSpecials.size()) != 0) {
419      rSpecial.specials = new SpecialMarkers::Special[tempSpecials.size()];
420      for (U32 i = 0; i < tempSpecials.size(); i++) {
421         rSpecial.specials[i].start       = tempSpecials[i].start;
422         rSpecial.specials[i].end         = tempSpecials[i].end;
423         rSpecial.specials[i].specialType = tempTypes[i];
424      }
425   } else {
426      rSpecial.specials = NULL;
427   }
428}
429
430
431//--------------------------------------------------------------------------
432void GuiMessageVectorCtrl::createLineWrapping(LineWrapping& rWrapping, const char* string)
433{
434   Vector<TempLineBreak> tempBreaks(__FILE__, __LINE__);
435
436   U32 i;
437   U32 currStart = 0;
438   U32 length = dStrlen(string);
439   if (length != 0) {
440      for (i = 0; i < length; i++) {
441         if (string[i] == '\n') {
442            tempBreaks.increment();
443            tempBreaks.last().start = currStart;
444            tempBreaks.last().end   = i-1;
445            currStart = i+1;
446         } else if (i == length - 1) {
447            tempBreaks.increment();
448            tempBreaks.last().start = currStart;
449            tempBreaks.last().end   = i;
450            currStart = i+1;
451         }
452      }
453   } else {
454      tempBreaks.increment();
455      tempBreaks.last().start = 0;
456      tempBreaks.last().end   = -1;
457   }
458
459   U32 splitWidth = getWidth();
460   U32 currLine = 0;
461   while (currLine < tempBreaks.size()) {
462      TempLineBreak& rLine = tempBreaks[currLine];
463      if (rLine.start >= rLine.end) {
464         if (currLine == 0)
465            splitWidth -= mLineContinuationIndent;
466         currLine++;
467         continue;
468      }
469
470      // Ok, there's some actual text in this line.  How long is it?
471      U32 baseLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], rLine.end-rLine.start+1);
472      if (baseLength > splitWidth) {
473         // DMMNOTE: Replace with bin search eventually
474         U32 currPos = 0;
475         U32 breakPos = 0;
476         for (currPos = 0; currPos < rLine.end-rLine.start+1; currPos++) {
477            U32 currLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], currPos+1);
478            if (currLength > splitWidth) 
479            {
480               // Make sure that the currPos has advanced, then set the breakPoint.
481               breakPos = currPos != 0 ? currPos - 1 : 0;
482               break;
483            }
484         }
485         if (currPos == rLine.end-rLine.start+1) {
486            AssertFatal(false, "Error, if the line must be broken, the position must be before this point!");
487            currLine++;
488            continue;
489         }
490
491         // Ok, the character at breakPos is the last valid char we can render.  We
492         //  want to scan back to the first whitespace character (which, in the bounds
493         //  of the line, is guaranteed to be a space or a tab).
494         U32 originalBreak = breakPos;
495         while (true) {
496            if (string[rLine.start + breakPos] == ' ' || string[rLine.start + breakPos] == '\t') {
497               break;
498            } else {
499               AssertFatal(string[rLine.start + breakPos] != '\n',
500                           "Bad characters in line range...");
501               if (breakPos == 0) {
502                  breakPos = originalBreak;
503                  break;
504               }
505               breakPos--;
506            }
507         }
508
509         // Ok, everything up to and including breakPos is in the currentLine.  Insert
510         //  a new line at this point, and put everything after in that line.
511         S32 oldStart = rLine.start;
512         S32 oldEnd   = rLine.end;
513         rLine.end    = rLine.start + breakPos;
514
515         // Note that rLine is NOTNOTNOTNOT valid after this point!
516         tempBreaks.insert(currLine+1);
517         tempBreaks[currLine+1].start = oldStart + breakPos + 1;
518         tempBreaks[currLine+1].end   = oldEnd;
519      }
520
521      if (currLine == 0)
522         splitWidth -= mLineContinuationIndent;
523      currLine++;
524   }
525
526   rWrapping.numLines = tempBreaks.size();
527   rWrapping.startEndPairs = new LineWrapping::SEPair[tempBreaks.size()];
528   for (i = 0; i < tempBreaks.size(); i++) {
529      rWrapping.startEndPairs[i].start = tempBreaks[i].start;
530      rWrapping.startEndPairs[i].end   = tempBreaks[i].end;
531   }
532}
533
534//--------------------------------------------------------------------------
535void GuiMessageVectorCtrl::createLineElement(LineElement&    rElement,
536                                             LineWrapping&   rWrapping,
537                                             SpecialMarkers& rSpecial)
538{
539   // First, do a straighforward translation of the wrapping...
540   TextElement** ppWalk = &rElement.headLineElements;
541   for (U32 i = 0; i < rWrapping.numLines; i++) {
542      *ppWalk = new TextElement;
543
544      (*ppWalk)->nextInLine       = NULL;
545      (*ppWalk)->nextPhysicalLine = NULL;
546      (*ppWalk)->specialReference = -1;
547
548      (*ppWalk)->start = rWrapping.startEndPairs[i].start;
549      (*ppWalk)->end   = rWrapping.startEndPairs[i].end;
550
551      ppWalk = &((*ppWalk)->nextPhysicalLine);
552   }
553
554   if (rSpecial.numSpecials != 0) {
555      // Ok.  Now, walk down the lines, and split the elements into their constituent parts...
556      //
557      TextElement* walk = rElement.headLineElements;
558      while (walk) {
559         TextElement* walkAcross = walk;
560         while (walkAcross) {
561            S32 specialMatch = -1;
562            for (U32 i = 0; i < rSpecial.numSpecials; i++) {
563               if (walkAcross->start <= rSpecial.specials[i].end &&
564                   walkAcross->end   >= rSpecial.specials[i].start) {
565                  specialMatch = i;
566                  break;
567               }
568            }
569
570            if (specialMatch != -1) {
571               // We have a match here.  Break it into the appropriate number of segments.
572               //  this will vary depending on how the overlap is occuring...
573               if (walkAcross->start >= rSpecial.specials[specialMatch].start) {
574                  if (walkAcross->end <= rSpecial.specials[specialMatch].end) {
575                     // The whole thing is part of the special
576                     AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!");
577                     walkAcross->specialReference = specialMatch;
578                  } else {
579                     // The first part is in the special, the tail is out
580                     AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!");
581                     walkAcross->nextInLine = new TextElement;
582                     walkAcross->nextInLine->nextInLine       = NULL;
583                     walkAcross->nextInLine->nextPhysicalLine = NULL;
584                     walkAcross->nextInLine->specialReference = -1;
585
586                     walkAcross->specialReference  = specialMatch;
587                     walkAcross->nextInLine->end   = walkAcross->end;
588                     walkAcross->end               = rSpecial.specials[specialMatch].end;
589                     walkAcross->nextInLine->start = rSpecial.specials[specialMatch].end + 1;
590
591                     AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!");
592                  }
593
594                  walkAcross = walkAcross->nextInLine;
595               } else {
596                  if (walkAcross->end <= rSpecial.specials[specialMatch].end) {
597                     // The first part is out of the special, the second part in.
598                     AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!");
599                     walkAcross->nextInLine = new TextElement;
600                     walkAcross->nextInLine->nextInLine       = NULL;
601                     walkAcross->nextInLine->nextPhysicalLine = NULL;
602                     walkAcross->nextInLine->specialReference = specialMatch;
603
604                     walkAcross->specialReference  = -1;
605                     walkAcross->nextInLine->end   = walkAcross->end;
606                     walkAcross->end               = rSpecial.specials[specialMatch].start - 1;
607                     walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start;
608
609                     AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!");
610                     walkAcross = walkAcross->nextInLine;
611                  } else {
612                     // First out, middle in, last out.  Oy.
613                     AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!");
614                     walkAcross->nextInLine = new TextElement;
615                     walkAcross->nextInLine->nextInLine       = NULL;
616                     walkAcross->nextInLine->nextPhysicalLine = NULL;
617                     walkAcross->nextInLine->specialReference = specialMatch;
618
619                     walkAcross->nextInLine->nextInLine                   = new TextElement;
620                     walkAcross->nextInLine->nextInLine->nextInLine       = NULL;
621                     walkAcross->nextInLine->nextInLine->nextPhysicalLine = NULL;
622                     walkAcross->nextInLine->nextInLine->specialReference = -1;
623
624                     walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start;
625                     walkAcross->nextInLine->end   = rSpecial.specials[specialMatch].end;
626                     walkAcross->nextInLine->nextInLine->start = rSpecial.specials[specialMatch].end+1;
627                     walkAcross->nextInLine->nextInLine->end   = walkAcross->end;
628                     walkAcross->end = walkAcross->nextInLine->start - 1;
629                     AssertFatal((walkAcross->end >= walkAcross->start &&
630                                  walkAcross->nextInLine->end >= walkAcross->nextInLine->start &&
631                                  walkAcross->nextInLine->nextInLine->end >= walkAcross->nextInLine->nextInLine->start),
632                                 "Bad textelements generated!");
633                     walkAcross = walkAcross->nextInLine->nextInLine;
634                  }
635               }
636            } else {
637               walkAcross = walkAcross->nextInLine;
638            }
639         }
640
641         walk = walk->nextPhysicalLine;
642      }
643   }
644}
645
646
647//--------------------------------------------------------------------------
648bool GuiMessageVectorCtrl::onWake()
649{
650   if (Parent::onWake() == false)
651      return false;
652
653   if (bool(mProfile->mFont) == false)
654      return false;
655
656   mMinSensibleWidth = 1;
657
658   for (U32 i = 0; i < 256; i++) {
659      if (mProfile->mFont->isValidChar(U8(i))) {
660         if (mProfile->mFont->getCharWidth(U8(i)) > mMinSensibleWidth)
661            mMinSensibleWidth = mProfile->mFont->getCharWidth(U8(i));
662      }
663   }
664
665   AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!");
666   return true;
667}
668
669
670//--------------------------------------------------------------------------
671void GuiMessageVectorCtrl::onSleep()
672{
673   if (isAttached())
674      detach();
675
676   Parent::onSleep();
677}
678
679
680//--------------------------------------------------------------------------
681void GuiMessageVectorCtrl::onRender(Point2I      offset,
682                                    const RectI& updateRect)
683{
684   GFXDrawUtil *drawer = GFX->getDrawUtil();
685
686   Parent::onRender(offset, updateRect);
687   if (isAttached()) {
688      U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels;
689      U32 currLine   = 0;
690      ColorI lastColor = mProfile->mFontColor;
691      for (U32 i = 0; i < mMessageVector->getNumLines(); i++) {
692
693         TextElement* pElement = mLineElements[i].headLineElements;
694         while (pElement != NULL) {
695            Point2I localStart(pElement == mLineElements[i].headLineElements ? 0 : mLineContinuationIndent, currLine * linePixels);
696
697            Point2I globalCheck  = localToGlobalCoord(localStart);
698            U32 globalRangeStart = globalCheck.y;
699            U32 globalRangeEnd   = globalCheck.y + mProfile->mFont->getHeight();
700            if (globalRangeStart > updateRect.point.y + updateRect.extent.y ||
701                globalRangeEnd   < updateRect.point.y) {
702               currLine++;
703               pElement = pElement->nextPhysicalLine;
704               continue;
705            }
706
707            TextElement* walkAcross = pElement;
708            while (walkAcross) {
709               if (walkAcross->start > walkAcross->end)
710                  break;
711
712               Point2I globalStart  = localToGlobalCoord(localStart);
713
714               U32 strWidth;
715               if (walkAcross->specialReference == -1) {
716                  drawer->setBitmapModulation(lastColor);
717                  drawer->setTextAnchorColor(mProfile->mFontColor);
718                  strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start],
719                                          walkAcross->end - walkAcross->start + 1, mProfile->mFontColors, mMaxColorIndex);
720                  drawer->getBitmapModulation(&lastColor);  // in case an embedded color tag changed it
721               } else {
722                  drawer->getBitmapModulation( &lastColor );
723                  drawer->setBitmapModulation(mSpecialColor);
724                  drawer->setTextAnchorColor(mProfile->mFontColor);
725                  strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start],
726                                          walkAcross->end - walkAcross->start + 1);
727
728                  // in case we have 2 in a row...
729                  drawer->setBitmapModulation(lastColor);
730               }
731
732               // drawTextN returns the rightmost X coord, so subtract leftmost coord to get the width
733               strWidth -= globalStart.x;
734
735               if (walkAcross->specialReference != -1) {
736                  Point2I lineStart = localStart;
737                  Point2I lineEnd   = localStart;
738                  lineStart.y += mProfile->mFont->getBaseline() + 1;
739                  lineEnd.x += strWidth;
740                  lineEnd.y += mProfile->mFont->getBaseline() + 1;
741
742                  drawer->drawLine(localToGlobalCoord(lineStart),
743                              localToGlobalCoord(lineEnd),
744                              mSpecialColor);
745               }
746
747               localStart.x += strWidth;
748               walkAcross = walkAcross->nextInLine;
749            }
750
751            currLine++;
752            pElement = pElement->nextPhysicalLine;
753         }
754      }
755      drawer->clearBitmapModulation();
756   }
757}
758
759
760//--------------------------------------------------------------------------
761void GuiMessageVectorCtrl::inspectPostApply()
762{
763   Parent::inspectPostApply();
764}
765
766
767//--------------------------------------------------------------------------
768void GuiMessageVectorCtrl::parentResized(const RectI& oldParentRect, const RectI& newParentRect)
769{
770   Parent::parentResized(oldParentRect, newParentRect);
771
772   // If we have a MesssageVector, detach/reattach so we can reflow the text.
773   if (mMessageVector)
774   {
775      MessageVector *reflowme = mMessageVector;
776
777      detach();
778      attach(reflowme);
779   }
780}
781
782//--------------------------------------------------------------------------
783void GuiMessageVectorCtrl::findSpecialFromCoord(const Point2I& point, S32* specialLine, S32* specialRef)
784{
785   if (mLineElements.size() == 0) {
786      *specialLine = -1;
787      *specialRef  = -1;
788      return;
789   }
790
791   U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels;
792   
793   if ((point.x < 0 || point.x >= getWidth()) ||
794       (point.y < 0 || point.y >= getHeight())) {
795      *specialLine = -1;
796      *specialRef  = -1;
797      return;
798   }
799
800   // Ok, we have real work to do here.  Let's determine the physical line that it's on...
801   U32 physLine = point.y / linePixels;
802   AssertFatal(physLine >= 0, "Bad physical line!");
803
804   // And now we find the LineElement that contains that physicalLine...
805   U32 elemIndex;
806   for (elemIndex = 0; elemIndex < mLineElements.size(); elemIndex++) {
807      if (mLineElements[elemIndex].physicalLineStart > physLine) {
808         // We've passed it.
809         AssertFatal(elemIndex != 0, "Error, bad elemIndex, check assumptions.");
810         elemIndex--;
811         break;
812      }
813   }
814   if (elemIndex == mLineElements.size()) {
815      // On the last line...
816      elemIndex = mLineElements.size() - 1;
817   }
818
819   TextElement* line = mLineElements[elemIndex].headLineElements;
820   for (U32 i = 0; i < physLine - mLineElements[elemIndex].physicalLineStart; i++)
821   {
822      if (line->nextPhysicalLine == NULL)
823      {
824         *specialLine = -1;
825         *specialRef  = -1;
826         return;
827      }
828      line = line->nextPhysicalLine;
829      AssertFatal(line != NULL, "Error, moved too far!");
830   }
831
832   // Ok, line represents the current line.  We now need to find out which textelement
833   //  the points x coord falls in.
834   U32 currX = 0;
835   if ((physLine - mLineElements[elemIndex].physicalLineStart) != 0) {
836      currX = mLineContinuationIndent;
837      // First, if this isn't the first line in this wrapping, we have to make sure
838      //  that the coord isn't in the margin...
839      if (point.x < mLineContinuationIndent) {
840         *specialLine = -1;
841         *specialRef  = -1;
842         return;
843      }
844   }
845   if (line->start > line->end) {
846      // Empty line special case...
847      *specialLine = -1;
848      *specialRef  = -1;
849      return;
850   }
851
852   while (line) {
853      U32 newX = currX + mProfile->mFont->getStrNWidth((const UTF8 *)mMessageVector->getLine(elemIndex).message,
854                                                       line->end - line->start + 1);
855      if (point.x < newX) {
856         // That's the one!
857         *specialLine = elemIndex;
858         *specialRef  = line->specialReference;
859         return;
860      }
861
862      currX = newX;
863      line = line->nextInLine;
864   }
865
866   // Off to the right.  Aw...
867   *specialLine = -1;
868   *specialRef  = -1;
869}
870
871
872void GuiMessageVectorCtrl::onMouseDown(const GuiEvent& event)
873{
874   Parent::onMouseDown(event);
875   mouseUnlock();
876
877   mMouseDown = true;
878
879   // Find the special we are in, if any...
880   findSpecialFromCoord(globalToLocalCoord(event.mousePoint),
881                        &mMouseSpecialLine, &mMouseSpecialRef);
882}
883
884void GuiMessageVectorCtrl::onMouseUp(const GuiEvent& event)
885{
886   Parent::onMouseUp(event);
887   mouseUnlock();
888
889   // Is this an up from a dragged click?
890   if (mMouseDown == false)
891      return;
892
893   // Find the special we are in, if any...
894
895   S32 currSpecialLine;
896   S32 currSpecialRef;
897   findSpecialFromCoord(globalToLocalCoord(event.mousePoint), &currSpecialLine, &currSpecialRef);
898
899   if (currSpecialRef != -1 &&
900       (currSpecialLine == mMouseSpecialLine &&
901        currSpecialRef  == mMouseSpecialRef)) {
902      // Execute the callback
903      SpecialMarkers& rSpecial = mSpecialMarkers[currSpecialLine];
904      S32 specialStart = rSpecial.specials[currSpecialRef].start;
905      S32 specialEnd   = rSpecial.specials[currSpecialRef].end;
906
907      char* copyURL = new char[specialEnd - specialStart + 2];
908      dStrncpy(copyURL, &mMessageVector->getLine(currSpecialLine).message[specialStart], specialEnd - specialStart + 1);
909      copyURL[specialEnd - specialStart + 1] = '\0';
910
911      Con::executef(this, "urlClickCallback", copyURL);
912      delete [] copyURL;
913   }
914
915   mMouseDown      = false;
916   mMouseSpecialLine = -1;
917   mMouseSpecialRef  = -1;
918}
919
920