guiMLTextEditCtrl.cpp
Engine/source/gui/controls/guiMLTextEditCtrl.cpp
Public Functions
ConsoleDocClass(GuiMLTextEditCtrl , "@brief A text entry <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that accepts the Gui Markup Language ('ML') tags and multiple <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">lines.\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classguimltexteditctrl/">GuiMLTextEditCtrl</a>()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " {\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lineSpacing = \"2\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " allowColorChars = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " maxChars = \"-1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " deniedSound = \"DeniedSoundProfile\";\n" " text = \"\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " escapeCommand = \"onEscapeScriptFunction();\";\n" " //Properties not specific <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> have been omitted from this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">example.\n</a>" " };\<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/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiMLTextCtrl\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiControl\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiControls\n</a>" )
Detailed Description
Public Functions
ConsoleDocClass(GuiMLTextEditCtrl , "@brief A text entry <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> that accepts the Gui Markup Language ('ML') tags and multiple <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">lines.\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classguimltexteditctrl/">GuiMLTextEditCtrl</a>()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " {\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lineSpacing = \"2\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " allowColorChars = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " maxChars = \"-1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " deniedSound = \"DeniedSoundProfile\";\n" " text = \"\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " escapeCommand = \"onEscapeScriptFunction();\";\n" " //Properties not specific <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> have been omitted from this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">example.\n</a>" " };\<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/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiMLTextCtrl\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiControl\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GuiControls\n</a>" )
IMPLEMENT_CONOBJECT(GuiMLTextEditCtrl )
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 "gui/controls/guiMLTextEditCtrl.h" 25#include "gui/containers/guiScrollCtrl.h" 26#include "gui/core/guiCanvas.h" 27#include "console/consoleTypes.h" 28#include "core/frameAllocator.h" 29#include "core/stringBuffer.h" 30#include "gfx/gfxDrawUtil.h" 31#include "console/engineAPI.h" 32 33IMPLEMENT_CONOBJECT(GuiMLTextEditCtrl); 34 35ConsoleDocClass( GuiMLTextEditCtrl, 36 "@brief A text entry control that accepts the Gui Markup Language ('ML') tags and multiple lines.\n\n" 37 38 "@tsexample\n" 39 "new GuiMLTextEditCtrl()\n" 40 " {\n" 41 " lineSpacing = \"2\";\n" 42 " allowColorChars = \"0\";\n" 43 " maxChars = \"-1\";\n" 44 " deniedSound = \"DeniedSoundProfile\";\n" 45 " text = \"\";\n" 46 " escapeCommand = \"onEscapeScriptFunction();\";\n" 47 " //Properties not specific to this control have been omitted from this example.\n" 48 " };\n" 49 "@endtsexample\n\n" 50 51 "@see GuiMLTextCtrl\n" 52 "@see GuiControl\n\n" 53 54 "@ingroup GuiControls\n" 55); 56 57//-------------------------------------------------------------------------- 58GuiMLTextEditCtrl::GuiMLTextEditCtrl() 59{ 60 mEscapeCommand = StringTable->insert( "" ); 61 62 mIsEditCtrl = true; 63 64 mActive = true; 65 66 mVertMoveAnchorValid = false; 67} 68 69 70//-------------------------------------------------------------------------- 71GuiMLTextEditCtrl::~GuiMLTextEditCtrl() 72{ 73 74} 75 76 77//-------------------------------------------------------------------------- 78bool GuiMLTextEditCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) 79{ 80 // We don't want to get any smaller than our parent: 81 Point2I newExt = newExtent; 82 GuiControl* parent = getParent(); 83 if ( parent ) 84 newExt.y = getMax( parent->getHeight(), newExt.y ); 85 86 return Parent::resize( newPosition, newExt ); 87} 88 89 90//-------------------------------------------------------------------------- 91void GuiMLTextEditCtrl::initPersistFields() 92{ 93 addField( "escapeCommand", TypeString, Offset( mEscapeCommand, GuiMLTextEditCtrl ), "Script function to run whenever the 'escape' key is pressed when this control is in focus.\n"); 94 Parent::initPersistFields(); 95} 96 97//-------------------------------------------------------------------------- 98void GuiMLTextEditCtrl::setFirstResponder() 99{ 100 Parent::setFirstResponder(); 101 102 GuiCanvas *root = getRoot(); 103 if (root != NULL) 104 { 105 root->enableKeyboardTranslation(); 106 107 // If the native OS accelerator keys are not disabled 108 // then some key events like Delete, ctrl+V, etc may 109 // not make it down to us. 110 root->setNativeAcceleratorsEnabled( false ); 111 } 112} 113 114void GuiMLTextEditCtrl::onLoseFirstResponder() 115{ 116 GuiCanvas *root = getRoot(); 117 if (root != NULL) 118 { 119 root->setNativeAcceleratorsEnabled( true ); 120 root->disableKeyboardTranslation(); 121 } 122 123 // Redraw the control: 124 setUpdate(); 125} 126 127//-------------------------------------------------------------------------- 128bool GuiMLTextEditCtrl::onWake() 129{ 130 if( !Parent::onWake() ) 131 return false; 132 133 getRoot()->enableKeyboardTranslation(); 134 return true; 135} 136 137//-------------------------------------------------------------------------- 138// Key events... 139bool GuiMLTextEditCtrl::onKeyDown(const GuiEvent& event) 140{ 141 if ( !isActive() ) 142 return false; 143 144 setUpdate(); 145 //handle modifiers first... 146 if (event.modifier & SI_PRIMARY_CTRL) 147 { 148 switch(event.keyCode) 149 { 150 //copy/cut 151 case KEY_C: 152 case KEY_X: 153 { 154 //make sure we actually have something selected 155 if (mSelectionActive) 156 { 157 copyToClipboard(mSelectionStart, mSelectionEnd); 158 159 //if we're cutting, also delete the selection 160 if (event.keyCode == KEY_X) 161 { 162 mSelectionActive = false; 163 deleteChars(mSelectionStart, mSelectionEnd); 164 mCursorPosition = mSelectionStart; 165 } 166 else 167 mCursorPosition = mSelectionEnd + 1; 168 } 169 return true; 170 } 171 172 //paste 173 case KEY_V: 174 { 175 const char *clipBuf = Platform::getClipboard(); 176 if (dStrlen(clipBuf) > 0) 177 { 178 // Normal ascii keypress. Go ahead and add the chars... 179 if (mSelectionActive == true) 180 { 181 mSelectionActive = false; 182 deleteChars(mSelectionStart, mSelectionEnd); 183 mCursorPosition = mSelectionStart; 184 } 185 186 insertChars(clipBuf, dStrlen(clipBuf), mCursorPosition); 187 } 188 return true; 189 } 190 191 default: 192 break; 193 } 194 } 195 else if ( event.modifier & SI_SHIFT ) 196 { 197 switch ( event.keyCode ) 198 { 199 case KEY_TAB: 200 return( Parent::onKeyDown( event ) ); 201 default: 202 break; 203 } 204 } 205 else if ( event.modifier == 0 ) 206 { 207 switch (event.keyCode) 208 { 209 // Escape: 210 case KEY_ESCAPE: 211 if ( mEscapeCommand[0] ) 212 { 213 Con::evaluate( mEscapeCommand ); 214 return( true ); 215 } 216 return( Parent::onKeyDown( event ) ); 217 218 // Deletion 219 case KEY_BACKSPACE: 220 case KEY_DELETE: 221 handleDeleteKeys(event); 222 return true; 223 224 // Cursor movement 225 case KEY_LEFT: 226 case KEY_RIGHT: 227 case KEY_UP: 228 case KEY_DOWN: 229 case KEY_HOME: 230 case KEY_END: 231 handleMoveKeys(event); 232 return true; 233 234 // Special chars... 235 case KEY_TAB: 236 // insert 3 spaces 237 if (mSelectionActive == true) 238 { 239 mSelectionActive = false; 240 deleteChars(mSelectionStart, mSelectionEnd); 241 mCursorPosition = mSelectionStart; 242 } 243 insertChars( "\t", 1, mCursorPosition ); 244 return true; 245 246 case KEY_RETURN: 247 // insert carriage return 248 if (mSelectionActive == true) 249 { 250 mSelectionActive = false; 251 deleteChars(mSelectionStart, mSelectionEnd); 252 mCursorPosition = mSelectionStart; 253 } 254 insertChars( "\n", 1, mCursorPosition ); 255 return true; 256 257 default: 258 break; 259 } 260 } 261 262 if ( (mFont && mFont->isValidChar(event.ascii)) || (!mFont && event.ascii != 0) ) 263 { 264 // Normal ascii keypress. Go ahead and add the chars... 265 if (mSelectionActive == true) 266 { 267 mSelectionActive = false; 268 deleteChars(mSelectionStart, mSelectionEnd); 269 mCursorPosition = mSelectionStart; 270 } 271 272 UTF8 *outString = NULL; 273 U32 outStringLen = 0; 274 275#ifdef TORQUE_UNICODE 276 277 UTF16 inData[2] = { event.ascii, 0 }; 278 StringBuffer inBuff(inData); 279 280 FrameTemp<UTF8> outBuff(4); 281 inBuff.getCopy8(outBuff, 4); 282 283 outString = outBuff; 284 outStringLen = dStrlen(outBuff); 285#else 286 char ascii = char(event.ascii); 287 outString = &ascii; 288 outStringLen = 1; 289#endif 290 291 insertChars(outString, outStringLen, mCursorPosition); 292 mVertMoveAnchorValid = false; 293 return true; 294 } 295 296 // Otherwise, let the parent have the event... 297 return Parent::onKeyDown(event); 298} 299 300 301//-------------------------------------- 302void GuiMLTextEditCtrl::handleDeleteKeys(const GuiEvent& event) 303{ 304 if ( isSelectionActive() ) 305 { 306 mSelectionActive = false; 307 deleteChars(mSelectionStart, mSelectionEnd+1); 308 mCursorPosition = mSelectionStart; 309 } 310 else 311 { 312 switch ( event.keyCode ) 313 { 314 case KEY_BACKSPACE: 315 if (mCursorPosition != 0) 316 { 317 // delete one character left 318 deleteChars(mCursorPosition-1, mCursorPosition); 319 setUpdate(); 320 } 321 break; 322 323 case KEY_DELETE: 324 if (mCursorPosition != mTextBuffer.length()) 325 { 326 // delete one character right 327 deleteChars(mCursorPosition, mCursorPosition+1); 328 setUpdate(); 329 } 330 break; 331 332 default: 333 AssertFatal(false, "Unknown key code received!"); 334 } 335 } 336} 337 338 339//-------------------------------------- 340void GuiMLTextEditCtrl::handleMoveKeys(const GuiEvent& event) 341{ 342 if ( event.modifier & SI_SHIFT ) 343 return; 344 345 mSelectionActive = false; 346 347 switch ( event.keyCode ) 348 { 349 case KEY_LEFT: 350 mVertMoveAnchorValid = false; 351 // move one left 352 if ( mCursorPosition != 0 ) 353 { 354 mCursorPosition--; 355 setUpdate(); 356 } 357 break; 358 359 case KEY_RIGHT: 360 mVertMoveAnchorValid = false; 361 // move one right 362 if ( mCursorPosition != mTextBuffer.length() ) 363 { 364 mCursorPosition++; 365 setUpdate(); 366 } 367 break; 368 369 case KEY_UP: 370 case KEY_DOWN: 371 { 372 Line* walk; 373 for ( walk = mLineList; walk->next; walk = walk->next ) 374 { 375 if ( mCursorPosition <= ( walk->textStart + walk->len ) ) 376 break; 377 } 378 379 if ( !walk ) 380 return; 381 382 if ( event.keyCode == KEY_UP ) 383 { 384 if ( walk == mLineList ) 385 return; 386 } 387 else if ( walk->next == NULL ) 388 return; 389 390 Point2I newPos; 391 newPos.set( 0, walk->y ); 392 393 // Find the x-position: 394 if ( !mVertMoveAnchorValid ) 395 { 396 Point2I cursorTopP, cursorBottomP; 397 ColorI color; 398 getCursorPositionAndColor(cursorTopP, cursorBottomP, color); 399 mVertMoveAnchor = cursorTopP.x; 400 mVertMoveAnchorValid = true; 401 } 402 403 newPos.x = mVertMoveAnchor; 404 405 // Set the new y-position: 406 if (event.keyCode == KEY_UP) 407 newPos.y--; 408 else 409 newPos.y += (walk->height + 1); 410 411 if (setCursorPosition(getTextPosition(newPos))) 412 mVertMoveAnchorValid = false; 413 break; 414 } 415 416 case KEY_HOME: 417 case KEY_END: 418 { 419 mVertMoveAnchorValid = false; 420 Line* walk; 421 for (walk = mLineList; walk->next; walk = walk->next) 422 { 423 if (mCursorPosition <= (walk->textStart + walk->len)) 424 break; 425 } 426 427 if (walk) 428 { 429 if (event.keyCode == KEY_HOME) 430 { 431 //place the cursor at the beginning of the first atom if there is one 432 if (walk->atomList) 433 mCursorPosition = walk->atomList->textStart; 434 else 435 mCursorPosition = walk->textStart; 436 } 437 else 438 { 439 mCursorPosition = walk->textStart; 440 mCursorPosition += walk->len; 441 } 442 setUpdate(); 443 } 444 break; 445 } 446 447 default: 448 AssertFatal(false, "Unknown move key code was received!"); 449 } 450 451 ensureCursorOnScreen(); 452} 453 454//-------------------------------------------------------------------------- 455void GuiMLTextEditCtrl::onRender(Point2I offset, const RectI& updateRect) 456{ 457 Parent::onRender(offset, updateRect); 458 459 // We are the first responder, draw our cursor in the appropriate position... 460 if (isFirstResponder()) 461 { 462 Point2I top, bottom; 463 ColorI color; 464 getCursorPositionAndColor(top, bottom, color); 465 GFX->getDrawUtil()->drawLine(top + offset, bottom + offset, mProfile->mCursorColor); 466 } 467} 468 469