ft2_textboxes.c (25651B)
1 // for finding memory leaks in debug mode with Visual Studio 2 #if defined _DEBUG && defined _MSC_VER 3 #include <crtdbg.h> 4 #endif 5 6 #include <stdint.h> 7 #include <stdbool.h> 8 #include "ft2_header.h" 9 #include "ft2_gui.h" 10 #include "ft2_video.h" 11 #include "ft2_config.h" 12 #include "ft2_diskop.h" 13 #include "ft2_keyboard.h" 14 #include "ft2_mouse.h" 15 #include "ft2_bmp.h" 16 #include "ft2_structs.h" 17 18 textBox_t textBoxes[NUM_TEXTBOXES] = 19 { 20 // ------ RESERVED TEXTBOXES ------ 21 { 0 }, 22 23 /* 24 ** -- STRUCT INFO: -- 25 ** x = x position 26 ** y = y position 27 ** w = width 28 ** h = height 29 ** tx = left padding for text 30 ** ty = top padding for text 31 ** maxc = max characters in box string 32 ** rmb = can only be triggered with right mouse button (f.ex. tracker instrument texts) 33 ** cmc = change mouse cursor when hovering over it 34 */ 35 36 // ------ INSTRUMENT/SAMPLE/SONG NAME TEXTBOXES ------ 37 // x, y, w, h, tx,ty, maxc, rmb, cmc 38 { 446, 5, 140, 10, 1, 0, 22, true, false }, 39 { 446, 16, 140, 10, 1, 0, 22, true, false }, 40 { 446, 27, 140, 10, 1, 0, 22, true, false }, 41 { 446, 38, 140, 10, 1, 0, 22, true, false }, 42 { 446, 49, 140, 10, 1, 0, 22, true, false }, 43 { 446, 60, 140, 10, 1, 0, 22, true, false }, 44 { 446, 71, 140, 10, 1, 0, 22, true, false }, 45 { 446, 82, 140, 10, 1, 0, 22, true, false }, 46 { 446, 99, 116, 10, 1, 0, 22, true, false }, 47 { 446, 110, 116, 10, 1, 0, 22, true, false }, 48 { 446, 121, 116, 10, 1, 0, 22, true, false }, 49 { 446, 132, 116, 10, 1, 0, 22, true, false }, 50 { 446, 143, 116, 10, 1, 0, 22, true, false }, 51 { 424, 158, 160, 12, 2, 1, 20, false, true }, 52 53 // ------ DISK OP. TEXTBOXES ------ 54 // x, y, w, h, tx,ty, maxc, rmb, cmc 55 { 31, 158, 134, 12, 2, 1, PATH_MAX, false, true }, 56 57 // ------ CONFIG TEXTBOXES ------ 58 // x, y, w, h, tx,ty, maxc, rmb, cmc 59 { 486, 16, 143, 12, 2, 1, 80, false, true }, 60 { 486, 31, 143, 12, 2, 1, 80, false, true }, 61 { 486, 46, 143, 12, 2, 1, 80, false, true }, 62 { 486, 61, 143, 12, 2, 1, 80, false, true }, 63 { 486, 76, 143, 12, 2, 1, 80, false, true } 64 }; 65 66 static int16_t markX1, markX2; 67 static uint16_t oldCursorPos; 68 static int32_t oldMouseX; 69 70 static void moveTextCursorLeft(int16_t i, bool updateTextBox); 71 static void moveTextCursorRight(int16_t i, bool updateTextBox); 72 73 static void setSongModifiedFlagIfNeeded(void) // called during keystrokes in text boxes 74 { 75 if (mouse.lastEditBox == TB_SONG_NAME || 76 (mouse.lastEditBox >= TB_INST1 && mouse.lastEditBox <= TB_INST8) || 77 (mouse.lastEditBox >= TB_SAMP1 && mouse.lastEditBox <= TB_SAMP5)) 78 { 79 setSongModifiedFlag(); 80 } 81 } 82 83 bool textIsMarked(void) 84 { 85 if (markX1 == markX2) 86 return false; 87 88 return true; 89 } 90 91 static void removeTextMarking(void) 92 { 93 markX1 = 0; 94 markX2 = 0; 95 } 96 97 static int16_t getTextMarkStart(void) 98 { 99 if (markX2 < markX1) 100 return markX2; 101 102 return markX1; 103 } 104 105 static int16_t getTextMarkEnd(void) 106 { 107 if (markX1 > markX2) 108 return markX1; 109 110 return markX2; 111 } 112 113 static int16_t getTextLength(textBox_t *t, uint16_t offset) 114 { 115 uint16_t i; 116 117 if (t->textPtr == NULL || offset >= t->maxChars) 118 return 0; 119 120 // count number of characters in text 121 for (i = offset; i < t->maxChars; i++) 122 { 123 if (t->textPtr[i] == '\0') 124 break; 125 } 126 127 i -= offset; // i now contains string length 128 assert(i <= t->maxChars); 129 130 return i; 131 } 132 133 static void deleteMarkedText(textBox_t *t) 134 { 135 if (!textIsMarked()) 136 return; 137 138 const int16_t start = getTextMarkStart(); 139 const int16_t end = getTextMarkEnd(); 140 141 assert(start < t->maxChars && end <= t->maxChars); 142 143 // calculate pixel width of string to delete 144 int32_t deleteTextWidth = 0; 145 for (int32_t i = start; i < end; i++) 146 deleteTextWidth += charWidth(t->textPtr[i]); 147 148 // copy markEnd part to markStart, and add NUL-termination 149 const int32_t length = (int32_t)strlen(&t->textPtr[end]); 150 if (length > 0) 151 memcpy(&t->textPtr[start], &t->textPtr[end], length); 152 t->textPtr[start+length] = '\0'; 153 154 // scroll buffer offset to the left if we are scrolled 155 if (t->bufOffset >= deleteTextWidth) 156 t->bufOffset -= deleteTextWidth; 157 else 158 t->bufOffset = 0; 159 160 // set text cursor to markStart 161 t->cursorPos = start; 162 163 setSongModifiedFlagIfNeeded(); 164 } 165 166 static void setCursorToMarkStart(textBox_t *t) 167 { 168 if (!textIsMarked()) 169 return; 170 171 const int16_t start = getTextMarkStart(); 172 assert(start < t->maxChars); 173 t->cursorPos = start; 174 175 int32_t startXPos = 0; 176 for (int32_t i = 0; i < start; i++) 177 { 178 const char ch = t->textPtr[i]; 179 if (ch == '\0') 180 break; 181 182 startXPos += charWidth(ch); 183 } 184 185 // change buffer offset, if needed 186 if (startXPos < t->bufOffset) 187 t->bufOffset = startXPos; 188 } 189 190 static void setCursorToMarkEnd(textBox_t *t) 191 { 192 if (!textIsMarked()) 193 return; 194 195 const int16_t end = getTextMarkEnd(); 196 assert(end <= t->maxChars); 197 t->cursorPos = end; 198 199 int32_t endXPos = 0; 200 for (int32_t i = 0; i < end; i++) 201 { 202 const char ch = t->textPtr[i]; 203 if (ch == '\0') 204 break; 205 206 endXPos += charWidth(ch); 207 } 208 209 // change buffer offset, if needed 210 if (endXPos > t->bufOffset+t->renderW) 211 t->bufOffset = endXPos - t->renderW; 212 } 213 214 static void copyMarkedText(textBox_t *t) 215 { 216 if (!textIsMarked()) 217 return; 218 219 const int32_t start = getTextMarkStart(); 220 const int32_t end = getTextMarkEnd(); 221 222 assert(start < t->maxChars && end <= t->maxChars); 223 224 const int32_t length = end - start; 225 if (length < 1) 226 return; 227 228 /* Change mark-end character to NUL so that we 229 ** we only copy the marked section of the string. 230 ** There's always room for a NUL at the end of 231 ** the text box string, so this is safe. 232 **/ 233 const char oldChar = t->textPtr[end]; 234 t->textPtr[end] = '\0'; 235 236 char *utf8Text = cp850ToUtf8(&t->textPtr[start]); 237 if (utf8Text != NULL) 238 { 239 SDL_SetClipboardText(utf8Text); 240 free(utf8Text); 241 } 242 243 t->textPtr[end] = oldChar; // set back original character 244 } 245 246 static void cutMarkedText(textBox_t *t) 247 { 248 if (!textIsMarked()) 249 return; 250 251 copyMarkedText(t); 252 deleteMarkedText(t); 253 removeTextMarking(); 254 255 drawTextBox(mouse.lastEditBox); 256 } 257 258 static void pasteText(textBox_t *t) 259 { 260 char *endPart; 261 262 if (!SDL_HasClipboardText()) 263 return; 264 265 // if we've marked text, delete it and remove text marking 266 if (textIsMarked()) 267 { 268 deleteMarkedText(t); 269 removeTextMarking(); 270 } 271 272 if (t->cursorPos >= t->maxChars) 273 return; 274 275 const int32_t textLength = getTextLength(t, 0); 276 277 const int32_t roomLeft = t->maxChars - textLength; 278 if (roomLeft <= 0) 279 return; // no more room! 280 281 char *copiedTextUtf8 = SDL_GetClipboardText(); 282 283 char *copiedText = utf8ToCp850(copiedTextUtf8, true); 284 if (copiedText == NULL) 285 return; 286 287 int32_t copiedTextLength = (int32_t)strlen(copiedText); 288 if (copiedTextLength > roomLeft) 289 copiedTextLength = roomLeft; 290 291 const uint16_t endOffset = t->cursorPos; 292 endPart = NULL; // prevent false compiler warning 293 294 const int32_t endPartLength = getTextLength(t, endOffset); 295 if (endPartLength > 0) 296 { 297 endPart = (char *)malloc(endPartLength+1); 298 if (endPart == NULL) 299 { 300 free(copiedText); 301 okBox(0, "System message", "Not enough memory!", NULL); 302 return; 303 } 304 } 305 306 // make a copy of end data 307 if (endPartLength > 0) 308 { 309 memcpy(endPart, &t->textPtr[endOffset], endPartLength); 310 endPart[endPartLength] = '\0'; 311 } 312 313 // paste copied data 314 memcpy(&t->textPtr[endOffset], copiedText, copiedTextLength); 315 t->textPtr[endOffset+copiedTextLength] = '\0'; 316 free(copiedText); 317 318 // append end data 319 if (endPartLength > 0) 320 { 321 strcat(&t->textPtr[endOffset+copiedTextLength], endPart); 322 free(endPart); 323 } 324 325 for (int32_t i = 0; i < copiedTextLength; i++) 326 moveTextCursorRight(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 327 328 drawTextBox(mouse.lastEditBox); 329 330 setSongModifiedFlagIfNeeded(); 331 } 332 333 void exitTextEditing(void) 334 { 335 if (!editor.editTextFlag) 336 return; 337 338 if (mouse.lastEditBox >= 0 && mouse.lastEditBox < NUM_TEXTBOXES) 339 { 340 textBoxes[mouse.lastEditBox].bufOffset = 0; 341 removeTextMarking(); 342 drawTextBox(mouse.lastEditBox); 343 } 344 345 if (mouse.lastEditBox == TB_DISKOP_FILENAME && getDiskOpItem() == DISKOP_ITEM_MODULE) 346 { 347 updateCurrSongFilename(); // for window title 348 updateWindowTitle(true); 349 } 350 351 keyb.ignoreCurrKeyUp = true; // prevent a note being played (on enter key) 352 editor.editTextFlag = false; 353 354 hideSprite(SPRITE_TEXT_CURSOR); 355 SDL_StopTextInput(); 356 } 357 358 static int16_t cursorPosToX(textBox_t *t) 359 { 360 assert(t->textPtr != NULL); 361 362 int32_t x = -1; // cursor starts one pixel before character 363 for (int16_t i = 0; i < t->cursorPos; i++) 364 x += charWidth(t->textPtr[i]); 365 366 x -= t->bufOffset; // subtract by buffer offset to get real X position 367 return (int16_t)x; 368 } 369 370 int16_t getTextCursorX(textBox_t *t) 371 { 372 return t->x + t->tx + cursorPosToX(t); 373 } 374 375 int16_t getTextCursorY(textBox_t *t) 376 { 377 return t->y + t->ty; 378 } 379 380 static void scrollTextBufferLeft(textBox_t *t) 381 { 382 // scroll buffer and clamp 383 t->bufOffset -= TEXT_SCROLL_VALUE; 384 if (t->bufOffset < 0) 385 t->bufOffset = 0; 386 } 387 388 static void scrollTextBufferRight(textBox_t *t, uint16_t numCharsInText) 389 { 390 assert(numCharsInText <= t->maxChars); 391 392 // get end of text position 393 int32_t textEnd = 0; 394 for (uint16_t j = 0; j < numCharsInText; j++) 395 textEnd += charWidth(t->textPtr[j]); 396 397 // subtract by text box width and clamp to 0 398 textEnd -= t->renderW; 399 if (textEnd < 0) 400 textEnd = 0; 401 402 // scroll buffer and clamp 403 t->bufOffset += TEXT_SCROLL_VALUE; 404 if (t->bufOffset > textEnd) 405 t->bufOffset = textEnd; 406 } 407 408 static void moveTextCursorToMouseX(uint16_t textBoxID) 409 { 410 int16_t i; 411 412 textBox_t *t = &textBoxes[textBoxID]; 413 if ((mouse.x == t->x && t->bufOffset == 0) || t->textPtr == NULL || t->textPtr[0] == '\0') 414 { 415 t->cursorPos = 0; 416 return; 417 } 418 419 int16_t numChars = getTextLength(t, 0); 420 421 // find out what character we are clicking at, and set cursor to that character 422 const int32_t mx = t->bufOffset + mouse.x; 423 int32_t tx = (t->x + t->tx) - 1; 424 int32_t cw = -1; 425 426 for (i = 0; i < numChars; i++) 427 { 428 cw = charWidth(t->textPtr[i]); 429 const int32_t tx2 = tx + cw; 430 431 if (mx >= tx && mx < tx2) 432 { 433 t->cursorPos = i; 434 break; 435 } 436 437 tx += cw; 438 } 439 440 // set to last character if we clicked outside the end of the text 441 if (i == numChars && mx >= tx) 442 t->cursorPos = numChars; 443 444 if (cw != -1) 445 { 446 const int16_t cursorPos = cursorPosToX(t); 447 448 // scroll buffer to the right if needed 449 if (cursorPos+cw > t->renderW) 450 scrollTextBufferRight(t, numChars); 451 452 // scroll buffer to the left if needed 453 else if (cursorPos < 0-1) 454 scrollTextBufferLeft(t); 455 } 456 457 editor.textCursorBlinkCounter = 0; 458 } 459 460 static void textOutBuf(uint8_t *dstBuffer, uint32_t dstWidth, uint8_t paletteIndex, char *text, uint32_t maxTextLen) 461 { 462 assert(text != NULL); 463 if (*text == '\0') 464 return; // empty string 465 466 uint16_t currX = 0; 467 for (uint32_t i = 0; i < maxTextLen; i++) 468 { 469 const char chr = *text++ & 0x7F; 470 if (chr == '\0') 471 break; 472 473 if (chr != ' ') 474 { 475 const uint8_t *srcPtr = &bmp.font1[chr * FONT1_CHAR_W]; 476 uint8_t *dstPtr = &dstBuffer[currX]; 477 478 for (uint32_t y = 0; y < FONT1_CHAR_H; y++) 479 { 480 for (uint32_t x = 0; x < FONT1_CHAR_W; x++) 481 { 482 if (srcPtr[x]) 483 dstPtr[x] = paletteIndex; 484 } 485 486 srcPtr += FONT1_WIDTH; 487 dstPtr += dstWidth; 488 } 489 } 490 491 currX += charWidth(chr); 492 } 493 } 494 495 // a lot of filling here, but textboxes are small so no problem... 496 void drawTextBox(uint16_t textBoxID) 497 { 498 int8_t cw; 499 uint8_t pal; 500 501 assert(textBoxID < NUM_TEXTBOXES); 502 textBox_t *t = &textBoxes[textBoxID]; 503 if (!t->visible) 504 return; 505 506 // test if buffer offset is not overflowing 507 #ifdef _DEBUG 508 if (t->renderBufW > t->renderW) 509 assert(t->bufOffset <= t->renderBufW-t->renderW); 510 #endif 511 512 // fill text rendering buffer with transparency key 513 memset(t->renderBuf, PAL_TRANSPR, t->renderBufW * t->renderBufH); 514 515 if (t->textPtr == NULL) 516 return; 517 518 // draw text mark background (if this is the textbox we last interacted with) 519 if (mouse.lastEditBox == textBoxID) 520 { 521 if (textIsMarked()) 522 { 523 hideSprite(SPRITE_TEXT_CURSOR); 524 525 int32_t start = getTextMarkStart(); 526 int32_t end = getTextMarkEnd(); 527 528 assert(start < t->maxChars && end <= t->maxChars); 529 530 // find pixel start/length from markX1 and markX2 531 532 int32_t x1 = 0; 533 int32_t x2 = 0; 534 535 for (int32_t i = 0; i < end; i++) 536 { 537 const char ch = t->textPtr[i]; 538 if (ch == '\0') 539 break; 540 541 cw = charWidth(ch); 542 if (i < start) 543 x1 += cw; 544 545 x2 += cw; 546 } 547 548 // render text mark background 549 if (x1 != x2) 550 { 551 start = x1; 552 const int32_t length = x2 - x1; 553 554 assert(start+length <= t->renderBufW); 555 556 uint8_t *ptr32 = &t->renderBuf[start]; 557 for (uint16_t y = 0; y < t->renderBufH; y++, ptr32 += t->renderBufW) 558 memset(ptr32, PAL_TEXTMRK, length); 559 } 560 } 561 } 562 563 // render text to text render buffer 564 textOutBuf(t->renderBuf, t->renderBufW, PAL_FORGRND, t->textPtr, t->maxChars); 565 566 // fill screen rect background color (not always needed, but I'm lazy) 567 pal = video.frameBuffer[(t->y * SCREEN_W) + t->x] >> 24; // get background palette (stored in alpha channel) 568 fillRect(t->x + t->tx, t->y + t->ty, t->renderW, 10, pal); // 10 = tallest possible glyph/char height 569 570 // render visible part of text render buffer to screen 571 blitClipX(t->x + t->tx, t->y + t->ty, &t->renderBuf[t->bufOffset], t->renderBufW, t->renderBufH, t->renderW); 572 } 573 574 void showTextBox(uint16_t textBoxID) 575 { 576 assert(textBoxID < NUM_TEXTBOXES); 577 textBoxes[textBoxID].visible = true; 578 } 579 580 void hideTextBox(uint16_t textBoxID) 581 { 582 assert(textBoxID < NUM_TEXTBOXES); 583 hideSprite(SPRITE_TEXT_CURSOR); 584 textBoxes[textBoxID].visible = false; 585 } 586 587 static void setMarkX2ToMouseX(textBox_t *t) 588 { 589 int16_t i; 590 591 if (t->textPtr == NULL || t->textPtr[0] == '\0') 592 { 593 removeTextMarking(); 594 return; 595 } 596 597 if (markX2 < markX1 && mouse.x < t->x+t->tx) 598 { 599 markX2 = 0; 600 return; 601 } 602 603 const int16_t numChars = getTextLength(t, 0); 604 605 // find out what character we are clicking at, and set markX2 to that character 606 const int32_t mx = t->bufOffset + mouse.x; 607 int32_t tx = (t->x + t->tx) - 1; 608 609 for (i = 0; i < numChars; i++) 610 { 611 const int32_t cw = charWidth(t->textPtr[i]); 612 const int32_t tx2 = tx + cw; 613 614 if (mx >= tx && mx < tx2) 615 { 616 markX2 = i; 617 break; 618 } 619 620 tx += cw; 621 } 622 623 // set to last character if we clicked outside the end of the text 624 if (i == numChars && mx >= tx) 625 markX2 = numChars; 626 627 if (mouse.x >= t->x+t->w-3) 628 { 629 scrollTextBufferRight(t, numChars); 630 if (++markX2 > numChars) 631 markX2 = numChars; 632 } 633 else if (mouse.x <= t->x+t->tx+3) 634 { 635 if (t->bufOffset > 0) 636 { 637 scrollTextBufferLeft(t); 638 if (--markX2 < 0) 639 markX2 = 0; 640 } 641 } 642 643 t->cursorPos = markX2; 644 assert(t->cursorPos >= 0 && t->cursorPos <= getTextLength(t, 0)); 645 646 editor.textCursorBlinkCounter = 0; 647 } 648 649 void handleTextBoxWhileMouseDown(void) 650 { 651 assert(mouse.lastUsedObjectID >= 0 && mouse.lastUsedObjectID < NUM_TEXTBOXES); 652 textBox_t *t = &textBoxes[mouse.lastUsedObjectID]; 653 if (!t->visible) 654 return; 655 656 if (mouse.x != oldMouseX) 657 { 658 oldMouseX = mouse.x; 659 660 markX1 = oldCursorPos; 661 setMarkX2ToMouseX(t); 662 663 drawTextBox(mouse.lastUsedObjectID); 664 } 665 } 666 667 bool testTextBoxMouseDown(void) 668 { 669 uint16_t start, end; 670 671 oldMouseX = mouse.x; 672 oldCursorPos = 0; 673 674 if (ui.sysReqShown) 675 { 676 // if a system request is open, only test the first textbox (reserved) 677 start = 0; 678 end = 1; 679 } 680 else 681 { 682 start = 1; 683 end = NUM_TEXTBOXES; 684 } 685 686 const int32_t mx = mouse.x; 687 const int32_t my = mouse.y; 688 689 textBox_t *t = &textBoxes[start]; 690 for (uint16_t i = start; i < end; i++, t++) 691 { 692 if (!t->visible || t->textPtr == NULL) 693 continue; 694 695 if (my >= t->y && my < t->y+t->h && 696 mx >= t->x && mx < t->x+t->w) 697 { 698 if (!mouse.rightButtonPressed && t->rightMouseButton) 699 break; 700 701 // if we were editing another text box and clicked on another one, properly end it 702 if (editor.editTextFlag && i != mouse.lastEditBox) 703 exitTextEditing(); 704 705 mouse.lastEditBox = i; 706 moveTextCursorToMouseX(mouse.lastEditBox); 707 708 oldCursorPos = t->cursorPos; 709 removeTextMarking(); 710 drawTextBox(mouse.lastEditBox); 711 712 editor.textCursorBlinkCounter = 0; 713 mouse.lastUsedObjectType = OBJECT_TEXTBOX; 714 mouse.lastUsedObjectID = i; 715 716 editor.editTextFlag = true; 717 718 SDL_StartTextInput(); 719 return true; 720 } 721 } 722 723 // if we were editing text and we clicked outside of a text box, exit text editing 724 if (editor.editTextFlag) 725 { 726 exitTextEditing(); 727 keyb.ignoreCurrKeyUp = false; // if we exited with mouse, don't handle key-up kludge 728 } 729 730 return false; 731 } 732 733 void updateTextBoxPointers(void) 734 { 735 int32_t i; 736 instr_t *curIns = instr[editor.curInstr]; 737 738 // instrument names 739 for (i = 0; i < 8; i++) 740 textBoxes[TB_INST1+i].textPtr = song.instrName[1+editor.instrBankOffset+i]; 741 742 // sample names 743 if (editor.curInstr == 0 || curIns == NULL) 744 { 745 for (i = 0; i < 5; i++) 746 textBoxes[TB_SAMP1+i].textPtr = NULL; 747 } 748 else 749 { 750 for (i = 0; i < 5; i++) 751 textBoxes[TB_SAMP1+i].textPtr = curIns->smp[editor.sampleBankOffset+i].name; 752 } 753 754 // song name 755 textBoxes[TB_SONG_NAME].textPtr = song.name; 756 } 757 758 void setupInitialTextBoxPointers(void) 759 { 760 textBoxes[TB_CONF_DEF_MODS_DIR].textPtr = config.modulesPath; 761 textBoxes[TB_CONF_DEF_INSTRS_DIR].textPtr = config.instrPath; 762 textBoxes[TB_CONF_DEF_SAMPS_DIR].textPtr = config.samplesPath; 763 textBoxes[TB_CONF_DEF_PATTS_DIR].textPtr = config.patternsPath; 764 textBoxes[TB_CONF_DEF_TRACKS_DIR].textPtr = config.tracksPath; 765 } 766 767 void setTextCursorToEnd(textBox_t *t) 768 { 769 uint16_t numChars; 770 771 // count number of chars and get full text width 772 uint32_t textWidth = 0; 773 for (numChars = 0; numChars < t->maxChars; numChars++) 774 { 775 const char ch = t->textPtr[numChars]; 776 if (ch == '\0') 777 break; 778 779 textWidth += charWidth(ch); 780 } 781 782 // if cursor is not at the end, handle text marking 783 if (t->cursorPos < numChars) 784 { 785 if (keyb.leftShiftPressed) 786 { 787 if (!textIsMarked()) 788 markX1 = t->cursorPos; 789 790 markX2 = numChars; 791 } 792 else 793 { 794 removeTextMarking(); 795 } 796 } 797 798 t->cursorPos = numChars; 799 800 t->bufOffset = 0; 801 if (textWidth > t->renderW) 802 t->bufOffset = textWidth - t->renderW; 803 804 drawTextBox(mouse.lastEditBox); 805 editor.textCursorBlinkCounter = 0; 806 } 807 808 void handleTextEditControl(SDL_Keycode keycode) 809 { 810 int16_t i; 811 uint16_t numChars; 812 int32_t textLength; 813 uint32_t textWidth; 814 815 assert(mouse.lastEditBox >= 0 && mouse.lastEditBox < NUM_TEXTBOXES); 816 817 textBox_t *t = &textBoxes[mouse.lastEditBox]; 818 assert(t->textPtr != NULL); 819 820 switch (keycode) 821 { 822 case SDLK_ESCAPE: 823 { 824 removeTextMarking(); 825 exitTextEditing(); 826 } 827 break; 828 829 case SDLK_a: 830 { 831 // CTRL+A - mark all text 832 #ifdef __APPLE__ 833 if (keyb.leftCtrlPressed || keyb.leftCommandPressed) 834 #else 835 if (keyb.leftCtrlPressed) 836 #endif 837 { 838 // count number of chars and get full text width 839 textWidth = 0; 840 for (numChars = 0; numChars < t->maxChars; numChars++) 841 { 842 if (t->textPtr[numChars] == '\0') 843 break; 844 845 textWidth += charWidth(t->textPtr[numChars]); 846 } 847 848 markX1 = 0; 849 markX2 = numChars; 850 t->cursorPos = markX2; 851 852 t->bufOffset = 0; 853 if (textWidth > t->renderW) 854 t->bufOffset = textWidth - t->renderW; 855 856 drawTextBox(mouse.lastEditBox); 857 } 858 } 859 break; 860 861 case SDLK_x: 862 { 863 // CTRL+X - cut marked text 864 #ifdef __APPLE__ 865 if (keyb.leftCtrlPressed || keyb.leftCommandPressed) 866 #else 867 if (keyb.leftCtrlPressed) 868 #endif 869 cutMarkedText(t); 870 } 871 break; 872 873 case SDLK_c: 874 { 875 // CTRL+C - copy marked text 876 #ifdef __APPLE__ 877 if (keyb.leftCtrlPressed || keyb.leftCommandPressed) 878 #else 879 if (keyb.leftCtrlPressed) 880 #endif 881 copyMarkedText(t); 882 } 883 break; 884 885 case SDLK_v: 886 { 887 // CTRL+V - paste text 888 #ifdef __APPLE__ 889 if (keyb.leftCtrlPressed || keyb.leftCommandPressed) 890 #else 891 if (keyb.leftCtrlPressed) 892 #endif 893 pasteText(t); 894 } 895 break; 896 897 case SDLK_KP_ENTER: 898 case SDLK_RETURN: 899 { 900 // ALT+ENTER = toggle fullscreen, even while text editing 901 if (keyb.leftAltPressed) 902 toggleFullscreen(); 903 else 904 exitTextEditing(); 905 } 906 break; 907 908 case SDLK_LEFT: 909 { 910 if (keyb.leftShiftPressed) 911 { 912 if (!textIsMarked()) 913 { 914 // no marking, mark character to left from cursor 915 if (t->cursorPos > 0) 916 { 917 markX1 = t->cursorPos; 918 moveTextCursorLeft(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 919 markX2 = t->cursorPos; 920 921 drawTextBox(mouse.lastEditBox); 922 } 923 } 924 else 925 { 926 // marking, extend/shrink marking 927 if (markX2 > 0) 928 { 929 t->cursorPos = markX2; 930 markX2--; 931 moveTextCursorLeft(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 932 drawTextBox(mouse.lastEditBox); 933 } 934 } 935 } 936 else 937 { 938 if (textIsMarked()) 939 { 940 setCursorToMarkStart(t); 941 removeTextMarking(); 942 } 943 else 944 { 945 removeTextMarking(); 946 moveTextCursorLeft(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 947 } 948 949 drawTextBox(mouse.lastEditBox); 950 } 951 } 952 break; 953 954 case SDLK_RIGHT: 955 { 956 if (keyb.leftShiftPressed) 957 { 958 textLength = getTextLength(t, 0); 959 960 if (!textIsMarked()) 961 { 962 if (t->cursorPos < textLength) 963 { 964 markX1 = t->cursorPos; 965 moveTextCursorRight(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 966 markX2 = t->cursorPos; 967 968 drawTextBox(mouse.lastEditBox); 969 } 970 } 971 else 972 { 973 // marking, extend/shrink marking 974 if (markX2 < textLength) 975 { 976 t->cursorPos = markX2; 977 markX2++; 978 moveTextCursorRight(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 979 drawTextBox(mouse.lastEditBox); 980 } 981 } 982 } 983 else 984 { 985 if (textIsMarked()) 986 { 987 setCursorToMarkEnd(t); 988 removeTextMarking(); 989 } 990 else 991 { 992 removeTextMarking(); 993 moveTextCursorRight(mouse.lastEditBox, TEXTBOX_NO_UPDATE); 994 } 995 996 drawTextBox(mouse.lastEditBox); 997 } 998 } 999 break; 1000 1001 case SDLK_BACKSPACE: 1002 { 1003 if (textIsMarked()) 1004 { 1005 deleteMarkedText(t); 1006 removeTextMarking(); 1007 drawTextBox(mouse.lastEditBox); 1008 break; 1009 } 1010 1011 removeTextMarking(); 1012 1013 if (t->cursorPos > 0 && t->textPtr[0] != '\0') 1014 { 1015 // scroll buffer offset if we are scrolled 1016 if (t->bufOffset > 0) 1017 { 1018 t->bufOffset -= charWidth(t->textPtr[t->cursorPos-1]); 1019 if (t->bufOffset < 0) 1020 t->bufOffset = 0; 1021 } 1022 1023 moveTextCursorLeft(mouse.lastEditBox, TEXTBOX_UPDATE); 1024 1025 i = t->cursorPos; 1026 while (i < t->maxChars) 1027 { 1028 t->textPtr[i] = t->textPtr[i+1]; 1029 if (t->textPtr[i] == '\0') 1030 break; 1031 i++; 1032 } 1033 1034 drawTextBox(mouse.lastEditBox); 1035 setSongModifiedFlagIfNeeded(); 1036 } 1037 } 1038 break; 1039 1040 case SDLK_DELETE: 1041 { 1042 if (textIsMarked()) 1043 { 1044 deleteMarkedText(t); 1045 removeTextMarking(); 1046 drawTextBox(mouse.lastEditBox); 1047 break; 1048 } 1049 1050 if (t->textPtr[t->cursorPos] != '\0' && t->textPtr[0] != '\0' && t->cursorPos < t->maxChars) 1051 { 1052 // scroll buffer offset if we are scrolled 1053 if (t->bufOffset > 0) 1054 { 1055 t->bufOffset -= charWidth(t->textPtr[t->cursorPos]); 1056 if (t->bufOffset < 0) 1057 t->bufOffset = 0; 1058 } 1059 1060 i = t->cursorPos; 1061 while (i < t->maxChars) 1062 { 1063 if (t->textPtr[i] == '\0') 1064 break; 1065 1066 t->textPtr[i] = t->textPtr[i+1]; 1067 i++; 1068 } 1069 1070 drawTextBox(mouse.lastEditBox); 1071 setSongModifiedFlagIfNeeded(); 1072 } 1073 } 1074 break; 1075 1076 case SDLK_HOME: 1077 { 1078 if (keyb.leftShiftPressed) 1079 { 1080 if (!textIsMarked()) 1081 markX1 = t->cursorPos; 1082 1083 markX2 = 0; 1084 t->bufOffset = 0; 1085 t->cursorPos = 0; 1086 } 1087 else 1088 { 1089 removeTextMarking(); 1090 1091 if (t->cursorPos > 0) 1092 { 1093 t->cursorPos = 0; 1094 t->bufOffset = 0; 1095 1096 editor.textCursorBlinkCounter = 0; 1097 } 1098 } 1099 1100 drawTextBox(mouse.lastEditBox); 1101 } 1102 break; 1103 1104 case SDLK_END: 1105 { 1106 setTextCursorToEnd(t); 1107 } 1108 break; 1109 1110 default: break; 1111 } 1112 } 1113 1114 void handleTextEditInputChar(char textChar) 1115 { 1116 assert(mouse.lastEditBox >= 0 && mouse.lastEditBox < NUM_TEXTBOXES); 1117 1118 textBox_t *t = &textBoxes[mouse.lastEditBox]; 1119 if (t->textPtr == NULL) 1120 return; 1121 1122 const int8_t ch = (const int8_t)textChar; 1123 if (ch < 32 && 1124 ch != -124 && ch != -108 && ch != -122 && ch != -114 && ch != -103 && 1125 ch != -113 && ch != -101 && ch != -99 && ch != -111 && ch != -110) 1126 { 1127 return; // only allow certain codepage 850 nordic characters 1128 } 1129 1130 if (textIsMarked()) 1131 { 1132 deleteMarkedText(t); 1133 removeTextMarking(); 1134 } 1135 1136 if (t->cursorPos >= 0 && t->cursorPos < t->maxChars) 1137 { 1138 int32_t i = getTextLength(t, 0); 1139 if (i < t->maxChars) // do we have room for a new character? 1140 { 1141 t->textPtr[i+1] = '\0'; 1142 1143 // if string not empty, shift string to the right to make space for char insertion 1144 if (i > 0) 1145 { 1146 for (; i > t->cursorPos; i--) 1147 t->textPtr[i] = t->textPtr[i-1]; 1148 } 1149 1150 t->textPtr[t->cursorPos] = textChar; 1151 1152 moveTextCursorRight(mouse.lastEditBox, TEXTBOX_UPDATE); // also updates textbox 1153 setSongModifiedFlagIfNeeded(); 1154 } 1155 } 1156 } 1157 1158 static void moveTextCursorLeft(int16_t i, bool updateTextBox) 1159 { 1160 textBox_t *t = &textBoxes[i]; 1161 if (t->cursorPos == 0) 1162 return; 1163 1164 t->cursorPos--; 1165 1166 // scroll buffer if needed 1167 if (cursorPosToX(t) < 0-1) 1168 scrollTextBufferLeft(t); 1169 1170 if (updateTextBox) 1171 drawTextBox(i); 1172 1173 editor.textCursorBlinkCounter = 0; // reset text cursor blink timer 1174 } 1175 1176 static void moveTextCursorRight(int16_t i, bool updateTextBox) 1177 { 1178 textBox_t *t = &textBoxes[i]; 1179 1180 const uint16_t numChars = getTextLength(t, 0); 1181 if (t->cursorPos >= numChars) 1182 return; 1183 1184 t->cursorPos++; 1185 1186 // scroll buffer if needed 1187 if (cursorPosToX(t) >= t->renderW) 1188 scrollTextBufferRight(t, numChars); 1189 1190 if (updateTextBox) 1191 drawTextBox(i); 1192 1193 editor.textCursorBlinkCounter = 0; // reset text cursor blink timer 1194 } 1195 1196 void freeTextBoxes(void) 1197 { 1198 // free text box buffers (skip first entry, it's reserved for inputBox()) 1199 textBox_t *t = &textBoxes[1]; 1200 for (int32_t i = 1; i < NUM_TEXTBOXES; i++, t++) 1201 { 1202 if (t->renderBuf != NULL) 1203 { 1204 free(t->renderBuf); 1205 t->renderBuf = NULL; 1206 } 1207 } 1208 }