ft2_sysreqs.c (18416B)
1 #include <stdio.h> // vsnprintf() 2 #include <stdint.h> 3 #include <stdbool.h> 4 #include "ft2_config.h" 5 #include "ft2_gui.h" 6 #include "ft2_mouse.h" 7 #include "ft2_keyboard.h" 8 #include "ft2_textboxes.h" 9 #include "ft2_video.h" 10 #include "ft2_sysreqs.h" 11 #include "ft2_structs.h" 12 #include "ft2_events.h" 13 #include "ft2_smpfx.h" 14 15 #define SYSTEM_REQUEST_H 67 16 #define SYSTEM_REQUEST_Y 249 17 #define SYSTEM_REQUEST_Y_EXT 91 18 19 // globalized 20 okBoxData_t okBoxData; 21 void (*loaderMsgBox)(const char *, ...); 22 int16_t (*loaderSysReq)(int16_t, const char *, const char *, void (*)(void)); 23 // ---------------- 24 25 #define NUM_SYSREQ_TYPES 7 26 27 static char *buttonText[NUM_SYSREQ_TYPES][5] = 28 { 29 // generic dialogs 30 { "OK", "","","","" }, 31 { "OK", "Cancel", "","","" }, 32 { "Yes", "No", "","","" }, 33 34 // custom dialogs 35 { "All", "Song", "Instruments", "Cancel", "" }, // "song clear" dialog 36 { "Read left", "Read right", "Convert", "", "" }, // "stereo sample loader" dialog 37 { "Mono", "Stereo", "Cancel", "","" }, // "audio sampling" dialog 38 { "OK", "Preview", "Cancel", "","" } // sample editor effects filters 39 }; 40 41 static SDL_Keycode shortCut[NUM_SYSREQ_TYPES][5] = 42 { 43 // generic dialogs 44 { SDLK_o, 0, 0, 0, 0 }, 45 { SDLK_o, SDLK_c, 0, 0, 0 }, 46 { SDLK_y, SDLK_n, 0, 0, 0 }, 47 48 // custom dialogs 49 { SDLK_a, SDLK_s, SDLK_i, SDLK_c, 0 }, // "song clear" dialog 50 { SDLK_l, SDLK_r, SDLK_c, 0, 0 }, // "stereo sample loader" dialog 51 { SDLK_m, SDLK_s, SDLK_c, 0, 0 }, // "audio sampling" dialog 52 { SDLK_o, SDLK_p, SDLK_c, 0, 0 } // sample editor effects filters 53 }; 54 55 typedef struct quitType_t 56 { 57 const char *text; 58 uint8_t type; 59 } quitType_t; 60 61 #define QUIT_MESSAGES 11 62 63 static quitType_t quitMessage[QUIT_MESSAGES] = 64 { 65 // edited (and removed) some of the original quit texts... 66 67 { "Do you really want to quit?", 2 }, 68 { "Tired already?", 2 }, 69 { "Dost thou wish to leave with such hasty abandon?", 2 }, 70 { "So, you think you can quit this easily, huh?", 2 }, 71 { "Hey, what is the matter? You are not quiting now, are you?", 2 }, 72 { "Rome was not built in one day! Quit really?", 2 }, 73 { "Did you really press the right key?", 2 }, 74 { "Hope ya did some good. Press >OK< to quit.", 1 }, 75 { "Quit? Only for a good reason you are allowed to press >OK<.", 1 }, 76 { "Are we at the end of a Fasttracker II round?", 2 }, 77 { "Hope you're doing the compulsory \"Exit ceremony\" before pressing >OK<.", 1 }, 78 }; 79 80 void myLoaderMsgBoxThreadSafe(const char *fmt, ...) 81 { 82 char strBuf[512]; 83 va_list args; 84 85 // format the text string 86 va_start(args, fmt); 87 vsnprintf(strBuf, sizeof (strBuf), fmt, args); 88 va_end(args); 89 90 okBoxThreadSafe(0, "System message", fmt, NULL); 91 } 92 93 void myLoaderMsgBox(const char *fmt, ...) 94 { 95 char strBuf[512]; 96 va_list args; 97 98 // format the text string 99 va_start(args, fmt); 100 vsnprintf(strBuf, sizeof (strBuf), fmt, args); 101 va_end(args); 102 103 okBox(0, "System message", fmt, NULL); 104 } 105 106 static void drawWindow(uint16_t w) 107 { 108 const uint16_t h = SYSTEM_REQUEST_H; 109 const uint16_t x = (SCREEN_W - w) / 2; 110 const uint16_t y = ui.extendedPatternEditor ? 91 : SYSTEM_REQUEST_Y; 111 112 // main fill 113 fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS); 114 115 // outer border 116 vLine(x, y, h - 1, PAL_BUTTON1); 117 hLine(x + 1, y, w - 2, PAL_BUTTON1); 118 vLine(x + w - 1, y, h, PAL_BUTTON2); 119 hLine(x, y + h - 1, w - 1, PAL_BUTTON2); 120 121 // inner border 122 vLine(x + 2, y + 2, h - 5, PAL_BUTTON2); 123 hLine(x + 3, y + 2, w - 6, PAL_BUTTON2); 124 vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1); 125 hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1); 126 127 // title bottom line 128 hLine(x + 3, y + 16, w - 6, PAL_BUTTON2); 129 hLine(x + 3, y + 17, w - 6, PAL_BUTTON1); 130 } 131 132 static bool mouseButtonDownLogic(uint8_t mouseButton) 133 { 134 // if already holding left button and clicking right, don't do mouse down handling 135 if (mouseButton == SDL_BUTTON_RIGHT && mouse.leftButtonPressed) 136 { 137 mouse.rightButtonPressed = true; 138 return false; 139 } 140 141 // if already holding right button and clicking left, don't do mouse down handling 142 if (mouseButton == SDL_BUTTON_LEFT && mouse.rightButtonPressed) 143 { 144 mouse.leftButtonPressed = true; 145 return false; 146 } 147 148 if (mouseButton == SDL_BUTTON_LEFT) 149 mouse.leftButtonPressed = true; 150 else if (mouseButton == SDL_BUTTON_RIGHT) 151 mouse.rightButtonPressed = true; 152 153 // don't do mouse down testing here if we already are using an object 154 if (mouse.lastUsedObjectType != OBJECT_NONE) 155 return false; 156 157 // kludge #2 158 if (mouse.lastUsedObjectType != OBJECT_PUSHBUTTON && mouse.lastUsedObjectID != OBJECT_ID_NONE) 159 return false; 160 161 // kludge #3 162 if (!mouse.rightButtonPressed) 163 mouse.lastUsedObjectID = OBJECT_ID_NONE; 164 165 return true; 166 } 167 168 static bool mouseButtonUpLogic(uint8_t mouseButton) 169 { 170 #if defined __APPLE__ && defined __aarch64__ 171 armMacGhostMouseCursorFix(); 172 #endif 173 174 if (mouseButton == SDL_BUTTON_LEFT) 175 mouse.leftButtonPressed = false; 176 else if (mouseButton == SDL_BUTTON_RIGHT) 177 mouse.rightButtonPressed = false; 178 179 editor.textCursorBlinkCounter = 0; 180 181 // if we used both mouse button at the same time and released *one*, don't release GUI object 182 if ( mouse.leftButtonPressed && !mouse.rightButtonPressed) return false; 183 if (!mouse.leftButtonPressed && mouse.rightButtonPressed) return false; 184 185 return true; 186 } 187 188 // WARNING: This routine must ONLY be called from the main input/video thread! 189 // If the checkBoxCallback argument is set, then you get a "Do not show again" checkbox. 190 int16_t okBox(int16_t type, const char *headline, const char *text, void (*checkBoxCallback)(void)) 191 { 192 #define PUSHBUTTON_W 80 193 194 SDL_Event inputEvent; 195 196 if (editor.editTextFlag) 197 { 198 exitTextEditing(); 199 keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here 200 } 201 202 // revert "delete/rename" mouse modes (disk op.) 203 if (mouse.mode != MOUSE_MODE_NORMAL) 204 setMouseMode(MOUSE_MODE_NORMAL); 205 206 if (ui.sysReqShown) 207 return 0; 208 209 SDL_EventState(SDL_DROPFILE, SDL_DISABLE); 210 211 ui.sysReqShown = true; 212 mouseAnimOff(); 213 214 int16_t oldLastUsedObjectID = mouse.lastUsedObjectID; 215 int16_t oldLastUsedObjectType = mouse.lastUsedObjectType; 216 217 // count number of buttons 218 uint16_t numButtons = 0; 219 while (buttonText[type][numButtons][0] != '\0' && numButtons < 5) 220 numButtons++; 221 222 uint16_t tlen = textWidth(text); 223 uint16_t hlen = textWidth(headline); 224 225 uint16_t wlen = tlen; 226 if (hlen > tlen) 227 wlen = hlen; 228 229 uint16_t tx = (numButtons * 100) - 20; 230 if (tx > wlen) 231 wlen = tx; 232 233 wlen += 100; 234 if (wlen > 600) 235 wlen = 600; 236 237 const uint16_t headlineX = (SCREEN_W - hlen) / 2; 238 const uint16_t textX = (SCREEN_W - tlen) / 2; 239 const uint16_t x = (SCREEN_W - wlen) / 2; 240 241 // the dialog's y position differs in extended pattern editor mode 242 const uint16_t y = ui.extendedPatternEditor ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y; 243 244 // set up buttons 245 for (uint16_t i = 0; i < numButtons; i++) 246 { 247 pushButton_t *p = &pushButtons[i]; 248 249 p->x = ((SCREEN_W - tx) / 2) + (i * 100); 250 p->y = y + 42; 251 p->w = PUSHBUTTON_W; 252 p->h = 16; 253 p->caption = buttonText[type][i]; 254 p->visible = true; 255 } 256 257 // set up "don't show again" checkbox (if callback present) 258 bool hasCheckbox = (checkBoxCallback != NULL); 259 if (hasCheckbox) 260 { 261 checkBox_t *c = &checkBoxes[0]; 262 c->x = x + 5; 263 c->y = y + 50; 264 c->clickAreaWidth = 116; 265 c->clickAreaHeight = 12; 266 c->checked = false; 267 c->callbackFunc = checkBoxCallback; 268 c->visible = true; 269 } 270 271 mouse.lastUsedObjectType = OBJECT_NONE; 272 mouse.lastUsedObjectID = OBJECT_ID_NONE; 273 mouse.leftButtonPressed = 0; 274 mouse.rightButtonPressed = 0; 275 276 // input/rendering loop 277 int16_t returnVal = 0; 278 while (ui.sysReqShown) 279 { 280 beginFPSCounter(); 281 readMouseXY(); 282 setSyncedReplayerVars(); 283 284 if (mouse.leftButtonPressed || mouse.rightButtonPressed) 285 { 286 if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON) 287 handlePushButtonsWhileMouseDown(); 288 else if (mouse.lastUsedObjectType == OBJECT_CHECKBOX) 289 handleCheckBoxesWhileMouseDown(); 290 } 291 292 while (SDL_PollEvent(&inputEvent)) 293 { 294 handleWaitVblQuirk(&inputEvent); 295 296 if (inputEvent.type == SDL_KEYDOWN) 297 { 298 if (inputEvent.key.keysym.sym == SDLK_ESCAPE) 299 { 300 if (!inputEvent.key.repeat) // don't let previously held-down ESC immediately close the box 301 { 302 returnVal = 0; 303 ui.sysReqShown = false; 304 } 305 } 306 else if (inputEvent.key.keysym.sym == SDLK_RETURN) 307 { 308 returnVal = 1; 309 ui.sysReqShown = false; 310 keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed 311 } 312 313 for (uint16_t i = 0; i < numButtons; i++) 314 { 315 if (shortCut[type][i] == inputEvent.key.keysym.sym) 316 { 317 returnVal = i + 1; 318 ui.sysReqShown = false; 319 keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed 320 break; 321 } 322 } 323 } 324 else if (inputEvent.type == SDL_MOUSEBUTTONUP) 325 { 326 if (mouseButtonUpLogic(inputEvent.button.button)) 327 { 328 if (hasCheckbox) 329 testCheckBoxMouseRelease(); 330 331 returnVal = testPushButtonMouseRelease(false) + 1; 332 if (returnVal > 0) 333 ui.sysReqShown = false; 334 335 mouse.lastUsedObjectID = OBJECT_ID_NONE; 336 mouse.lastUsedObjectType = OBJECT_NONE; 337 } 338 } 339 else if (inputEvent.type == SDL_MOUSEBUTTONDOWN) 340 { 341 if (mouseButtonDownLogic(inputEvent.button.button)) 342 { 343 if (testPushButtonMouseDown()) continue; 344 if (testCheckBoxMouseDown()) continue; 345 } 346 } 347 #if defined __APPLE__ && defined __aarch64__ 348 else if (inputEvent.type == SDL_MOUSEMOTION) 349 { 350 armMacGhostMouseCursorFix(); 351 } 352 #endif 353 if (!ui.sysReqShown) 354 break; 355 } 356 357 if (!ui.sysReqShown) 358 break; 359 360 handleRedrawing(); 361 362 // draw OK box 363 drawWindow(wlen); 364 textOutShadow(headlineX, y + 4, PAL_FORGRND, PAL_BUTTON2, headline); 365 textOutShadow(textX, y + 24, PAL_FORGRND, PAL_BUTTON2, text); 366 for (uint16_t i = 0; i < numButtons; i++) drawPushButton(i); 367 if (hasCheckbox) 368 { 369 drawCheckBox(0); 370 textOutShadow(x + 21, y + 52, PAL_FORGRND, PAL_BUTTON2, "Don't show again"); 371 } 372 373 flipFrame(); 374 endFPSCounter(); 375 } 376 377 for (uint16_t i = 0; i < numButtons; i++) 378 hidePushButton(i); 379 380 if (hasCheckbox) 381 hideCheckBox(0); 382 383 mouse.lastUsedObjectID = oldLastUsedObjectID; 384 mouse.lastUsedObjectType = oldLastUsedObjectType; 385 unstuckLastUsedGUIElement(); 386 387 showBottomScreen(); 388 389 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); 390 return returnVal; 391 } 392 393 /* WARNING: 394 ** - This routine must ONLY be called from the main input/video thread!! 395 ** - edText must be NUL-terminated 396 */ 397 int16_t inputBox(int16_t type, const char *headline, char *edText, uint16_t maxStrLen) 398 { 399 #define PUSHBUTTON_W 80 400 #define TEXTBOX_W 250 401 402 SDL_Event inputEvent; 403 404 if (editor.editTextFlag) 405 { 406 exitTextEditing(); 407 keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here 408 } 409 410 // revert "delete/rename" mouse modes (disk op.) 411 if (mouse.mode != MOUSE_MODE_NORMAL) 412 setMouseMode(MOUSE_MODE_NORMAL); 413 414 if (ui.sysReqShown) 415 return 0; 416 417 int16_t oldLastUsedObjectID = mouse.lastUsedObjectID; 418 int16_t oldLastUsedObjectType = mouse.lastUsedObjectType; 419 420 textBox_t *t = &textBoxes[0]; 421 422 // set up text box 423 memset(t, 0, sizeof (textBox_t)); 424 t->w = TEXTBOX_W; 425 t->h = 12; 426 t->tx = 2; 427 t->ty = 1; 428 t->textPtr = edText; 429 t->maxChars = maxStrLen; 430 t->changeMouseCursor = true; 431 t->renderBufW = (9 + 1) * t->maxChars; // 9 = max character/glyph width possible 432 t->renderBufH = 10; // 10 = max character height possible 433 t->renderW = t->w - (t->tx * 2); 434 435 t->renderBuf = (uint8_t *)malloc(t->renderBufW * t->renderBufH * sizeof (int8_t)); 436 if (t->renderBuf == NULL) 437 { 438 okBox(0, "System message", "Not enough memory!", NULL); 439 return 0; 440 } 441 442 SDL_EventState(SDL_DROPFILE, SDL_DISABLE); 443 444 ui.sysReqShown = true; 445 mouseAnimOff(); 446 447 uint16_t wlen = textWidth(headline); 448 const uint16_t headlineX = (SCREEN_W - wlen) / 2; 449 450 // count number of buttons 451 uint16_t numButtons = 0; 452 while (buttonText[type][numButtons][0] != '\0' && numButtons < 5) 453 numButtons++; 454 455 uint16_t tx = TEXTBOX_W; 456 if (tx > wlen) 457 wlen = tx; 458 459 tx = (numButtons * 100) - 20; 460 if (tx > wlen) 461 wlen = tx; 462 463 wlen += 100; 464 if (wlen > 600) 465 wlen = 600; 466 467 // the box y position differs in extended pattern editor mode 468 const uint16_t y = ui.extendedPatternEditor ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y; 469 470 // set further text box settings 471 t->x = (SCREEN_W - TEXTBOX_W) / 2; 472 t->y = y + 24; 473 t->visible = true; 474 475 // setup buttons 476 477 pushButton_t *p = pushButtons; 478 for (uint16_t i = 0; i < numButtons; i++, p++) 479 { 480 p->w = PUSHBUTTON_W; 481 p->h = 16; 482 p->x = ((SCREEN_W - tx) / 2) + (i * 100); 483 p->y = y + 42; 484 p->caption = buttonText[type][i]; 485 p->visible = true; 486 } 487 488 keyb.leftShiftPressed = false; // kludge 489 setTextCursorToEnd(t); 490 491 mouse.lastEditBox = 0; 492 editor.editTextFlag = true; 493 SDL_StartTextInput(); 494 495 mouse.lastUsedObjectType = OBJECT_NONE; 496 mouse.lastUsedObjectID = OBJECT_ID_NONE; 497 mouse.leftButtonPressed = 0; 498 mouse.rightButtonPressed = 0; 499 500 // input/rendering loop 501 int16_t returnVal = 0; 502 while (ui.sysReqShown) 503 { 504 beginFPSCounter(); 505 readMouseXY(); 506 readKeyModifiers(); 507 setSyncedReplayerVars(); 508 509 if (mouse.leftButtonPressed || mouse.rightButtonPressed) 510 { 511 if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON) 512 handlePushButtonsWhileMouseDown(); 513 else if (mouse.lastUsedObjectType == OBJECT_TEXTBOX) 514 handleTextBoxWhileMouseDown(); 515 } 516 517 while (SDL_PollEvent(&inputEvent)) 518 { 519 handleWaitVblQuirk(&inputEvent); 520 521 if (inputEvent.type == SDL_TEXTINPUT) 522 { 523 if (editor.editTextFlag) 524 { 525 if (keyb.ignoreTextEditKey) 526 { 527 keyb.ignoreTextEditKey = false; 528 continue; 529 } 530 531 char *inputText = utf8ToCp850(inputEvent.text.text, false); 532 if (inputText != NULL) 533 { 534 if (inputText[0] != '\0') 535 handleTextEditInputChar(inputText[0]); 536 537 free(inputText); 538 } 539 } 540 } 541 else if (inputEvent.type == SDL_KEYDOWN) 542 { 543 if (inputEvent.key.keysym.sym == SDLK_ESCAPE) 544 { 545 returnVal = 0; 546 ui.sysReqShown = false; 547 } 548 else if (inputEvent.key.keysym.sym == SDLK_RETURN) 549 { 550 returnVal = 1; 551 ui.sysReqShown = false; 552 keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed 553 } 554 555 if (editor.editTextFlag) 556 { 557 handleTextEditControl(inputEvent.key.keysym.sym); 558 } 559 else 560 { 561 for (uint16_t i = 0; i < numButtons; i++) 562 { 563 if (shortCut[1][i] == inputEvent.key.keysym.sym) 564 { 565 if (type == 6 && returnVal == 2) 566 { 567 // special case for filters in sample editor "effects" 568 if (edText[0] != '\0') 569 sfxPreviewFilter(atoi(edText)); 570 } 571 else 572 { 573 returnVal = i + 1; 574 ui.sysReqShown = false; 575 keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed 576 break; 577 } 578 } 579 } 580 } 581 } 582 else if (inputEvent.type == SDL_MOUSEBUTTONUP) 583 { 584 if (mouseButtonUpLogic(inputEvent.button.button)) 585 { 586 returnVal = testPushButtonMouseRelease(false) + 1; 587 if (returnVal > 0) 588 { 589 if (type == 6 && returnVal == 2) 590 { 591 if (edText[0] != '\0') 592 sfxPreviewFilter(atoi(edText)); // special case for filters in sample editor "effects" 593 } 594 else 595 { 596 ui.sysReqShown = false; 597 } 598 } 599 600 mouse.lastUsedObjectID = OBJECT_ID_NONE; 601 mouse.lastUsedObjectType = OBJECT_NONE; 602 } 603 } 604 else if (inputEvent.type == SDL_MOUSEBUTTONDOWN) 605 { 606 if (mouseButtonDownLogic(inputEvent.button.button)) 607 { 608 if (testTextBoxMouseDown()) continue; 609 if (testPushButtonMouseDown()) continue; 610 } 611 } 612 #if defined __APPLE__ && defined __aarch64__ 613 else if (inputEvent.type == SDL_MOUSEMOTION) 614 { 615 armMacGhostMouseCursorFix(); 616 } 617 #endif 618 if (!ui.sysReqShown) 619 break; 620 } 621 622 if (!ui.sysReqShown) 623 break; 624 625 handleRedrawing(); 626 627 // draw input box 628 drawWindow(wlen); 629 textOutShadow(headlineX, y + 4, PAL_FORGRND, PAL_BUTTON2, headline); 630 clearRect(t->x, t->y, t->w, t->h); 631 hLine(t->x - 1, t->y - 1, t->w + 2, PAL_BUTTON2); 632 vLine(t->x - 1, t->y, t->h + 1, PAL_BUTTON2); 633 hLine(t->x, t->y + t->h, t->w + 1, PAL_BUTTON1); 634 vLine(t->x + t->w, t->y, t->h, PAL_BUTTON1); 635 drawTextBox(0); 636 for (uint16_t i = 0; i < numButtons; i++) drawPushButton(i); 637 638 flipFrame(); 639 endFPSCounter(); 640 } 641 642 editor.editTextFlag = false; 643 SDL_StopTextInput(); 644 645 for (uint16_t i = 0; i < numButtons; i++) 646 hidePushButton(i); 647 hideTextBox(0); 648 649 free(t->renderBuf); 650 651 mouse.lastUsedObjectID = oldLastUsedObjectID; 652 mouse.lastUsedObjectType = oldLastUsedObjectType; 653 unstuckLastUsedGUIElement(); 654 655 showBottomScreen(); 656 657 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); 658 return returnVal; 659 } 660 661 // WARNING: This routine must NOT be called from the main input/video thread! 662 // If the checkBoxCallback argument is set, then you get a "Do not show again" checkbox. 663 int16_t okBoxThreadSafe(int16_t type, const char *headline, const char *text, void (*checkBoxCallback)(void)) 664 { 665 if (!editor.mainLoopOngoing) 666 return 0; // main loop was not even started yet, bail out. 667 668 // the amount of time to wait is not important, but close to one video frame makes sense 669 const uint32_t waitTime = (uint32_t)((1000.0 / VBLANK_HZ) + 0.5); 670 671 // block multiple calls before they are completed (just in case, for safety) 672 while (okBoxData.active) 673 SDL_Delay(waitTime); 674 675 okBoxData.checkBoxCallback = checkBoxCallback; 676 okBoxData.type = type; 677 okBoxData.headline = headline; 678 okBoxData.text = text; 679 okBoxData.active = true; 680 681 while (okBoxData.active) 682 SDL_Delay(waitTime); 683 684 return okBoxData.returnData; 685 } 686 687 static bool askQuit_RandomMsg(void) 688 { 689 quitType_t *q = &quitMessage[rand() % QUIT_MESSAGES]; 690 691 int16_t button = okBox(q->type, "System request", q->text, NULL); 692 return (button == 1) ? true : false; 693 } 694 695 bool askUnsavedChanges(uint8_t type) 696 { 697 int16_t button; 698 699 if (type == ASK_TYPE_QUIT) 700 { 701 button = okBox(2, "System request", 702 "You have unsaved changes in your song. Do you still want to quit and lose ALL changes?", NULL); 703 } 704 else 705 { 706 button = okBox(2, "System request", 707 "You have unsaved changes in your song. Load new song and lose ALL changes?", NULL); 708 } 709 710 return (button == 1) ? true : false; 711 } 712 713 int16_t quitBox(bool skipQuitMsg) 714 { 715 if (ui.sysReqShown) 716 return 0; 717 718 if (!song.isModified && skipQuitMsg) 719 return 1; 720 721 if (song.isModified) 722 return askUnsavedChanges(ASK_TYPE_QUIT); 723 724 return askQuit_RandomMsg(); 725 }