422 lines
11 KiB
C
422 lines
11 KiB
C
/*
|
|
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
|
|
* Daniel Caujolle-Bert, F1RMB
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
|
|
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
#include "user_interface/uiGlobals.h"
|
|
#include "user_interface/menuSystem.h"
|
|
#include "user_interface/uiLocalisation.h"
|
|
#include "user_interface/uiUtilities.h"
|
|
#include "utils.h"
|
|
|
|
static menuStatus_t menuMessageBoxExitCode;
|
|
|
|
static void handleEvent(uiEvent_t *ev);
|
|
static void updateScreen(bool forceRedraw);
|
|
static void displayMessage(void);
|
|
static void displayButtons(void);
|
|
|
|
menuStatus_t uiMessageBox(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
static uint32_t curm = 0;
|
|
|
|
if (isFirstRun)
|
|
{
|
|
freqEnterReset();
|
|
uiDataGlobal.MessageBox.keyPressed = 0;
|
|
|
|
updateScreen(true);
|
|
menuMessageBoxExitCode = MENU_STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
bool forceRedraw = false;
|
|
|
|
menuMessageBoxExitCode = MENU_STATUS_SUCCESS;
|
|
|
|
if (ev->events == NO_EVENT)
|
|
{
|
|
// We are entering digits, so update the screen as we have a cursor to blink
|
|
if ((uiDataGlobal.MessageBox.type == MESSAGEBOX_TYPE_PIN_CODE) &&
|
|
(uiDataGlobal.FreqEnter.index >= 0) && ((ev->time - curm) > 300))
|
|
{
|
|
curm = ev->time;
|
|
forceRedraw = true;
|
|
}
|
|
|
|
if (forceRedraw)
|
|
{
|
|
updateScreen(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ev->hasEvent)
|
|
{
|
|
handleEvent(ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
return menuMessageBoxExitCode;
|
|
}
|
|
|
|
static void handleEvent(uiEvent_t *ev)
|
|
{
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW))
|
|
{
|
|
updateScreen(true);
|
|
return;
|
|
}
|
|
|
|
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
int greenHandled = (uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_DISMISS);
|
|
bool res = false;
|
|
|
|
// Ensure we have a complete pin code before calling the validator function
|
|
if ((uiDataGlobal.MessageBox.type == MESSAGEBOX_TYPE_PIN_CODE) &&
|
|
(uiDataGlobal.FreqEnter.index < uiDataGlobal.MessageBox.pinLength))
|
|
{
|
|
return;
|
|
}
|
|
|
|
uiDataGlobal.MessageBox.keyPressed = KEY_GREEN;
|
|
|
|
// Call validator
|
|
if (uiDataGlobal.MessageBox.validatorCallback)
|
|
{
|
|
res = uiDataGlobal.MessageBox.validatorCallback();
|
|
|
|
if (uiDataGlobal.MessageBox.type == MESSAGEBOX_TYPE_PIN_CODE)
|
|
{
|
|
freqEnterReset();
|
|
|
|
if (res == false)
|
|
{
|
|
menuMessageBoxExitCode |= MENU_STATUS_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (greenHandled && (((uiDataGlobal.MessageBox.validatorCallback != NULL) && res) || (uiDataGlobal.MessageBox.validatorCallback == NULL)))
|
|
{
|
|
uiDataGlobal.MessageBox.validatorCallback = NULL;
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
bool redHandled = (uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_OK)
|
|
#if defined(PLATFORM_MD9600)
|
|
&& (uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_ENT)
|
|
#endif
|
|
;
|
|
bool res = false;
|
|
|
|
uiDataGlobal.MessageBox.keyPressed = KEY_RED;
|
|
|
|
// Call validator
|
|
if (uiDataGlobal.MessageBox.validatorCallback)
|
|
{
|
|
res = uiDataGlobal.MessageBox.validatorCallback();
|
|
}
|
|
|
|
if (redHandled && (((uiDataGlobal.MessageBox.validatorCallback != NULL) && res) || (uiDataGlobal.MessageBox.validatorCallback == NULL)))
|
|
{
|
|
uiDataGlobal.MessageBox.validatorCallback = NULL;
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (uiDataGlobal.MessageBox.type == MESSAGEBOX_TYPE_PIN_CODE)
|
|
{
|
|
bool redraw = false;
|
|
|
|
if ((uiDataGlobal.FreqEnter.index > 0) && KEYCHECK_PRESS(ev->keys, KEY_LEFT))
|
|
{
|
|
uiDataGlobal.FreqEnter.index--;
|
|
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = '-';
|
|
redraw = true;
|
|
}
|
|
|
|
if (uiDataGlobal.FreqEnter.index < uiDataGlobal.MessageBox.pinLength)
|
|
{
|
|
int keyval = menuGetKeypadKeyValue(ev, true);
|
|
|
|
if (keyval != 99)
|
|
{
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_0 + keyval);
|
|
voicePromptsPlay();
|
|
|
|
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = (char) keyval + '0';
|
|
uiDataGlobal.FreqEnter.index++;
|
|
redraw = true;
|
|
}
|
|
}
|
|
|
|
if (redraw)
|
|
{
|
|
updateScreen(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void updateScreen(bool forceRedraw)
|
|
{
|
|
static bool blink = false;
|
|
static uint32_t blinkTime = 0;
|
|
|
|
if (forceRedraw)
|
|
{
|
|
displayClearBuf();
|
|
}
|
|
|
|
switch (uiDataGlobal.MessageBox.type)
|
|
{
|
|
case MESSAGEBOX_TYPE_INFO:
|
|
{
|
|
if (uiDataGlobal.MessageBox.decoration != MESSAGEBOX_DECORATION_NONE)
|
|
{
|
|
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG_NOTIFICATION);
|
|
displayDrawRoundRectWithDropShadow(4, (4 + (DISPLAY_V_OFFSET - (DISPLAY_V_OFFSET / 2))), (DISPLAY_SIZE_X - (2 * 4)),
|
|
(((DISPLAY_SIZE_Y - ((uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_NONE) ? FONT_SIZE_3_HEIGHT : 0)) - 6 - DISPLAY_V_EXTRA_PIXELS) + DISPLAY_V_OFFSET), 5, true);
|
|
}
|
|
|
|
if (strlen(uiDataGlobal.MessageBox.message))
|
|
{
|
|
displayThemeApply(THEME_ITEM_FG_NOTIFICATION, THEME_ITEM_BG_NOTIFICATION);
|
|
displayMessage();
|
|
}
|
|
|
|
displayButtons();
|
|
}
|
|
break;
|
|
|
|
case MESSAGEBOX_TYPE_PIN_CODE:
|
|
{
|
|
char pinStr[SCREEN_LINE_BUFFER_SIZE] = { 0 };
|
|
int16_t xCursor = -1;
|
|
int16_t yCursor = -1;
|
|
#if defined(HAS_COLOURS)
|
|
bool dblHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
|
|
#else
|
|
const bool dblHeight = false;
|
|
#endif
|
|
|
|
if (forceRedraw)
|
|
{
|
|
displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
|
|
displayMessage();
|
|
|
|
uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_OK;
|
|
displayButtons();
|
|
}
|
|
else
|
|
{
|
|
// Clear input
|
|
displayThemeResetToDefault();
|
|
displayFillRect(0, TITLE_BOX_HEIGHT + 2, DISPLAY_SIZE_X,
|
|
DISPLAY_SIZE_Y - TITLE_BOX_HEIGHT - FONT_SIZE_3_HEIGHT - 2, true);
|
|
}
|
|
|
|
memset(pinStr, 0, sizeof(pinStr));
|
|
memcpy(pinStr, uiDataGlobal.FreqEnter.digits, SAFE_MIN(uiDataGlobal.MessageBox.pinLength, FREQ_ENTER_DIGITS_MAX));
|
|
|
|
displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
|
|
displayPrintCenteredDoubleHeight((DISPLAY_Y_POS_RX_FREQ - (dblHeight ? FONT_SIZE_3_HEIGHT : 0)), pinStr, FONT_SIZE_3, dblHeight);
|
|
|
|
// Cursor
|
|
if (uiDataGlobal.FreqEnter.index < uiDataGlobal.MessageBox.pinLength)
|
|
{
|
|
xCursor = ((DISPLAY_SIZE_X - (strlen(pinStr) * 8)) >> 1) + (uiDataGlobal.FreqEnter.index * 8);
|
|
yCursor = DISPLAY_Y_POS_RX_FREQ + (FONT_SIZE_3_HEIGHT - 2);
|
|
}
|
|
|
|
if ((xCursor >= 0) && (yCursor >= 0))
|
|
{
|
|
displayDrawFastHLine(xCursor + 1, yCursor, 6, blink);
|
|
|
|
if ((ticksGetMillis() - blinkTime) > 500)
|
|
{
|
|
blinkTime = ticksGetMillis();
|
|
blink = !blink;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
displayThemeResetToDefault();
|
|
displayRender();
|
|
}
|
|
|
|
static void displayMessage(void)
|
|
{
|
|
char msg[SCREEN_LINE_BUFFER_SIZE];
|
|
bool decoration = (uiDataGlobal.MessageBox.decoration != MESSAGEBOX_DECORATION_NONE);
|
|
char *p = strchr(uiDataGlobal.MessageBox.message, '\n');
|
|
uint16_t y = 0;
|
|
uint8_t linesCount = (p ? 1U : 0U);
|
|
uint8_t maxLines =
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_DM1801) || defined(PLATFORM_DM1801A)
|
|
(decoration ? 3U : 4U);
|
|
#else
|
|
4U;
|
|
#endif
|
|
|
|
if (uiDataGlobal.MessageBox.type != MESSAGEBOX_TYPE_PIN_CODE)
|
|
{
|
|
#if ! defined(HAS_COLOURS)
|
|
if (uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_NONE)
|
|
{
|
|
maxLines--;
|
|
}
|
|
#endif
|
|
|
|
while (p)
|
|
{
|
|
linesCount++;
|
|
|
|
if (linesCount == maxLines)
|
|
{
|
|
break;
|
|
}
|
|
|
|
p = strchr(p + 1, '\n');
|
|
}
|
|
|
|
// Y start
|
|
y = (DISPLAY_SIZE_Y - (uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_NONE ? FONT_SIZE_3_HEIGHT : 0) - (linesCount * FONT_SIZE_3_HEIGHT)) >> 1;
|
|
}
|
|
|
|
if ((uiDataGlobal.MessageBox.type != MESSAGEBOX_TYPE_PIN_CODE) && linesCount)
|
|
{
|
|
size_t len = strlen(uiDataGlobal.MessageBox.message);
|
|
char *pb = uiDataGlobal.MessageBox.message;
|
|
char *pe = &uiDataGlobal.MessageBox.message[0] + len;
|
|
uint16_t count;
|
|
|
|
p = strchr(uiDataGlobal.MessageBox.message, '\n');
|
|
|
|
while (linesCount > 1U)
|
|
{
|
|
count = SAFE_MIN((uint16_t)(p - pb), (decoration ? 14U : 16U));
|
|
|
|
memcpy(msg, pb, count);
|
|
msg[count] = '\0';
|
|
|
|
displayPrintCentered(y, msg, FONT_SIZE_3);
|
|
y += FONT_SIZE_3_HEIGHT;
|
|
|
|
pb = p + 1;
|
|
p = strchr(pb, '\n');
|
|
|
|
linesCount--;
|
|
}
|
|
|
|
if (pb < pe)
|
|
{
|
|
count = SAFE_MIN((uint16_t)(pe - pb), (decoration ? 14U : 16U));
|
|
|
|
p++;
|
|
|
|
// Looking for another new line
|
|
if ((p = strchr(pb, '\n')) != NULL)
|
|
{
|
|
count = (uint16_t)(p - pb);
|
|
}
|
|
|
|
memcpy(msg, pb, count);
|
|
msg[count] = '\0';
|
|
|
|
displayPrintCentered(y, msg, FONT_SIZE_3);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (uiDataGlobal.MessageBox.type == MESSAGEBOX_TYPE_PIN_CODE)
|
|
{
|
|
decoration = true; // Override decoration
|
|
|
|
// Display title
|
|
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG_MENU_NAME);
|
|
displayDrawRoundRectWithDropShadow(2, 2, (DISPLAY_SIZE_X - 6), TITLE_BOX_HEIGHT, 3, true);
|
|
displayThemeApply(THEME_ITEM_FG_MENU_NAME, THEME_ITEM_BG_MENU_NAME);
|
|
|
|
y = 3U;
|
|
}
|
|
else
|
|
{
|
|
// V center into the decoration frame area
|
|
uint16_t startY = (4 + (DISPLAY_V_OFFSET - (DISPLAY_V_OFFSET / 2)));
|
|
uint16_t height = (((DISPLAY_SIZE_Y - ((uiDataGlobal.MessageBox.buttons != MESSAGEBOX_BUTTONS_NONE) ? FONT_SIZE_3_HEIGHT : 0)) - 6 - DISPLAY_V_EXTRA_PIXELS) + DISPLAY_V_OFFSET) - 2;
|
|
|
|
y = ((startY + (height >> 1)) - (FONT_SIZE_3_HEIGHT >> 1));
|
|
}
|
|
|
|
snprintf(msg, (decoration ? 15 : 17), "%s", uiDataGlobal.MessageBox.message);
|
|
displayPrintCentered(y, msg, FONT_SIZE_3);
|
|
}
|
|
}
|
|
|
|
static void displayButtons(void)
|
|
{
|
|
switch (uiDataGlobal.MessageBox.buttons)
|
|
{
|
|
case MESSAGEBOX_BUTTONS_OK:
|
|
displayDrawChoice(CHOICE_OK, false);
|
|
break;
|
|
#if defined(PLATFORM_MD9600)
|
|
case MESSAGEBOX_BUTTONS_ENT:
|
|
displayDrawChoice(CHOICE_ENT, false);
|
|
break;
|
|
#endif
|
|
case MESSAGEBOX_BUTTONS_YESNO:
|
|
displayDrawChoice(CHOICE_YESNO, false);
|
|
break;
|
|
case MESSAGEBOX_BUTTONS_DISMISS:
|
|
displayDrawChoice(CHOICE_DISMISS, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|