ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

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 }