guiMessageVectorCtrl.cpp
Engine/source/gui/game/guiMessageVectorCtrl.cpp
Classes:
class
Public Functions
ConsoleDocClass(GuiMessageVectorCtrl , "@brief A chat HUD <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that displays messages from <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector.\n\n</a>" "This renders messages from <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 <a href="/coding/file/document_8h/#document_8h_1a071cf97155ba72ac9a1fc4ad7e63d481">Value</a>" )
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_1a4e4fa7e3399708e0777b5308db01278c">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 <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> 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>" )
sMVCtrlCallback(void * spectatorKey, const MessageVector::MessageCode code, const U32 argument)
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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">MessageVector.\n\n</a>" "This renders messages from <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 <a href="/coding/file/document_8h/#document_8h_1a071cf97155ba72ac9a1fc4ad7e63d481">Value</a>" )
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_1a4e4fa7e3399708e0777b5308db01278c">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 <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> 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 mMinSensibleWidth = 1; 169 mLineSpacingPixels = 0; 170 mLineContinuationIndent = 10; 171 172 mMouseDown = false; 173 mMouseSpecialLine = -1; 174 mMouseSpecialRef = -1; 175 176 for (U32 i = 0; i < 16; i++) 177 mAllowedMatches[i] = ""; 178 mSpecialColor.set(0, 0, 255); 179 180 mMaxColorIndex = 9; 181} 182 183 184//-------------------------------------------------------------------------- 185GuiMessageVectorCtrl::~GuiMessageVectorCtrl() 186{ 187 AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); 188 AssertFatal(mSpecialMarkers.size() == 0, "Error, special markers not properly cleared!"); 189 AssertFatal(mLineElements.size() == 0, "Error, line elements not properly cleared!"); 190} 191 192 193//-------------------------------------------------------------------------- 194void GuiMessageVectorCtrl::initPersistFields() 195{ 196 addField("lineSpacing", TypeS32, Offset(mLineSpacingPixels, GuiMessageVectorCtrl)); 197 addField("lineContinuedIndex", TypeS32, Offset(mLineContinuationIndent, GuiMessageVectorCtrl)); 198 addField("allowedMatches", TypeString, Offset(mAllowedMatches, GuiMessageVectorCtrl), 16); 199 addField("matchColor", TypeColorI, Offset(mSpecialColor, GuiMessageVectorCtrl)); 200 addField("maxColorIndex", TypeS32, Offset(mMaxColorIndex, GuiMessageVectorCtrl)); 201 Parent::initPersistFields(); 202} 203 204 205bool GuiMessageVectorCtrl::onAdd() 206{ 207 return Parent::onAdd(); 208} 209 210 211void GuiMessageVectorCtrl::onRemove() 212{ 213 Parent::onRemove(); 214} 215 216 217//-------------------------------------------------------------------------- 218bool GuiMessageVectorCtrl::isAttached() const 219{ 220 return (mMessageVector != NULL); 221} 222 223 224//-------------------------------------------------------------------------- 225bool GuiMessageVectorCtrl::attach(MessageVector* newAttachment) 226{ 227 AssertFatal(newAttachment, "No attachment!"); 228 if (newAttachment == NULL || !isAwake()) 229 return false; 230 231 if (isAttached()) { 232 Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::attach: overriding attachment"); 233 detach(); 234 } 235 AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); 236 237 mMessageVector = newAttachment; 238 mMessageVector->registerSpectator(sMVCtrlCallback, this); 239 240 return true; 241} 242 243//-------------------------------------------------------------------------- 244void GuiMessageVectorCtrl::detach() 245{ 246 if (isAttached() == false) { 247 Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::detach: not attached!"); 248 return; 249 } 250 251 mMessageVector->unregisterSpectator(this); 252 mMessageVector = NULL; 253 AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); 254} 255 256 257//-------------------------------------------------------------------------- 258void GuiMessageVectorCtrl::lineInserted(const U32 arg) 259{ 260 AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); 261 262 GuiScrollCtrl* pScroll = dynamic_cast<GuiScrollCtrl*>(getParent()); 263 bool fullyScrolled = pScroll->isScrolledToBottom(); 264 265 mSpecialMarkers.insert(arg); 266 createSpecialMarkers(mSpecialMarkers[arg], mMessageVector->getLine(arg).message); 267 268 mLineWrappings.insert(arg); 269 createLineWrapping(mLineWrappings[arg], mMessageVector->getLine(arg).message); 270 271 mLineElements.insert(arg); 272 createLineElement(mLineElements[arg], mLineWrappings[arg], mSpecialMarkers[arg]); 273 274 U32 numLines = 0; 275 for (U32 i = 0; i < mLineWrappings.size(); i++) { 276 // We need to rebuild the physicalLineStart markers at the same time as 277 // we find out how many of them are left... 278 mLineElements[i].physicalLineStart = numLines; 279 280 numLines += mLineWrappings[i].numLines; 281 } 282 283 Point2I newExtent = getExtent(); 284 newExtent.y = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); 285 setExtent(newExtent); 286 if(fullyScrolled) 287 pScroll->scrollTo(0, 0x7FFFFFFF); 288} 289 290 291void GuiMessageVectorCtrl::lineDeleted(const U32 arg) 292{ 293 AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); 294 AssertFatal(arg < mLineWrappings.size(), "Error, out of bounds line deleted!"); 295 296 // It's a somewhat involved process to delete the lineelements... 297 LineElement& rElement = mLineElements[arg]; 298 299 TextElement* walk = rElement.headLineElements; 300 while (walk != NULL) { 301 TextElement* lineWalk = walk->nextInLine; 302 while (lineWalk != NULL) { 303 TextElement* temp = lineWalk; 304 lineWalk = lineWalk->nextPhysicalLine; 305 delete temp; 306 } 307 308 TextElement* temp = walk; 309 walk = walk->nextPhysicalLine; 310 delete temp; 311 } 312 rElement.headLineElements = NULL; 313 mLineElements.erase(arg); 314 315 delete [] mLineWrappings[arg].startEndPairs; 316 mLineWrappings.erase(arg); 317 318 delete [] mSpecialMarkers[arg].specials; 319 mSpecialMarkers.erase(arg); 320 321 U32 numLines = 0; 322 for (U32 i = 0; i < mLineWrappings.size(); i++) { 323 // We need to rebuild the physicalLineStart markers at the same time as 324 // we find out how many of them are left... 325 mLineElements[i].physicalLineStart = numLines; 326 327 numLines += mLineWrappings[i].numLines; 328 } 329 330 U32 newHeight = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); 331 resize(getPosition(), Point2I(getWidth(), newHeight)); 332} 333 334 335void GuiMessageVectorCtrl::vectorDeleted() 336{ 337 AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); 338 AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared out!"); 339 340 mMessageVector = NULL; 341 U32 newHeight = mProfile->mFont->getHeight() + mLineSpacingPixels; 342 resize(getPosition(), Point2I(getWidth(), newHeight)); 343} 344 345 346void GuiMessageVectorCtrl::callbackRouter(const MessageVector::MessageCode code, 347 const U32 arg) 348{ 349 switch (code) { 350 case MessageVector::LineInserted: 351 lineInserted(arg); 352 break; 353 case MessageVector::LineDeleted: 354 lineDeleted(arg); 355 break; 356 case MessageVector::VectorDeletion: 357 vectorDeleted(); 358 break; 359 } 360} 361 362 363//-------------------------------------------------------------------------- 364void GuiMessageVectorCtrl::createSpecialMarkers(SpecialMarkers& rSpecial, const char* string) 365{ 366 // The first thing we need to do is create a version of the string with no uppercase 367 // chars for matching... 368 369 String pLCCopyStr = String::ToLower( string ); 370 const char* pLCCopy = pLCCopyStr.c_str(); 371 372 Vector<TempLineBreak> tempSpecials(__FILE__, __LINE__); 373 Vector<S32> tempTypes(__FILE__, __LINE__); 374 375 const char* pCurr = pLCCopy; 376 while (pCurr[0] != '\0') { 377 const char* pMinMatch = &pLCCopy[dStrlen(string)]; 378 U32 minMatchType = 0xFFFFFFFF; 379 AssertFatal(pMinMatch[0] == '\0', "Error, bad positioning of sentry..."); 380 381 // Find the earliest match 382 for (U32 i = 0; i < 16; i++) { 383 if (mAllowedMatches[i][0] == '\0') 384 continue; 385 386 const char* pMatch = dStrstr(pCurr, mAllowedMatches[i]); 387 if (pMatch != NULL && pMatch < pMinMatch) { 388 pMinMatch = pMatch; 389 minMatchType = i; 390 } 391 } 392 393 if (pMinMatch[0] != '\0') { 394 AssertFatal(minMatchType != 0xFFFFFFFF, "Hm, that's bad"); 395 // Found a match => now find the end 396 U32 start = pMinMatch - pLCCopy; 397 U32 j; 398 for (j = 1; pLCCopy[start + j] != '\0'; j++) { 399 if (pLCCopy[start + j] == '\n' || 400 pLCCopy[start + j] == ' ' || 401 pLCCopy[start + j] == '\t') 402 break; 403 } 404 AssertFatal(j > 0, "Error, j must be > 0 at this point!"); 405 U32 end = start + j - 1; 406 407 tempSpecials.increment(); 408 tempSpecials.last().start = start; 409 tempSpecials.last().end = end; 410 tempTypes.push_back(minMatchType); 411 412 pCurr = &pLCCopy[end + 1]; 413 } else { 414 // No match. This will cause the while loop to terminate... 415 pCurr = pMinMatch; 416 } 417 } 418 419 if ((rSpecial.numSpecials = tempSpecials.size()) != 0) { 420 rSpecial.specials = new SpecialMarkers::Special[tempSpecials.size()]; 421 for (U32 i = 0; i < tempSpecials.size(); i++) { 422 rSpecial.specials[i].start = tempSpecials[i].start; 423 rSpecial.specials[i].end = tempSpecials[i].end; 424 rSpecial.specials[i].specialType = tempTypes[i]; 425 } 426 } else { 427 rSpecial.specials = NULL; 428 } 429} 430 431 432//-------------------------------------------------------------------------- 433void GuiMessageVectorCtrl::createLineWrapping(LineWrapping& rWrapping, const char* string) 434{ 435 Vector<TempLineBreak> tempBreaks(__FILE__, __LINE__); 436 437 U32 i; 438 U32 currStart = 0; 439 U32 length = dStrlen(string); 440 if (length != 0) { 441 for (i = 0; i < length; i++) { 442 if (string[i] == '\n') { 443 tempBreaks.increment(); 444 tempBreaks.last().start = currStart; 445 tempBreaks.last().end = i-1; 446 currStart = i+1; 447 } else if (i == length - 1) { 448 tempBreaks.increment(); 449 tempBreaks.last().start = currStart; 450 tempBreaks.last().end = i; 451 currStart = i+1; 452 } 453 } 454 } else { 455 tempBreaks.increment(); 456 tempBreaks.last().start = 0; 457 tempBreaks.last().end = -1; 458 } 459 460 U32 splitWidth = getWidth(); 461 U32 currLine = 0; 462 while (currLine < tempBreaks.size()) { 463 TempLineBreak& rLine = tempBreaks[currLine]; 464 if (rLine.start >= rLine.end) { 465 if (currLine == 0) 466 splitWidth -= mLineContinuationIndent; 467 currLine++; 468 continue; 469 } 470 471 // Ok, there's some actual text in this line. How long is it? 472 U32 baseLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], rLine.end-rLine.start+1); 473 if (baseLength > splitWidth) { 474 // DMMNOTE: Replace with bin search eventually 475 U32 currPos = 0; 476 U32 breakPos = 0; 477 for (currPos = 0; currPos < rLine.end-rLine.start+1; currPos++) { 478 U32 currLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], currPos+1); 479 if (currLength > splitWidth) 480 { 481 // Make sure that the currPos has advanced, then set the breakPoint. 482 breakPos = currPos != 0 ? currPos - 1 : 0; 483 break; 484 } 485 } 486 if (currPos == rLine.end-rLine.start+1) { 487 AssertFatal(false, "Error, if the line must be broken, the position must be before this point!"); 488 currLine++; 489 continue; 490 } 491 492 // Ok, the character at breakPos is the last valid char we can render. We 493 // want to scan back to the first whitespace character (which, in the bounds 494 // of the line, is guaranteed to be a space or a tab). 495 U32 originalBreak = breakPos; 496 while (true) { 497 if (string[rLine.start + breakPos] == ' ' || string[rLine.start + breakPos] == '\t') { 498 break; 499 } else { 500 AssertFatal(string[rLine.start + breakPos] != '\n', 501 "Bad characters in line range..."); 502 if (breakPos == 0) { 503 breakPos = originalBreak; 504 break; 505 } 506 breakPos--; 507 } 508 } 509 510 // Ok, everything up to and including breakPos is in the currentLine. Insert 511 // a new line at this point, and put everything after in that line. 512 S32 oldStart = rLine.start; 513 S32 oldEnd = rLine.end; 514 rLine.end = rLine.start + breakPos; 515 516 // Note that rLine is NOTNOTNOTNOT valid after this point! 517 tempBreaks.insert(currLine+1); 518 tempBreaks[currLine+1].start = oldStart + breakPos + 1; 519 tempBreaks[currLine+1].end = oldEnd; 520 } 521 522 if (currLine == 0) 523 splitWidth -= mLineContinuationIndent; 524 currLine++; 525 } 526 527 rWrapping.numLines = tempBreaks.size(); 528 rWrapping.startEndPairs = new LineWrapping::SEPair[tempBreaks.size()]; 529 for (i = 0; i < tempBreaks.size(); i++) { 530 rWrapping.startEndPairs[i].start = tempBreaks[i].start; 531 rWrapping.startEndPairs[i].end = tempBreaks[i].end; 532 } 533} 534 535//-------------------------------------------------------------------------- 536void GuiMessageVectorCtrl::createLineElement(LineElement& rElement, 537 LineWrapping& rWrapping, 538 SpecialMarkers& rSpecial) 539{ 540 // First, do a straighforward translation of the wrapping... 541 TextElement** ppWalk = &rElement.headLineElements; 542 for (U32 i = 0; i < rWrapping.numLines; i++) { 543 *ppWalk = new TextElement; 544 545 (*ppWalk)->nextInLine = NULL; 546 (*ppWalk)->nextPhysicalLine = NULL; 547 (*ppWalk)->specialReference = -1; 548 549 (*ppWalk)->start = rWrapping.startEndPairs[i].start; 550 (*ppWalk)->end = rWrapping.startEndPairs[i].end; 551 552 ppWalk = &((*ppWalk)->nextPhysicalLine); 553 } 554 555 if (rSpecial.numSpecials != 0) { 556 // Ok. Now, walk down the lines, and split the elements into their constituent parts... 557 // 558 TextElement* walk = rElement.headLineElements; 559 while (walk) { 560 TextElement* walkAcross = walk; 561 while (walkAcross) { 562 S32 specialMatch = -1; 563 for (U32 i = 0; i < rSpecial.numSpecials; i++) { 564 if (walkAcross->start <= rSpecial.specials[i].end && 565 walkAcross->end >= rSpecial.specials[i].start) { 566 specialMatch = i; 567 break; 568 } 569 } 570 571 if (specialMatch != -1) { 572 // We have a match here. Break it into the appropriate number of segments. 573 // this will vary depending on how the overlap is occuring... 574 if (walkAcross->start >= rSpecial.specials[specialMatch].start) { 575 if (walkAcross->end <= rSpecial.specials[specialMatch].end) { 576 // The whole thing is part of the special 577 AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); 578 walkAcross->specialReference = specialMatch; 579 } else { 580 // The first part is in the special, the tail is out 581 AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); 582 walkAcross->nextInLine = new TextElement; 583 walkAcross->nextInLine->nextInLine = NULL; 584 walkAcross->nextInLine->nextPhysicalLine = NULL; 585 walkAcross->nextInLine->specialReference = -1; 586 587 walkAcross->specialReference = specialMatch; 588 walkAcross->nextInLine->end = walkAcross->end; 589 walkAcross->end = rSpecial.specials[specialMatch].end; 590 walkAcross->nextInLine->start = rSpecial.specials[specialMatch].end + 1; 591 592 AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!"); 593 } 594 595 walkAcross = walkAcross->nextInLine; 596 } else { 597 if (walkAcross->end <= rSpecial.specials[specialMatch].end) { 598 // The first part is out of the special, the second part in. 599 AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); 600 walkAcross->nextInLine = new TextElement; 601 walkAcross->nextInLine->nextInLine = NULL; 602 walkAcross->nextInLine->nextPhysicalLine = NULL; 603 walkAcross->nextInLine->specialReference = specialMatch; 604 605 walkAcross->specialReference = -1; 606 walkAcross->nextInLine->end = walkAcross->end; 607 walkAcross->end = rSpecial.specials[specialMatch].start - 1; 608 walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start; 609 610 AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!"); 611 walkAcross = walkAcross->nextInLine; 612 } else { 613 // First out, middle in, last out. Oy. 614 AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); 615 walkAcross->nextInLine = new TextElement; 616 walkAcross->nextInLine->nextInLine = NULL; 617 walkAcross->nextInLine->nextPhysicalLine = NULL; 618 walkAcross->nextInLine->specialReference = specialMatch; 619 620 walkAcross->nextInLine->nextInLine = new TextElement; 621 walkAcross->nextInLine->nextInLine->nextInLine = NULL; 622 walkAcross->nextInLine->nextInLine->nextPhysicalLine = NULL; 623 walkAcross->nextInLine->nextInLine->specialReference = -1; 624 625 walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start; 626 walkAcross->nextInLine->end = rSpecial.specials[specialMatch].end; 627 walkAcross->nextInLine->nextInLine->start = rSpecial.specials[specialMatch].end+1; 628 walkAcross->nextInLine->nextInLine->end = walkAcross->end; 629 walkAcross->end = walkAcross->nextInLine->start - 1; 630 AssertFatal((walkAcross->end >= walkAcross->start && 631 walkAcross->nextInLine->end >= walkAcross->nextInLine->start && 632 walkAcross->nextInLine->nextInLine->end >= walkAcross->nextInLine->nextInLine->start), 633 "Bad textelements generated!"); 634 walkAcross = walkAcross->nextInLine->nextInLine; 635 } 636 } 637 } else { 638 walkAcross = walkAcross->nextInLine; 639 } 640 } 641 642 walk = walk->nextPhysicalLine; 643 } 644 } 645} 646 647 648//-------------------------------------------------------------------------- 649bool GuiMessageVectorCtrl::onWake() 650{ 651 if (Parent::onWake() == false) 652 return false; 653 654 if (bool(mProfile->mFont) == false) 655 return false; 656 657 mMinSensibleWidth = 1; 658 659 for (U32 i = 0; i < 256; i++) { 660 if (mProfile->mFont->isValidChar(U8(i))) { 661 if (mProfile->mFont->getCharWidth(U8(i)) > mMinSensibleWidth) 662 mMinSensibleWidth = mProfile->mFont->getCharWidth(U8(i)); 663 } 664 } 665 666 AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); 667 return true; 668} 669 670 671//-------------------------------------------------------------------------- 672void GuiMessageVectorCtrl::onSleep() 673{ 674 if (isAttached()) 675 detach(); 676 677 Parent::onSleep(); 678} 679 680 681//-------------------------------------------------------------------------- 682void GuiMessageVectorCtrl::onRender(Point2I offset, 683 const RectI& updateRect) 684{ 685 GFXDrawUtil *drawer = GFX->getDrawUtil(); 686 687 Parent::onRender(offset, updateRect); 688 if (isAttached()) { 689 U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels; 690 U32 currLine = 0; 691 ColorI lastColor = mProfile->mFontColor; 692 for (U32 i = 0; i < mMessageVector->getNumLines(); i++) { 693 694 TextElement* pElement = mLineElements[i].headLineElements; 695 while (pElement != NULL) { 696 Point2I localStart(pElement == mLineElements[i].headLineElements ? 0 : mLineContinuationIndent, currLine * linePixels); 697 698 Point2I globalCheck = localToGlobalCoord(localStart); 699 U32 globalRangeStart = globalCheck.y; 700 U32 globalRangeEnd = globalCheck.y + mProfile->mFont->getHeight(); 701 if (globalRangeStart > updateRect.point.y + updateRect.extent.y || 702 globalRangeEnd < updateRect.point.y) { 703 currLine++; 704 pElement = pElement->nextPhysicalLine; 705 continue; 706 } 707 708 TextElement* walkAcross = pElement; 709 while (walkAcross) { 710 if (walkAcross->start > walkAcross->end) 711 break; 712 713 Point2I globalStart = localToGlobalCoord(localStart); 714 715 U32 strWidth; 716 if (walkAcross->specialReference == -1) { 717 drawer->setBitmapModulation(lastColor); 718 drawer->setTextAnchorColor(mProfile->mFontColor); 719 strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], 720 walkAcross->end - walkAcross->start + 1, mProfile->mFontColors, mMaxColorIndex); 721 drawer->getBitmapModulation(&lastColor); // in case an embedded color tag changed it 722 } else { 723 drawer->getBitmapModulation( &lastColor ); 724 drawer->setBitmapModulation(mSpecialColor); 725 drawer->setTextAnchorColor(mProfile->mFontColor); 726 strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], 727 walkAcross->end - walkAcross->start + 1); 728 729 // in case we have 2 in a row... 730 drawer->setBitmapModulation(lastColor); 731 } 732 733 // drawTextN returns the rightmost X coord, so subtract leftmost coord to get the width 734 strWidth -= globalStart.x; 735 736 if (walkAcross->specialReference != -1) { 737 Point2I lineStart = localStart; 738 Point2I lineEnd = localStart; 739 lineStart.y += mProfile->mFont->getBaseline() + 1; 740 lineEnd.x += strWidth; 741 lineEnd.y += mProfile->mFont->getBaseline() + 1; 742 743 drawer->drawLine(localToGlobalCoord(lineStart), 744 localToGlobalCoord(lineEnd), 745 mSpecialColor); 746 } 747 748 localStart.x += strWidth; 749 walkAcross = walkAcross->nextInLine; 750 } 751 752 currLine++; 753 pElement = pElement->nextPhysicalLine; 754 } 755 } 756 drawer->clearBitmapModulation(); 757 } 758} 759 760 761//-------------------------------------------------------------------------- 762void GuiMessageVectorCtrl::inspectPostApply() 763{ 764 Parent::inspectPostApply(); 765} 766 767 768//-------------------------------------------------------------------------- 769void GuiMessageVectorCtrl::parentResized(const RectI& oldParentRect, const RectI& newParentRect) 770{ 771 Parent::parentResized(oldParentRect, newParentRect); 772 773 // If we have a MesssageVector, detach/reattach so we can reflow the text. 774 if (mMessageVector) 775 { 776 MessageVector *reflowme = mMessageVector; 777 778 detach(); 779 attach(reflowme); 780 } 781} 782 783//-------------------------------------------------------------------------- 784void GuiMessageVectorCtrl::findSpecialFromCoord(const Point2I& point, S32* specialLine, S32* specialRef) 785{ 786 if (mLineElements.size() == 0) { 787 *specialLine = -1; 788 *specialRef = -1; 789 return; 790 } 791 792 U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels; 793 794 if ((point.x < 0 || point.x >= getWidth()) || 795 (point.y < 0 || point.y >= getHeight())) { 796 *specialLine = -1; 797 *specialRef = -1; 798 return; 799 } 800 801 // Ok, we have real work to do here. Let's determine the physical line that it's on... 802 U32 physLine = point.y / linePixels; 803 AssertFatal(physLine >= 0, "Bad physical line!"); 804 805 // And now we find the LineElement that contains that physicalLine... 806 U32 elemIndex; 807 for (elemIndex = 0; elemIndex < mLineElements.size(); elemIndex++) { 808 if (mLineElements[elemIndex].physicalLineStart > physLine) { 809 // We've passed it. 810 AssertFatal(elemIndex != 0, "Error, bad elemIndex, check assumptions."); 811 elemIndex--; 812 break; 813 } 814 } 815 if (elemIndex == mLineElements.size()) { 816 // On the last line... 817 elemIndex = mLineElements.size() - 1; 818 } 819 820 TextElement* line = mLineElements[elemIndex].headLineElements; 821 for (U32 i = 0; i < physLine - mLineElements[elemIndex].physicalLineStart; i++) 822 { 823 if (line->nextPhysicalLine == NULL) 824 { 825 *specialLine = -1; 826 *specialRef = -1; 827 return; 828 } 829 line = line->nextPhysicalLine; 830 AssertFatal(line != NULL, "Error, moved too far!"); 831 } 832 833 // Ok, line represents the current line. We now need to find out which textelement 834 // the points x coord falls in. 835 U32 currX = 0; 836 if ((physLine - mLineElements[elemIndex].physicalLineStart) != 0) { 837 currX = mLineContinuationIndent; 838 // First, if this isn't the first line in this wrapping, we have to make sure 839 // that the coord isn't in the margin... 840 if (point.x < mLineContinuationIndent) { 841 *specialLine = -1; 842 *specialRef = -1; 843 return; 844 } 845 } 846 if (line->start > line->end) { 847 // Empty line special case... 848 *specialLine = -1; 849 *specialRef = -1; 850 return; 851 } 852 853 while (line) { 854 U32 newX = currX + mProfile->mFont->getStrNWidth((const UTF8 *)mMessageVector->getLine(elemIndex).message, 855 line->end - line->start + 1); 856 if (point.x < newX) { 857 // That's the one! 858 *specialLine = elemIndex; 859 *specialRef = line->specialReference; 860 return; 861 } 862 863 currX = newX; 864 line = line->nextInLine; 865 } 866 867 // Off to the right. Aw... 868 *specialLine = -1; 869 *specialRef = -1; 870} 871 872 873void GuiMessageVectorCtrl::onMouseDown(const GuiEvent& event) 874{ 875 Parent::onMouseDown(event); 876 mouseUnlock(); 877 878 mMouseDown = true; 879 880 // Find the special we are in, if any... 881 findSpecialFromCoord(globalToLocalCoord(event.mousePoint), 882 &mMouseSpecialLine, &mMouseSpecialRef); 883} 884 885void GuiMessageVectorCtrl::onMouseUp(const GuiEvent& event) 886{ 887 Parent::onMouseUp(event); 888 mouseUnlock(); 889 890 // Is this an up from a dragged click? 891 if (mMouseDown == false) 892 return; 893 894 // Find the special we are in, if any... 895 896 S32 currSpecialLine; 897 S32 currSpecialRef; 898 findSpecialFromCoord(globalToLocalCoord(event.mousePoint), &currSpecialLine, &currSpecialRef); 899 900 if (currSpecialRef != -1 && 901 (currSpecialLine == mMouseSpecialLine && 902 currSpecialRef == mMouseSpecialRef)) { 903 // Execute the callback 904 SpecialMarkers& rSpecial = mSpecialMarkers[currSpecialLine]; 905 S32 specialStart = rSpecial.specials[currSpecialRef].start; 906 S32 specialEnd = rSpecial.specials[currSpecialRef].end; 907 908 char* copyURL = new char[specialEnd - specialStart + 2]; 909 dStrncpy(copyURL, &mMessageVector->getLine(currSpecialLine).message[specialStart], specialEnd - specialStart + 1); 910 copyURL[specialEnd - specialStart + 1] = '\0'; 911 912 Con::executef(this, "urlClickCallback", copyURL); 913 delete [] copyURL; 914 } 915 916 mMouseDown = false; 917 mMouseSpecialLine = -1; 918 mMouseSpecialRef = -1; 919} 920 921