EditField.cpp (14080B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 29 #include "../idlib/precompiled.h" 30 #pragma hdrstop 31 32 static autoComplete_t globalAutoComplete; 33 34 /* 35 =============== 36 FindMatches 37 =============== 38 */ 39 static void FindMatches( const char *s ) { 40 int i; 41 42 if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { 43 return; 44 } 45 globalAutoComplete.matchCount++; 46 if ( globalAutoComplete.matchCount == 1 ) { 47 idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); 48 return; 49 } 50 51 // cut currentMatch to the amount common with s 52 for ( i = 0; s[i]; i++ ) { 53 if ( tolower( globalAutoComplete.currentMatch[i] ) != tolower( s[i] ) ) { 54 globalAutoComplete.currentMatch[i] = 0; 55 break; 56 } 57 } 58 globalAutoComplete.currentMatch[i] = 0; 59 } 60 61 /* 62 =============== 63 FindIndexMatch 64 =============== 65 */ 66 static void FindIndexMatch( const char *s ) { 67 68 if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { 69 return; 70 } 71 72 if( globalAutoComplete.findMatchIndex == globalAutoComplete.matchIndex ) { 73 idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); 74 } 75 76 globalAutoComplete.findMatchIndex++; 77 } 78 79 /* 80 =============== 81 PrintMatches 82 =============== 83 */ 84 static void PrintMatches( const char *s ) { 85 if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { 86 common->Printf( " %s\n", s ); 87 } 88 } 89 90 /* 91 =============== 92 PrintCvarMatches 93 =============== 94 */ 95 static void PrintCvarMatches( const char *s ) { 96 if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { 97 common->Printf( " %s" S_COLOR_WHITE " = \"%s\"\n", s, cvarSystem->GetCVarString( s ) ); 98 } 99 } 100 101 /* 102 =============== 103 idEditField::idEditField 104 =============== 105 */ 106 idEditField::idEditField() { 107 widthInChars = 0; 108 Clear(); 109 } 110 111 /* 112 =============== 113 idEditField::~idEditField 114 =============== 115 */ 116 idEditField::~idEditField() { 117 } 118 119 /* 120 =============== 121 idEditField::Clear 122 =============== 123 */ 124 void idEditField::Clear() { 125 buffer[0] = 0; 126 cursor = 0; 127 scroll = 0; 128 autoComplete.length = 0; 129 autoComplete.valid = false; 130 } 131 132 /* 133 =============== 134 idEditField::SetWidthInChars 135 =============== 136 */ 137 void idEditField::SetWidthInChars( int w ) { 138 assert( w <= MAX_EDIT_LINE ); 139 widthInChars = w; 140 } 141 142 /* 143 =============== 144 idEditField::SetCursor 145 =============== 146 */ 147 void idEditField::SetCursor( int c ) { 148 assert( c <= MAX_EDIT_LINE ); 149 cursor = c; 150 } 151 152 /* 153 =============== 154 idEditField::GetCursor 155 =============== 156 */ 157 int idEditField::GetCursor() const { 158 return cursor; 159 } 160 161 /* 162 =============== 163 idEditField::ClearAutoComplete 164 =============== 165 */ 166 void idEditField::ClearAutoComplete() { 167 if ( autoComplete.length > 0 && autoComplete.length <= (int) strlen( buffer ) ) { 168 buffer[autoComplete.length] = '\0'; 169 if ( cursor > autoComplete.length ) { 170 cursor = autoComplete.length; 171 } 172 } 173 autoComplete.length = 0; 174 autoComplete.valid = false; 175 } 176 177 /* 178 =============== 179 idEditField::GetAutoCompleteLength 180 =============== 181 */ 182 int idEditField::GetAutoCompleteLength() const { 183 return autoComplete.length; 184 } 185 186 /* 187 =============== 188 idEditField::AutoComplete 189 =============== 190 */ 191 void idEditField::AutoComplete() { 192 char completionArgString[MAX_EDIT_LINE]; 193 idCmdArgs args; 194 195 if ( !autoComplete.valid ) { 196 args.TokenizeString( buffer, false ); 197 idStr::Copynz( autoComplete.completionString, args.Argv( 0 ), sizeof( autoComplete.completionString ) ); 198 idStr::Copynz( completionArgString, args.Args(), sizeof( completionArgString ) ); 199 autoComplete.matchCount = 0; 200 autoComplete.matchIndex = 0; 201 autoComplete.currentMatch[0] = 0; 202 203 if ( strlen( autoComplete.completionString ) == 0 ) { 204 return; 205 } 206 207 globalAutoComplete = autoComplete; 208 209 cmdSystem->CommandCompletion( FindMatches ); 210 cvarSystem->CommandCompletion( FindMatches ); 211 212 autoComplete = globalAutoComplete; 213 214 if ( autoComplete.matchCount == 0 ) { 215 return; // no matches 216 } 217 218 // when there's only one match or there's an argument 219 if ( autoComplete.matchCount == 1 || completionArgString[0] != '\0' ) { 220 221 /// try completing arguments 222 idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), " " ); 223 idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), completionArgString ); 224 autoComplete.matchCount = 0; 225 226 globalAutoComplete = autoComplete; 227 228 cmdSystem->ArgCompletion( autoComplete.completionString, FindMatches ); 229 cvarSystem->ArgCompletion( autoComplete.completionString, FindMatches ); 230 231 autoComplete = globalAutoComplete; 232 233 idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); 234 235 if ( autoComplete.matchCount == 0 ) { 236 // no argument matches 237 idStr::Append( buffer, sizeof( buffer ), " " ); 238 idStr::Append( buffer, sizeof( buffer ), completionArgString ); 239 SetCursor( strlen( buffer ) ); 240 return; 241 } 242 } else { 243 244 // multiple matches, complete to shortest 245 idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); 246 if ( strlen( completionArgString ) ) { 247 idStr::Append( buffer, sizeof( buffer ), " " ); 248 idStr::Append( buffer, sizeof( buffer ), completionArgString ); 249 } 250 } 251 252 autoComplete.length = strlen( buffer ); 253 autoComplete.valid = ( autoComplete.matchCount != 1 ); 254 SetCursor( autoComplete.length ); 255 256 common->Printf( "]%s\n", buffer ); 257 258 // run through again, printing matches 259 globalAutoComplete = autoComplete; 260 261 cmdSystem->CommandCompletion( PrintMatches ); 262 cmdSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); 263 cvarSystem->CommandCompletion( PrintCvarMatches ); 264 cvarSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); 265 266 } else if ( autoComplete.matchCount != 1 ) { 267 268 // get the next match and show instead 269 autoComplete.matchIndex++; 270 if ( autoComplete.matchIndex == autoComplete.matchCount ) { 271 autoComplete.matchIndex = 0; 272 } 273 autoComplete.findMatchIndex = 0; 274 275 globalAutoComplete = autoComplete; 276 277 cmdSystem->CommandCompletion( FindIndexMatch ); 278 cmdSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); 279 cvarSystem->CommandCompletion( FindIndexMatch ); 280 cvarSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); 281 282 autoComplete = globalAutoComplete; 283 284 // and print it 285 idStr::snPrintf( buffer, sizeof( buffer ), autoComplete.currentMatch ); 286 if ( autoComplete.length > (int)strlen( buffer ) ) { 287 autoComplete.length = strlen( buffer ); 288 } 289 SetCursor( autoComplete.length ); 290 } 291 } 292 293 /* 294 =============== 295 idEditField::CharEvent 296 =============== 297 */ 298 void idEditField::CharEvent( int ch ) { 299 int len; 300 301 if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste 302 Paste(); 303 return; 304 } 305 306 if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field 307 Clear(); 308 return; 309 } 310 311 len = strlen( buffer ); 312 313 if ( ch == 'h' - 'a' + 1 || ch == K_BACKSPACE ) { // ctrl-h is backspace 314 if ( cursor > 0 ) { 315 memmove( buffer + cursor - 1, buffer + cursor, len + 1 - cursor ); 316 cursor--; 317 if ( cursor < scroll ) { 318 scroll--; 319 } 320 } 321 return; 322 } 323 324 if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home 325 cursor = 0; 326 scroll = 0; 327 return; 328 } 329 330 if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end 331 cursor = len; 332 scroll = cursor - widthInChars; 333 return; 334 } 335 336 // 337 // ignore any other non printable chars 338 // 339 if ( ch < 32 ) { 340 return; 341 } 342 343 if ( idKeyInput::GetOverstrikeMode() ) { 344 if ( cursor == MAX_EDIT_LINE - 1 ) { 345 return; 346 } 347 buffer[cursor] = ch; 348 cursor++; 349 } else { // insert mode 350 if ( len == MAX_EDIT_LINE - 1 ) { 351 return; // all full 352 } 353 memmove( buffer + cursor + 1, buffer + cursor, len + 1 - cursor ); 354 buffer[cursor] = ch; 355 cursor++; 356 } 357 358 359 if ( cursor >= widthInChars ) { 360 scroll++; 361 } 362 363 if ( cursor == len + 1 ) { 364 buffer[cursor] = 0; 365 } 366 } 367 368 /* 369 =============== 370 idEditField::KeyDownEvent 371 =============== 372 */ 373 void idEditField::KeyDownEvent( int key ) { 374 int len; 375 376 // shift-insert is paste 377 if ( ( ( key == K_INS ) || ( key == K_KP_0 ) ) && ( idKeyInput::IsDown( K_LSHIFT ) || idKeyInput::IsDown( K_RSHIFT ) ) ) { 378 ClearAutoComplete(); 379 Paste(); 380 return; 381 } 382 383 len = strlen( buffer ); 384 385 if ( key == K_DEL ) { 386 if ( autoComplete.length ) { 387 ClearAutoComplete(); 388 } else if ( cursor < len ) { 389 memmove( buffer + cursor, buffer + cursor + 1, len - cursor ); 390 } 391 return; 392 } 393 394 if ( key == K_RIGHTARROW ) { 395 if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { 396 // skip to next word 397 while( ( cursor < len ) && ( buffer[ cursor ] != ' ' ) ) { 398 cursor++; 399 } 400 401 while( ( cursor < len ) && ( buffer[ cursor ] == ' ' ) ) { 402 cursor++; 403 } 404 } else { 405 cursor++; 406 } 407 408 if ( cursor > len ) { 409 cursor = len; 410 } 411 412 if ( cursor >= scroll + widthInChars ) { 413 scroll = cursor - widthInChars + 1; 414 } 415 416 if ( autoComplete.length > 0 ) { 417 autoComplete.length = cursor; 418 } 419 return; 420 } 421 422 if ( key == K_LEFTARROW ) { 423 if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { 424 // skip to previous word 425 while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] == ' ' ) ) { 426 cursor--; 427 } 428 429 while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] != ' ' ) ) { 430 cursor--; 431 } 432 } else { 433 cursor--; 434 } 435 436 if ( cursor < 0 ) { 437 cursor = 0; 438 } 439 if ( cursor < scroll ) { 440 scroll = cursor; 441 } 442 443 if ( autoComplete.length ) { 444 autoComplete.length = cursor; 445 } 446 return; 447 } 448 449 if ( key == K_HOME || ( key == K_A && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { 450 cursor = 0; 451 scroll = 0; 452 if ( autoComplete.length ) { 453 autoComplete.length = cursor; 454 autoComplete.valid = false; 455 } 456 return; 457 } 458 459 if ( key == K_END || ( key == K_E && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { 460 cursor = len; 461 if ( cursor >= scroll + widthInChars ) { 462 scroll = cursor - widthInChars + 1; 463 } 464 if ( autoComplete.length ) { 465 autoComplete.length = cursor; 466 autoComplete.valid = false; 467 } 468 return; 469 } 470 471 if ( key == K_INS ) { 472 idKeyInput::SetOverstrikeMode( !idKeyInput::GetOverstrikeMode() ); 473 return; 474 } 475 476 // clear autocompletion buffer on normal key input 477 if ( key != K_CAPSLOCK && key != K_LALT && key != K_LCTRL && key != K_LSHIFT && key != K_RALT && key != K_RCTRL && key != K_RSHIFT ) { 478 ClearAutoComplete(); 479 } 480 } 481 482 /* 483 =============== 484 idEditField::Paste 485 =============== 486 */ 487 void idEditField::Paste() { 488 char *cbd; 489 int pasteLen, i; 490 491 cbd = Sys_GetClipboardData(); 492 493 if ( !cbd ) { 494 return; 495 } 496 497 // send as if typed, so insert / overstrike works properly 498 pasteLen = strlen( cbd ); 499 for ( i = 0; i < pasteLen; i++ ) { 500 CharEvent( cbd[i] ); 501 } 502 503 Mem_Free( cbd ); 504 } 505 506 /* 507 =============== 508 idEditField::GetBuffer 509 =============== 510 */ 511 char *idEditField::GetBuffer() { 512 return buffer; 513 } 514 515 /* 516 =============== 517 idEditField::SetBuffer 518 =============== 519 */ 520 void idEditField::SetBuffer( const char *buf ) { 521 Clear(); 522 idStr::Copynz( buffer, buf, sizeof( buffer ) ); 523 SetCursor( strlen( buffer ) ); 524 } 525 526 /* 527 =============== 528 idEditField::Draw 529 =============== 530 */ 531 void idEditField::Draw( int x, int y, int width, bool showCursor ) { 532 int len; 533 int drawLen; 534 int prestep; 535 int cursorChar; 536 char str[MAX_EDIT_LINE]; 537 int size; 538 539 size = SMALLCHAR_WIDTH; 540 541 drawLen = widthInChars; 542 len = strlen( buffer ) + 1; 543 544 // guarantee that cursor will be visible 545 if ( len <= drawLen ) { 546 prestep = 0; 547 } else { 548 if ( scroll + drawLen > len ) { 549 scroll = len - drawLen; 550 if ( scroll < 0 ) { 551 scroll = 0; 552 } 553 } 554 prestep = scroll; 555 556 // Skip color code 557 if ( idStr::IsColor( buffer + prestep ) ) { 558 prestep += 2; 559 } 560 if ( prestep > 0 && idStr::IsColor( buffer + prestep - 1 ) ) { 561 prestep++; 562 } 563 } 564 565 if ( prestep + drawLen > len ) { 566 drawLen = len - prestep; 567 } 568 569 // extract <drawLen> characters from the field at <prestep> 570 if ( drawLen >= MAX_EDIT_LINE ) { 571 common->Error( "drawLen >= MAX_EDIT_LINE" ); 572 } 573 574 memcpy( str, buffer + prestep, drawLen ); 575 str[ drawLen ] = 0; 576 577 // draw it 578 renderSystem->DrawSmallStringExt( x, y, str, colorWhite, false ); 579 580 // draw the cursor 581 if ( !showCursor ) { 582 return; 583 } 584 585 if ( (int)( idLib::frameNumber >> 4 ) & 1 ) { 586 return; // off blink 587 } 588 589 if ( idKeyInput::GetOverstrikeMode() ) { 590 cursorChar = 11; 591 } else { 592 cursorChar = 10; 593 } 594 595 // Move the cursor back to account for color codes 596 for ( int i = 0; i<cursor; i++ ) { 597 if ( idStr::IsColor( &str[i] ) ) { 598 i++; 599 prestep += 2; 600 } 601 } 602 603 renderSystem->DrawSmallChar( x + ( cursor - prestep ) * size, y, cursorChar ); 604 }