Files
uv-k1-k5v3-firmware-custom/App/app/menu.c
T

2318 lines
64 KiB
C

/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#if !defined(ENABLE_OVERLAY)
#include "py32f0xx.h"
#endif
#include "app/dtmf.h"
#if defined(ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG) && defined(ENABLE_FMRADIO_EMBEDDED)
#include "app/fm.h"
#endif
#include "app/generic.h"
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
#include "app/action.h"
#endif
#include "app/menu.h"
#include "app/scanner.h"
#include "audio.h"
#include "board.h"
#include "driver/backlight.h"
#include "driver/bk4819.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "driver/keyboard.h"
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
#include "driver/mb_flash.h"
#include "ui/multiboot.h"
#endif
#include "frequencies.h"
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
#include "functions.h"
#endif
#include "helper/battery.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "../driver/st7565.h"
#if defined(ENABLE_OVERLAY)
#include "sram-overlay.h"
#endif
#include "ui/inputbox.h"
#include "ui/menu.h"
#include "ui/ui.h"
uint8_t gUnlockAllTxConfCnt;
bool gScanMixEditorActive;
uint8_t gScanMixEditorCursor;
uint32_t gScanMixEditorMask;
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
// Keep the confirmation key routed to the menu until its release is consumed.
static bool gConfigBankExitPending;
static void MENU_ApplyConfigBank(uint8_t bank)
{
SCANNER_Stop();
#ifdef ENABLE_FMRADIO_EMBEDDED
if (gFmRadioMode)
FM_TurnOff();
#endif
FUNCTION_Select(FUNCTION_FOREGROUND);
AUDIO_AudioPathOff();
gEnableSpeaker = false;
gMonitor = false;
gRxReceptionMode = RX_MODE_NONE;
gDualWatchActive = false;
gScheduleDualWatch = true;
gDualWatchCountdown_10ms = 0;
/* No state originating in the old bank may be written after remapping. */
gRequestSaveSettings = false;
gRequestSaveVFO = false;
gRequestSaveChannel = 0;
#ifdef ENABLE_FMRADIO_EMBEDDED
gRequestSaveFM = false;
gFlagSaveFM = false;
#endif
MB_ApplyBankMapping(bank);
SETTINGS_InitEEPROM(true);
/* These shadows preserve the configured RX mode while scan and Full Watch
* temporarily alter the live values. They must follow the new bank. */
gDW = gEeprom.DUAL_WATCH;
gCB = gEeprom.CROSS_BAND_RX_TX;
gSaveRxMode = false;
/* Cancel a temporary forced-backlight mode from the previous bank. */
gBackLight = false;
gBacklightTimeOriginal = 0;
BACKLIGHT_SetBrightness(gEeprom.BACKLIGHT_MAX);
/* Reapply the complete live LCD setup without a software reset. This also
* commits the new bank's inversion and contrast before the next redraw. */
ST7565_FixInterfGlitch();
UI_MENU_BuildView();
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
gFlagReconfigureVfos = false;
gConfigBankExitPending = true;
gUpdateStatus = true;
gUpdateDisplay = true;
}
#endif
static void MENU_OpenScanMixEditor(void)
{
gScanMixEditorMask = gEeprom.SCAN_LIST_MIX_MASK & SCAN_LIST_MIX_MASK_ALL;
if (gScanMixEditorMask == 0)
gScanMixEditorMask = SCAN_LIST_MIX_MASK_ALL;
gScanMixEditorCursor = 0;
while (gScanMixEditorCursor < MR_CHANNELS_LIST &&
(gScanMixEditorMask & (1u << gScanMixEditorCursor)) == 0)
gScanMixEditorCursor++;
if (gScanMixEditorCursor >= MR_CHANNELS_LIST)
gScanMixEditorCursor = 0;
gInputBoxIndex = 0;
gScanMixEditorActive = true;
gRequestDisplayScreen = DISPLAY_MENU;
gUpdateDisplay = true;
}
static void MENU_SaveScanMixEditor(void)
{
gEeprom.SCAN_LIST_MIX_MASK = gScanMixEditorMask & SCAN_LIST_MIX_MASK_ALL;
gEeprom.SCAN_LIST_DEFAULT = SCAN_LIST_MODE_MIX;
gSubMenuSelection = SCAN_LIST_MODE_MIX;
gScanMixEditorActive = false;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
gRequestSaveSettings = true;
gRequestDisplayScreen = DISPLAY_MENU;
}
#ifdef ENABLE_F_CAL_MENU
void writeXtalFreqCal(const int32_t value, const bool update_eeprom)
{
BK4819_WriteRegister(BK4819_REG_3B, 22656 + value);
if (update_eeprom)
{
struct
{
int16_t BK4819_XtalFreqLow;
uint16_t EEPROM_1F8A;
uint16_t EEPROM_1F8C;
uint8_t VOLUME_GAIN;
uint8_t DAC_GAIN;
} __attribute__((packed)) misc;
gEeprom.BK4819_XTAL_FREQ_LOW = value;
// radio 1 .. 04 00 46 00 50 00 2C 0E
// radio 2 .. 05 00 46 00 50 00 2C 0E
//
EEPROM_ReadBuffer(0x1F88, &misc, 8);
misc.BK4819_XtalFreqLow = value;
EEPROM_WriteBuffer(0x1F88, &misc, 8);
}
}
#endif
void MENU_StartCssScan(void)
{
SCANNER_Start(true);
gUpdateStatus = true;
gCssBackgroundScan = true;
gRequestDisplayScreen = DISPLAY_MENU;
}
void MENU_CssScanFound(void)
{
if(gScanCssResultType == CODE_TYPE_DIGITAL || gScanCssResultType == CODE_TYPE_REVERSE_DIGITAL) {
gMenuCursor = UI_MENU_GetViewPos(MENU_R_DCS);
}
else if(gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
gMenuCursor = UI_MENU_GetViewPos(MENU_R_CTCS);
}
MENU_ShowCurrentSetting();
gUpdateStatus = true;
gUpdateDisplay = true;
}
void MENU_StopCssScan(void)
{
gCssBackgroundScan = false;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
gUpdateDisplay = true;
gUpdateStatus = true;
}
// Access object representations through unsigned char; all entries are bytes.
// Boolean settings only accept 0/1 after the common range clamp.
#define MENU_SETTING(id, value, minimum, maximum) \
_Static_assert(sizeof(value) == 1, "Menu setting must occupy one byte"); \
_Static_assert(_Generic((value), bool: (minimum == 0 && maximum <= 1), \
unsigned char: 1, default: 0), "Unsupported menu setting type"); \
_Static_assert((id) <= UINT8_MAX && (minimum) >= 0 && \
(maximum) <= UINT8_MAX && (minimum) <= (maximum), "Invalid menu descriptor");
#include "menu_settings.def"
#undef MENU_SETTING
typedef struct {
unsigned char *value;
uint8_t id;
uint8_t minimum;
uint8_t maximum;
} MenuSetting;
static const MenuSetting gMenuSettings[] = {
#define MENU_SETTING(id, value, minimum, maximum) \
{ (unsigned char *)&(value), (id), (minimum), (maximum) },
#include "menu_settings.def"
#undef MENU_SETTING
};
static const MenuSetting *MENU_FindSetting(uint8_t id)
{
for (unsigned i = 0; i < ARRAY_SIZE(gMenuSettings); i++)
if (gMenuSettings[i].id == id)
return &gMenuSettings[i];
return NULL;
}
int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
{
const MenuSetting *setting = MENU_FindSetting(menu_id);
if (setting != NULL)
{
*pMin = setting->minimum;
*pMax = setting->maximum;
return 0;
}
*pMin = 0;
switch (menu_id)
{
case MENU_SQL:
//*pMin = 0;
*pMax = 9;
break;
case MENU_STEP:
//*pMin = 0;
*pMax = STEP_N_ELEM - 1;
break;
case MENU_ABR:
//*pMin = 0;
*pMax = 61;
break;
case MENU_ABR_MIN:
//*pMin = 0;
*pMax = 9;
break;
case MENU_ABR_MAX:
*pMin = 1;
*pMax = 10;
break;
case MENU_F_LOCK:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_F_LOCK) - 1;
break;
case MENU_TXP:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_TXP) - 1;
break;
case MENU_SFT_D:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SFT_D) - 1;
break;
case MENU_TDR:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_RXMode) - 1;
break;
#ifdef ENABLE_VOICE
case MENU_VOICE:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_VOICE) - 1;
break;
#endif
case MENU_ROGER:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_ROGER) - 1;
break;
case MENU_PONMSG:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_PONMSG) - 1;
break;
case MENU_R_DCS:
case MENU_T_DCS:
//*pMin = 0;
*pMax = 208;
//*pMax = (DCS_OPTION_COUNT * 2);
break;
case MENU_R_CTCS:
case MENU_T_CTCS:
//*pMin = 0;
*pMax = ARRAY_SIZE(CTCSS_Options);
break;
case MENU_W_N:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_W_N) - 1;
break;
case MENU_RESET:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_RESET) - 1;
break;
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
case MENU_SET_CFG:
//*pMin = 0;
*pMax = MB_BANK_COUNT - 1;
break;
#endif
case MENU_COMPAND:
case MENU_ABR_ON_TX_RX:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_RX_TX) - 1;
break;
case MENU_BCL:
#ifdef ENABLE_DTMF_CALLING
case MENU_D_DCD:
#endif
case MENU_D_LIVE_DEC:
#ifdef ENABLE_NOAA
case MENU_NOAA_S:
#endif
#ifndef ENABLE_FEAT_F4HWN
case MENU_350TX:
case MENU_200TX:
case MENU_500TX:
#endif
case MENU_350EN:
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCREN:
#endif
#ifdef ENABLE_FEAT_F4HWN
case MENU_S_PRI:
#endif
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_OFF_ON) - 1;
break;
case MENU_AM:
//*pMin = 0;
*pMax = ARRAY_SIZE(gModulationStr) - 1;
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCR:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SCRAMBLER) - 1;
break;
#endif
case MENU_AUTOLK:
*pMax = 40;
break;
#ifdef ENABLE_VOX
case MENU_VOX:
*pMax = 10;
break;
#endif
case MENU_MEM_CH:
case MENU_1_CALL:
case MENU_DEL_CH:
case MENU_MEM_NAME:
//*pMin = 0;
*pMax = MR_CHANNEL_LAST;
break;
case MENU_S_PRI_CH_1:
case MENU_S_PRI_CH_2:
//*pMin = 0;
*pMax = MR_CHANNEL_LAST + 2;
break;
case MENU_MIC:
//*pMin = 0;
*pMax = 8;
break;
case MENU_LIST_CH:
//*pMin = 0;
*pMax = SCAN_LIST_MODE_ALL;
break;
case MENU_PTT_ID:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_PTT_ID) - 1;
break;
case MENU_D_PRE:
*pMin = 3;
*pMax = 99;
break;
#ifdef ENABLE_DTMF_CALLING
case MENU_D_LIST:
*pMin = 1;
*pMax = 16;
break;
#endif
#ifdef ENABLE_F_CAL_MENU
case MENU_F_CALI:
*pMin = -50;
*pMax = +50;
break;
#endif
case MENU_BATCAL:
*pMin = 1500;
*pMax = 3500;
break;
case MENU_F1SHRT:
case MENU_F1LONG:
case MENU_F2SHRT:
case MENU_F2LONG:
case MENU_MLONG:
//*pMin = 0;
*pMax = SIDEFUNCTION_COUNT - 1;
break;
#ifdef ENABLE_FEAT_F4HWN
case MENU_SET_PWR:
*pMax = ARRAY_SIZE(gSubMenu_SET_PWR) - 1;
break;
case MENU_SET_PTT:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_PTT) - 1;
break;
case MENU_TX_LOCK:
#ifdef ENABLE_FEAT_F4HWN_INV
case MENU_SET_INV:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_OFF_ON) - 1;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
//*pMin = 0;
if(gTxVfo->Modulation == MODULATION_AM)
*pMax = ARRAY_SIZE(gSubMenu_SET_AUD_AM) - 1;
else if (gTxVfo->Modulation == MODULATION_USB)
*pMax = 0;
else
*pMax = ARRAY_SIZE(gSubMenu_SET_AUD_FM) - 1;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
case MENU_SET_NFM:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_NFM) - 1;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
case MENU_SET_KEY:
//*pMin = 0;
*pMax = 4;
break;
#endif
#endif
case MENU_VOL: {
// SysInf paginates:
// page 0 = identity,
// page 1 = build date/time,
// page 2 = battery,
// +1 if F4HWN_MEM (Flash/SRAM),
// +2 if F4HWN_QRCODE (Code QR + Wiki QR).
int32_t vol_max = 0;
#ifdef ENABLE_FEAT_F4HWN
vol_max += 2;
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
vol_max += 1;
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
vol_max += 2;
#endif
if (vol_max == 0) return -1;
*pMax = vol_max;
break;
}
default:
return -1;
}
return 0;
}
void MENU_AcceptSetting(void)
{
const uint8_t menu_id = UI_MENU_GetCurrentMenuId();
const MenuSetting *setting = MENU_FindSetting(menu_id);
if (setting != NULL)
{
if (gSubMenuSelection < setting->minimum)
gSubMenuSelection = setting->minimum;
else if (gSubMenuSelection > setting->maximum)
gSubMenuSelection = setting->maximum;
*setting->value = (unsigned char)gSubMenuSelection;
gRequestSaveSettings = true;
return;
}
int32_t Min;
int32_t Max;
FREQ_Config_t *pConfig = &gTxVfo->freq_config_RX;
if (!MENU_GetLimits(menu_id, &Min, &Max))
{
if (gSubMenuSelection < Min) gSubMenuSelection = Min;
else
if (gSubMenuSelection > Max) gSubMenuSelection = Max;
}
switch (menu_id)
{
default:
return;
case MENU_SQL:
gEeprom.SQUELCH_LEVEL = gSubMenuSelection;
gVfoConfigureMode = VFO_CONFIGURE;
#ifdef ENABLE_FEAT_F4HWN
gSquelchLevelOriginal = 10;
#endif
break;
case MENU_STEP:
gTxVfo->STEP_SETTING = FREQUENCY_GetStepIdxFromSortedIdx(gSubMenuSelection);
if (IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
gRequestSaveChannel = 1;
}
return;
case MENU_TXP:
gTxVfo->OUTPUT_POWER = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
case MENU_T_DCS:
pConfig = &gTxVfo->freq_config_TX;
// Fallthrough
case MENU_R_DCS: {
if (gSubMenuSelection == 0) {
if (pConfig->CodeType == CODE_TYPE_CONTINUOUS_TONE) {
return;
}
pConfig->Code = 0;
pConfig->CodeType = CODE_TYPE_OFF;
}
else if (gSubMenuSelection < 105) {
pConfig->CodeType = CODE_TYPE_DIGITAL;
pConfig->Code = gSubMenuSelection - 1;
}
else {
pConfig->CodeType = CODE_TYPE_REVERSE_DIGITAL;
pConfig->Code = gSubMenuSelection - 105;
}
gRequestSaveChannel = 1;
return;
}
case MENU_T_CTCS:
pConfig = &gTxVfo->freq_config_TX;
[[fallthrough]];
case MENU_R_CTCS: {
if (gSubMenuSelection == 0) {
if (pConfig->CodeType != CODE_TYPE_CONTINUOUS_TONE) {
return;
}
pConfig->Code = 0;
pConfig->CodeType = CODE_TYPE_OFF;
}
else {
pConfig->Code = gSubMenuSelection - 1;
pConfig->CodeType = CODE_TYPE_CONTINUOUS_TONE;
}
gRequestSaveChannel = 1;
return;
}
case MENU_SFT_D:
gTxVfo->TX_OFFSET_FREQUENCY_DIRECTION = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
case MENU_OFFSET:
gTxVfo->TX_OFFSET_FREQUENCY = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
case MENU_W_N:
gTxVfo->CHANNEL_BANDWIDTH = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCR:
gTxVfo->SCRAMBLING_TYPE = gSubMenuSelection;
#if 0
if (gSubMenuSelection > 0 && gSetting_ScrambleEnable)
BK4819_EnableScramble(gSubMenuSelection - 1);
else
BK4819_DisableScramble();
#endif
gRequestSaveChannel = 1;
return;
#endif
case MENU_BCL:
gTxVfo->BUSY_CHANNEL_LOCK = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
case MENU_MEM_CH:
gTxVfo->CHANNEL_SAVE = gSubMenuSelection;
#if 0
gEeprom.MrChannel[0] = gSubMenuSelection;
#else
gEeprom.MrChannel[gEeprom.TX_VFO] = gSubMenuSelection;
#endif
gRequestSaveChannel = 2;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
return;
case MENU_MEM_NAME:
for (int i = 9; i >= 0; i--) {
if (edit[i] != ' ' && edit[i] != 0x00 && edit[i] != 0xff)
break;
edit[i] = ' ';
}
SETTINGS_SaveChannelName(gSubMenuSelection, edit);
return;
case MENU_S_PRI_CH_1:
gEeprom.SCANLIST_PRIORITY_CH[0] = gSubMenuSelection;
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
gFlagReconfigureVfos = true;
#endif
break;
case MENU_S_PRI_CH_2:
gEeprom.SCANLIST_PRIORITY_CH[1] = gSubMenuSelection;
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
gFlagReconfigureVfos = true;
#endif
break;
#ifdef ENABLE_VOX
case MENU_VOX:
gEeprom.VOX_SWITCH = gSubMenuSelection != 0;
if (gEeprom.VOX_SWITCH)
gEeprom.VOX_LEVEL = gSubMenuSelection - 1;
SETTINGS_LoadCalibration();
gFlagReconfigureVfos = true;
gUpdateStatus = true;
break;
#endif
case MENU_ABR:
gEeprom.BACKLIGHT_TIME = gSubMenuSelection;
#ifdef ENABLE_FEAT_F4HWN
gBackLight = false;
#endif
break;
case MENU_ABR_MIN:
gEeprom.BACKLIGHT_MIN = gSubMenuSelection;
gEeprom.BACKLIGHT_MAX = MAX(gSubMenuSelection + 1 , gEeprom.BACKLIGHT_MAX);
break;
case MENU_ABR_MAX:
gEeprom.BACKLIGHT_MAX = gSubMenuSelection;
gEeprom.BACKLIGHT_MIN = MIN(gSubMenuSelection - 1, gEeprom.BACKLIGHT_MIN);
break;
case MENU_ABR_ON_TX_RX:
gSetting_backlight_on_tx_rx = gSubMenuSelection;
break;
case MENU_TDR:
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
ACTION_SetRxMode(gSubMenuSelection);
#else
gEeprom.DUAL_WATCH = (gEeprom.TX_VFO + 1) * (gSubMenuSelection & 1);
gEeprom.CROSS_BAND_RX_TX = (gEeprom.TX_VFO + 1) * ((gSubMenuSelection & 2) > 0);
#endif
#ifdef ENABLE_FEAT_F4HWN
gDW = gEeprom.DUAL_WATCH;
gCB = gEeprom.CROSS_BAND_RX_TX;
gSaveRxMode = true;
#endif
gFlagReconfigureVfos = true;
gUpdateStatus = true;
break;
#ifdef ENABLE_VOICE
case MENU_VOICE:
gEeprom.VOICE_PROMPT = gSubMenuSelection;
gUpdateStatus = true;
break;
#endif
case MENU_AUTOLK:
gEeprom.AUTO_KEYPAD_LOCK = gSubMenuSelection;
gKeyLockCountdown = gEeprom.AUTO_KEYPAD_LOCK * 30; // 15 seconds step
break;
case MENU_LIST_CH:
gTxVfo->SCANLIST_PARTICIPATION = gSubMenuSelection;
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
return;
case MENU_MIC:
gEeprom.MIC_SENSITIVITY = gSubMenuSelection;
SETTINGS_LoadCalibration();
gFlagReconfigureVfos = true;
break;
case MENU_COMPAND:
gTxVfo->Compander = gSubMenuSelection;
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
// gRequestSaveChannel = 1;
return;
case MENU_1_CALL:
gEeprom.CHAN_1_CALL = gSubMenuSelection;
break;
case MENU_S_PRI:
gEeprom.SCAN_LIST_ENABLED = gSubMenuSelection;
break;
case MENU_D_PRE:
gEeprom.DTMF_PRELOAD_TIME = gSubMenuSelection * 10;
break;
case MENU_PTT_ID:
gTxVfo->DTMF_PTT_ID_TX_MODE = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
#ifdef ENABLE_DTMF_CALLING
case MENU_D_DCD:
gTxVfo->DTMF_DECODING_ENABLE = gSubMenuSelection;
DTMF_clear_RX();
gRequestSaveChannel = 1;
return;
#endif
case MENU_D_LIVE_DEC:
gSetting_live_DTMF_decoder = gSubMenuSelection;
gDTMF_RX_live_timeout = 0;
DTMF_clear_input_box_memory();
if (!gSetting_live_DTMF_decoder)
BK4819_DisableDTMF();
gFlagReconfigureVfos = true;
gUpdateStatus = true;
break;
#ifdef ENABLE_DTMF_CALLING
case MENU_D_LIST:
gDTMF_chosen_contact = gSubMenuSelection - 1;
if (gIsDtmfContactValid)
{
GUI_SelectNextDisplay(DISPLAY_MAIN);
gDTMF_InputMode = true;
gDTMF_InputBox_Index = 3;
memcpy(gDTMF_InputBox, gDTMF_ID, 4);
gRequestDisplayScreen = DISPLAY_INVALID;
}
return;
#endif
case MENU_PONMSG:
gEeprom.POWER_ON_DISPLAY_MODE = gSubMenuSelection;
break;
case MENU_ROGER:
gEeprom.ROGER = gSubMenuSelection;
break;
case MENU_AM:
gTxVfo->Modulation = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
#ifdef ENABLE_NOAA
case MENU_NOAA_S:
gEeprom.NOAA_AUTO_SCAN = gSubMenuSelection;
gFlagReconfigureVfos = true;
break;
#endif
case MENU_DEL_CH:
SETTINGS_UpdateChannel(gSubMenuSelection, NULL, false);
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
return;
case MENU_RESET:
SETTINGS_FactoryReset(gSubMenuSelection);
return;
#ifndef ENABLE_FEAT_F4HWN
case MENU_350TX:
gSetting_350TX = gSubMenuSelection;
break;
#endif
case MENU_F_LOCK: {
if(gSubMenuSelection == F_LOCK_NONE) { // select 10 times to enable
gUnlockAllTxConfCnt++;
#ifdef ENABLE_FEAT_F4HWN
if(gUnlockAllTxConfCnt < 3)
#else
if(gUnlockAllTxConfCnt < 10)
#endif
return;
}
else
gUnlockAllTxConfCnt = 0;
gSetting_F_LOCK = gSubMenuSelection;
#ifdef ENABLE_FEAT_F4HWN
if(gSetting_F_LOCK == F_LOCK_ALL) {
SETTINGS_ResetTxLock();
}
#endif
break;
}
#ifndef ENABLE_FEAT_F4HWN
case MENU_200TX:
gSetting_200TX = gSubMenuSelection;
break;
case MENU_500TX:
gSetting_500TX = gSubMenuSelection;
break;
#endif
case MENU_350EN:
gSetting_350EN = gSubMenuSelection;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCREN:
gSetting_ScrambleEnable = gSubMenuSelection;
gFlagReconfigureVfos = true;
break;
#endif
#ifdef ENABLE_F_CAL_MENU
case MENU_F_CALI:
writeXtalFreqCal(gSubMenuSelection, true);
return;
#endif
case MENU_BATCAL:
{ // voltages are averages between discharge curves of 1600 and 2200 mAh
// gBatteryCalibration[0] = (520ul * gSubMenuSelection) / 760; // 5.20V empty, blinking above this value, reduced functionality below
// gBatteryCalibration[1] = (689ul * gSubMenuSelection) / 760; // 6.89V, ~5%, 1 bars above this value
// gBatteryCalibration[2] = (724ul * gSubMenuSelection) / 760; // 7.24V, ~17%, 2 bars above this value
gBatteryCalibration[3] = gSubMenuSelection; // 7.6V, ~29%, 3 bars above this value
// gBatteryCalibration[4] = (771ul * gSubMenuSelection) / 760; // 7.71V, ~65%, 4 bars above this value
// gBatteryCalibration[5] = 2300;
SETTINGS_SaveBatteryCalibration(gBatteryCalibration);
return;
}
case MENU_F1SHRT:
case MENU_F1LONG:
case MENU_F2SHRT:
case MENU_F2LONG:
case MENU_MLONG:
{
uint8_t * fun[]= {
&gEeprom.KEY_1_SHORT_PRESS_ACTION,
&gEeprom.KEY_1_LONG_PRESS_ACTION,
&gEeprom.KEY_2_SHORT_PRESS_ACTION,
&gEeprom.KEY_2_LONG_PRESS_ACTION,
&gEeprom.KEY_M_LONG_PRESS_ACTION};
*fun[UI_MENU_GetCurrentMenuId()-MENU_F1SHRT] = gSubMenuSelection;
}
break;
#ifdef ENABLE_FEAT_F4HWN
case MENU_SET_PWR:
gSetting_set_pwr = gSubMenuSelection;
gRequestSaveChannel = 1;
break;
case MENU_SET_PTT:
gSetting_set_ptt = gSubMenuSelection;
gSetting_set_ptt_session = gSetting_set_ptt; // Special for action
break;
case MENU_SET_INV:
gSetting_set_inv = gSubMenuSelection;
break;
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
gSetting_set_audio_am = gSubMenuSelection;
else if (gTxVfo->Modulation == MODULATION_FM)
gSetting_set_audio_fm = gSubMenuSelection;
RADIO_SetModulation(gTxVfo->Modulation);
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
case MENU_SET_NFM:
gSetting_set_nfm = gSubMenuSelection;
RADIO_SetTxParameters();
RADIO_SetupRegisters(true);
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
case MENU_SET_KEY:
gEeprom.SET_KEY = gSubMenuSelection;
break;
#endif
case MENU_TX_LOCK:
gTxVfo->TX_LOCK = gSubMenuSelection;
gRequestSaveChannel = 1;
return;
#endif
}
gRequestSaveSettings = true;
}
static void MENU_ClampSelection(int8_t Direction)
{
int32_t Min;
int32_t Max;
if (UI_MENU_GetCurrentMenuId() == MENU_S_LIST)
{
gSubMenuSelection = RADIO_GetAdjacentScanList(gSubMenuSelection, Direction);
return;
}
if (!MENU_GetLimits(UI_MENU_GetCurrentMenuId(), &Min, &Max))
{
int32_t Selection = gSubMenuSelection;
if (Selection < Min) Selection = Min;
else
if (Selection > Max) Selection = Max;
gSubMenuSelection = NUMBER_AddWithWraparound(Selection, Direction, Min, Max);
}
}
void MENU_ShowCurrentSetting(void)
{
const uint8_t menu_id = UI_MENU_GetCurrentMenuId();
const MenuSetting *setting = MENU_FindSetting(menu_id);
if (setting != NULL)
{
gSubMenuSelection = *setting->value;
return;
}
switch (menu_id)
{
case MENU_SQL:
gSubMenuSelection = gEeprom.SQUELCH_LEVEL;
break;
case MENU_VOL:
// SysInf is paginated; always start on page 0 (identity).
gSubMenuSelection = 0;
break;
case MENU_STEP:
gSubMenuSelection = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING);
break;
case MENU_TXP:
gSubMenuSelection = gTxVfo->OUTPUT_POWER;
break;
case MENU_RESET:
gSubMenuSelection = 0;
break;
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
case MENU_SET_CFG:
gSubMenuSelection = MB_GetActiveBank();
break;
#endif
case MENU_R_DCS:
case MENU_R_CTCS:
{
DCS_CodeType_t type = gTxVfo->freq_config_RX.CodeType;
uint8_t code = gTxVfo->freq_config_RX.Code;
int menuid = UI_MENU_GetCurrentMenuId();
if(gScanUseCssResult) {
gScanUseCssResult = false;
type = gScanCssResultType;
code = gScanCssResultCode;
}
if((menuid==MENU_R_CTCS) ^ (type==CODE_TYPE_CONTINUOUS_TONE)) { //not the same type
gSubMenuSelection = 0;
break;
}
switch (type) {
case CODE_TYPE_CONTINUOUS_TONE:
case CODE_TYPE_DIGITAL:
gSubMenuSelection = code + 1;
break;
case CODE_TYPE_REVERSE_DIGITAL:
gSubMenuSelection = code + 105;
break;
default:
gSubMenuSelection = 0;
break;
}
break;
}
case MENU_T_DCS:
switch (gTxVfo->freq_config_TX.CodeType)
{
case CODE_TYPE_DIGITAL:
gSubMenuSelection = gTxVfo->freq_config_TX.Code + 1;
break;
case CODE_TYPE_REVERSE_DIGITAL:
gSubMenuSelection = gTxVfo->freq_config_TX.Code + 105;
break;
default:
gSubMenuSelection = 0;
break;
}
break;
case MENU_T_CTCS:
gSubMenuSelection = (gTxVfo->freq_config_TX.CodeType == CODE_TYPE_CONTINUOUS_TONE) ? gTxVfo->freq_config_TX.Code + 1 : 0;
break;
case MENU_SFT_D:
gSubMenuSelection = gTxVfo->TX_OFFSET_FREQUENCY_DIRECTION;
break;
case MENU_OFFSET:
gSubMenuSelection = gTxVfo->TX_OFFSET_FREQUENCY;
break;
case MENU_W_N:
gSubMenuSelection = gTxVfo->CHANNEL_BANDWIDTH;
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCR:
gSubMenuSelection = gTxVfo->SCRAMBLING_TYPE;
break;
#endif
case MENU_BCL:
gSubMenuSelection = gTxVfo->BUSY_CHANNEL_LOCK;
break;
case MENU_MEM_CH:
#if 0
gSubMenuSelection = gEeprom.MrChannel[0];
#else
gSubMenuSelection = gEeprom.MrChannel[gEeprom.TX_VFO];
#endif
break;
case MENU_MEM_NAME:
gSubMenuSelection = gEeprom.MrChannel[gEeprom.TX_VFO];
break;
#ifdef ENABLE_VOX
case MENU_VOX:
gSubMenuSelection = gEeprom.VOX_SWITCH ? gEeprom.VOX_LEVEL + 1 : 0;
break;
#endif
case MENU_ABR:
#ifdef ENABLE_FEAT_F4HWN
if(gBackLight)
{
gSubMenuSelection = gBacklightTimeOriginal;
}
else
{
gSubMenuSelection = gEeprom.BACKLIGHT_TIME;
}
#else
gSubMenuSelection = gEeprom.BACKLIGHT_TIME;
#endif
break;
case MENU_ABR_MIN:
gSubMenuSelection = gEeprom.BACKLIGHT_MIN;
break;
case MENU_ABR_MAX:
gSubMenuSelection = gEeprom.BACKLIGHT_MAX;
break;
case MENU_ABR_ON_TX_RX:
gSubMenuSelection = gSetting_backlight_on_tx_rx;
break;
case MENU_TDR:
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
gSubMenuSelection = ACTION_GetRxMode();
#else
gSubMenuSelection = (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) + (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF) * 2;
#endif
break;
#ifdef ENABLE_VOICE
case MENU_VOICE:
gSubMenuSelection = gEeprom.VOICE_PROMPT;
break;
#endif
case MENU_AUTOLK:
gSubMenuSelection = gEeprom.AUTO_KEYPAD_LOCK;
break;
case MENU_LIST_CH:
gSubMenuSelection = gTxVfo->SCANLIST_PARTICIPATION;
break;
case MENU_MIC:
gSubMenuSelection = gEeprom.MIC_SENSITIVITY;
break;
case MENU_COMPAND:
gSubMenuSelection = gTxVfo->Compander;
return;
case MENU_1_CALL:
gSubMenuSelection = gEeprom.CHAN_1_CALL;
break;
case MENU_S_PRI:
gSubMenuSelection = gEeprom.SCAN_LIST_ENABLED;
break;
case MENU_S_PRI_CH_1:
gSubMenuSelection = gEeprom.SCANLIST_PRIORITY_CH[0];
break;
case MENU_S_PRI_CH_2:
gSubMenuSelection = gEeprom.SCANLIST_PRIORITY_CH[1];
break;
case MENU_D_PRE:
gSubMenuSelection = gEeprom.DTMF_PRELOAD_TIME / 10;
break;
case MENU_PTT_ID:
gSubMenuSelection = gTxVfo->DTMF_PTT_ID_TX_MODE;
break;
#ifdef ENABLE_DTMF_CALLING
case MENU_D_DCD:
gSubMenuSelection = gTxVfo->DTMF_DECODING_ENABLE;
break;
case MENU_D_LIST:
gSubMenuSelection = gDTMF_chosen_contact + 1;
break;
#endif
case MENU_D_LIVE_DEC:
gSubMenuSelection = gSetting_live_DTMF_decoder;
break;
case MENU_PONMSG:
gSubMenuSelection = gEeprom.POWER_ON_DISPLAY_MODE;
break;
case MENU_ROGER:
gSubMenuSelection = gEeprom.ROGER;
break;
case MENU_AM:
gSubMenuSelection = gTxVfo->Modulation;
break;
#ifdef ENABLE_NOAA
case MENU_NOAA_S:
gSubMenuSelection = gEeprom.NOAA_AUTO_SCAN;
break;
#endif
case MENU_DEL_CH:
#if 0
gSubMenuSelection = RADIO_FindNextChannel(gEeprom.MrChannel[0], 1, false, 1);
#else
gSubMenuSelection = RADIO_FindNextChannel(gEeprom.MrChannel[gEeprom.TX_VFO], 1, false, 1);
#endif
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_350TX:
gSubMenuSelection = gSetting_350TX;
break;
#endif
case MENU_F_LOCK:
gSubMenuSelection = gSetting_F_LOCK;
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_200TX:
gSubMenuSelection = gSetting_200TX;
break;
case MENU_500TX:
gSubMenuSelection = gSetting_500TX;
break;
#endif
case MENU_350EN:
gSubMenuSelection = gSetting_350EN;
break;
#ifndef ENABLE_FEAT_F4HWN
case MENU_SCREN:
gSubMenuSelection = gSetting_ScrambleEnable;
break;
#endif
#ifdef ENABLE_F_CAL_MENU
case MENU_F_CALI:
gSubMenuSelection = gEeprom.BK4819_XTAL_FREQ_LOW;
break;
#endif
case MENU_BATCAL:
gSubMenuSelection = gBatteryCalibration[3];
break;
case MENU_F1SHRT:
case MENU_F1LONG:
case MENU_F2SHRT:
case MENU_F2LONG:
case MENU_MLONG:
{
uint8_t * fun[]= {
&gEeprom.KEY_1_SHORT_PRESS_ACTION,
&gEeprom.KEY_1_LONG_PRESS_ACTION,
&gEeprom.KEY_2_SHORT_PRESS_ACTION,
&gEeprom.KEY_2_LONG_PRESS_ACTION,
&gEeprom.KEY_M_LONG_PRESS_ACTION};
uint8_t id = *fun[UI_MENU_GetCurrentMenuId()-MENU_F1SHRT];
gSubMenuSelection = id < ACTION_OPT_LEN ? id : ACTION_OPT_NONE;
break;
}
#ifdef ENABLE_FEAT_F4HWN
case MENU_SET_PWR:
gSubMenuSelection = gSetting_set_pwr;
break;
case MENU_SET_PTT:
gSubMenuSelection = gSetting_set_ptt_session;
break;
case MENU_SET_INV:
gSubMenuSelection = gSetting_set_inv;
break;
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
gSubMenuSelection = gSetting_set_audio_am;
else if (gTxVfo->Modulation == MODULATION_USB)
gSubMenuSelection = 0;
else
gSubMenuSelection = gSetting_set_audio_fm;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
case MENU_SET_NFM:
gSubMenuSelection = gSetting_set_nfm;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
case MENU_SET_KEY:
gSubMenuSelection = gEeprom.SET_KEY;
break;
#endif
case MENU_TX_LOCK:
gSubMenuSelection = gTxVfo->TX_LOCK;
break;
#endif
default:
return;
}
}
static KEY_Code_t edit_last_key = 255;
static uint8_t edit_char_index = 0;
static const char* const char_map[10] = {
" 0", // KEY_0
".,-()@/\\+=*#<>[]~1", // KEY_1
"abc2", // KEY_2
"def3", // KEY_3
"ghi4", // KEY_4
"jkl5", // KEY_5
"mno6", // KEY_6
"pqrs7", // KEY_7
"tuv8", // KEY_8
"wxyz9" // KEY_9
};
static bool MENU_IsEditingName() {
return !gCssBackgroundScan
&& UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME
&& gIsInSubMenu
&& edit_index >= 0;
}
static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Offset;
int32_t Min;
int32_t Max;
uint16_t Value = 0;
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (gScanMixEditorActive)
{
if (bKeyHeld)
return;
INPUTBOX_Append(Key);
if (gInputBoxIndex < 2)
return;
gInputBoxIndex = 0;
Value = (gInputBox[0] * 10) + gInputBox[1];
if (Value >= 1 && Value <= MR_CHANNELS_LIST) {
gScanMixEditorCursor = (uint8_t)(Value - 1);
gRequestDisplayScreen = DISPLAY_MENU;
} else {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
return;
}
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (edit_index >= 10)
return;
uint8_t key_idx = Key - KEY_0;
if (bKeyHeld)
{
edit[edit_index] = '0' + key_idx;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (Key != edit_last_key)
{
edit_last_key = Key;
edit_char_index = 0;
}
else
{
edit_char_index++;
if (char_map[key_idx][edit_char_index] == '\0')
{
edit_char_index = 0;
}
}
char c = char_map[key_idx][edit_char_index];
if (edit_is_uppercase && c >= 'a' && c <= 'z')
{
c -= 32;
}
edit[edit_index] = c;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld)
return;
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_MENU;
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
if (gMenuLevel == MENU_LEVEL_CAT)
{ // saut-par-numero global : depuis l'ecran categories, entre dans All a l'item N
const uint8_t allCount = UI_MENU_CategoryItemCount(CAT_ALL);
uint16_t value;
if (gInputBoxIndex >= 2) {
gInputBoxIndex = 0;
value = (gInputBox[0] * 10) + gInputBox[1];
} else {
value = gInputBox[0];
}
if (value > 0 && value <= allCount)
{
gMenuCategory = CAT_ALL;
gMenuCatCursor = gMenuListCount - 1; // All = derniere entree de gCatOrder
UI_MENU_BuildView();
gMenuLevel = MENU_LEVEL_ITEMS;
gMenuCursor = value - 1;
gFlagRefreshSetting = true;
}
else if (gInputBoxIndex == 0)
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
return;
}
#endif
if (!gIsInSubMenu)
{
switch (gInputBoxIndex)
{
case 2:
gInputBoxIndex = 0;
Value = (gInputBox[0] * 10) + gInputBox[1];
if (Value > 0 && Value <= gMenuListCount)
{
gMenuCursor = Value - 1;
gFlagRefreshSetting = true;
return;
}
if (Value <= gMenuListCount)
break;
gInputBox[0] = gInputBox[1];
gInputBoxIndex = 1;
[[fallthrough]];
case 1:
Value = gInputBox[0];
if (Value > 0 && Value <= gMenuListCount)
{
gMenuCursor = Value - 1;
gFlagRefreshSetting = true;
return;
}
break;
}
gInputBoxIndex = 0;
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (UI_MENU_GetCurrentMenuId() == MENU_OFFSET)
{
uint32_t Frequency;
if (gInputBoxIndex < 6)
{ // invalid frequency
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
return;
}
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
Frequency = StrToUL(INPUTBOX_GetAscii())*100;
gSubMenuSelection = FREQUENCY_RoundToStep(Frequency, gTxVfo->StepFrequency);
gInputBoxIndex = 0;
return;
}
const int m = UI_MENU_GetCurrentMenuId();
if (m == MENU_MEM_CH ||
m == MENU_DEL_CH ||
m == MENU_1_CALL ||
m == MENU_S_PRI_CH_1 ||
m == MENU_S_PRI_CH_2 ||
m == MENU_MEM_NAME)
{ // enter 4-digit channel number
if (gInputBoxIndex < 4)
{
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
gInputBoxIndex = 0;
//Value = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
Value = (((gInputBox[0] * 10 + gInputBox[1]) * 10 + gInputBox[2]) * 10 + gInputBox[3]) - 1;
if (IS_MR_CHANNEL(Value))
{
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gSubMenuSelection = Value;
return;
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (m == MENU_S_LIST)
{
if (gInputBoxIndex < 2)
return;
gInputBoxIndex = 0;
Value = (gInputBox[0] * 10) + gInputBox[1];
if (Value == 0)
gSubMenuSelection = SCAN_LIST_MODE_ALL;
else if (Value == SCAN_LIST_MIX_SHORTCUT)
gSubMenuSelection = SCAN_LIST_MODE_MIX;
else if (Value <= MR_CHANNELS_LIST)
gSubMenuSelection = Value;
else
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (MENU_GetLimits(UI_MENU_GetCurrentMenuId(), &Min, &Max))
{
gInputBoxIndex = 0;
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
Offset = (Max >= 100) ? 3 : (Max >= 10) ? 2 : 1;
/*
switch (gInputBoxIndex)
{
case 1:
Value = gInputBox[0];
break;
case 2:
Value = (gInputBox[0] * 10) + gInputBox[1];
break;
case 3:
Value = (gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2];
break;
}
*/
for (uint8_t i = 0; i < gInputBoxIndex; i++) {
Value = (Value * 10) + gInputBox[i];
}
if (Offset == gInputBoxIndex)
gInputBoxIndex = 0;
if (Value <= Max)
{
gSubMenuSelection = Value;
return;
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (gScanMixEditorActive)
{
if (bKeyHeld || !bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
MENU_SaveScanMixEditor();
return;
}
if (MENU_IsEditingName())
{
if (!bKeyPressed)
{
if (bKeyHeld)
return; // release after a long press, keep editing
if (edit_index == 0)
goto Skip;
if (edit_index > 0)
{ // step back one character while editing the channel name
edit_index--;
edit_last_key = 255;
gAskForConfirmation = 0;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (!bKeyHeld)
{ // wait to see if the user wants a short exit or a long backspace
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
Skip:
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are editing from one of them. */
BACKLIGHT_TurnOn();
gAskForConfirmation = 0;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld || !bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (!gCssBackgroundScan)
{
if (gIsInSubMenu)
{
if (gInputBoxIndex == 0 || UI_MENU_GetCurrentMenuId() != MENU_OFFSET)
{
goto Skip;
}
else
{
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are exiting from one of them. */
BACKLIGHT_TurnOn();
gInputBox[--gInputBoxIndex] = 10;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
if (gMenuLevel == MENU_LEVEL_ITEMS)
{ // remonter aux categories au lieu de quitter le menu
gCatLastPos[gMenuCategory] = gMenuCursor; // memorise la position dans la categorie
gMenuLevel = MENU_LEVEL_CAT;
UI_MENU_BuildCategoryScreen();
gMenuCursor = gMenuCatCursor;
gRequestDisplayScreen = DISPLAY_MENU;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gPttWasReleased = true;
return;
}
#endif
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_MAIN;
if (gEeprom.BACKLIGHT_TIME == 0) // backlight set to always off
{
BACKLIGHT_TurnOff(); // turn the backlight OFF
}
}
else
{
MENU_StopCssScan();
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
gRequestDisplayScreen = DISPLAY_MENU;
}
gPttWasReleased = true;
}
static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
{
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
if (gConfigBankExitPending)
{
if (!bKeyPressed)
{
gConfigBankExitPending = false;
gRequestDisplayScreen = DISPLAY_MAIN;
}
return;
}
#endif
if (gScanMixEditorActive)
{
if (!bKeyPressed || bKeyHeld)
return;
const uint32_t bit = 1u << gScanMixEditorCursor;
if ((gScanMixEditorMask & bit) != 0 && (gScanMixEditorMask & ~bit) == 0) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
gScanMixEditorMask ^= bit;
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (!bKeyPressed || (bKeyHeld && (!MENU_IsEditingName() || gAskForConfirmation)))
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_MENU;
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
if (gMenuLevel == MENU_LEVEL_CAT)
{ // niveau categories : MENU descend dans la categorie choisie
gMenuCatCursor = gMenuCursor;
gMenuCategory = gCatOrder[gMenuCursor];
UI_MENU_BuildView();
gMenuLevel = MENU_LEVEL_ITEMS;
gMenuCursor = (gCatLastPos[gMenuCategory] < gMenuListCount) ? gCatLastPos[gMenuCategory] : 0;
gIsInSubMenu = false;
gFlagRefreshSetting = true;
return;
}
#endif
if (!gIsInSubMenu)
{
const int m = UI_MENU_GetCurrentMenuId();
#ifdef ENABLE_VOICE
if (m != MENU_SCR)
gAnotherVoiceID = MenuList[gMenuIndices[gMenuCursor]].voice_id;
#endif
if (m == MENU_UPCODE
|| m == MENU_DWCODE
#ifdef ENABLE_DTMF_CALLING
|| m == MENU_ANI_ID
#endif
)
return;
#if 1
if (m == MENU_DEL_CH || m == MENU_MEM_NAME)
if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0))
return; // invalid channel
#endif
gAskForConfirmation = 0;
gIsInSubMenu = true;
// if (m != MENU_D_LIST)
{
gInputBoxIndex = 0;
edit_index = -1;
}
return;
}
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME)
{
if (edit_index < 0)
{ // enter channel name edit mode
if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0))
return;
SETTINGS_FetchChannelName(edit, gSubMenuSelection);
// pad the channel name out with ' '
size_t len = strlen(edit);
if (len < 10)
{
memset(edit + len, ' ', 10 - len);
edit[10] = '\0';
}
edit_index = 0; // 'edit_index' is going to be used as the cursor position
edit_last_key = 255;
edit_char_index = 0;
edit_is_uppercase = false;
// make a copy so we can test for change when exiting the menu item
memcpy(edit_original, edit, sizeof(edit_original));
return;
}
else
if (edit_index >= 0 && edit_index < 10)
{ // editing the channel name characters
edit_last_key = 255;
if (bKeyHeld) {
edit_index = 10;
}
else if (++edit_index < 10) {
return;
}
// exit
gFlagAcceptSetting = false;
gAskForConfirmation = 0;
if (memcmp(edit_original, edit, sizeof(edit_original)) == 0) {
// no change - drop it
gIsInSubMenu = false;
}
}
}
if (UI_MENU_GetCurrentMenuId() == MENU_S_LIST &&
gSubMenuSelection == SCAN_LIST_MODE_MIX)
{
MENU_OpenScanMixEditor();
return;
}
// exiting the sub menu
if (gIsInSubMenu)
{
const int m = UI_MENU_GetCurrentMenuId();
if (m == MENU_RESET ||
m == MENU_MEM_CH ||
m == MENU_DEL_CH ||
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
m == MENU_SET_CFG ||
#endif
m == MENU_MEM_NAME)
{
switch (gAskForConfirmation)
{
case 0:
gAskForConfirmation = 1;
break;
case 1:
gAskForConfirmation = 2;
UI_DisplayMenu();
if (m == MENU_RESET)
{
#ifdef ENABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_CONFIRM);
AUDIO_PlaySingleVoice(true);
#endif
MENU_AcceptSetting();
#if defined(ENABLE_OVERLAY)
overlay_FLASH_RebootToBootloader();
#else
NVIC_SystemReset();
#endif
}
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
else if (m == MENU_SET_CFG)
{
/* Persist the chosen config bank before applying it. With
* hot switching disabled, the reset maps it at next boot.
* Confirming the current bank remains a no-op. */
if (gSubMenuSelection == MB_GetActiveBank())
{
gFlagAcceptSetting = false;
gIsInSubMenu = false;
gAskForConfirmation = 0;
SCANNER_Stop();
return;
}
const uint8_t err = MB_SetActiveBank(gSubMenuSelection);
if (err != MB_OK)
{
/* The previous redundant marker remains authoritative.
* Explain the failure and keep the selector open. */
UI_MultibootShowConfigError(err);
gAskForConfirmation = 0;
gRequestDisplayScreen = DISPLAY_MENU;
SCANNER_Stop();
return;
}
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
MENU_ApplyConfigBank(gSubMenuSelection);
gFlagAcceptSetting = false;
gIsInSubMenu = false;
gAskForConfirmation = 0;
return;
#else
#if defined(ENABLE_OVERLAY)
overlay_FLASH_RebootToBootloader();
#else
NVIC_SystemReset();
#endif
#endif
}
#endif
gFlagAcceptSetting = true;
gIsInSubMenu = false;
gAskForConfirmation = 0;
}
}
else
{
gFlagAcceptSetting = true;
gIsInSubMenu = false;
}
}
SCANNER_Stop();
#ifdef ENABLE_VOICE
if (UI_MENU_GetCurrentMenuId() == MENU_SCR)
gAnotherVoiceID = (gSubMenuSelection == 0) ? VOICE_ID_SCRAMBLER_OFF : VOICE_ID_SCRAMBLER_ON;
else
gAnotherVoiceID = VOICE_ID_CONFIRM;
#endif
gInputBoxIndex = 0;
}
static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
{
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (edit_index < 10)
{
edit[edit_index] = !bKeyHeld ? '-' : '*';
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (bKeyHeld)
return;
RADIO_SelectVfos();
#ifdef ENABLE_NOAA
if (!IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE) && gRxVfo->Modulation == MODULATION_FM)
#else
if (gRxVfo->Modulation == MODULATION_FM)
#endif
{
const int m = UI_MENU_GetCurrentMenuId();
if ((m == MENU_R_CTCS || m == MENU_R_DCS) && gIsInSubMenu)
{ // scan CTCSS or DCS to find the tone/code of the incoming signal
if (!SCANNER_IsScanning())
MENU_StartCssScan();
else
MENU_StopCssScan();
}
gPttWasReleased = true;
return;
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
{
uint8_t VFO;
uint16_t Channel;
bool bCheckScanList;
if (!bKeyPressed)
return;
if (gScanMixEditorActive)
{
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
#ifdef ENABLE_FEAT_F4HWN
if (!gEeprom.SET_NAV)
Direction = -Direction;
#else
Direction = -Direction;
#endif
gScanMixEditorCursor = NUMBER_AddWithWraparound(gScanMixEditorCursor,
Direction,
0,
MR_CHANNELS_LIST - 1);
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
if (gMenuLevel == MENU_LEVEL_CAT)
{ // niveau categories : deplacement simple du curseur
gMenuCursor = NUMBER_AddWithWraparound(gMenuCursor, -Direction, 0, gMenuListCount - 1);
gFlagRefreshSetting = true; // rearme le timeout menu (ShowCurrentSetting saute au niveau CAT)
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
#endif
if (!gEeprom.SET_NAV && gIsInSubMenu) {
Direction = -Direction;
}
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0)
{ // change the character
if (edit_index < 10 && Direction != 0)
{
const char unwanted[] = "$%&!\"':;?^`|{}_";
char c = edit[edit_index] + Direction;
unsigned int i = 0;
while (i < sizeof(unwanted) && c >= 32 && c <= 126)
{
if (c == unwanted[i++])
{ // choose next character
c += Direction;
i = 0;
}
}
edit[edit_index] = (c < 32) ? 126 : (c > 126) ? 32 : c;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (SCANNER_IsScanning()) {
return;
}
if (!gIsInSubMenu)
{
gMenuCursor = NUMBER_AddWithWraparound(gMenuCursor, -Direction, 0, gMenuListCount - 1);
gFlagRefreshSetting = true;
gRequestDisplayScreen = DISPLAY_MENU;
const int m = UI_MENU_GetCurrentMenuId();
if (m != MENU_ABR
&& m != MENU_ABR_MIN
&& m != MENU_ABR_MAX
&& gEeprom.BACKLIGHT_TIME == 0) // backlight always off and not in the backlight menu
{
BACKLIGHT_TurnOff();
}
return;
}
if (UI_MENU_GetCurrentMenuId() == MENU_OFFSET)
{
int32_t Offset = (Direction * gTxVfo->StepFrequency) + gSubMenuSelection;
if (Offset < 99999990)
{
if (Offset < 0)
Offset = 99999990;
}
else
Offset = 0;
gSubMenuSelection = FREQUENCY_RoundToStep(Offset, gTxVfo->StepFrequency);
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
VFO = 0;
const int m = UI_MENU_GetCurrentMenuId();
switch (m)
{
case MENU_DEL_CH:
case MENU_1_CALL:
case MENU_S_PRI_CH_1:
case MENU_S_PRI_CH_2:
case MENU_MEM_NAME:
bCheckScanList = false;
break;
default:
MENU_ClampSelection(Direction);
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if(m == MENU_S_PRI_CH_1 || m == MENU_S_PRI_CH_2)
{
static int16_t last;
if(gSubMenuSelection == MR_CHANNELS_MAX) {
if(Direction > 0)
{
gSubMenuSelection = -1;
last = -1;
}
else if(Direction < 0)
{
gSubMenuSelection = MR_CHANNELS_MAX;
last = MR_CHANNELS_MAX;
}
}
Channel = RADIO_FindNextChannel(gSubMenuSelection + Direction, Direction, bCheckScanList, VFO);
if (Channel != 0xFFFF)
gSubMenuSelection = Channel;
if(Direction > 0 && gSubMenuSelection < last)
{
gSubMenuSelection = MR_CHANNELS_MAX;
}
else if(Direction < 0 && gSubMenuSelection > last)
{
gSubMenuSelection = MR_CHANNELS_MAX;
}
else
{
last = Channel;
}
gRequestDisplayScreen = DISPLAY_MENU;
}
else
{
Channel = RADIO_FindNextChannel(gSubMenuSelection + Direction, Direction, bCheckScanList, VFO);
if (Channel != 0xFFFF)
gSubMenuSelection = Channel;
gRequestDisplayScreen = DISPLAY_MENU;
}
}
void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
switch (Key)
{
case KEY_0...KEY_9:
MENU_Key_0_to_9(Key, bKeyPressed, bKeyHeld);
break;
case KEY_MENU:
MENU_Key_MENU(bKeyPressed, bKeyHeld);
break;
case KEY_UP:
case KEY_DOWN:
MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1);
break;
case KEY_EXIT:
MENU_Key_EXIT(bKeyPressed, bKeyHeld);
break;
case KEY_STAR:
MENU_Key_STAR(bKeyPressed, bKeyHeld);
break;
case KEY_F:
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (!bKeyPressed)
break;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (edit_index < 10)
{
if (bKeyHeld)
edit[edit_index] = '#';
edit_is_uppercase = !edit_is_uppercase;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
break;
}
GENERIC_Key_F(bKeyPressed, bKeyHeld);
break;
case KEY_PTT:
GENERIC_Key_PTT(bKeyPressed);
break;
default:
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
if (gScreenToDisplay == DISPLAY_MENU)
{
const int m = UI_MENU_GetCurrentMenuId();
if (m == MENU_VOL ||
#ifdef ENABLE_F_CAL_MENU
m == MENU_F_CALI ||
#endif
m == MENU_BATCAL)
{
gMenuCountdown = menu_timeout_long_500ms;
}
else
{
gMenuCountdown = menu_timeout_500ms;
}
}
}