ref: 3e7b18472cba687a04f6cd9be66ad531ed24283c
dir: /src/ft2_sysreqs.c/
#include <stdio.h> // vsnprintf()
#include <stdint.h>
#include <stdbool.h>
#include "ft2_config.h"
#include "ft2_gui.h"
#include "ft2_mouse.h"
#include "ft2_keyboard.h"
#include "ft2_textboxes.h"
#include "ft2_video.h"
#include "ft2_sysreqs.h"
#include "ft2_structs.h"
#include "ft2_events.h"
#define SYSTEM_REQUEST_H 67
#define SYSTEM_REQUEST_Y 249
#define SYSTEM_REQUEST_Y_EXT 91
#define NUM_SYSREQ_TYPES 10
// globalized
okBoxData_t okBoxData;
void (*loaderMsgBox)(const char *, ...);
int16_t (*loaderSysReq)(int16_t, const char *, const char *);
// ----------------
static char *buttonText[NUM_SYSREQ_TYPES][5] =
{
{ "OK", "","","","" },
{ "OK", "Cancel", "","","" },
{ "Yes", "No", "","","" },
{ "","","","","" }, // deprecated
{ "All", "Song", "Instruments", "Cancel", "" },
{ "Read left", "Read right", "Convert", "", "" },
{ "OK", "","","","" },
{ "OK", "","","","" },
{ "Sorry...", "","","","" },
{ "Mono", "Stereo", "Cancel", "","" }
};
static SDL_Keycode shortCut[NUM_SYSREQ_TYPES][5] =
{
{ SDLK_o, 0, 0, 0, 0 },
{ SDLK_o, SDLK_c, 0, 0, 0 },
{ SDLK_y, SDLK_n, 0, 0, 0 },
{ 0, 0, 0, 0, 0 }, // deprecated
{ SDLK_a, SDLK_s, SDLK_i, SDLK_c, 0 },
{ SDLK_l, SDLK_r, SDLK_c, 0, 0 },
{ SDLK_o, 0, 0, 0, 0 },
{ SDLK_o, 0, 0, 0, 0 },
{ SDLK_s, 0, 0, 0, 0 },
{ SDLK_m, SDLK_s, SDLK_c, 0, 0 },
};
typedef struct quitType_t
{
const char *text;
uint8_t typ;
} quitType_t;
#define QUIT_MESSAGES 11
// 8bitbubsy: Removed unsuitable/offensive ones...
static quitType_t quitMessage[QUIT_MESSAGES] =
{
{ "Do you really want to quit?", 2 },
{ "Tired already?", 2 },
{ "Dost thou wish to leave with such hasty abandon?", 2 },
{ "So, you think you can quit this easily, huh?", 2 },
{ "Hey, what is the matter? You are not quiting now, are you?", 2 },
{ "Rome was not built in one day! Quit really?", 2 },
{ "Did you really press the right key?", 2 },
{ "Hope ya did some good. Press >OK< to quit.", 1 },
{ "Quit? Only for a good reason you are allowed to press >OK<.", 1 },
{ "Are we at the end of a Fasttracker round?", 2 },
{ "Hope you're doing the compulsory \"Exit ceremony\" before pressing >OK<.", 1 },
};
void myLoaderMsgBoxThreadSafe(const char *fmt, ...)
{
char strBuf[512];
va_list args;
// format the text string
va_start(args, fmt);
vsnprintf(strBuf, sizeof (strBuf), fmt, args);
va_end(args);
okBoxThreadSafe(0, "System message", fmt);
}
void myLoaderMsgBox(const char *fmt, ...)
{
char strBuf[512];
va_list args;
// format the text string
va_start(args, fmt);
vsnprintf(strBuf, sizeof (strBuf), fmt, args);
va_end(args);
okBox(0, "System message", fmt);
}
static void drawWindow(uint16_t w)
{
const uint16_t h = SYSTEM_REQUEST_H;
const uint16_t x = (SCREEN_W - w) >> 1;
const uint16_t y = ui.extended ? 91 : SYSTEM_REQUEST_Y;
// main fill
fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
// outer border
vLine(x, y, h - 1, PAL_BUTTON1);
hLine(x + 1, y, w - 2, PAL_BUTTON1);
vLine(x + w - 1, y, h, PAL_BUTTON2);
hLine(x, y + h - 1, w - 1, PAL_BUTTON2);
// inner border
vLine(x + 2, y + 2, h - 5, PAL_BUTTON2);
hLine(x + 3, y + 2, w - 6, PAL_BUTTON2);
vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1);
hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1);
// title bottom line
hLine(x + 3, y + 16, w - 6, PAL_BUTTON2);
hLine(x + 3, y + 17, w - 6, PAL_BUTTON1);
}
static bool mouseButtonDownLogic(uint8_t mouseButton)
{
// if already holding left button and clicking right, don't do mouse down handling
if (mouseButton == SDL_BUTTON_RIGHT && mouse.leftButtonPressed)
{
mouse.rightButtonPressed = true;
return false;
}
// if already holding right button and clicking left, don't do mouse down handling
if (mouseButton == SDL_BUTTON_LEFT && mouse.rightButtonPressed)
{
mouse.leftButtonPressed = true;
return false;
}
if (mouseButton == SDL_BUTTON_LEFT)
mouse.leftButtonPressed = true;
else if (mouseButton == SDL_BUTTON_RIGHT)
mouse.rightButtonPressed = true;
// don't do mouse down testing here if we already are using an object
if (mouse.lastUsedObjectType != OBJECT_NONE)
return false;
// kludge #2
if (mouse.lastUsedObjectType != OBJECT_PUSHBUTTON && mouse.lastUsedObjectID != OBJECT_ID_NONE)
return false;
// kludge #3
if (!mouse.rightButtonPressed)
mouse.lastUsedObjectID = OBJECT_ID_NONE;
return true;
}
static bool mouseButtonUpLogic(uint8_t mouseButton)
{
if (mouseButton == SDL_BUTTON_LEFT)
mouse.leftButtonPressed = false;
else if (mouseButton == SDL_BUTTON_RIGHT)
mouse.rightButtonPressed = false;
editor.textCursorBlinkCounter = 0;
// if we used both mouse button at the same time and released *one*, don't release GUI object
if ( mouse.leftButtonPressed && !mouse.rightButtonPressed) return false;
if (!mouse.leftButtonPressed && mouse.rightButtonPressed) return false;
return true;
}
// WARNING: This routine must ONLY be called from the main input/video thread!
int16_t okBox(int16_t typ, const char *headline, const char *text)
{
#define PUSHBUTTON_W 80
uint16_t i;
SDL_Event inputEvent;
pushButton_t *p;
if (editor.editTextFlag)
exitTextEditing();
// revert "delete/rename" mouse modes (disk op.)
if (mouse.mode != MOUSE_MODE_NORMAL)
setMouseMode(MOUSE_MODE_NORMAL);
if (ui.sysReqShown)
return 0;
SDL_EventState(SDL_DROPFILE, SDL_DISABLE);
ui.sysReqShown = true;
mouseAnimOff();
int16_t oldLastUsedObjectID = mouse.lastUsedObjectID;
int16_t oldLastUsedObjectType = mouse.lastUsedObjectType;
// count number of buttons
uint16_t knp = 0;
while (buttonText[typ][knp][0] != '\0' && knp < 5)
knp++;
uint16_t tlen = textWidth(text);
uint16_t hlen = textWidth(headline);
uint16_t wlen = tlen;
if (hlen > tlen)
wlen = hlen;
uint16_t tx = (knp * 100) - 20;
if (tx > wlen)
wlen = tx;
wlen += 100;
if (wlen > 600)
wlen = 600;
uint16_t headlineX = (SCREEN_W - hlen) >> 1;
uint16_t textX = (SCREEN_W - tlen) >> 1;
uint16_t x = (SCREEN_W - wlen) >> 1;
// the box y position differs in extended pattern editor mode
uint16_t y = ui.extended ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y;
// set up buttons
for (i = 0; i < knp; i++)
{
p = &pushButtons[i];
p->x = ((SCREEN_W - tx) >> 1) + (i * 100);
p->y = y + 42;
p->w = PUSHBUTTON_W;
p->h = 16;
p->caption = buttonText[typ][i];
p->visible = true;
}
// set up checkbox (special okBox types only!)
if (typ >= 6 && typ <= 7)
{
checkBox_t *c = &checkBoxes[0];
c->x = x + 5;
c->y = y + 50;
c->clickAreaWidth = 116;
c->clickAreaHeight = 12;
c->checked = false;
if (typ == 6)
{
// S3M load warning
c->callbackFunc = configToggleImportWarning;
}
else if (typ == 7)
{
// "setting not yet applied"
c->callbackFunc = configToggleNotYetAppliedWarning;
}
c->visible = true;
}
mouse.lastUsedObjectType = OBJECT_NONE;
mouse.lastUsedObjectID = OBJECT_ID_NONE;
mouse.leftButtonPressed = 0;
mouse.rightButtonPressed = 0;
// input/rendering loop
int16_t returnVal = 0;
while (ui.sysReqShown)
{
beginFPSCounter();
readMouseXY();
setSyncedReplayerVars();
if (mouse.leftButtonPressed || mouse.rightButtonPressed)
{
if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON)
handlePushButtonsWhileMouseDown();
else if (mouse.lastUsedObjectType == OBJECT_CHECKBOX)
handleCheckBoxesWhileMouseDown();
}
while (SDL_PollEvent(&inputEvent))
{
handleWaitVblQuirk(&inputEvent);
if (inputEvent.type == SDL_KEYDOWN)
{
if (inputEvent.key.keysym.sym == SDLK_ESCAPE)
{
if (!inputEvent.key.repeat) // don't let previously held-down ESC immediately close the box
{
returnVal = 0;
ui.sysReqShown = false;
}
}
else if (inputEvent.key.keysym.sym == SDLK_RETURN)
{
returnVal = 1;
ui.sysReqShown = false;
keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
}
for (i = 0; i < knp; i++)
{
if (shortCut[typ][i] == inputEvent.key.keysym.sym)
{
returnVal = i + 1;
ui.sysReqShown = false;
keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
break;
}
}
}
else if (inputEvent.type == SDL_MOUSEBUTTONUP)
{
if (mouseButtonUpLogic(inputEvent.button.button))
{
if (typ >= 6 && typ <= 7)
testCheckBoxMouseRelease();
returnVal = testPushButtonMouseRelease(false) + 1;
if (returnVal > 0)
ui.sysReqShown = false;
mouse.lastUsedObjectID = OBJECT_ID_NONE;
mouse.lastUsedObjectType = OBJECT_NONE;
}
}
else if (inputEvent.type == SDL_MOUSEBUTTONDOWN)
{
if (mouseButtonDownLogic(inputEvent.button.button))
{
if (testPushButtonMouseDown()) continue;
if (testCheckBoxMouseDown()) continue;
}
}
if (!ui.sysReqShown)
break;
}
if (!ui.sysReqShown)
break;
handleRedrawing();
// draw OK box
drawWindow(wlen);
textOutShadow(headlineX, y + 4, PAL_FORGRND, PAL_BUTTON2, headline);
textOutShadow(textX, y + 24, PAL_FORGRND, PAL_BUTTON2, text);
for (i = 0; i < knp; i++) drawPushButton(i);
if (typ >= 6 && typ <= 7)
{
drawCheckBox(0);
textOutShadow(x + 21, y + 52, PAL_FORGRND, PAL_BUTTON2, "Don't show again");
}
flipFrame();
endFPSCounter();
}
for (i = 0; i < knp; i++)
hidePushButton(i);
if (typ >= 6 && typ <= 7)
hideCheckBox(0);
mouse.lastUsedObjectID = oldLastUsedObjectID;
mouse.lastUsedObjectType = oldLastUsedObjectType;
unstuckLastUsedGUIElement();
showBottomScreen();
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
return returnVal;
}
/* WARNING:
** - This routine must ONLY be called from the main input/video thread!!
** - edText must be null-terminated
*/
int16_t inputBox(int16_t typ, const char *headline, char *edText, uint16_t maxStrLen)
{
#define PUSHBUTTON_W 80
#define TEXTBOX_W 250
uint16_t wlen, i;
SDL_Event inputEvent;
if (editor.editTextFlag)
exitTextEditing();
// revert "delete/rename" mouse modes (disk op.)
if (mouse.mode != MOUSE_MODE_NORMAL)
setMouseMode(MOUSE_MODE_NORMAL);
if (ui.sysReqShown)
return 0;
int16_t oldLastUsedObjectID = mouse.lastUsedObjectID;
int16_t oldLastUsedObjectType = mouse.lastUsedObjectType;
textBox_t *t = &textBoxes[0];
// set up text box
memset(t, 0, sizeof (textBox_t));
t->w = TEXTBOX_W;
t->h = 12;
t->tx = 2;
t->ty = 1;
t->textPtr = edText;
t->maxChars = maxStrLen;
t->changeMouseCursor = true;
t->renderBufW = (9 + 1) * t->maxChars; // 9 = max character/glyph width possible
t->renderBufH = 10; // 10 = max character height possible
t->renderW = t->w - (t->tx << 1);
t->renderBuf = (uint8_t *)malloc(t->renderBufW * t->renderBufH * sizeof (int8_t));
if (t->renderBuf == NULL)
{
okBox(0, "System message", "Not enough memory!");
return 0;
}
SDL_EventState(SDL_DROPFILE, SDL_DISABLE);
ui.sysReqShown = true;
mouseAnimOff();
wlen = textWidth(headline);
uint16_t headlineX = (SCREEN_W - wlen) >> 1;
// count number of buttons
uint16_t knp = 0;
while (buttonText[typ][knp][0] != '\0' && knp < 5)
knp++;
uint16_t tx = TEXTBOX_W;
if (tx > wlen)
wlen = tx;
tx = (knp * 100) - 20;
if (tx > wlen)
wlen = tx;
wlen += 100;
if (wlen > 600)
wlen = 600;
// the box y position differs in extended pattern editor mode
uint16_t y = ui.extended ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y;
// set further text box settings
t->x = (SCREEN_W - TEXTBOX_W) >> 1;
t->y = y + 24;
t->visible = true;
// setup buttons
pushButton_t *p = pushButtons;
for (i = 0; i < knp; i++, p++)
{
p->w = PUSHBUTTON_W;
p->h = 16;
p->x = ((SCREEN_W - tx) >> 1) + (i * 100);
p->y = y + 42;
p->caption = buttonText[typ][i];
p->visible = true;
}
keyb.leftShiftPressed = false; // kludge
setTextCursorToEnd(t);
mouse.lastEditBox = 0;
editor.editTextFlag = true;
SDL_StartTextInput();
mouse.lastUsedObjectType = OBJECT_NONE;
mouse.lastUsedObjectID = OBJECT_ID_NONE;
mouse.leftButtonPressed = 0;
mouse.rightButtonPressed = 0;
// input/rendering loop
int16_t returnVal = 0;
while (ui.sysReqShown)
{
beginFPSCounter();
readMouseXY();
readKeyModifiers();
setSyncedReplayerVars();
if (mouse.leftButtonPressed || mouse.rightButtonPressed)
{
if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON)
handlePushButtonsWhileMouseDown();
else if (mouse.lastUsedObjectType == OBJECT_TEXTBOX)
handleTextBoxWhileMouseDown();
}
while (SDL_PollEvent(&inputEvent))
{
handleWaitVblQuirk(&inputEvent);
if (inputEvent.type == SDL_TEXTINPUT)
{
if (editor.editTextFlag)
{
if (keyb.ignoreTextEditKey)
{
keyb.ignoreTextEditKey = false;
continue;
}
char *inputText = utf8ToCp437(inputEvent.text.text, false);
if (inputText != NULL)
{
if (inputText[0] != '\0')
handleTextEditInputChar(inputText[0]);
free(inputText);
}
}
}
else if (inputEvent.type == SDL_KEYDOWN)
{
if (inputEvent.key.keysym.sym == SDLK_ESCAPE)
{
returnVal = 0;
ui.sysReqShown = false;
}
else if (inputEvent.key.keysym.sym == SDLK_RETURN)
{
returnVal = 1;
ui.sysReqShown = false;
keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
}
if (editor.editTextFlag)
{
handleTextEditControl(inputEvent.key.keysym.sym);
}
else
{
for (i = 0; i < knp; i++)
{
if (shortCut[1][i] == inputEvent.key.keysym.sym)
{
returnVal = i + 1;
ui.sysReqShown = false;
keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
break;
}
}
}
}
else if (inputEvent.type == SDL_MOUSEBUTTONUP)
{
if (mouseButtonUpLogic(inputEvent.button.button))
{
returnVal = testPushButtonMouseRelease(false) + 1;
if (returnVal > 0)
ui.sysReqShown = false;
mouse.lastUsedObjectID = OBJECT_ID_NONE;
mouse.lastUsedObjectType = OBJECT_NONE;
}
}
else if (inputEvent.type == SDL_MOUSEBUTTONDOWN)
{
if (mouseButtonDownLogic(inputEvent.button.button))
{
if (testTextBoxMouseDown()) continue;
if (testPushButtonMouseDown()) continue;
}
}
if (!ui.sysReqShown)
break;
}
if (!ui.sysReqShown)
break;
handleRedrawing();
// draw input box
drawWindow(wlen);
textOutShadow(headlineX, y + 4, PAL_FORGRND, PAL_BUTTON2, headline);
clearRect(t->x, t->y, t->w, t->h);
hLine(t->x - 1, t->y - 1, t->w + 2, PAL_BUTTON2);
vLine(t->x - 1, t->y, t->h + 1, PAL_BUTTON2);
hLine(t->x, t->y + t->h, t->w + 1, PAL_BUTTON1);
vLine(t->x + t->w, t->y, t->h, PAL_BUTTON1);
drawTextBox(0);
for (i = 0; i < knp; i++) drawPushButton(i);
flipFrame();
endFPSCounter();
}
editor.editTextFlag = false;
SDL_StopTextInput();
for (i = 0; i < knp; i++)
hidePushButton(i);
hideTextBox(0);
free(t->renderBuf);
mouse.lastUsedObjectID = oldLastUsedObjectID;
mouse.lastUsedObjectType = oldLastUsedObjectType;
unstuckLastUsedGUIElement();
showBottomScreen();
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
return returnVal;
}
// WARNING: This routine must NOT be called from the main input/video thread!
int16_t okBoxThreadSafe(int16_t typ, const char *headline, const char *text)
{
if (!editor.mainLoopOngoing)
return 0; // main loop was not even started yet, bail out.
// block multiple calls before they are completed (for safety)
while (okBoxData.active)
SDL_Delay(1000 / VBLANK_HZ);
okBoxData.typ = typ;
okBoxData.headline = headline;
okBoxData.text = text;
okBoxData.active = true;
while (okBoxData.active)
SDL_Delay(1000 / VBLANK_HZ);
return okBoxData.returnData;
}
static bool askQuit_RandomMsg(void)
{
uint8_t msg = rand() % QUIT_MESSAGES;
int16_t button = okBox(quitMessage[msg].typ, "System request", quitMessage[msg].text);
return (button == 1) ? true : false;
}
bool askUnsavedChanges(uint8_t type)
{
int16_t button;
if (type == ASK_TYPE_QUIT)
{
button = okBox(2, "System request",
"You have unsaved changes in your song. Do you still want to quit and lose ALL changes?");
}
else
{
button = okBox(2, "System request",
"You have unsaved changes in your song. Load new song and lose ALL changes?");
}
return (button == 1) ? true : false;
}
int16_t quitBox(bool skipQuitMsg)
{
if (ui.sysReqShown)
return 0;
if (!song.isModified && skipQuitMsg)
return 1;
if (song.isModified)
return askUnsavedChanges(ASK_TYPE_QUIT);
return askQuit_RandomMsg();
}