From dc7f0ef3821c42876356ae1bf077907c1b7ac549 Mon Sep 17 00:00:00 2001 From: Joshua Murphy Date: Sat, 18 Apr 2026 13:40:22 -0230 Subject: [PATCH 1/4] C++ conversion experiment --- meson.build | 6 +- openrtx/include/core/battery.h | 8 + openrtx/include/core/beeps.h | 8 + openrtx/include/core/input.h | 8 + openrtx/include/core/state.h | 8 + openrtx/include/core/ui.h | 8 + openrtx/include/core/voicePromptUtils.h | 8 + openrtx/include/core/voicePrompts.h | 8 + openrtx/include/ui/Screen.hpp | 27 + openrtx/include/ui/ui_common.h | 48 + openrtx/include/ui/ui_default.h | 30 +- openrtx/include/ui/ui_events.h | 30 + openrtx/include/ui/ui_mod17.h | 30 +- openrtx/src/ui/common/ui_events.cpp | 48 + openrtx/src/ui/default/ui.c | 34 +- openrtx/src/ui/default/ui.cpp | 2669 +++++++++++++++++++++++ openrtx/src/ui/module17/ui.c | 33 +- openrtx/src/ui/module17/ui.cpp | 924 ++++++++ ui_refactor_plan.rtf | 197 ++ 19 files changed, 4011 insertions(+), 121 deletions(-) create mode 100644 openrtx/include/ui/Screen.hpp create mode 100644 openrtx/include/ui/ui_common.h create mode 100644 openrtx/include/ui/ui_events.h create mode 100644 openrtx/src/ui/common/ui_events.cpp create mode 100644 openrtx/src/ui/default/ui.cpp create mode 100644 openrtx/src/ui/module17/ui.cpp create mode 100644 ui_refactor_plan.rtf diff --git a/meson.build b/meson.build index 9c5a84ce20..8db4cae20d 100644 --- a/meson.build +++ b/meson.build @@ -75,12 +75,14 @@ openrtx_inc = ['openrtx/include', 'platform'] ## OpenRTX UI sources ## -ui_src_default = ['openrtx/src/ui/default/ui.c', +ui_src_common = ['openrtx/src/ui/common/ui_events.cpp'] + +ui_src_default = ui_src_common + ['openrtx/src/ui/default/ui.cpp', 'openrtx/src/ui/default/ui_main.c', 'openrtx/src/ui/default/ui_menu.c', 'openrtx/src/ui/default/ui_strings.c'] -ui_src_module17 = ['openrtx/src/ui/module17/ui.c', +ui_src_module17 = ui_src_common + ['openrtx/src/ui/module17/ui.cpp', 'openrtx/src/ui/module17/ui_main.c', 'openrtx/src/ui/module17/ui_menu.c'] diff --git a/openrtx/include/core/battery.h b/openrtx/include/core/battery.h index 6fd4029c66..762c61244e 100644 --- a/openrtx/include/core/battery.h +++ b/openrtx/include/core/battery.h @@ -9,6 +9,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /** * This function computes the battery's state of charge given its current voltage. * @param vbat: battery voltage in millivolt. @@ -16,4 +20,8 @@ */ uint8_t battery_getCharge(uint16_t vbat); +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* BATTERY_H */ diff --git a/openrtx/include/core/beeps.h b/openrtx/include/core/beeps.h index f19136cd80..6b030329e4 100644 --- a/openrtx/include/core/beeps.h +++ b/openrtx/include/core/beeps.h @@ -6,6 +6,10 @@ #ifndef BEEPS_H_INCLUDED #define BEEPS_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // Duration in tenths of a second. #define SHORT_BEEP 3 #define LONG_BEEP 7 @@ -18,4 +22,8 @@ #define BEEP_KEY_GENERIC 750 extern const uint16_t BOOT_MELODY[]; +#ifdef __cplusplus +} // extern "C" +#endif + #endif // BEEPS_H_INCLUDED diff --git a/openrtx/include/core/input.h b/openrtx/include/core/input.h index af1daf1cf7..f68a508485 100644 --- a/openrtx/include/core/input.h +++ b/openrtx/include/core/input.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Time interval in milliseconds after which a keypress is considered a long-press */ @@ -82,4 +86,8 @@ uint8_t input_getPressedNumber(kbd_msg_t msg); */ uint8_t input_getPressedChar(kbd_msg_t msg); +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* INPUT_H */ diff --git a/openrtx/include/core/state.h b/openrtx/include/core/state.h index 52461a6aa9..af68dc71c7 100644 --- a/openrtx/include/core/state.h +++ b/openrtx/include/core/state.h @@ -14,6 +14,10 @@ #include "core/cps.h" #include "core/gps.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * Part of this structure has been commented because the corresponding * functionality is not yet implemented. @@ -103,4 +107,8 @@ void state_task(); */ void state_resetSettingsAndVfo(); +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* STATE_H */ diff --git a/openrtx/include/core/ui.h b/openrtx/include/core/ui.h index 8a645c1769..4ba6ba8f44 100644 --- a/openrtx/include/core/ui.h +++ b/openrtx/include/core/ui.h @@ -10,6 +10,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * This function initialises the User Interface, starting the * Finite State Machine describing the user interaction. @@ -58,4 +62,8 @@ bool ui_pushEvent(const uint8_t type, const uint32_t data); */ void ui_terminate(); +#ifdef __cplusplus +} +#endif + #endif /* UI_H */ diff --git a/openrtx/include/core/voicePromptUtils.h b/openrtx/include/core/voicePromptUtils.h index 9a78bc2cf0..31f0d1e035 100644 --- a/openrtx/include/core/voicePromptUtils.h +++ b/openrtx/include/core/voicePromptUtils.h @@ -7,6 +7,10 @@ #ifndef VOICE_PROMPT_UTILS_H #define VOICE_PROMPT_UTILS_H +#ifdef __cplusplus +extern "C" { +#endif + #include "core/cps.h" #include "ui/ui_strings.h" #include "core/voicePrompts.h" @@ -225,4 +229,8 @@ void vp_announceSplashScreen(); */ void vp_announceTimeZone(const int8_t timeZone, const vpQueueFlags_t flags); +#ifdef __cplusplus +} // extern "C" +#endif + #endif // VOICE_PROMPT_UTILS_H diff --git a/openrtx/include/core/voicePrompts.h b/openrtx/include/core/voicePrompts.h index 6b719f6b0c..7e1c4386be 100644 --- a/openrtx/include/core/voicePrompts.h +++ b/openrtx/include/core/voicePrompts.h @@ -7,6 +7,10 @@ #ifndef VOICEPROMPTS_H #define VOICEPROMPTS_H +#ifdef __cplusplus +extern "C" { +#endif + #include "core/datatypes.h" #include @@ -328,4 +332,8 @@ void vp_beep(uint16_t freq, uint16_t duration); */ void vp_beepSeries(const uint16_t* beepSeries); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/openrtx/include/ui/Screen.hpp b/openrtx/include/ui/Screen.hpp new file mode 100644 index 0000000000..b108b6a415 --- /dev/null +++ b/openrtx/include/ui/Screen.hpp @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "core/event.h" + +/** + * Abstract base class for UI screens. + * + * Mirrors the OpMode hierarchy used in rtx/: concrete screens are + * stack-allocated static instances; dispatch goes through a Screen* pointer. + * No RTTI, no exceptions, no dynamic_cast — consistent with the rest of the + * embedded codebase. + */ +class Screen +{ +public: + virtual void enter() {} + virtual void exit() {} + virtual bool handleInput(event_t ev) = 0; + virtual void render() = 0; + virtual ~Screen() = default; +}; diff --git a/openrtx/include/ui/ui_common.h b/openrtx/include/ui/ui_common.h new file mode 100644 index 0000000000..c73f6f32f2 --- /dev/null +++ b/openrtx/include/ui/ui_common.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef UI_COMMON_H +#define UI_COMMON_H + +#include "core/graphics.h" +#include +#include + +// Maximum menu entry length +#define MAX_ENTRY_LEN 21 +// Frequency digits +#define FREQ_DIGITS 7 +// Time & Date digits +#define TIMEDATE_DIGITS 10 +// Max number of UI events +#define MAX_NUM_EVENTS 16 + +enum SetRxTx +{ + SET_RX = 0, + SET_TX +}; + +enum backupRestoreItems +{ + BR_BACKUP = 0, + BR_RESTORE +}; + +#ifdef __cplusplus +extern "C" { +#endif + +extern const color_t color_black; +extern const color_t color_grey; +extern const color_t color_white; +extern const color_t yellow_fab413; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_COMMON_H */ diff --git a/openrtx/include/ui/ui_default.h b/openrtx/include/ui/ui_default.h index 28ba229f0c..5bc9750908 100644 --- a/openrtx/include/ui/ui_default.h +++ b/openrtx/include/ui/ui_default.h @@ -7,24 +7,13 @@ #ifndef UI_DEFAULT_H #define UI_DEFAULT_H -#include +#include "ui/ui_common.h" #include "core/state.h" -#include "core/graphics.h" #include "interfaces/keyboard.h" -#include #include "core/event.h" #include "hwconfig.h" #include "core/ui.h" -// Maximum menu entry length -#define MAX_ENTRY_LEN 21 -// Frequency digits -#define FREQ_DIGITS 7 -// Time & Date digits -#define TIMEDATE_DIGITS 10 -// Max number of UI events -#define MAX_NUM_EVENTS 16 - enum uiScreen { MAIN_VFO = 0, @@ -55,12 +44,6 @@ enum uiScreen LOW_BAT }; -enum SetRxTx -{ - SET_RX = 0, - SET_TX -}; - // This enum is needed to have item numbers that match // menu elements even if some elements may be missing (GPS) enum menuItems @@ -94,12 +77,6 @@ enum settingsItems S_RESET2DEFAULTS, }; -enum backupRestoreItems -{ - BR_BACKUP = 0, - BR_RESTORE -}; - enum displayItems { #ifdef CONFIG_SCREEN_BRIGHTNESS @@ -262,9 +239,4 @@ extern const uint8_t settings_accessibility_num; extern const uint8_t backup_restore_num; extern const uint8_t info_num; extern const uint8_t author_num; -extern const color_t color_black; -extern const color_t color_grey; -extern const color_t color_white; -extern const color_t yellow_fab413; - #endif /* UI_DEFAULT_H */ diff --git a/openrtx/include/ui/ui_events.h b/openrtx/include/ui/ui_events.h new file mode 100644 index 0000000000..1f6ba84eff --- /dev/null +++ b/openrtx/include/ui/ui_events.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef UI_EVENTS_H +#define UI_EVENTS_H + +#include "core/event.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Pop one event from the UI event queue. + * + * @param ev: output parameter filled with the popped event. + * @return true if an event was available, false if the queue was empty. + */ +bool ui_popEvent(event_t *ev); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_EVENTS_H */ diff --git a/openrtx/include/ui/ui_mod17.h b/openrtx/include/ui/ui_mod17.h index b49d475008..5203670eaa 100644 --- a/openrtx/include/ui/ui_mod17.h +++ b/openrtx/include/ui/ui_mod17.h @@ -7,25 +7,14 @@ #ifndef UI_MOD17_H #define UI_MOD17_H -#include +#include "ui/ui_common.h" #include "core/state.h" -#include "core/graphics.h" #include "interfaces/keyboard.h" #include "calibration/calibInfo_Mod17.h" -#include #include "core/event.h" #include "hwconfig.h" #include "core/ui.h" -// Maximum menu entry length -#define MAX_ENTRY_LEN 21 -// Frequency digits -#define FREQ_DIGITS 7 -// Time & Date digits -#define TIMEDATE_DIGITS 10 -// Max number of UI events -#define MAX_NUM_EVENTS 16 - enum uiScreen { MAIN_VFO = 0, @@ -54,12 +43,6 @@ enum uiScreen LOW_BAT }; -enum SetRxTx -{ - SET_RX = 0, - SET_TX -}; - // This enum is needed to have item numbers that match // menu elements even if some elements may be missing (GPS) enum menuItems @@ -87,12 +70,6 @@ enum settingsItems ,S_RESET2DEFAULTS }; -enum backupRestoreItems -{ - BR_BACKUP = 0, - BR_RESTORE -}; - enum displayItems { D_BRIGHTNESS = 0 @@ -231,11 +208,6 @@ extern const uint8_t m17_num; extern const uint8_t module17_num; extern const uint8_t info_num; extern const uint8_t author_num; -extern const color_t color_black; -extern const color_t color_grey; -extern const color_t color_white; -extern const color_t yellow_fab413; - // Calibration data, for digital pot and phase inversion extern mod17Calib_t mod17CalData; diff --git a/openrtx/src/ui/common/ui_events.cpp b/openrtx/src/ui/common/ui_events.cpp new file mode 100644 index 0000000000..d6144cff3c --- /dev/null +++ b/openrtx/src/ui/common/ui_events.cpp @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include "core/event.h" +#include "core/ui.h" +#include "ui/ui_common.h" +#include "ui/ui_events.h" + +namespace { + uint8_t evQueue_rdPos = 0; + uint8_t evQueue_wrPos = 0; + event_t evQueue[MAX_NUM_EVENTS]; +} + +extern "C" { + +bool ui_pushEvent(const uint8_t type, const uint32_t data) +{ + uint8_t newHead = (evQueue_wrPos + 1) % MAX_NUM_EVENTS; + + if(newHead == evQueue_rdPos) return false; + + event_t event; + event.type = type; + event.payload = data; + + evQueue[evQueue_wrPos] = event; + evQueue_wrPos = newHead; + + return true; +} + +bool ui_popEvent(event_t *ev) +{ + if(evQueue_wrPos == evQueue_rdPos) return false; + + uint8_t newTail = (evQueue_rdPos + 1) % MAX_NUM_EVENTS; + *ev = evQueue[evQueue_rdPos]; + evQueue_rdPos = newTail; + + return true; +} + +} // extern "C" diff --git a/openrtx/src/ui/default/ui.c b/openrtx/src/ui/default/ui.c index 1b7085c381..7e926cc49e 100644 --- a/openrtx/src/ui/default/ui.c +++ b/openrtx/src/ui/default/ui.c @@ -65,6 +65,7 @@ #include "hwconfig.h" #include "core/voicePromptUtils.h" #include "core/beeps.h" +#include "ui/ui_events.h" /* UI main screen functions, their implementation is in "ui_main.c" */ extern void _ui_drawMainBackground(); @@ -286,12 +287,6 @@ static bool redraw_needed = true; static bool standby = false; static long long last_event_tick = 0; -// UI event queue -static uint8_t evQueue_rdPos; -static uint8_t evQueue_wrPos; -static event_t evQueue[MAX_NUM_EVENTS]; - - static void _ui_calculateLayout(layout_t *layout) { // Horizontal line height @@ -1375,13 +1370,8 @@ static vpGPSInfoFlags_t GetGPSDirectionOrSpeedChanged() void ui_updateFSM(bool *sync_rtx) { - // Check for events - if(evQueue_wrPos == evQueue_rdPos) return; - - // Pop an event from the queue - uint8_t newTail = (evQueue_rdPos + 1) % MAX_NUM_EVENTS; - event_t event = evQueue[evQueue_rdPos]; - evQueue_rdPos = newTail; + event_t event; + if(!ui_popEvent(&event)) return; // There is some event to process, we need an UI redraw. // UI redraw request is cancelled if we're in standby mode. @@ -2755,24 +2745,6 @@ bool ui_updateGUI() return true; } -bool ui_pushEvent(const uint8_t type, const uint32_t data) -{ - uint8_t newHead = (evQueue_wrPos + 1) % MAX_NUM_EVENTS; - - // Queue is full - if(newHead == evQueue_rdPos) return false; - - // Preserve atomicity when writing the new element into the queue. - event_t event; - event.type = type; - event.payload = data; - - evQueue[evQueue_wrPos] = event; - evQueue_wrPos = newHead; - - return true; -} - void ui_terminate() { } diff --git a/openrtx/src/ui/default/ui.cpp b/openrtx/src/ui/default/ui.cpp new file mode 100644 index 0000000000..f639f7841f --- /dev/null +++ b/openrtx/src/ui/default/ui.cpp @@ -0,0 +1,2669 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* + * The graphical user interface (GUI) works by splitting the screen in + * horizontal rows, with row height depending on vertical resolution. + * + * The general screen layout is composed by an upper status bar at the + * top of the screen and a lower status bar at the bottom. + * The central portion of the screen is filled by two big text/number rows + * And a small row. + * + * Below is shown the row height for two common display densities. + * + * 160x128 display (MD380) Recommended font size + * ┌─────────────────────────┐ + * │ top_status_bar (16px) │ 8 pt (11 px) font with 2 px vertical padding + * │ top_pad (4px) │ 4 px padding + * │ Line 1 (20px) │ 8 pt (11 px) font with 4 px vertical padding + * │ Line 2 (20px) │ 8 pt (11 px) font with 4 px vertical padding + * │ │ + * │ Line 3 (40px) │ 16 pt (xx px) font with 6 px vertical padding + * │ RSSI+squelch bar (20px) │ 20 px + * │ bottom_pad (4px) │ 4 px padding + * └─────────────────────────┘ + * + * 128x64 display (GD-77) + * ┌─────────────────────────┐ + * │ top_status_bar (11 px) │ 6 pt (9 px) font with 1 px vertical padding + * │ top_pad (1px) │ 1 px padding + * │ Line 1 (10px) │ 6 pt (9 px) font without vertical padding + * │ Line 2 (10px) │ 6 pt (9 px) font with 2 px vertical padding + * │ Line 3 (18px) │ 12 pt (xx px) font with 0 px vertical padding + * │ RSSI+squelch bar (11px) │ 11 px + * │ bottom_pad (1px) │ 1 px padding + * └─────────────────────────┘ + * + * 128x48 display (RD-5R) + * ┌─────────────────────────┐ + * │ top_status_bar (11 px) │ 6 pt (9 px) font with 1 px vertical padding + * ├─────────────────────────┤ 1 px line + * │ Line 2 (10px) │ 8 pt (11 px) font with 4 px vertical padding + * │ Line 3 (18px) │ 8 pt (11 px) font with 4 px vertical padding + * └─────────────────────────┘ + */ + +#include +#include +#include +#include +#include "ui/ui_default.h" +#include "rtx/rtx.h" +#include "interfaces/platform.h" +#include "interfaces/display.h" +#include "interfaces/cps_io.h" +#include "interfaces/nvmem.h" +#include "interfaces/delays.h" +#include +#include "core/battery.h" +#include "core/input.h" +#include "core/utils.h" +#include "hwconfig.h" +#include "core/voicePromptUtils.h" +#include "core/beeps.h" +#include "ui/ui_events.h" +#include "ui/Screen.hpp" + +extern "C" { +/* UI main screen functions, their implementation is in "ui_main.c" */ +void _ui_drawMainBackground(); +void _ui_drawMainTop(ui_state_t* ui_state); +void _ui_drawVFOMiddle(); +void _ui_drawMEMMiddle(); +void _ui_drawVFOBottom(); +void _ui_drawMEMBottom(); +void _ui_drawMainVFO(ui_state_t* ui_state); +void _ui_drawMainVFOInput(ui_state_t* ui_state); +void _ui_drawMainMEM(ui_state_t* ui_state); +/* UI menu functions, their implementation is in "ui_menu.c" */ +void _ui_drawMenuTop(ui_state_t* ui_state); +void _ui_drawMenuBank(ui_state_t* ui_state); +void _ui_drawMenuChannel(ui_state_t* ui_state); +void _ui_drawMenuContacts(ui_state_t* ui_state); +#ifdef CONFIG_GPS +void _ui_drawMenuGPS(); +void _ui_drawSettingsGPS(ui_state_t* ui_state); +#endif +void _ui_drawSettingsAccessibility(ui_state_t* ui_state); +void _ui_drawMenuSettings(ui_state_t* ui_state); +void _ui_drawMenuBackupRestore(ui_state_t* ui_state); +void _ui_drawMenuBackup(ui_state_t* ui_state); +void _ui_drawMenuRestore(ui_state_t* ui_state); +void _ui_drawMenuInfo(ui_state_t* ui_state); +void _ui_drawMenuAbout(ui_state_t* ui_state); +#ifdef CONFIG_RTC +void _ui_drawSettingsTimeDate(); +void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); +#endif +void _ui_drawSettingsDisplay(ui_state_t* ui_state); +void _ui_drawSettingsM17(ui_state_t* ui_state); +void _ui_drawSettingsFM(ui_state_t* ui_state); +void _ui_drawSettingsVoicePrompts(ui_state_t* ui_state); +void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); +void _ui_drawSettingsRadio(ui_state_t* ui_state); +bool _ui_drawMacroMenu(ui_state_t* ui_state); +void _ui_reset_menu_anouncement_tracking(); +} // extern "C" ui_main / ui_menu +// TODO: get these from ui strings / currentLanguage +const char *menu_items[] = +{ + "Banks", + "Channels", + "Contacts", +#ifdef CONFIG_GPS + "GPS", +#endif + "Settings", + "Info", + "About" +}; + +const char *settings_items[] = +{ + "Display", +#ifdef CONFIG_RTC + "Time & Date", +#endif +#ifdef CONFIG_GPS + "GPS", +#endif + "Radio", +#ifdef CONFIG_M17 + "M17", +#endif + "FM", + "Accessibility", + "Default Settings" +}; + +const char *display_items[] = +{ +#ifdef CONFIG_SCREEN_BRIGHTNESS + "Brightness", +#endif +#ifdef CONFIG_SCREEN_CONTRAST + "Contrast", +#endif + "Timer", + "Battery Icon" +}; + +#ifdef CONFIG_GPS +const char *settings_gps_items[] = +{ + "GPS Enabled", +#ifdef CONFIG_RTC + "GPS Set Time", + "UTC Timezone" +#endif +}; +#endif + +const char *settings_radio_items[] = +{ + "Offset", + "Direction", + "Step", +}; + +const char * settings_m17_items[] = +{ + "Callsign", + "Meta Txt", + "CAN", + "CAN RX Check" +}; + +const char* settings_fm_items[] = +{ + "CTCSS Tone", + "CTCSS En." +}; + +const char * settings_accessibility_items[] = +{ + "Macro Latch", + "Voice", + "Phonetic" +}; + +const char *backup_restore_items[] = +{ + "Backup", + "Restore" +}; + +const char *info_items[] = +{ + "", + "Bat. Voltage", + "Bat. Charge", + "RSSI", + "Used heap", + "Band", + "VHF", + "UHF", + "Hw Version", +#ifdef PLATFORM_TTWRPLUS + "Radio", + "Radio FW", +#endif +}; + +const char *authors[] = +{ + "Niccolo' IU2KIN", + "Silvano IU2KWO", + "Federico IU2NUO", + "Fred IU2NRO", + "Joseph VK7JS", + "Morgan ON4MOD", + "Marco DM4RCO" +}; + +static const char *symbols_ITU_T_E161[] = +{ + " 0", + ",.?1", + "abc2ABC", + "def3DEF", + "ghi4GHI", + "jkl5JKL", + "mno6MNO", + "pqrs7PQRS", + "tuv8TUV", + "wxyz9WXYZ", + "-/*", + "#" +}; + +static const char *symbols_ITU_T_E161_callsign[] = +{ + "0 ", + "1", + "ABC2", + "DEF3", + "GHI4", + "JKL5", + "MNO6", + "PQRS7", + "TUV8", + "WXYZ9", + "-/", + "" +}; + +// Calculate number of menu entries +const uint8_t menu_num = sizeof(menu_items)/sizeof(menu_items[0]); +const uint8_t settings_num = sizeof(settings_items)/sizeof(settings_items[0]); +const uint8_t display_num = sizeof(display_items)/sizeof(display_items[0]); +#ifdef CONFIG_GPS +const uint8_t settings_gps_num = sizeof(settings_gps_items)/sizeof(settings_gps_items[0]); +#endif +const uint8_t settings_radio_num = sizeof(settings_radio_items)/sizeof(settings_radio_items[0]); +#ifdef CONFIG_M17 +const uint8_t settings_m17_num = sizeof(settings_m17_items)/sizeof(settings_m17_items[0]); +#endif +const uint8_t settings_fm_num = sizeof(settings_fm_items) / sizeof(settings_fm_items[0]); +const uint8_t settings_accessibility_num = sizeof(settings_accessibility_items)/sizeof(settings_accessibility_items[0]); +const uint8_t backup_restore_num = sizeof(backup_restore_items)/sizeof(backup_restore_items[0]); +const uint8_t info_num = sizeof(info_items)/sizeof(info_items[0]); +const uint8_t author_num = sizeof(authors)/sizeof(authors[0]); + +const color_t color_black = {0, 0, 0, 255}; +const color_t color_grey = {60, 60, 60, 255}; +const color_t color_white = {255, 255, 255, 255}; +const color_t yellow_fab413 = {250, 180, 19, 255}; + +layout_t layout; +state_t last_state; +bool macro_latched; +static ui_state_t ui_state; +static bool macro_menu = false; +static bool layout_ready = false; +static bool redraw_needed = true; + +static bool standby = false; +static long long last_event_tick = 0; + +static void _ui_calculateLayout(layout_t *layout) +{ + // Horizontal line height + static const uint16_t hline_h = 1; + // Compensate for fonts printing below the start position + static const uint16_t text_v_offset = 1; + + // Calculate UI layout depending on vertical resolution + // Tytera MD380, MD-UV380 + #if CONFIG_SCREEN_HEIGHT > 127 + + // Height and padding shown in diagram at beginning of file + static const uint16_t top_h = 16; + static const uint16_t top_pad = 4; + static const uint16_t line1_h = 20; + static const uint16_t line2_h = 20; + static const uint16_t line3_h = 20; + static const uint16_t line3_large_h = 40; + static const uint16_t line4_h = 20; + static const uint16_t menu_h = 16; + static const uint16_t bottom_h = 23; + static const uint16_t bottom_pad = top_pad; + static const uint16_t status_v_pad = 2; + static const uint16_t small_line_v_pad = 2; + static const uint16_t big_line_v_pad = 6; + static const uint16_t horizontal_pad = 4; + + // Top bar font: 8 pt + static const fontSize_t top_font = FONT_SIZE_8PT; + static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_8PT; + // Text line font: 8 pt + static const fontSize_t line1_font = FONT_SIZE_8PT; + static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_8PT; + static const fontSize_t line2_font = FONT_SIZE_8PT; + static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_8PT; + static const fontSize_t line3_font = FONT_SIZE_8PT; + static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_8PT; + static const fontSize_t line4_font = FONT_SIZE_8PT; + static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_8PT; + // Message font + const fontSize_t message_font = FONT_SIZE_6PT; + // Frequency line font: 16 pt + static const fontSize_t line3_large_font = FONT_SIZE_16PT; + // Bottom bar font: 8 pt + static const fontSize_t bottom_font = FONT_SIZE_8PT; + // TimeDate/Frequency input font + static const fontSize_t input_font = FONT_SIZE_12PT; + // Menu font + static const fontSize_t menu_font = FONT_SIZE_8PT; + + // Radioddity GD-77 + #elif CONFIG_SCREEN_HEIGHT > 63 + + // Height and padding shown in diagram at beginning of file + static const uint16_t top_h = 11; + static const uint16_t top_pad = 1; + static const uint16_t line1_h = 10; + static const uint16_t line2_h = 10; + static const uint16_t line3_h = 10; + static const uint16_t line3_large_h = 16; + static const uint16_t line4_h = 10; + static const uint16_t menu_h = 10; + static const uint16_t bottom_h = 15; + static const uint16_t bottom_pad = 0; + static const uint16_t status_v_pad = 1; + static const uint16_t small_line_v_pad = 1; + static const uint16_t big_line_v_pad = 0; + static const uint16_t horizontal_pad = 4; + + // Top bar font: 6 pt + static const fontSize_t top_font = FONT_SIZE_6PT; + static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; + // Middle line fonts: 5, 8, 8 pt + static const fontSize_t line1_font = FONT_SIZE_6PT; + static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; + static const fontSize_t line2_font = FONT_SIZE_6PT; + static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; + static const fontSize_t line3_font = FONT_SIZE_6PT; + static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; + static const fontSize_t line3_large_font = FONT_SIZE_10PT; + static const fontSize_t line4_font = FONT_SIZE_6PT; + static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; + // Message font + const fontSize_t message_font = FONT_SIZE_6PT; + // Bottom bar font: 6 pt + static const fontSize_t bottom_font = FONT_SIZE_6PT; + // TimeDate/Frequency input font + static const fontSize_t input_font = FONT_SIZE_8PT; + // Menu font + static const fontSize_t menu_font = FONT_SIZE_6PT; + + // Radioddity RD-5R + #elif CONFIG_SCREEN_HEIGHT > 47 + + // Height and padding shown in diagram at beginning of file + static const uint16_t top_h = 11; + static const uint16_t top_pad = 1; + static const uint16_t line1_h = 0; + static const uint16_t line2_h = 10; + static const uint16_t line3_h = 10; + static const uint16_t line3_large_h = 18; + static const uint16_t line4_h = 10; + static const uint16_t menu_h = 10; + static const uint16_t bottom_h = 0; + static const uint16_t bottom_pad = 0; + static const uint16_t status_v_pad = 1; + static const uint16_t small_line_v_pad = 1; + static const uint16_t big_line_v_pad = 0; + static const uint16_t horizontal_pad = 4; + + // Top bar font: 6 pt + static const fontSize_t top_font = FONT_SIZE_6PT; + static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; + // Middle line fonts: 16, 16 + static const fontSize_t line2_font = FONT_SIZE_6PT; + static const fontSize_t line3_font = FONT_SIZE_6PT; + static const fontSize_t line4_font = FONT_SIZE_6PT; + static const fontSize_t line3_large_font = FONT_SIZE_12PT; + // TimeDate/Frequency input font + static const fontSize_t input_font = FONT_SIZE_8PT; + // Menu font + static const fontSize_t menu_font = FONT_SIZE_6PT; + // Message font + const fontSize_t message_font = FONT_SIZE_6PT; + // Not present on this resolution + static const fontSize_t line1_font = 0; + static const fontSize_t bottom_font = 0; + + #else + #error Unsupported vertical resolution! + #endif + + // Calculate printing positions + static const uint16_t top_pos = top_h - status_v_pad - text_v_offset; + static const uint16_t line1_pos = top_h + top_pad + line1_h - small_line_v_pad - text_v_offset; + static const uint16_t line2_pos = top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset; + static const uint16_t line3_pos = top_h + top_pad + line1_h + line2_h + line3_h - small_line_v_pad - text_v_offset; + static const uint16_t line4_pos = top_h + top_pad + line1_h + line2_h + line3_h + line4_h - small_line_v_pad - text_v_offset; + static const uint16_t line3_large_pos = top_h + top_pad + line1_h + line2_h + line3_large_h - big_line_v_pad - text_v_offset; + static const uint16_t bottom_pos = CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset; + + layout_t new_layout = + { + hline_h, + top_h, + line1_h, + line2_h, + line3_h, + line3_large_h, + line4_h, + menu_h, + bottom_h, + bottom_pad, + status_v_pad, + horizontal_pad, + text_v_offset, + {horizontal_pad, top_pos}, + {horizontal_pad, line1_pos}, + {horizontal_pad, line2_pos}, + {horizontal_pad, line3_pos}, + {horizontal_pad, line3_large_pos}, + {horizontal_pad, line4_pos}, + {horizontal_pad, bottom_pos}, + top_font, + top_symbol_size, + line1_font, + line1_symbol_size, + line2_font, + line2_symbol_size, + line3_font, + line3_symbol_size, + line3_large_font, + line4_font, + line4_symbol_size, + bottom_font, + input_font, + menu_font, + message_font + }; + + memcpy(layout, &new_layout, sizeof(layout_t)); +} + +static void _ui_drawLowBatteryScreen() +{ + gfx_clearScreen(); + uint16_t bat_width = CONFIG_SCREEN_WIDTH / 2; + uint16_t bat_height = CONFIG_SCREEN_HEIGHT / 3; + point_t bat_pos = {CONFIG_SCREEN_WIDTH / 4, CONFIG_SCREEN_HEIGHT / 8}; + gfx_drawBattery(bat_pos, bat_width, bat_height, 10); + point_t text_pos_1 = {0, CONFIG_SCREEN_HEIGHT * 2 / 3}; + point_t text_pos_2 = {0, CONFIG_SCREEN_HEIGHT * 2 / 3 + 16}; + + gfx_print(text_pos_1, + FONT_SIZE_6PT, + TEXT_ALIGN_CENTER, + color_white, + currentLanguage->forEmergencyUse); + gfx_print(text_pos_2, + FONT_SIZE_6PT, + TEXT_ALIGN_CENTER, + color_white, + currentLanguage->pressAnyButton); +} + +static freq_t _ui_freq_add_digit(freq_t freq, uint8_t pos, uint8_t number) +{ + freq_t coefficient = 100; + for(uint8_t i=0; i < FREQ_DIGITS - pos; i++) + { + coefficient *= 10; + } + return freq += number * coefficient; +} + +#ifdef CONFIG_RTC +static void _ui_timedate_add_digit(datetime_t *timedate, uint8_t pos, + uint8_t number) +{ + vp_flush(); + vp_queueInteger(number); + if (pos == 2 || pos == 4) + vp_queuePrompt(PROMPT_SLASH); + // just indicates separation of date and time. + if (pos==6) // start of time. + vp_queueString("hh:mm", (vpFlags_t)(vpAnnounceCommonSymbols|vpAnnounceLessCommonSymbols)); + if (pos == 8) + vp_queuePrompt(PROMPT_COLON); + vp_play(); + + switch(pos) + { + // Set date + case 1: + timedate->date += number * 10; + break; + case 2: + timedate->date += number; + break; + // Set month + case 3: + timedate->month += number * 10; + break; + case 4: + timedate->month += number; + break; + // Set year + case 5: + timedate->year += number * 10; + break; + case 6: + timedate->year += number; + break; + // Set hour + case 7: + timedate->hour += number * 10; + break; + case 8: + timedate->hour += number; + break; + // Set minute + case 9: + timedate->minute += number * 10; + break; + case 10: + timedate->minute += number; + break; + } +} +#endif + +static bool _ui_freq_check_limits(freq_t freq) +{ + bool valid = false; + const hwInfo_t* hwinfo = platform_getHwInfo(); + if(hwinfo->vhf_band) + { + // hwInfo_t frequencies are in MHz + if(freq >= (hwinfo->vhf_minFreq * 1000000) && + freq <= (hwinfo->vhf_maxFreq * 1000000)) + valid = true; + } + if(hwinfo->uhf_band) + { + // hwInfo_t frequencies are in MHz + if(freq >= (hwinfo->uhf_minFreq * 1000000) && + freq <= (hwinfo->uhf_maxFreq * 1000000)) + valid = true; + } + return valid; +} + +static bool _ui_channel_valid(channel_t* channel) +{ +return _ui_freq_check_limits(channel->rx_frequency) && + _ui_freq_check_limits(channel->tx_frequency); +} + +static bool _ui_drawDarkOverlay() +{ + color_t alpha_grey = {0, 0, 0, 255}; + point_t origin = {0, 0}; + gfx_drawRect(origin, CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_HEIGHT, alpha_grey, true); + return true; +} + +static int _ui_fsm_loadChannel(int16_t channel_index, bool *sync_rtx) +{ + channel_t channel; + int32_t selected_channel = channel_index; + // If a bank is active, get index from current bank + if(state.bank_enabled) + { + bankHdr_t bank = { 0 }; + cps_readBankHeader(&bank, state.bank); + if((channel_index < 0) || (channel_index >= bank.ch_count)) + return -1; + channel_index = cps_readBankData(state.bank, channel_index); + } + + int result = cps_readChannel(&channel, channel_index); + // Read successful and channel is valid + if((result != -1) && _ui_channel_valid(&channel)) + { + // Set new channel index + state.channel_index = selected_channel; + // Copy channel read to state + state.channel = channel; + *sync_rtx = true; + } + + return result; +} + +static void _ui_fsm_confirmVFOInput(bool *sync_rtx) +{ + vp_flush(); + // Switch to TX input + if(ui_state.input_set == SET_RX) + { + ui_state.input_set = SET_TX; + // Reset input position + ui_state.input_position = 0; + // announce the rx frequency just confirmed with Enter. + vp_queueFrequency(ui_state.new_rx_frequency); + // defer playing till the end. + // indicate that the user has moved to the tx freq field. + vp_announceInputReceiveOrTransmit(true, vpqDefault); + } + else if(ui_state.input_set == SET_TX) + { + // Save new frequency setting + // If TX frequency was not set, TX = RX + if(ui_state.new_tx_frequency == 0) + { + ui_state.new_tx_frequency = ui_state.new_rx_frequency; + } + // Apply new frequencies if they are valid + if(_ui_freq_check_limits(ui_state.new_rx_frequency) && + _ui_freq_check_limits(ui_state.new_tx_frequency)) + { + state.channel.rx_frequency = ui_state.new_rx_frequency; + state.channel.tx_frequency = ui_state.new_tx_frequency; + *sync_rtx = true; + // force init to clear any prompts in progress. + // defer play because play is called at the end of the function + //due to above freq queuing. + vp_announceFrequencies(state.channel.rx_frequency, + state.channel.tx_frequency, vpqInit); + } + else + { + vp_announceError(vpqInit); + } + + state.ui_screen = MAIN_VFO; + } + + vp_play(); +} + +static void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) +{ + // Advance input position + ui_state.input_position += 1; + // clear any prompts in progress. + vp_flush(); + // Save pressed number to calculate frequency and show in GUI + ui_state.input_number = input_getPressedNumber(msg); + // queue the digit just pressed. + vp_queueInteger(ui_state.input_number); + // queue point if user has entered three digits. + if (ui_state.input_position == 3) + vp_queuePrompt(PROMPT_POINT); + + if(ui_state.input_set == SET_RX) + { + if(ui_state.input_position == 1) + ui_state.new_rx_frequency = 0; + // Calculate portion of the new RX frequency + ui_state.new_rx_frequency = _ui_freq_add_digit(ui_state.new_rx_frequency, + ui_state.input_position, + ui_state.input_number); + if(ui_state.input_position >= FREQ_DIGITS) + {// queue the rx freq just completed. + vp_queueFrequency(ui_state.new_rx_frequency); + /// now queue tx as user has changed fields. + vp_queuePrompt(PROMPT_TRANSMIT); + // Switch to TX input + ui_state.input_set = SET_TX; + // Reset input position + ui_state.input_position = 0; + // Reset TX frequency + ui_state.new_tx_frequency = 0; + } + } + else if(ui_state.input_set == SET_TX) + { + if(ui_state.input_position == 1) + ui_state.new_tx_frequency = 0; + // Calculate portion of the new TX frequency + ui_state.new_tx_frequency = _ui_freq_add_digit(ui_state.new_tx_frequency, + ui_state.input_position, + ui_state.input_number); + if(ui_state.input_position >= FREQ_DIGITS) + { + // Save both inserted frequencies + if(_ui_freq_check_limits(ui_state.new_rx_frequency) && + _ui_freq_check_limits(ui_state.new_tx_frequency)) + { + state.channel.rx_frequency = ui_state.new_rx_frequency; + state.channel.tx_frequency = ui_state.new_tx_frequency; + *sync_rtx = true; + // play is called at end. + vp_announceFrequencies(state.channel.rx_frequency, + state.channel.tx_frequency, vpqInit); + } + + state.ui_screen = MAIN_VFO; + } + } + + vp_play(); +} + +#ifdef CONFIG_SCREEN_BRIGHTNESS +static void _ui_changeBrightness(int variation) +{ + state.settings.brightness += variation; + + // Max value for brightness is 100, min value is set to 5 to avoid complete + // display shutdown. + if(state.settings.brightness > 100) state.settings.brightness = 100; + if(state.settings.brightness < 5) state.settings.brightness = 5; + + display_setBacklightLevel(state.settings.brightness); +} +#endif + +#ifdef CONFIG_SCREEN_CONTRAST +static void _ui_changeContrast(int variation) +{ + if(variation >= 0) + state.settings.contrast = + (255 - state.settings.contrast < variation) ? 255 : state.settings.contrast + variation; + else + state.settings.contrast = + (state.settings.contrast < -variation) ? 0 : state.settings.contrast + variation; + + display_setContrast(state.settings.contrast); +} +#endif + +static void _ui_changeTimer(int variation) +{ + if ((state.settings.display_timer == TIMER_OFF && variation < 0) || + (state.settings.display_timer == TIMER_1H && variation > 0)) + { + return; + } + + state.settings.display_timer += variation; +} + +static void _ui_changeMacroLatch(bool newVal) +{ + state.settings.macroMenuLatch = newVal ? 1 : 0; + vp_announceSettingsOnOffToggle(¤tLanguage->macroLatching, + vp_getVoiceLevelQueueFlags(), + state.settings.macroMenuLatch); +} + +#ifdef CONFIG_M17 +static inline void _ui_changeM17Can(int variation) +{ + uint8_t can = state.settings.m17_can; + state.settings.m17_can = (can + variation) % 16; +} +#endif + +static void _ui_changeVoiceLevel(int variation) +{ + if ((state.settings.vpLevel == vpNone && variation < 0) || + (state.settings.vpLevel == vpHigh && variation > 0)) + { + return; + } + + state.settings.vpLevel += variation; + + // Force these flags to ensure the changes are spoken for levels 1 through 3. + vpQueueFlags_t flags = (vpQueueFlags_t)(vpqInit + | vpqAddSeparatingSilence + | vpqPlayImmediately); + + if (!vp_isPlaying()) + { + flags = (vpQueueFlags_t)(flags | vpqIncludeDescriptions); + } + + vp_announceSettingsVoiceLevel(flags); +} + +static void _ui_changePhoneticSpell(bool newVal) +{ + state.settings.vpPhoneticSpell = newVal ? 1 : 0; + + vp_announceSettingsOnOffToggle(¤tLanguage->phonetic, + vp_getVoiceLevelQueueFlags(), + state.settings.vpPhoneticSpell); +} + +bool _ui_checkStandby(long long time_since_last_event) +{ + if (standby) + { + return false; + } + + switch (state.settings.display_timer) + { + case TIMER_OFF: + return false; + case TIMER_5S: + case TIMER_10S: + case TIMER_15S: + case TIMER_20S: + case TIMER_25S: + case TIMER_30S: + return time_since_last_event >= (5000 * state.settings.display_timer); + case TIMER_1M: + case TIMER_2M: + case TIMER_3M: + case TIMER_4M: + case TIMER_5M: + return time_since_last_event >= + (60000 * (state.settings.display_timer - (TIMER_1M - 1))); + case TIMER_15M: + case TIMER_30M: + case TIMER_45M: + return time_since_last_event >= + (60000 * 15 * (state.settings.display_timer - (TIMER_15M - 1))); + case TIMER_1H: + return time_since_last_event >= 60 * 60 * 1000; + } + + // unreachable code + return false; +} + +static void _ui_enterStandby() +{ + if(standby) + return; + + standby = true; + redraw_needed = false; + display_setBacklightLevel(0); +} + +static bool _ui_exitStandby(long long now) +{ + last_event_tick = now; + + if(!standby) + return false; + + standby = false; + redraw_needed = true; + display_setBacklightLevel(state.settings.brightness); + + return true; +} + +// TODO: find a better home for this function +int _ui_handleToneSelectScroll(bool direction_up) +{ + bool tone_tx_enable = state.channel.fm.txToneEn; + bool tone_rx_enable = state.channel.fm.rxToneEn; + uint8_t tone_flags = tone_tx_enable << 1 | tone_rx_enable; + + if(direction_up) + tone_flags++; + else + tone_flags--; + + tone_flags %= 4; + tone_tx_enable = tone_flags >> 1; + tone_rx_enable = tone_flags & 1; + state.channel.fm.txToneEn = tone_tx_enable; + state.channel.fm.rxToneEn = tone_rx_enable; + + return 1; +} + +static void _ui_fsm_menuMacro(kbd_msg_t msg, bool *sync_rtx) +{ + // If there is no keyboard left and right select the menu entry to edit +#if defined(CONFIG_UI_NO_KEYBOARD) + if (msg.keys & KNOB_LEFT) + { + ui_state.macro_menu_selected--; + ui_state.macro_menu_selected += 9; + ui_state.macro_menu_selected %= 9; + } + if (msg.keys & KNOB_RIGHT) + { + ui_state.macro_menu_selected++; + ui_state.macro_menu_selected %= 9; + } + if ((msg.keys & KEY_ENTER) && !msg.long_press) + ui_state.input_number = ui_state.macro_menu_selected + 1; + else + ui_state.input_number = 0; +#else // CONFIG_UI_NO_KEYBOARD + ui_state.input_number = input_getPressedNumber(msg); +#endif // CONFIG_UI_NO_KEYBOARD + // CTCSS Encode/Decode Selection + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + switch(ui_state.input_number) + { + case 1: + if(state.channel.mode == OPMODE_FM) + { + _ui_handleToneSelectScroll(true); + *sync_rtx = true; + vp_announceCTCSS( + state.channel.fm.rxToneEn, state.channel.fm.rxTone, + state.channel.fm.txToneEn, state.channel.fm.txTone, + (vpQueueFlags_t)(queueFlags | vpqIncludeDescriptions)); + } + break; + case 2: + if (state.channel.mode == OPMODE_FM) + { + if (state.channel.fm.txTone == 0) + { + state.channel.fm.txTone = CTCSS_FREQ_NUM-1; + } + else + { + state.channel.fm.txTone--; + } + + state.channel.fm.txTone %= CTCSS_FREQ_NUM; + state.channel.fm.rxTone = state.channel.fm.txTone; + *sync_rtx = true; + vp_announceCTCSS(state.channel.fm.rxToneEn, + state.channel.fm.rxTone, + state.channel.fm.txToneEn, + state.channel.fm.txTone, + queueFlags); + } + break; + + case 3: + if(state.channel.mode == OPMODE_FM) + { + state.channel.fm.txTone++; + state.channel.fm.txTone %= CTCSS_FREQ_NUM; + state.channel.fm.rxTone = state.channel.fm.txTone; + *sync_rtx = true; + vp_announceCTCSS(state.channel.fm.rxToneEn, + state.channel.fm.rxTone, + state.channel.fm.txToneEn, + state.channel.fm.txTone, + (vpQueueFlags_t)(queueFlags | vpqIncludeDescriptions)); + } + break; + case 4: + if(state.channel.mode == OPMODE_FM) + { + state.channel.bandwidth++; + state.channel.bandwidth %= 2; + *sync_rtx = true; + vp_announceBandwidth(state.channel.bandwidth, queueFlags); + } + break; + case 5: + // Cycle through radio modes + #ifdef CONFIG_M17 + if(state.channel.mode == OPMODE_FM) + state.channel.mode = OPMODE_M17; + else if(state.channel.mode == OPMODE_M17) + state.channel.mode = OPMODE_FM; + else //catch any invalid states so they don't get locked out + #endif + state.channel.mode = OPMODE_FM; + *sync_rtx = true; + vp_announceRadioMode(state.channel.mode, queueFlags); + break; + case 6: + + switch(state.channel.power) + { + case 1000: + state.channel.power = 2500; + break; + + case 2500: + state.channel.power = 5000; + break; + + default: + state.channel.power = 1000; + } + + *sync_rtx = true; + vp_announcePower(state.channel.power, queueFlags); + break; +#ifdef CONFIG_SCREEN_BRIGHTNESS + case 7: + _ui_changeBrightness(-5); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, + state.settings.brightness); + break; + case 8: + _ui_changeBrightness(+5); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, + state.settings.brightness); + break; +#endif + case 9: + if (!ui_state.input_locked) + ui_state.input_locked = true; + else + ui_state.input_locked = false; + break; + } + +#if defined(PLATFORM_TTWRPLUS) + if(msg.keys & KEY_VOLDOWN) +#else + if(msg.keys & KEY_LEFT || msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) +#endif // PLATFORM_TTWRPLUS + { +#ifdef CONFIG_KNOB_ABSOLUTE // If the radio has an absolute position knob + state.settings.sqlLevel = platform_getChSelector() - 1; +#endif // CONFIG_KNOB_ABSOLUTE + if(state.settings.sqlLevel > 0) + { + state.settings.sqlLevel -= 1; + *sync_rtx = true; + vp_announceSquelch(state.settings.sqlLevel, queueFlags); + } + } + +#if defined(PLATFORM_TTWRPLUS) + else if(msg.keys & KEY_VOLUP) +#else + else if(msg.keys & KEY_RIGHT || msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) +#endif // PLATFORM_TTWRPLUS + { +#ifdef CONFIG_KNOB_ABSOLUTE + state.settings.sqlLevel = platform_getChSelector() - 1; +#endif + if(state.settings.sqlLevel < 15) + { + state.settings.sqlLevel += 1; + *sync_rtx = true; + vp_announceSquelch(state.settings.sqlLevel, queueFlags); + } + } +} + +static void _ui_menuUp(uint8_t menu_entries) +{ + if(ui_state.menu_selected > 0) + ui_state.menu_selected -= 1; + else + ui_state.menu_selected = menu_entries - 1; + vp_playMenuBeepIfNeeded(ui_state.menu_selected==0); +} + +static void _ui_menuDown(uint8_t menu_entries) +{ + if(ui_state.menu_selected < menu_entries - 1) + ui_state.menu_selected += 1; + else + ui_state.menu_selected = 0; + vp_playMenuBeepIfNeeded(ui_state.menu_selected==0); +} + +static void transition(Screen *next); // defined after screen instances + +static void _ui_menuBack(Screen *prev) +{ + if(ui_state.edit_mode) + { + ui_state.edit_mode = false; + } + else + { + transition(prev); + ui_state.menu_selected = 0; + vp_playMenuBeepIfNeeded(true); + } +} + +static void _ui_textInputReset(char *buf) +{ + ui_state.input_number = 0; + ui_state.input_position = 0; + ui_state.input_set = 0; + ui_state.last_keypress = 0; + memset(buf, 0, 9); + buf[0] = '_'; +} + +static void _ui_textInputKeypad(char *buf, uint8_t max_len, kbd_msg_t msg, + bool callsign) +{ + long long now = getTick(); + // Get currently pressed number key + uint8_t num_key = input_getPressedChar(msg); + + bool key_timeout = ((now - ui_state.last_keypress) >= input_longPressTimeout); + bool same_key = ui_state.input_number == num_key; + // Get number of symbols related to currently pressed key + uint8_t num_symbols = 0; + if(callsign) + { + num_symbols = strlen(symbols_ITU_T_E161_callsign[num_key]); + if(num_symbols == 0) + return; + } + else + num_symbols = strlen(symbols_ITU_T_E161[num_key]); + + // Return if max length is reached or finished editing last character + if((ui_state.input_position >= max_len) || ((ui_state.input_position == (max_len-1)) && (key_timeout || !same_key))) + return; + + // Skip keypad logic for first keypress + if(ui_state.last_keypress != 0) + { + // Same key pressed and timeout not expired: cycle over chars of current key + if(same_key && !key_timeout) + { + ui_state.input_set = (ui_state.input_set + 1) % num_symbols; + } + // Different key pressed: save current char and change key + else + { + ui_state.input_position += 1; + ui_state.input_set = 0; + } + } + // Show current character on buffer + if(callsign) + buf[ui_state.input_position] = symbols_ITU_T_E161_callsign[num_key][ui_state.input_set]; + else + { + buf[ui_state.input_position] = symbols_ITU_T_E161[num_key][ui_state.input_set]; + } + // Announce the character + vp_announceInputChar(buf[ui_state.input_position]); + // Update reference values + ui_state.input_number = num_key; + ui_state.last_keypress = now; +} + +static void _ui_textInputConfirm(char *buf) +{ + buf[ui_state.input_position + 1] = '\0'; +} + +static void _ui_textInputDel(char *buf) +{ + // announce the char about to be backspaced. + // Note this assumes editing callsign. + // If we edit a different buffer which allows the underline char, we may + // not want to exclude it, but when editing callsign, we do not want to say + // underline since it means the field is empty. + if(buf[ui_state.input_position] + && buf[ui_state.input_position]!='_') + vp_announceInputChar(buf[ui_state.input_position]); + + buf[ui_state.input_position] = '\0'; + // Move back input cursor + if(ui_state.input_position > 0) + { + ui_state.input_position--; + // If we deleted the initial character, reset starting condition + } + else + ui_state.last_keypress = 0; + ui_state.input_set = 0; +} + +static void _ui_numberInputKeypad(uint32_t *num, kbd_msg_t msg) +{ + long long now = getTick(); + +#ifdef CONFIG_UI_NO_KEYBOARD + // If knob is turned, increment or Decrement + if (msg.keys & KNOB_LEFT) + { + *num = *num + 1; + if (*num % 10 == 0) + *num = *num - 10; + } + + if (msg.keys & KNOB_RIGHT) + { + if (*num == 0) + *num = 9; + else + { + *num = *num - 1; + if (*num % 10 == 9) + *num = *num + 10; + } + } + + // If enter is pressed, advance to the next digit + if (msg.keys & KEY_ENTER) + *num *= 10; + + // Announce the character + vp_announceInputChar('0' + *num % 10); + + // Update reference values + ui_state.input_number = *num % 10; +#else + // Maximum frequency len is uint32_t max value number of decimal digits + if(ui_state.input_position >= 10) + return; + + // Get currently pressed number key + uint8_t num_key = input_getPressedNumber(msg); + *num *= 10; + *num += num_key; + + // Announce the character + vp_announceInputChar('0' + num_key); + + // Update reference values + ui_state.input_number = num_key; +#endif + + ui_state.last_keypress = now; +} + +static void _ui_numberInputDel(uint32_t *num) +{ + // announce the digit about to be backspaced. + vp_announceInputChar('0' + *num % 10); + + // Move back input cursor + if(ui_state.input_position > 0) + ui_state.input_position--; + else + ui_state.last_keypress = 0; + + ui_state.input_set = 0; +} + +// ─── Screen subclass declarations ─────────────────────────────────────────── + +class VfoScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class VfoInputScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MemScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuTopScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuBankScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuChannelScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuContactsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#ifdef CONFIG_GPS +class MenuGpsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#endif +class MenuSettingsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuBackupRestoreScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuBackupScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuRestoreScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuInfoScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuAboutScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#ifdef CONFIG_RTC +class SettingsTimedateScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class SettingsTimedateSetScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#endif +class SettingsDisplayScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#ifdef CONFIG_GPS +class SettingsGpsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#endif +class SettingsRadioScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#ifdef CONFIG_M17 +class SettingsM17Screen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +#endif +class SettingsFmScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class SettingsAccessibilityScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class SettingsReset2DefaultsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class LowBatScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; + +// ─── Static screen instances ──────────────────────────────────────────────── + +static VfoScreen vfoScreen; +static VfoInputScreen vfoInputScreen; +static MemScreen memScreen; +static MenuTopScreen menuTopScreen; +static MenuBankScreen menuBankScreen; +static MenuChannelScreen menuChannelScreen; +static MenuContactsScreen menuContactsScreen; +#ifdef CONFIG_GPS +static MenuGpsScreen menuGpsScreen; +#endif +static MenuSettingsScreen menuSettingsScreen; +static MenuBackupRestoreScreen menuBackupRestoreScreen; +static MenuBackupScreen menuBackupScreen; +static MenuRestoreScreen menuRestoreScreen; +static MenuInfoScreen menuInfoScreen; +static MenuAboutScreen menuAboutScreen; +#ifdef CONFIG_RTC +static SettingsTimedateScreen settingsTimedateScreen; +static SettingsTimedateSetScreen settingsTimedateSetScreen; +#endif +static SettingsDisplayScreen settingsDisplayScreen; +#ifdef CONFIG_GPS +static SettingsGpsScreen settingsGpsScreen; +#endif +static SettingsRadioScreen settingsRadioScreen; +#ifdef CONFIG_M17 +static SettingsM17Screen settingsM17Screen; +#endif +static SettingsFmScreen settingsFmScreen; +static SettingsAccessibilityScreen settingsAccessibilityScreen; +static SettingsReset2DefaultsScreen settingsReset2DefaultsScreen; +static LowBatScreen lowBatScreen; + +// ─── Navigation state ─────────────────────────────────────────────────────── + +static Screen *current = &vfoScreen; +static Screen *lastMainScreen = nullptr; +static bool *gSyncRtx = nullptr; +static bool gF1Handled = false; + +static void transition(Screen *next) +{ + current->exit(); + next->enter(); + current = next; +} + +// ─── Render methods ───────────────────────────────────────────────────────── + +void VfoScreen::render() { _ui_drawMainVFO(&ui_state); } +void VfoInputScreen::render() { _ui_drawMainVFOInput(&ui_state); } +void MemScreen::render() { _ui_drawMainMEM(&ui_state); } +void MenuTopScreen::render() { _ui_drawMenuTop(&ui_state); } +void MenuBankScreen::render() { _ui_drawMenuBank(&ui_state); } +void MenuChannelScreen::render() { _ui_drawMenuChannel(&ui_state); } +void MenuContactsScreen::render() { _ui_drawMenuContacts(&ui_state); } +#ifdef CONFIG_GPS +void MenuGpsScreen::render() { _ui_drawMenuGPS(); } +#endif +void MenuSettingsScreen::render() { _ui_drawMenuSettings(&ui_state); } +void MenuBackupRestoreScreen::render() { _ui_drawMenuBackupRestore(&ui_state); } +void MenuBackupScreen::render() { _ui_drawMenuBackup(&ui_state); } +void MenuRestoreScreen::render() { _ui_drawMenuRestore(&ui_state); } +void MenuInfoScreen::render() { _ui_drawMenuInfo(&ui_state); } +void MenuAboutScreen::render() { _ui_drawMenuAbout(&ui_state); } +#ifdef CONFIG_RTC +void SettingsTimedateScreen::render() { _ui_drawSettingsTimeDate(); } +void SettingsTimedateSetScreen::render() { _ui_drawSettingsTimeDateSet(&ui_state); } +#endif +void SettingsDisplayScreen::render() { _ui_drawSettingsDisplay(&ui_state); } +#ifdef CONFIG_GPS +void SettingsGpsScreen::render() { _ui_drawSettingsGPS(&ui_state); } +#endif +void SettingsRadioScreen::render() { _ui_drawSettingsRadio(&ui_state); } +#ifdef CONFIG_M17 +void SettingsM17Screen::render() { _ui_drawSettingsM17(&ui_state); } +#endif +void SettingsFmScreen::render() { _ui_drawSettingsFM(&ui_state); } +void SettingsAccessibilityScreen::render() { _ui_drawSettingsAccessibility(&ui_state); } +void SettingsReset2DefaultsScreen::render() { _ui_drawSettingsReset2Defaults(&ui_state); } +void LowBatScreen::render() { _ui_drawLowBatteryScreen(); } + +// ─── handleInput implementations ──────────────────────────────────────────── + +bool VfoScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(state.txDisable) { state.txDisable = false; *gSyncRtx = true; } + bool skipLock = (state.channel.mode == OPMODE_FM) && (msg.keys == KEY_HASH); + if(ui_state.input_locked && !skipLock) return true; + + if(ui_state.edit_mode) + { +#ifdef CONFIG_M17 + if(state.channel.mode == OPMODE_M17) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); + ui_state.edit_mode = false; + *gSyncRtx = true; + vp_announceM17Info(NULL, ui_state.edit_mode, queueFlags); + } + else if(msg.keys & KEY_HASH) + { + strncpy(state.settings.m17_dest, "", 1); + ui_state.edit_mode = false; + *gSyncRtx = true; + vp_announceM17Info(NULL, ui_state.edit_mode, queueFlags); + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) + _ui_textInputDel(ui_state.new_callsign); + else if(input_isCharPressed(msg)) + _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); + } +#endif + } + else + { + if(msg.keys & KEY_ENTER) + { + lastMainScreen = current; + transition(&menuTopScreen); + } + else if(msg.keys & KEY_ESC) + { + state.vfo_channel = state.channel; + int result = _ui_fsm_loadChannel(state.channel_index, gSyncRtx); + if(result != -1) + { + transition(&memScreen); + vp_announceChannelName(&state.channel, state.channel_index, queueFlags); + } + } + else if(msg.keys & KEY_HASH) + { +#ifdef CONFIG_M17 + if(state.channel.mode == OPMODE_M17) + { + ui_state.edit_mode = true; + _ui_textInputReset(ui_state.new_callsign); + vp_announceM17Info(NULL, ui_state.edit_mode, queueFlags); + } + else +#endif + { + if(!state.tone_enabled) { state.tone_enabled = true; *gSyncRtx = true; } + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + { + if(_ui_freq_check_limits(state.channel.rx_frequency + freq_steps[state.step_index]) && + _ui_freq_check_limits(state.channel.tx_frequency + freq_steps[state.step_index])) + { + state.channel.rx_frequency += freq_steps[state.step_index]; + state.channel.tx_frequency += freq_steps[state.step_index]; + *gSyncRtx = true; + vp_announceFrequencies(state.channel.rx_frequency, state.channel.tx_frequency, queueFlags); + } + } + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + { + if(_ui_freq_check_limits(state.channel.rx_frequency - freq_steps[state.step_index]) && + _ui_freq_check_limits(state.channel.tx_frequency - freq_steps[state.step_index])) + { + state.channel.rx_frequency -= freq_steps[state.step_index]; + state.channel.tx_frequency -= freq_steps[state.step_index]; + *gSyncRtx = true; + vp_announceFrequencies(state.channel.rx_frequency, state.channel.tx_frequency, queueFlags); + } + } + else if(msg.keys & KEY_F1) + { + if(state.settings.vpLevel > vpBeep) + { + if(msg.long_press) + vp_announceChannelSummary(&state.channel, 0, state.bank, vpAllInfo); + else + vp_replayLastPrompt(); + gF1Handled = true; + } + } + else if(input_isNumberPressed(msg)) + { + transition(&vfoInputScreen); + ui_state.input_position = 1; + ui_state.input_set = SET_RX; + vp_announceInputReceiveOrTransmit(false, vpqInit); + vp_queueInteger(input_getPressedNumber(msg)); + vp_play(); + ui_state.new_rx_frequency = 0; + ui_state.new_tx_frequency = 0; + ui_state.input_number = input_getPressedNumber(msg); + ui_state.new_rx_frequency = _ui_freq_add_digit(ui_state.new_rx_frequency, + ui_state.input_position, + ui_state.input_number); + } + } + return true; +} + +bool VfoInputScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(msg.keys & KEY_ENTER) + _ui_fsm_confirmVFOInput(gSyncRtx); + else if(msg.keys & KEY_ESC) + transition(&vfoScreen); + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN) + { + if(ui_state.input_set == SET_RX) + { + ui_state.input_set = SET_TX; + vp_announceInputReceiveOrTransmit(true, queueFlags); + } + else if(ui_state.input_set == SET_TX) + { + ui_state.input_set = SET_RX; + vp_announceInputReceiveOrTransmit(false, queueFlags); + } + ui_state.input_position = 0; + } + else if(input_isNumberPressed(msg)) + _ui_fsm_insertVFONumber(msg, gSyncRtx); + return true; +} + +bool MemScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(state.txDisable) { state.txDisable = false; *gSyncRtx = true; } + if(ui_state.input_locked) return true; + + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); + ui_state.edit_mode = false; + *gSyncRtx = true; + } + else if(msg.keys & KEY_HASH) + { + strncpy(state.settings.m17_dest, "", 1); + ui_state.edit_mode = false; + *gSyncRtx = true; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else if(msg.keys & KEY_F1) + { + if(state.settings.vpLevel > vpBeep) + { + if(msg.long_press) + vp_announceChannelSummary(&state.channel, state.channel_index, state.bank, vpAllInfo); + else + vp_replayLastPrompt(); + gF1Handled = true; + } + } + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) + _ui_textInputDel(ui_state.new_callsign); + else if(input_isCharPressed(msg)) + _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); + } + else + { + if(msg.keys & KEY_ENTER) + { + lastMainScreen = current; + transition(&menuTopScreen); + } + else if(msg.keys & KEY_ESC) + { + state.channel = state.vfo_channel; + *gSyncRtx = true; + transition(&vfoScreen); + } + else if(msg.keys & KEY_HASH) + { + if(state.channel.mode == OPMODE_M17) + { + ui_state.edit_mode = true; + _ui_textInputReset(ui_state.new_callsign); + } + else + { + if(!state.tone_enabled) { state.tone_enabled = true; *gSyncRtx = true; } + } + } + else if(msg.keys & KEY_F1) + { + if(state.settings.vpLevel > vpBeep) + { + if(msg.long_press) + vp_announceChannelSummary(&state.channel, state.channel_index + 1, state.bank, vpAllInfo); + else + vp_replayLastPrompt(); + gF1Handled = true; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + { + _ui_fsm_loadChannel(state.channel_index + 1, gSyncRtx); + vp_announceChannelName(&state.channel, state.channel_index + 1, queueFlags); + } + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + { + _ui_fsm_loadChannel(state.channel_index - 1, gSyncRtx); + vp_announceChannelName(&state.channel, state.channel_index + 1, queueFlags); + } + } + return true; +} + +bool MenuTopScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(menu_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(menu_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case M_BANK: transition(&menuBankScreen); break; + case M_CHANNEL: transition(&menuChannelScreen); break; + case M_CONTACTS: transition(&menuContactsScreen); break; +#ifdef CONFIG_GPS + case M_GPS: transition(&menuGpsScreen); break; +#endif + case M_SETTINGS: transition(&menuSettingsScreen); break; + case M_INFO: transition(&menuInfoScreen); break; + case M_ABOUT: transition(&menuAboutScreen); break; + } + ui_state.menu_selected = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(lastMainScreen); + return true; +} + +bool MenuBankScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(1); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + { + bankHdr_t bank; + if(cps_readBankHeader(&bank, ui_state.menu_selected) != -1) + ui_state.menu_selected += 1; + } + else if(msg.keys & KEY_ENTER) + { + bankHdr_t newbank; + int result = 0; + if(ui_state.menu_selected == 0) + state.bank_enabled = false; + else + { + state.bank_enabled = true; + result = cps_readBankHeader(&newbank, ui_state.menu_selected - 1); + } + if(result != -1) + { + state.bank = ui_state.menu_selected - 1; + if(lastMainScreen == &vfoScreen) + state.vfo_channel = state.channel; + _ui_fsm_loadChannel(0, gSyncRtx); + transition(&memScreen); + } + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuChannelScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(1); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + { + channel_t channel; + if(cps_readChannel(&channel, ui_state.menu_selected + 1) != -1) + ui_state.menu_selected += 1; + } + else if(msg.keys & KEY_ENTER) + { + if(lastMainScreen == &vfoScreen) + state.vfo_channel = state.channel; + _ui_fsm_loadChannel(ui_state.menu_selected, gSyncRtx); + transition(&memScreen); + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuContactsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(1); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + { + contact_t contact; + if(cps_readContact(&contact, ui_state.menu_selected + 1) != -1) + ui_state.menu_selected += 1; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +#ifdef CONFIG_GPS +bool MenuGpsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if((msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) + { + if(msg.long_press) + vp_announceGPSInfo(vpGPSAll); + else + vp_replayLastPrompt(); + gF1Handled = true; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} +#endif + +bool MenuSettingsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case S_DISPLAY: transition(&settingsDisplayScreen); break; +#ifdef CONFIG_RTC + case S_TIMEDATE: transition(&settingsTimedateScreen); break; +#endif +#ifdef CONFIG_GPS + case S_GPS: transition(&settingsGpsScreen); break; +#endif + case S_RADIO: transition(&settingsRadioScreen); break; +#ifdef CONFIG_M17 + case S_M17: transition(&settingsM17Screen); break; +#endif + case S_FM: transition(&settingsFmScreen); break; + case S_ACCESSIBILITY: transition(&settingsAccessibilityScreen); break; + case S_RESET2DEFAULTS: transition(&settingsReset2DefaultsScreen); break; + default: break; + } + ui_state.menu_selected = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuBackupRestoreScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(backup_restore_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(backup_restore_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case BR_BACKUP: transition(&menuBackupScreen); break; + case BR_RESTORE: transition(&menuRestoreScreen); break; + default: break; + } + ui_state.menu_selected = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuBackupScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + if(msg.keys & KEY_ESC) _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuRestoreScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + if(msg.keys & KEY_ESC) _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuInfoScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(info_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(info_num); + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuAboutScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + { + if(ui_state.menu_selected > 0) ui_state.menu_selected -= 1; + } + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + ui_state.menu_selected += 1; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +#ifdef CONFIG_RTC +bool SettingsTimedateScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_ENTER) + { + transition(&settingsTimedateSetScreen); + ui_state.input_position = 0; + memset(&ui_state.new_timedate, 0, sizeof(datetime_t)); + vp_announceBuffer(¤tLanguage->timeAndDate, true, false, "dd/mm/yy"); + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + return true; +} + +bool SettingsTimedateSetScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_ENTER) + { + if(ui_state.input_position < TIMEDATE_DIGITS) return true; + datetime_t utc_time = localTimeToUtc(ui_state.new_timedate, state.settings.utc_timezone); + platform_setTime(utc_time); + state.time = utc_time; + vp_announceSettingsTimeDate(); + transition(&settingsTimedateScreen); + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&settingsTimedateScreen); + else if(input_isNumberPressed(msg)) + { + if(ui_state.input_position > TIMEDATE_DIGITS) return true; + ui_state.input_position += 1; + ui_state.input_number = input_getPressedNumber(msg); + _ui_timedate_add_digit(&ui_state.new_timedate, ui_state.input_position, + ui_state.input_number); + } + return true; +} +#endif + +bool SettingsDisplayScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(msg.keys & KEY_LEFT || (ui_state.edit_mode && + (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT))) + { + switch(ui_state.menu_selected) + { +#ifdef CONFIG_SCREEN_BRIGHTNESS + case D_BRIGHTNESS: + _ui_changeBrightness(-5); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, state.settings.brightness); + break; +#endif +#ifdef CONFIG_SCREEN_CONTRAST + case D_CONTRAST: + _ui_changeContrast(-4); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, state.settings.contrast); + break; +#endif + case D_TIMER: + _ui_changeTimer(-1); + vp_announceDisplayTimer(); + break; + case D_BATTERY: + state.settings.showBatteryIcon = !state.settings.showBatteryIcon; + break; + default: break; + } + } + else if(msg.keys & KEY_RIGHT || (ui_state.edit_mode && + (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) + { + switch(ui_state.menu_selected) + { +#ifdef CONFIG_SCREEN_BRIGHTNESS + case D_BRIGHTNESS: + _ui_changeBrightness(+5); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, state.settings.brightness); + break; +#endif +#ifdef CONFIG_SCREEN_CONTRAST + case D_CONTRAST: + _ui_changeContrast(+4); + vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, state.settings.contrast); + break; +#endif + case D_TIMER: + _ui_changeTimer(+1); + vp_announceDisplayTimer(); + break; + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(display_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(display_num); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + return true; +} + +#ifdef CONFIG_GPS +bool SettingsGpsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || + (ui_state.edit_mode && + (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT || + msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) + { + switch(ui_state.menu_selected) + { + case G_ENABLED: + if(state.settings.gps_enabled) state.settings.gps_enabled = 0; + else state.settings.gps_enabled = 1; + vp_announceSettingsOnOffToggle(¤tLanguage->gpsEnabled, queueFlags, + state.settings.gps_enabled); + break; +#ifdef CONFIG_RTC + case G_SET_TIME: + state.settings.gpsSetTime = !state.settings.gpsSetTime; + vp_announceSettingsOnOffToggle(¤tLanguage->gpsSetTime, queueFlags, + state.settings.gpsSetTime); + break; + case G_TIMEZONE: + if(msg.keys & KEY_LEFT || msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + state.settings.utc_timezone -= 1; + else if(msg.keys & KEY_RIGHT || msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + state.settings.utc_timezone += 1; + vp_announceTimeZone(state.settings.utc_timezone, queueFlags); + break; +#endif + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_gps_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_gps_num); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + return true; +} +#endif + +bool SettingsRadioScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(ui_state.edit_mode) + { + switch(ui_state.menu_selected) + { + case R_OFFSET: +#if defined(CONFIG_UI_NO_KEYBOARD) + if(msg.long_press && msg.keys & KEY_ENTER) + { + ui_state.new_offset /= 10; +#else + if(msg.keys & KEY_ENTER) + { +#endif + freq_t new_freq = state.channel.rx_frequency + ui_state.new_offset; + if(_ui_freq_check_limits(new_freq)) + { + state.channel.tx_frequency = new_freq; + vp_queueStringTableEntry(¤tLanguage->offset); + vp_queueFrequency(ui_state.new_offset); + ui_state.edit_mode = false; + } + } + else if(msg.keys & KEY_ESC) + { + vp_queueStringTableEntry(¤tLanguage->offset); + vp_queueFrequency((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); + } + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) + _ui_numberInputDel(&ui_state.new_offset); +#if defined(CONFIG_UI_NO_KEYBOARD) + else if(msg.keys & KNOB_LEFT || msg.keys & KNOB_RIGHT || msg.keys & KEY_ENTER) +#else + else if(input_isNumberPressed(msg)) +#endif + { + _ui_numberInputKeypad(&ui_state.new_offset, msg); + ui_state.input_position += 1; + } + else if(msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) + { + vp_queueFrequency(ui_state.new_offset); + gF1Handled = true; + } + break; + case R_DIRECTION: + if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || + msg.keys & KNOB_LEFT || msg.keys & KNOB_RIGHT) + { + if(state.channel.tx_frequency >= state.channel.rx_frequency) + state.channel.tx_frequency -= 2 * ((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); + else + state.channel.tx_frequency -= 2 * ((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); + } + break; + case R_STEP: + if(msg.keys & KEY_UP || msg.keys & KEY_RIGHT || msg.keys & KNOB_RIGHT) + { + state.step_index++; + state.step_index %= n_freq_steps; + } + else if(msg.keys & KEY_DOWN || msg.keys & KEY_LEFT || msg.keys & KNOB_LEFT) + { + state.step_index += n_freq_steps; + state.step_index--; + state.step_index %= n_freq_steps; + } + break; + default: break; + } + if((ui_state.menu_selected != R_OFFSET && msg.keys & KEY_ENTER) || msg.keys & KEY_ESC) + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_radio_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_radio_num); + else if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = true; + if(ui_state.menu_selected == R_OFFSET) ui_state.new_offset = 0; + ui_state.input_position = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + return true; +} + +#ifdef CONFIG_M17 +bool SettingsM17Screen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(ui_state.edit_mode) + { + switch(ui_state.menu_selected) + { + case M17_CALLSIGN: + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + strncpy(state.settings.callsign, ui_state.new_callsign, 10); + ui_state.edit_mode = false; + vp_announceBuffer(¤tLanguage->callsign, false, true, state.settings.callsign); + } + else if(msg.keys & KEY_ESC) + { + ui_state.edit_mode = false; + vp_announceBuffer(¤tLanguage->callsign, false, true, state.settings.callsign); + } + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) + _ui_textInputDel(ui_state.new_callsign); + else if(input_isCharPressed(msg)) + _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); + else if(msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) + { + vp_announceBuffer(¤tLanguage->callsign, true, true, ui_state.new_callsign); + gF1Handled = true; + } + break; + case M17_METATEXT: + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_message); + strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); + ui_state.edit_message = false; + ui_state.edit_mode = false; + vp_announceBuffer(¤tLanguage->metaText, false, true, state.settings.M17_meta_text); + } + else if(msg.keys & KEY_ESC) + { + ui_state.edit_message = false; + ui_state.edit_mode = false; + vp_announceBuffer(¤tLanguage->metaText, false, true, state.settings.M17_meta_text); + } + else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || + msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) + _ui_textInputDel(ui_state.new_message); + else if(input_isCharPressed(msg)) + _ui_textInputKeypad(ui_state.new_message, 52, msg, false); + else if(msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) + { + vp_announceBuffer(¤tLanguage->metaText, true, true, ui_state.new_message); + gF1Handled = true; + } + break; + case M17_CAN: + if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + _ui_changeM17Can(-1); + else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + _ui_changeM17Can(+1); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + break; + case M17_CAN_RX: + if(msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || + (ui_state.edit_mode && + (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT || + msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) + state.settings.m17_can_rx = !state.settings.m17_can_rx; + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + break; + } + } + else + { + if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = true; + if(ui_state.menu_selected == M17_CALLSIGN) + { + _ui_textInputReset(ui_state.new_callsign); + vp_announceBuffer(¤tLanguage->callsign, true, true, ui_state.new_callsign); + } + if(ui_state.menu_selected == M17_METATEXT) + { + ui_state.edit_message = true; + _ui_textInputReset(ui_state.new_message); + vp_announceBuffer(¤tLanguage->metaText, true, true, ui_state.new_message); + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_m17_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_m17_num); + else if((msg.keys & KEY_RIGHT) && (ui_state.menu_selected == M17_CAN)) + _ui_changeM17Can(+1); + else if((msg.keys & KEY_LEFT) && (ui_state.menu_selected == M17_CAN)) + _ui_changeM17Can(-1); + else if(msg.keys & KEY_ESC) + { + *gSyncRtx = true; + _ui_menuBack(&menuSettingsScreen); + } + } + return true; +} +#endif + +bool SettingsFmScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ESC) ui_state.edit_mode = false; + + switch(ui_state.menu_selected) + { + case CTCSS_Tone: + if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + { + if(state.channel.fm.txTone == 0) + state.channel.fm.txTone = CTCSS_FREQ_NUM - 1; + else + state.channel.fm.txTone--; + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + state.channel.fm.txTone++; + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = false; + state.channel.fm.txTone %= CTCSS_FREQ_NUM; + state.channel.fm.rxTone = state.channel.fm.txTone; + *gSyncRtx = true; + break; + case CTCSS_Enabled: + if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) + _ui_handleToneSelectScroll(true); + else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) + _ui_handleToneSelectScroll(false); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = false; + *gSyncRtx = true; + break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_fm_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_fm_num); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + return true; +} + +bool SettingsAccessibilityScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + + if(msg.keys & KEY_LEFT || (ui_state.edit_mode && + (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT))) + { + switch(ui_state.menu_selected) + { + case A_MACRO_LATCH: _ui_changeMacroLatch(false); break; + case A_LEVEL: _ui_changeVoiceLevel(-1); break; + case A_PHONETIC: _ui_changePhoneticSpell(false); break; + default: break; + } + } + else if(msg.keys & KEY_RIGHT || (ui_state.edit_mode && + (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) + { + switch(ui_state.menu_selected) + { + case A_MACRO_LATCH: _ui_changeMacroLatch(true); break; + case A_LEVEL: _ui_changeVoiceLevel(1); break; + case A_PHONETIC: _ui_changePhoneticSpell(true); break; + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_accessibility_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_accessibility_num); + else if(msg.keys & KEY_ENTER) + ui_state.edit_mode = !ui_state.edit_mode; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + (void)queueFlags; + return true; +} + +bool SettingsReset2DefaultsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(!ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + ui_state.edit_mode = true; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + } + else + { + if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = false; + state_resetSettingsAndVfo(); + _ui_menuBack(&menuSettingsScreen); + } + else if(msg.keys & KEY_ESC) + { + ui_state.edit_mode = false; + _ui_menuBack(&menuSettingsScreen); + } + } + return true; +} + +bool LowBatScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + if(msg.keys) + { + transition(&vfoScreen); + state.emergency = true; + } + return true; +} + +// ──────────────────────────────────────────────────────────────────────────── + +void ui_init() +{ + last_event_tick = getTick(); + redraw_needed = true; + _ui_calculateLayout(&layout); + layout_ready = true; + ui_state = ui_state_t{}; +} + +void ui_drawSplashScreen() +{ + gfx_clearScreen(); + + #if CONFIG_SCREEN_HEIGHT > 64 + static const point_t logo_orig = {0, (CONFIG_SCREEN_HEIGHT / 2) - 6}; + static const point_t call_orig = {0, CONFIG_SCREEN_HEIGHT - 8}; + static const fontSize_t logo_font = FONT_SIZE_12PT; + static const fontSize_t call_font = FONT_SIZE_8PT; + #else + static const point_t logo_orig = {0, 19}; + static const point_t call_orig = {0, CONFIG_SCREEN_HEIGHT - 8}; + static const fontSize_t logo_font = FONT_SIZE_8PT; + static const fontSize_t call_font = FONT_SIZE_6PT; + #endif + + gfx_print(logo_orig, logo_font, TEXT_ALIGN_CENTER, yellow_fab413, "O P N\nR T X"); + gfx_print(call_orig, call_font, TEXT_ALIGN_CENTER, color_white, state.settings.callsign); + + vp_announceSplashScreen(); +} + +void ui_saveState() +{ + last_state = state; +} + +#ifdef CONFIG_GPS +static uint16_t priorGPSSpeed = 0; +static int16_t priorGPSAltitude = 0; +static int16_t priorGPSDirection = 500; // impossible value init. +static uint8_t priorGPSFixQuality= 0; +static uint8_t priorGPSFixType = 0; +static uint8_t priorSatellitesInView = 0; +static uint32_t vpGPSLastUpdate = 0; + +static vpGPSInfoFlags_t GetGPSDirectionOrSpeedChanged() +{ + if (!state.settings.gps_enabled) + return vpGPSNone; + + uint32_t now = getTick(); + if (now - vpGPSLastUpdate < 8000) + return vpGPSNone; + + vpGPSInfoFlags_t whatChanged= vpGPSNone; + + if (state.gps_data.fix_quality != priorGPSFixQuality) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSFixQuality); + priorGPSFixQuality= state.gps_data.fix_quality; + } + + if (state.gps_data.fix_type != priorGPSFixType) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSFixType); + priorGPSFixType = state.gps_data.fix_type; + } + + if (state.gps_data.speed != priorGPSSpeed) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSSpeed); + priorGPSSpeed = state.gps_data.speed; + } + + if (state.gps_data.altitude != priorGPSAltitude) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSAltitude); + priorGPSAltitude = state.gps_data.altitude; + } + + if (state.gps_data.tmg_true != priorGPSDirection) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSDirection); + priorGPSDirection = state.gps_data.tmg_true; + } + + if (state.gps_data.satellites_in_view != priorSatellitesInView) + { + whatChanged = (vpGPSInfoFlags_t)(whatChanged | vpGPSSatCount); + priorSatellitesInView = state.gps_data.satellites_in_view; + } + + if (whatChanged) + vpGPSLastUpdate=now; + + return whatChanged; +} +#endif // CONFIG_GPS + +void ui_updateFSM(bool *sync_rtx) +{ + event_t event; + if(!ui_popEvent(&event)) return; + + // There is some event to process, we need an UI redraw. + // UI redraw request is cancelled if we're in standby mode. + redraw_needed = true; + if(standby) redraw_needed = false; + + // Check if battery has enough charge to operate. + // Check is skipped if there is an ongoing transmission, since the voltage + // drop caused by the RF PA power absorption causes spurious triggers of + // the low battery alert. + bool txOngoing = platform_getPttStatus(); +#if !defined(PLATFORM_TTWRPLUS) + if ((!state.emergency) && (!txOngoing) && (state.charge <= 0)) + { + state.ui_screen = LOW_BAT; + if(event.type == EVENT_KBD && event.payload) + { + state.ui_screen = MAIN_VFO; + state.emergency = true; + } + return; + } +#endif // PLATFORM_TTWRPLUS + + // Unlatch and exit from macro menu on PTT press + if(macro_latched && txOngoing) + { + macro_latched = false; + macro_menu = false; + } + + long long now = getTick(); + // Process pressed keys + if(event.type == EVENT_KBD) + { + kbd_msg_t msg; + msg.value = event.payload; + bool f1Handled = false; + vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); + // If we get out of standby, we ignore the kdb event + // unless is the MONI key for the MACRO functions + if (_ui_exitStandby(now) && !(msg.keys & KEY_MONI)) + return; + // If MONI is pressed, activate MACRO functions + bool moniPressed = msg.keys & KEY_MONI; + if(moniPressed || macro_latched) + { + macro_menu = true; + + if(state.settings.macroMenuLatch == 1) + { + // long press moni on its own latches function. + if (moniPressed && msg.long_press && !macro_latched) + { + macro_latched = true; + vp_beep(BEEP_FUNCTION_LATCH_ON, LONG_BEEP); + } + else if (moniPressed && macro_latched) + { + macro_latched = false; + vp_beep(BEEP_FUNCTION_LATCH_OFF, LONG_BEEP); + } + } + + _ui_fsm_menuMacro(msg, sync_rtx); + return; + } + else + { + macro_menu = false; + } +#if defined(PLATFORM_TTWRPLUS) + // T-TWR Plus has no KEY_MONI, using KEY_VOLDOWN long press instead + if ((msg.keys & KEY_VOLDOWN) && msg.long_press) + { + macro_menu = true; + macro_latched = true; + } +#endif // PLA%FORM_TTWRPLUS + + if(state.tone_enabled && !(msg.keys & KEY_HASH)) + { + state.tone_enabled = false; + *sync_rtx = true; + } + + gSyncRtx = sync_rtx; + gF1Handled = false; + current->handleInput(event); + + // Enable Tx only if in MAIN_VFO or MAIN_MEM states + bool inMemOrVfo = (current == &vfoScreen) || (current == &memScreen); + if((macro_menu == true) || ((inMemOrVfo == false) && (state.txDisable == false))) + { + state.txDisable = true; + *sync_rtx = true; + } + if(!gF1Handled && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) + vp_replayLastPrompt(); + // TODO Phase 5: restore screen-change voice announcement via Screen::enter() + if((msg.keys & 0xffff) && (state.settings.vpLevel == vpBeep)) + vp_beep(BEEP_KEY_GENERIC, SHORT_BEEP); + if(msg.keys & KEY_ESC) + _ui_reset_menu_anouncement_tracking(); + + } + else if(event.type == EVENT_STATUS) + { +#ifdef CONFIG_GPS + if ((current == &menuGpsScreen) && + (!vp_isPlaying()) && + (state.settings.vpLevel > vpLow) && + (!txOngoing && !rtx_rxSquelchOpen())) + {// automatically read speed and direction changes only! + vpGPSInfoFlags_t whatChanged = GetGPSDirectionOrSpeedChanged(); + if (whatChanged != vpGPSNone) + vp_announceGPSInfo(whatChanged); + } +#endif // CONFIG_GPS + + if (txOngoing || rtx_rxSquelchOpen() || (state.volume != last_state.volume)) + { + _ui_exitStandby(now); + return; + } + + if (_ui_checkStandby(now - last_event_tick)) + { + _ui_enterStandby(); + } + } +} + +bool ui_updateGUI() +{ + if(redraw_needed == false) + return false; + + if(!layout_ready) + { + _ui_calculateLayout(&layout); + layout_ready = true; + } + current->render(); + + // If MACRO menu is active draw it + if(macro_menu) + { + _ui_drawDarkOverlay(); + _ui_drawMacroMenu(&ui_state); + } + + redraw_needed = false; + return true; +} + +void ui_terminate() +{ +} diff --git a/openrtx/src/ui/module17/ui.c b/openrtx/src/ui/module17/ui.c index 55a80a14a5..3840692214 100644 --- a/openrtx/src/ui/module17/ui.c +++ b/openrtx/src/ui/module17/ui.c @@ -17,6 +17,7 @@ #include "core/battery.h" #include "core/input.h" #include "hwconfig.h" +#include "ui/ui_events.h" /* UI main screen functions, their implementation is in "ui_main.c" */ extern void _ui_drawMainBackground(); @@ -148,11 +149,6 @@ state_t last_state; static ui_state_t ui_state; static bool layout_ready = false; -// UI event queue -static uint8_t evQueue_rdPos; -static uint8_t evQueue_wrPos; -static event_t evQueue[MAX_NUM_EVENTS]; - static layout_t _ui_calculateLayout() { // Horizontal line height @@ -488,13 +484,8 @@ void ui_saveState() void ui_updateFSM(bool *sync_rtx) { - // Check for events - if(evQueue_wrPos == evQueue_rdPos) return; - - // Pop an event from the queue - uint8_t newTail = (evQueue_rdPos + 1) % MAX_NUM_EVENTS; - event_t event = evQueue[evQueue_rdPos]; - evQueue_rdPos = newTail; + event_t event; + if(!ui_popEvent(&event)) return; // Process pressed keys if(event.type == EVENT_KBD) @@ -928,24 +919,6 @@ bool ui_updateGUI() return true; } -bool ui_pushEvent(const uint8_t type, const uint32_t data) -{ - uint8_t newHead = (evQueue_wrPos + 1) % MAX_NUM_EVENTS; - - // Queue is full - if(newHead == evQueue_rdPos) return false; - - // Preserve atomicity when writing the new element into the queue. - event_t event; - event.type = type; - event.payload = data; - - evQueue[evQueue_wrPos] = event; - evQueue_wrPos = newHead; - - return true; -} - void ui_terminate() { } diff --git a/openrtx/src/ui/module17/ui.cpp b/openrtx/src/ui/module17/ui.cpp new file mode 100644 index 0000000000..3840692214 --- /dev/null +++ b/openrtx/src/ui/module17/ui.cpp @@ -0,0 +1,924 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include "ui/ui_mod17.h" +#include "rtx/rtx.h" +#include "interfaces/platform.h" +#include "interfaces/display.h" +#include "interfaces/cps_io.h" +#include "interfaces/nvmem.h" +#include "interfaces/delays.h" +#include +#include "core/battery.h" +#include "core/input.h" +#include "hwconfig.h" +#include "ui/ui_events.h" + +/* UI main screen functions, their implementation is in "ui_main.c" */ +extern void _ui_drawMainBackground(); +extern void _ui_drawMainTop(); +extern void _ui_drawVFOMiddle(); +extern void _ui_drawMEMMiddle(); +extern void _ui_drawVFOBottom(); +extern void _ui_drawMEMBottom(); +extern void _ui_drawMainVFO(ui_state_t* ui_state); +extern void _ui_drawMainVFOInput(ui_state_t* ui_state); +extern void _ui_drawMainMEM(ui_state_t* ui_state); +/* UI menu functions, their implementation is in "ui_menu.c" */ +extern void _ui_drawMenuTop(ui_state_t* ui_state); +#ifdef CONFIG_GPS +extern void _ui_drawMenuGPS(); +extern void _ui_drawSettingsGPS(ui_state_t* ui_state); +#endif +extern void _ui_drawMenuSettings(ui_state_t* ui_state); +extern void _ui_drawMenuInfo(ui_state_t* ui_state); +extern void _ui_drawMenuAbout(ui_state_t* ui_state); +#ifdef CONFIG_RTC +extern void _ui_drawSettingsTimeDate(); +extern void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); +#endif +extern void _ui_drawSettingsDisplay(ui_state_t* ui_state); +extern void _ui_drawSettingsM17(ui_state_t* ui_state); +extern void _ui_drawSettingsModule17(ui_state_t* ui_state); +extern void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); +extern bool _ui_drawMacroMenu(ui_state_t* ui_state); + +const char *menu_items[] = +{ + "Settings", +#ifdef CONFIG_GPS + "GPS", +#endif + "Info", + "About", + "Shutdown" +}; + +const char *settings_items[] = +{ + "Display", +#ifdef CONFIG_RTC + "Time & Date", +#endif +#ifdef CONFIG_GPS + "GPS", +#endif + "M17", + "Module 17", + "Default Settings" +}; + +const char *display_items[] = +{ + "Brightness", +}; + +const char *m17_items[] = +{ + "Callsign", + "Meta Txt", + "CAN", + "CAN RX Check" +}; + +const char *module17_items[] = +{ + "Mic Gain", + "PTT In", + "PTT Out", + "TX Phase", + "RX Phase", + "TX Softpot", + "RX Softpot" +}; + +#ifdef CONFIG_GPS +const char *settings_gps_items[] = +{ + "GPS Enabled", + "GPS Set Time", + "UTC Timezone" +}; +#endif + +const char *info_items[] = +{ + "", + "Used heap", + "Hw Version", + "HMI", + "BB Tuning Pot", +}; + +const char *authors[] = +{ + "Niccolo' IU2KIN", + "Silvano IU2KWO", + "Federico IU2NUO", + "Mathis DB9MAT", + "Morgan ON4MOD", + "Marco DM4RCO" +}; + +static const char symbols_callsign[] = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890/-."; + +// Calculate number of menu entries +const uint8_t menu_num = sizeof(menu_items)/sizeof(menu_items[0]); +const uint8_t settings_num = sizeof(settings_items)/sizeof(settings_items[0]); +const uint8_t display_num = sizeof(display_items)/sizeof(display_items[0]); +#ifdef CONFIG_GPS +const uint8_t settings_gps_num = sizeof(settings_gps_items)/sizeof(settings_gps_items[0]); +#endif +const uint8_t m17_num = sizeof(m17_items)/sizeof(m17_items[0]); +const uint8_t module17_num = sizeof(module17_items)/sizeof(module17_items[0]); +const uint8_t info_num = sizeof(info_items)/sizeof(info_items[0]); +const uint8_t author_num = sizeof(authors)/sizeof(authors[0]); + +const color_t color_black = {0, 0, 0, 255}; +const color_t color_grey = {60, 60, 60, 255}; +const color_t color_white = {255, 255, 255, 255}; +const color_t yellow_fab413 = {250, 180, 19, 255}; + +layout_t layout; +state_t last_state; +static ui_state_t ui_state; +static bool layout_ready = false; + +static layout_t _ui_calculateLayout() +{ + // Horizontal line height + const uint16_t hline_h = 1; + // Compensate for fonts printing below the start position + const uint16_t text_v_offset = 1; + + // Calculate UI layout for the Module 17 + + // Height and padding shown in diagram at beginning of file + const uint16_t top_h = 11; + const uint16_t top_pad = 1; + const uint16_t line1_h = 10; + const uint16_t line2_h = 10; + const uint16_t line3_h = 10; + const uint16_t line4_h = 10; + const uint16_t line5_h = 10; + const uint16_t menu_h = 10; + const uint16_t bottom_h = 15; + const uint16_t bottom_pad = 0; + const uint16_t status_v_pad = 1; + const uint16_t small_line_v_pad = 1; + const uint16_t big_line_v_pad = 0; + const uint16_t horizontal_pad = 4; + + // Top bar font: 6 pt + const fontSize_t top_font = FONT_SIZE_6PT; + const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; + // Middle line fonts: 5, 8, 8 pt + const fontSize_t line1_font = FONT_SIZE_6PT; + const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line2_font = FONT_SIZE_6PT; + const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line3_font = FONT_SIZE_6PT; + const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line4_font = FONT_SIZE_6PT; + const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line5_font = FONT_SIZE_6PT; + const symbolSize_t line5_symbol_size = SYMBOLS_SIZE_6PT; + // Bottom bar font: 6 pt + const fontSize_t bottom_font = FONT_SIZE_6PT; + // TimeDate/Frequency input font + const fontSize_t input_font = FONT_SIZE_8PT; + // Menu font + const fontSize_t menu_font = FONT_SIZE_6PT; + // Message font + const fontSize_t message_font = FONT_SIZE_6PT; + // Mode screen frequency font: 9 pt + const fontSize_t mode_font_big = FONT_SIZE_9PT; + // Mode screen details font: 6 pt + const fontSize_t mode_font_small = FONT_SIZE_6PT; + + // Calculate printing positions + point_t top_pos = {horizontal_pad, top_h - status_v_pad - text_v_offset}; + point_t line1_pos = {horizontal_pad, top_h + top_pad + line1_h - small_line_v_pad - text_v_offset}; + point_t line2_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset}; + point_t line3_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h - big_line_v_pad - text_v_offset}; + point_t line4_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h + line4_h - big_line_v_pad - text_v_offset}; + point_t line5_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h + line4_h + line5_h - big_line_v_pad - text_v_offset}; + point_t bottom_pos = {horizontal_pad, CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset}; + + layout_t new_layout = + { + hline_h, + top_h, + line1_h, + line2_h, + line3_h, + line4_h, + line5_h, + menu_h, + bottom_h, + bottom_pad, + status_v_pad, + horizontal_pad, + text_v_offset, + top_pos, + line1_pos, + line2_pos, + line3_pos, + line4_pos, + line5_pos, + bottom_pos, + top_font, + top_symbol_size, + line1_font, + line1_symbol_size, + line2_font, + line2_symbol_size, + line3_font, + line3_symbol_size, + line4_font, + line4_symbol_size, + line5_font, + line5_symbol_size, + bottom_font, + input_font, + menu_font, + message_font, + mode_font_big, + mode_font_small + }; + return new_layout; +} + + +void ui_init() +{ + layout = _ui_calculateLayout(); + layout_ready = true; + // Initialize struct ui_state to all zeroes + // This syntax is called compound literal + // https://stackoverflow.com/questions/6891720/initialize-reset-struct-to-zero-null + ui_state = (const struct ui_state_t){ 0 }; +} + +void ui_drawSplashScreen() +{ + gfx_clearScreen(); + + point_t origin = {0, (CONFIG_SCREEN_HEIGHT / 2) - 6}; + gfx_print(origin, FONT_SIZE_12PT, TEXT_ALIGN_CENTER, yellow_fab413, "O P N\nR T X"); +} + +#ifdef CONFIG_RTC +void _ui_timedate_add_digit(datetime_t *timedate, uint8_t pos, uint8_t number) +{ + switch(pos) + { + // Set date + case 1: + timedate->date += number * 10; + break; + case 2: + timedate->date += number; + break; + // Set month + case 3: + timedate->month += number * 10; + break; + case 4: + timedate->month += number; + break; + // Set year + case 5: + timedate->year += number * 10; + break; + case 6: + timedate->year += number; + break; + // Set hour + case 7: + timedate->hour += number * 10; + break; + case 8: + timedate->hour += number; + break; + // Set minute + case 9: + timedate->minute += number * 10; + break; + case 10: + timedate->minute += number; + break; + } +} +#endif + +static void _ui_changeBrightness(int variation) +{ + // Avoid rollover if current value is zero. + if((state.settings.brightness == 0) && (variation < 0)) + return; + + // Cap max brightness to 100 + if((state.settings.brightness == 100) && (variation > 0)) + return; + + state.settings.brightness += variation; + display_setBacklightLevel(state.settings.brightness); +} + +static void _ui_changeCAN(int variation) +{ + int8_t can = state.settings.m17_can + variation; + + // M17 CAN ranges from 0 to 15 + if(can > 15) + can = 0; + + if(can < 0) + can = 15; + + state.settings.m17_can = can; +} + +static void _ui_changeWiper(uint16_t *wiper, int variation) +{ + uint16_t value = *wiper; + value += variation; + + // Max value for softpot is 0x100, min value is set to 0x001 + if(value > 0x100) + value = 0x100; + + if(value < 0x001) + value = 0x001; + + *wiper = value; +} + +static void _ui_changeMicGain(int variation) +{ + int8_t gain = mod17CalData.mic_gain + variation; + + if(gain > 2) + gain = 0; + + if(gain < 0) + gain = 2; + + mod17CalData.mic_gain = gain; +} + +static void _ui_menuUp(uint8_t menu_entries) +{ + uint8_t maxEntries = menu_entries - 1; + const hwInfo_t* hwinfo = platform_getHwInfo(); + + // Hide the "shutdown" main menu entry for versions lower than 0.1e + if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) + maxEntries -= 1; + + // Hide the softpot menu entries if hardware does not have them + uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; + if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) + maxEntries -= 2; + + if(ui_state.menu_selected > 0) + ui_state.menu_selected -= 1; + else + ui_state.menu_selected = maxEntries; +} + +static void _ui_menuDown(uint8_t menu_entries) +{ + uint8_t maxEntries = menu_entries - 1; + const hwInfo_t* hwinfo = platform_getHwInfo(); + + // Hide the "shutdown" main menu entry for versions lower than 0.1e + if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) + maxEntries -= 1; + + // Hide the softpot menu entries if hardware does not have them + uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; + if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) + maxEntries -= 2; + + if(ui_state.menu_selected < maxEntries) + ui_state.menu_selected += 1; + else + ui_state.menu_selected = 0; +} + +static void _ui_menuBack(uint8_t prev_state) +{ + if(ui_state.edit_mode) + { + ui_state.edit_mode = false; + } + else + { + // Return to previous menu + state.ui_screen = prev_state; + // Reset menu selection + ui_state.menu_selected = 0; + } +} + +static void _ui_textInputReset(char *buf) +{ + ui_state.input_number = 0; + ui_state.input_position = 0; + ui_state.input_set = 0; + ui_state.last_keypress = 0; + memset(buf, 0, 9); +} + +static void _ui_textInputArrows(char *buf, uint8_t max_len, kbd_msg_t msg) +{ + if(ui_state.input_position >= max_len) + return; + + uint8_t num_symbols = 0; + num_symbols = strlen(symbols_callsign); + + if (msg.keys & KEY_RIGHT) + { + if (ui_state.input_position < (max_len - 1)) + { + ui_state.input_position = ui_state.input_position + 1; + ui_state.input_set = 0; + } + } + else if (msg.keys & KEY_LEFT) + { + if (ui_state.input_position > 0) + { + buf[ui_state.input_position] = '\0'; + ui_state.input_position = ui_state.input_position - 1; + } + + // get index of current selected character in symbol table + ui_state.input_set = strcspn(symbols_callsign, &buf[ui_state.input_position]); + } + else if (msg.keys & KEY_UP) + ui_state.input_set = (ui_state.input_set + 1) % num_symbols; + else if (msg.keys & KEY_DOWN) + ui_state.input_set = ui_state.input_set==0 ? num_symbols-1 : ui_state.input_set-1; + + buf[ui_state.input_position] = symbols_callsign[ui_state.input_set]; +} + +static void _ui_textInputConfirm(char *buf) +{ + buf[ui_state.input_position + 1] = '\0'; +} + +void ui_saveState() +{ + last_state = state; +} + +void ui_updateFSM(bool *sync_rtx) +{ + event_t event; + if(!ui_popEvent(&event)) return; + + // Process pressed keys + if(event.type == EVENT_KBD) + { + kbd_msg_t msg; + msg.value = event.payload; + + switch(state.ui_screen) + { + // VFO screen + case MAIN_VFO: + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + // Save selected callsign and disable input mode + strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); + *sync_rtx = true; + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else + _ui_textInputArrows(ui_state.new_callsign, 9, msg); + } + else if(ui_state.edit_message) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_message); + // Save selected message and disable input mode + strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); + ui_state.edit_message = false; + *sync_rtx = true; + } + else if(msg.keys & KEY_ESC) + // Discard selected message and disable input mode + ui_state.edit_message = false; + else + _ui_textInputArrows(ui_state.new_message, 52, msg); + } + else + { + if(msg.keys & KEY_ENTER) + { + // Save current main state + ui_state.last_main_state = state.ui_screen; + // Open Menu + state.ui_screen = MENU_TOP; + } + else if (msg.keys & KEY_RIGHT) + { + ui_state.edit_mode = true; + } + } + break; + + // Top menu screen + case MENU_TOP: + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(menu_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(menu_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case M_SETTINGS: + state.ui_screen = MENU_SETTINGS; + break; + case M_INFO: + state.ui_screen = MENU_INFO; + break; + case M_ABOUT: + state.ui_screen = MENU_ABOUT; + break; + case M_SHUTDOWN: + state.devStatus = SHUTDOWN; + break; + } + // Reset menu selection + ui_state.menu_selected = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(ui_state.last_main_state); + break; + + // Settings menu screen + case MENU_SETTINGS: + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_num); + else if(msg.keys & KEY_ENTER) + { + + switch(ui_state.menu_selected) + { + case S_DISPLAY: + state.ui_screen = SETTINGS_DISPLAY; + break; + case S_M17: + state.ui_screen = SETTINGS_M17; + break; + case S_MOD17: + state.ui_screen = SETTINGS_MODULE17; + break; + case S_RESET2DEFAULTS: + state.ui_screen = SETTINGS_RESET2DEFAULTS; + break; + default: + state.ui_screen = MENU_SETTINGS; + } + // Reset menu selection + ui_state.menu_selected = 0; + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(MENU_TOP); + break; + // Info menu screen + case MENU_INFO: + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(info_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(info_num); + else if(msg.keys & KEY_ESC) + _ui_menuBack(MENU_TOP); + break; + // About screen, scroll without rollover + case MENU_ABOUT: + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + { + if(ui_state.menu_selected > 0) + ui_state.menu_selected -= 1; + } + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + ui_state.menu_selected += 1; + else if(msg.keys & KEY_ESC) + _ui_menuBack(MENU_TOP); + break; + + case SETTINGS_DISPLAY: + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case D_BRIGHTNESS: + _ui_changeBrightness(-5); + break; + default: + state.ui_screen = SETTINGS_DISPLAY; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case D_BRIGHTNESS: + _ui_changeBrightness(+5); + break; + default: + state.ui_screen = SETTINGS_DISPLAY; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(display_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(display_num); + else if(msg.keys & KEY_ESC) + { + nvm_writeSettings(&state.settings); + _ui_menuBack(MENU_SETTINGS); + } + break; + + // M17 Settings + case SETTINGS_M17: + + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + // Save selected callsign and disable input mode + strncpy(state.settings.callsign, ui_state.new_callsign, 10); + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else + _ui_textInputArrows(ui_state.new_callsign, 9, msg); + } + else if(ui_state.edit_message) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_message); + // Save selected message and disable input mode + strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); + ui_state.edit_message = false; + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + // Discard selected message and disable input mode + ui_state.edit_message = false; + else + _ui_textInputArrows(ui_state.new_message, 52, msg); + } + else + { + // Not in edit mode: handle CAN setting + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case M_CAN: + _ui_changeCAN(-1); + break; + case M_CAN_RX: + state.settings.m17_can_rx = !state.settings.m17_can_rx; + break; + default: + state.ui_screen = SETTINGS_M17; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case M_CAN: + _ui_changeCAN(+1); + break; + case M_CAN_RX: + state.settings.m17_can_rx = !state.settings.m17_can_rx; + break; + default: + state.ui_screen = SETTINGS_M17; + } + } + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + // Enable callsign input + case M_CALLSIGN: + ui_state.edit_mode = true; + _ui_textInputReset(ui_state.new_callsign); + break; + // Enable meta text input + case M_METATEXT: + ui_state.edit_message = true; + _ui_textInputReset(ui_state.new_message); + break; + default: + state.ui_screen = SETTINGS_M17; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(m17_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(m17_num); + else if(msg.keys & KEY_ESC) + { + *sync_rtx = true; + nvm_writeSettings(&state.settings); + _ui_menuBack(MENU_SETTINGS); + } + } + break; + case SETTINGS_RESET2DEFAULTS: + if(! ui_state.edit_mode) + { + //require a confirmation ENTER, then another + //edit_mode is slightly misused to allow for this + if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = true; + } + else if(msg.keys & KEY_ESC) + { + _ui_menuBack(MENU_SETTINGS); + } + } + else + { + if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = false; + + // Reset calibration values + mod17CalData.tx_wiper = 0x080; + mod17CalData.rx_wiper = 0x080; + mod17CalData.bb_tx_invert = 0; + mod17CalData.bb_rx_invert = 0; + mod17CalData.mic_gain = 0; + + state_resetSettingsAndVfo(); + nvm_writeSettings(&state.settings); + _ui_menuBack(MENU_SETTINGS); + } + else if(msg.keys & KEY_ESC) + { + ui_state.edit_mode = false; + _ui_menuBack(MENU_SETTINGS); + } + } + break; + // Module17 Settings + case SETTINGS_MODULE17: + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case D_TXWIPER: + _ui_changeWiper(&mod17CalData.tx_wiper, -1); + break; + case D_RXWIPER: + _ui_changeWiper(&mod17CalData.rx_wiper, -1); + break; + case D_TXINVERT: + mod17CalData.bb_tx_invert -= 1; + break; + case D_RXINVERT: + mod17CalData.bb_rx_invert -= 1; + break; + case D_MICGAIN: + _ui_changeMicGain(-1); + break; + case D_PTTINLEVEL: + mod17CalData.ptt_in_level -= 1; + break; + case D_PTTOUTLEVEL: + mod17CalData.ptt_out_level -= 1; + break; + default: + state.ui_screen = SETTINGS_MODULE17; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case D_TXWIPER: + _ui_changeWiper(&mod17CalData.tx_wiper, +1); + break; + case D_RXWIPER: + _ui_changeWiper(&mod17CalData.rx_wiper, +1); + break; + case D_TXINVERT: + mod17CalData.bb_tx_invert += 1; + break; + case D_RXINVERT: + mod17CalData.bb_rx_invert += 1; + break; + case D_MICGAIN: + _ui_changeMicGain(+1); + break; + case D_PTTINLEVEL: + mod17CalData.ptt_in_level += 1; + break; + case D_PTTOUTLEVEL: + mod17CalData.ptt_out_level += 1; + break; + default: + state.ui_screen = SETTINGS_MODULE17; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(module17_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(module17_num); + else if(msg.keys & KEY_ESC) + { + nvm_writeSettings(&state.settings); + _ui_menuBack(MENU_SETTINGS); + } + break; + } + } +} + +bool ui_updateGUI() +{ + if(!layout_ready) + { + layout = _ui_calculateLayout(); + layout_ready = true; + } + // Draw current GUI page + switch(last_state.ui_screen) + { + // VFO main screen + case MAIN_VFO: + _ui_drawMainVFO(&ui_state); + break; + // Top menu screen + case MENU_TOP: + _ui_drawMenuTop(&ui_state); + break; + // Settings menu screen + case MENU_SETTINGS: + _ui_drawMenuSettings(&ui_state); + break; + // Info menu screen + case MENU_INFO: + _ui_drawMenuInfo(&ui_state); + break; + // About menu screen + case MENU_ABOUT: + _ui_drawMenuAbout(&ui_state); + break; + // Display settings screen + case SETTINGS_DISPLAY: + _ui_drawSettingsDisplay(&ui_state); + break; + // M17 settings screen + case SETTINGS_M17: + _ui_drawSettingsM17(&ui_state); + break; + // Module 17 settings screen + case SETTINGS_MODULE17: + _ui_drawSettingsModule17(&ui_state); + break; + // Screen to support resetting Settings and VFO to defaults + case SETTINGS_RESET2DEFAULTS: + _ui_drawSettingsReset2Defaults(&ui_state); + break; + } + + return true; +} + +void ui_terminate() +{ +} diff --git a/ui_refactor_plan.rtf b/ui_refactor_plan.rtf new file mode 100644 index 0000000000..63f73752d5 --- /dev/null +++ b/ui_refactor_plan.rtf @@ -0,0 +1,197 @@ +{\rtf1\ansi\deff0 +{\fonttbl + {\f0\froman\fcharset0 Times New Roman;} + {\f1\fmodern\fcharset0 Courier New;} +} +{\colortbl;\red0\green0\blue0;\red80\green80\blue80;} +\f0\fs24 + +{\fs36\b UI Refactor Plan}\line +OpenRTX \emdash April 2026\line +\line + +{\fs28\b Decision: Rework (Option B)}\line +\line +The graphics HAL, display/keyboard drivers, threading model, and platform abstraction are sound. The problem is concentrated in {\f1 ui.c} (2,778 lines) and its parallel {\f1 module17/ui.c} clone. Rewriting from scratch risks regressions across 9 display drivers and 8 keyboard targets with no incremental validation path. The pain points are surgical and addressable without replacing the rendering pipeline.\line +\line +The UI will be rewritten in C++ to match the existing {\f1 OpMode} hierarchy pattern already used in {\f1 rtx.cpp}. The UI is currently the only major pure-C subsystem; this change makes it consistent with the radio layer.\line +\line + +{\fs28\b What Stays Untouched}\line +\line +The following are not touched by this refactor:\line +\line +\bullet All display drivers\line +\bullet All keyboard drivers\line +\bullet {\f1 graphics.c} / {\f1 graphics.h}\line +\bullet The three-thread model (main, ui, rtx)\line +\bullet The Linux SDL2 emulator\line +\bullet The {\f1 display.h} / {\f1 keyboard.h} HAL interfaces\line +\line + +{\fs28\b Current Architecture Pain Points}\line +\line +{\b 1. Monolithic FSM} \emdash {\f1 ui_updateFSM()} is ~1,200 lines of deeply nested switch/if/else with platform guards interspersed. Adding a screen requires touching this function, {\f1 ui_updateGUI()}, the {\f1 uiScreen} enum, and likely {\f1 ui_menu.c} or {\f1 ui_main.c}.\line +\line +{\b 2. {\f1 state.ui_screen} in shared global state} \emdash The current screen enum lives in {\f1 state_t}, shared under mutex with the radio and device threads. Screen transitions and radio frequency are coupled in the same lock domain.\line +\line +{\b 3. Two parallel, nearly-identical UI variants} \emdash {\f1 default/} and {\f1 module17/} each duplicate the entire FSM, event queue, layout engine, and most draw functions. A bug fix in one must be manually propagated to the other.\line +\line +{\b 4. Hard-coded menu strings} \emdash {\f1 menu_items[]}, {\f1 settings_items[]}, etc. in {\f1 ui.c} are C string literals separate from the {\f1 stringsTable_t} i18n system. There is even an inline TODO at line 107 acknowledging this.\line +\line +{\b 5. Compile-time layout via runtime {\f1 \#if} ladders} \emdash {\f1 _ui_calculateLayout()} has three {\f1 \#if CONFIG_SCREEN_HEIGHT > N} branches with fully duplicated variable declarations. The values are entirely compile-time constants and could be {\f1 static const} tables.\line +\line +{\b 6. Platform key bindings inside the FSM} \emdash {\f1 \#if defined(PLATFORM_TTWRPLUS)} key remapping is scattered through FSM case arms, coupling platform identity directly into UI logic.\line +\line +{\b 7. Voice prompt coupling} \emdash {\f1 vp_announce*()} calls are sprinkled throughout every FSM case arm, interleaved with state mutations, making the FSM hard to read in isolation.\line +\line +{\b 8. Event queue not thread-safe by contract} \emdash {\f1 ui_pushEvent()} is in the public {\f1 ui.h} API with no mutex. Any future cross-thread event push would be a silent data race.\line +\line +{\b 9. {\f1 ui_terminate()} is empty} \emdash The body is literally empty (lines 2776\endash 2778).\line +\line + +{\fs28\b Phase 1 \emdash Shared Infrastructure Foundation}\line +\line +{\i Goal: eliminate parallel variant duplication at the infrastructure level.}\line +\line +1. Create {\f1 openrtx/src/ui/common/} and {\f1 openrtx/include/ui/common/}.\line +\line +2. Merge {\f1 ui_default.h} and {\f1 ui_mod17.h} into a single {\f1 ui_screen.h} with a shared {\f1 enum uiScreen} base and variant-specific screen additions via {\f1 \#include "ui_screen_.h"}. Single {\f1 ui_state_t} definition.\line +\line +3. Move the event queue ({\f1 evQueue[]}, {\f1 ui_pushEvent()}, ring-buffer logic) into {\f1 common/ui_events.cpp} with an internal mutex so cross-thread pushes are safe.\line +\line +4. Move the {\f1 ui_threadFunc} loop skeleton into {\f1 common/ui_thread.cpp}. Both variants plug in via the {\f1 Screen*} pointer rather than variant-specific function calls.\line +\line +{\b Outcome:} One copy of the event queue and thread loop; both variants use it.\line +\line + +{\fs28\b Phase 2 \emdash C++ Virtual Screen Abstraction}\line +\line +{\i Goal: decompose the 1,200-line monolithic FSM into per-screen C++ classes, mirroring the existing OpMode hierarchy.}\line +\line +{\b Base class} ({\f1 openrtx/include/ui/Screen.hpp}):\line +\line +{\f1 +class Screen \{\line +public:\line + virtual void enter() \{\}\line + virtual void exit() \{\}\line + virtual bool handleInput(event_t ev) = 0;\line + virtual void render() = 0;\line + virtual ~Screen() = default;\line +\};\line +}\line +\line +No RTTI, no exceptions, no {\f1 dynamic_cast} \emdash identical constraints to {\f1 OpMode}. The pure virtuals enforce that every concrete screen implements both responsibilities.\line +\line +{\b Concrete screens} ({\f1 openrtx/src/ui/screens/}):\line +\line +One {\f1 .cpp} file per screen. Logic and rendering co-located. No file needs to know about any other screen's internals. Example:\line +\line +{\f1 +class VfoScreen : public Screen \{\line +public:\line + bool handleInput(event_t ev) override;\line + void render() override;\line +private:\line + // screen-local transient state only\line +\};\line +}\line +\line +{\b Stack-allocated instances + raw pointer dispatch} (same pattern as {\f1 rtx.cpp}):\line +\line +{\f1 +static VfoScreen vfoScreen;\line +static MenuTopScreen menuScreen;\line +static GpsScreen gpsScreen;\line +\line +static Screen *current = &vfoScreen;\line +\line +static void transition(Screen *next) \{\line + current->exit();\line + next->enter();\line + current = next;\line +\}\line +}\line +\line +No heap. No {\f1 new}. Instances live for the program lifetime, same as today's static op-mode instances.\line +\line +{\b Dispatchers collapse to two lines each:}\line +\line +{\f1 +void ui_updateFSM(event_t ev) \{ current->handleInput(ev); \}\line +void ui_updateGUI() \{ current->render(); \}\line +}\line +\line +{\b Screen enum goes away entirely} \emdash {\f1 state.ui_screen} is removed from {\f1 state_t}. The current screen is represented by the {\f1 current} pointer type, not an integer. This removes UI navigation state from the radio-shared mutex domain.\line +\line +{\b Outcome:} Adding a screen = one new {\f1 .cpp} file + one static instance + one {\f1 transition()} call site. The FSM monolith is gone.\line +\line + +{\fs28\b Phase 3 \emdash Unify the Two UI Variants}\line +\line +{\i Goal: default/ and module17/ share all common screens; only divergent screens are variant-specific.}\line +\line +1. Audit which screens are shared (VFO main, GPS, contacts, most settings) vs. variant-specific (Module17 calibration screens; FM radio screens in default).\line +\line +2. Move shared screen {\f1 .cpp} files into {\f1 common/screens/}.\line +\line +3. Variant directories ({\f1 default/screens/}, {\f1 module17/screens/}) contain only the screens that differ.\line +\line +4. Build system picks up {\f1 common/screens/*.cpp} + {\f1 \$\{VARIANT\}/screens/*.cpp}.\line +\line +5. Merge string tables \emdash one copy of {\f1 ui_strings.c}, {\f1 EnglishStrings.h}, {\f1 SpanishStrings.h} used by both variants.\line +\line +{\b Outcome:} A bug fix in the VFO screen applies to both variants automatically.\line +\line + +{\fs28\b Phase 4 \emdash Fix the Layout System}\line +\line +{\i Goal: eliminate the runtime \#if ladder and make layout a compile-time constant.}\line +\line +1. Replace {\f1 _ui_calculateLayout()} with three {\f1 static const layout_t} entries selected by a compile-time {\f1 \#if} on {\f1 CONFIG_SCREEN_HEIGHT}. The function is called once at startup and the values never change \emdash they are effectively constants.\line +\line +2. Remove {\f1 layout} from mutable global state.\line +\line +3. Add a {\f1 _Static_assert} (or {\f1 static_assert} in C++) that {\f1 CONFIG_SCREEN_HEIGHT} matches one of the three tiers rather than a silent {\f1 \#error}.\line +\line +{\b Outcome:} ~180 lines of duplicated conditional declarations become a simple initializer. No runtime cost.\line +\line + +{\fs28\b Phase 5 \emdash Peripheral Fixes}\line +\line +{\i Goal: address the remaining structural warts.}\line +\line +{\b String table:} Move all {\f1 menu_items[]}, {\f1 settings_items[]} literals from {\f1 ui.c} into {\f1 stringsTable_t}. Fulfills the existing TODO at line 107, makes strings localizable.\line +\line +{\b Platform key bindings:} Move {\f1 \#if defined(PLATFORM_TTWRPLUS)} key remapping from FSM case arms into the relevant platform keyboard driver. The FSM sees normalized keys only.\line +\line +{\b Voice prompt decoupling:} Add an {\f1 announce()} virtual method to {\f1 Screen} (with a default no-op implementation). Call it from {\f1 transition()} rather than sprinkling {\f1 vp_announce*()} throughout {\f1 handleInput()} implementations.\line +\line +{\b {\f1 _ui_handleToneSelectScroll}:} Move to {\f1 rtx.cpp} or a dedicated {\f1 ui_radio_helpers.cpp}. It manipulates radio state, not UI state.\line +\line +{\b {\f1 ui_terminate()}:} Implement \emdash flush event queue, save settings, call {\f1 gfx_terminate()}.\line +\line +{\b Event queue mutex:} Add an internal mutex to {\f1 ui_pushEvent()} so the public API is safe to call from any thread.\line +\line + +{\fs28\b Phase Order Rationale}\line +\line +Phases must be executed in order:\line +\line +\bullet Phase 1 provides the shared foundation before splitting into per-screen files.\line +\bullet Phase 2 creates per-screen files before variants can meaningfully be unified.\line +\bullet Phase 3 depends on Phase 2 having established the screen file structure.\line +\bullet Phases 4 and 5 are independent of each other but depend on Phase 2 (no point fixing layout in the old monolith).\line +\line +Each phase is independently mergeable with the radio stack running throughout. No big-bang cutover required.\line +\line + +{\fs28\b C++ Constraints}\line +\line +The project targets {\b C++14} across all platforms ({\f1 -std=c++14} in all cross and native build files). The following C++17 features are {\b not} available without a build system change: structured bindings, {\f1 if constexpr}, {\f1 std::optional}, {\f1 std::variant}.\line +\line +RTTI is disabled on embedded targets ({\f1 -fno-rtti}). Do not use {\f1 dynamic_cast} or {\f1 typeid}. Exceptions are disabled. Do not use {\f1 try}/{\f1 catch}/{\f1 throw}.\line +\line +Heap allocation is permitted at init/teardown boundaries (consistent with existing {\f1 std::make_unique} usage in Modulator/Demodulator) but must not occur in the UI hot path (25 Hz render loop).\line +} From b67864925d1a78b9ed7f6d17173e87c07a45b181 Mon Sep 17 00:00:00 2001 From: Joshua Murphy Date: Sat, 18 Apr 2026 18:14:13 -0230 Subject: [PATCH 2/4] Phase 2 --- openrtx/include/core/voicePromptUtils.h | 8 +- openrtx/include/core/voicePrompts.h | 6 +- openrtx/src/ui/default/ui.cpp | 3 - openrtx/src/ui/module17/ui.cpp | 917 ++++++++++++------------ 4 files changed, 466 insertions(+), 468 deletions(-) diff --git a/openrtx/include/core/voicePromptUtils.h b/openrtx/include/core/voicePromptUtils.h index 31f0d1e035..8b3e6a547f 100644 --- a/openrtx/include/core/voicePromptUtils.h +++ b/openrtx/include/core/voicePromptUtils.h @@ -7,14 +7,14 @@ #ifndef VOICE_PROMPT_UTILS_H #define VOICE_PROMPT_UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include "core/cps.h" #include "ui/ui_strings.h" #include "core/voicePrompts.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Please Note! diff --git a/openrtx/include/core/voicePrompts.h b/openrtx/include/core/voicePrompts.h index 7e1c4386be..660d60bc4d 100644 --- a/openrtx/include/core/voicePrompts.h +++ b/openrtx/include/core/voicePrompts.h @@ -7,13 +7,13 @@ #ifndef VOICEPROMPTS_H #define VOICEPROMPTS_H +#include "core/datatypes.h" +#include + #ifdef __cplusplus extern "C" { #endif -#include "core/datatypes.h" -#include - /** * List of voice prompts for spoken words or phrases which are not in the UI * string table. The voice prompt data file stores these first, then after the diff --git a/openrtx/src/ui/default/ui.cpp b/openrtx/src/ui/default/ui.cpp index f639f7841f..771ba65b83 100644 --- a/openrtx/src/ui/default/ui.cpp +++ b/openrtx/src/ui/default/ui.cpp @@ -2155,7 +2155,6 @@ bool SettingsM17Screen::handleInput(event_t ev) if(ev.type != EVENT_KBD) return true; kbd_msg_t msg; msg.value = ev.payload; - vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); if(ui_state.edit_mode) { @@ -2545,8 +2544,6 @@ void ui_updateFSM(bool *sync_rtx) { kbd_msg_t msg; msg.value = event.payload; - bool f1Handled = false; - vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); // If we get out of standby, we ignore the kdb event // unless is the MONI key for the MACRO functions if (_ui_exitStandby(now) && !(msg.keys & KEY_MONI)) diff --git a/openrtx/src/ui/module17/ui.cpp b/openrtx/src/ui/module17/ui.cpp index 3840692214..50892a9dd4 100644 --- a/openrtx/src/ui/module17/ui.cpp +++ b/openrtx/src/ui/module17/ui.cpp @@ -18,35 +18,39 @@ #include "core/input.h" #include "hwconfig.h" #include "ui/ui_events.h" +#include "ui/Screen.hpp" +extern "C" { /* UI main screen functions, their implementation is in "ui_main.c" */ -extern void _ui_drawMainBackground(); -extern void _ui_drawMainTop(); -extern void _ui_drawVFOMiddle(); -extern void _ui_drawMEMMiddle(); -extern void _ui_drawVFOBottom(); -extern void _ui_drawMEMBottom(); -extern void _ui_drawMainVFO(ui_state_t* ui_state); -extern void _ui_drawMainVFOInput(ui_state_t* ui_state); -extern void _ui_drawMainMEM(ui_state_t* ui_state); +void _ui_drawMainBackground(); +void _ui_drawMainTop(); +void _ui_drawVFOMiddle(); +void _ui_drawMEMMiddle(); +void _ui_drawVFOBottom(); +void _ui_drawMEMBottom(); +void _ui_drawMainVFO(ui_state_t* ui_state); +void _ui_drawMainVFOInput(ui_state_t* ui_state); +void _ui_drawMainMEM(ui_state_t* ui_state); /* UI menu functions, their implementation is in "ui_menu.c" */ -extern void _ui_drawMenuTop(ui_state_t* ui_state); +void _ui_drawMenuTop(ui_state_t* ui_state); #ifdef CONFIG_GPS -extern void _ui_drawMenuGPS(); -extern void _ui_drawSettingsGPS(ui_state_t* ui_state); +void _ui_drawMenuGPS(); +void _ui_drawSettingsGPS(ui_state_t* ui_state); #endif -extern void _ui_drawMenuSettings(ui_state_t* ui_state); -extern void _ui_drawMenuInfo(ui_state_t* ui_state); -extern void _ui_drawMenuAbout(ui_state_t* ui_state); +void _ui_drawMenuSettings(ui_state_t* ui_state); +void _ui_drawMenuInfo(ui_state_t* ui_state); +void _ui_drawMenuAbout(ui_state_t* ui_state); #ifdef CONFIG_RTC -extern void _ui_drawSettingsTimeDate(); -extern void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); +void _ui_drawSettingsTimeDate(); +void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); #endif -extern void _ui_drawSettingsDisplay(ui_state_t* ui_state); -extern void _ui_drawSettingsM17(ui_state_t* ui_state); -extern void _ui_drawSettingsModule17(ui_state_t* ui_state); -extern void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); -extern bool _ui_drawMacroMenu(ui_state_t* ui_state); +void _ui_drawSettingsDisplay(ui_state_t* ui_state); +void _ui_drawSettingsM17(ui_state_t* ui_state); +void _ui_drawSettingsModule17(ui_state_t* ui_state); +void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); +bool _ui_drawMacroMenu(ui_state_t* ui_state); +void state_resetSettingsAndVfo(); +} // extern "C" ui_main / ui_menu const char *menu_items[] = { @@ -148,6 +152,23 @@ layout_t layout; state_t last_state; static ui_state_t ui_state; static bool layout_ready = false; +static Screen *current = nullptr; +static Screen *lastMainScreen = nullptr; +static bool *gSyncRtx = nullptr; + +static void transition(Screen *next); + +// Forward declarations of static helpers used in Screen::handleInput bodies +static void _ui_changeBrightness(int variation); +static void _ui_changeCAN(int variation); +static void _ui_changeWiper(uint16_t *wiper, int variation); +static void _ui_changeMicGain(int variation); +static void _ui_menuUp(uint8_t menu_entries); +static void _ui_menuDown(uint8_t menu_entries); +static void _ui_menuBack(Screen *prev); +static void _ui_textInputReset(char *buf); +static void _ui_textInputArrows(char *buf, uint8_t max_len, kbd_msg_t msg); +static void _ui_textInputConfirm(char *buf); static layout_t _ui_calculateLayout() { @@ -254,15 +275,418 @@ static layout_t _ui_calculateLayout() return new_layout; } +// ---- Screen subclasses ---- + +class VfoScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawMainVFO(&ui_state); } +}; + +class MenuTopScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawMenuTop(&ui_state); } +}; + +class MenuSettingsScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawMenuSettings(&ui_state); } +}; + +class MenuInfoScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawMenuInfo(&ui_state); } +}; + +class MenuAboutScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawMenuAbout(&ui_state); } +}; + +class SettingsDisplayScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawSettingsDisplay(&ui_state); } +}; + +class SettingsM17Screen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawSettingsM17(&ui_state); } +}; + +class SettingsModule17Screen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawSettingsModule17(&ui_state); } +}; + +class SettingsReset2DefaultsScreen : public Screen { +public: + bool handleInput(event_t ev) override; + void render() override { _ui_drawSettingsReset2Defaults(&ui_state); } +}; + +static VfoScreen vfoScreen; +static MenuTopScreen menuTopScreen; +static MenuSettingsScreen menuSettingsScreen; +static MenuInfoScreen menuInfoScreen; +static MenuAboutScreen menuAboutScreen; +static SettingsDisplayScreen settingsDisplayScreen; +static SettingsM17Screen settingsM17Screen; +static SettingsModule17Screen settingsModule17Screen; +static SettingsReset2DefaultsScreen settingsReset2DefaultsScreen; + +static void transition(Screen *next) +{ + current = next; + ui_state.menu_selected = 0; +} + +// ---- handleInput implementations ---- + +bool VfoScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); + *gSyncRtx = true; + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else + _ui_textInputArrows(ui_state.new_callsign, 9, msg); + } + else if(ui_state.edit_message) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_message); + strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); + ui_state.edit_message = false; + *gSyncRtx = true; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_message = false; + else + _ui_textInputArrows(ui_state.new_message, 52, msg); + } + else + { + if(msg.keys & KEY_ENTER) + { + lastMainScreen = current; + transition(&menuTopScreen); + } + else if(msg.keys & KEY_RIGHT) + ui_state.edit_mode = true; + } + return true; +} + +bool MenuTopScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(menu_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(menu_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case M_SETTINGS: transition(&menuSettingsScreen); break; + case M_INFO: transition(&menuInfoScreen); break; + case M_ABOUT: transition(&menuAboutScreen); break; + case M_SHUTDOWN: state.devStatus = SHUTDOWN; break; + } + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(lastMainScreen); + return true; +} + +bool MenuSettingsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(settings_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(settings_num); + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case S_DISPLAY: transition(&settingsDisplayScreen); break; + case S_M17: transition(&settingsM17Screen); break; + case S_MOD17: transition(&settingsModule17Screen); break; + case S_RESET2DEFAULTS: transition(&settingsReset2DefaultsScreen); break; + default: break; + } + } + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuInfoScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(info_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(info_num); + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool MenuAboutScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + { + if(ui_state.menu_selected > 0) + ui_state.menu_selected -= 1; + } + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + ui_state.menu_selected += 1; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuTopScreen); + return true; +} + +bool SettingsDisplayScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case D_BRIGHTNESS: _ui_changeBrightness(-5); break; + default: break; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case D_BRIGHTNESS: _ui_changeBrightness(+5); break; + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(display_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(display_num); + else if(msg.keys & KEY_ESC) + { + nvm_writeSettings(&state.settings); + _ui_menuBack(&menuSettingsScreen); + } + return true; +} + +bool SettingsM17Screen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_callsign); + strncpy(state.settings.callsign, ui_state.new_callsign, 10); + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_mode = false; + else + _ui_textInputArrows(ui_state.new_callsign, 9, msg); + } + else if(ui_state.edit_message) + { + if(msg.keys & KEY_ENTER) + { + _ui_textInputConfirm(ui_state.new_message); + strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); + ui_state.edit_message = false; + ui_state.edit_mode = false; + } + else if(msg.keys & KEY_ESC) + ui_state.edit_message = false; + else + _ui_textInputArrows(ui_state.new_message, 52, msg); + } + else + { + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case M_CAN: _ui_changeCAN(-1); break; + case M_CAN_RX: state.settings.m17_can_rx = !state.settings.m17_can_rx; break; + default: break; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case M_CAN: _ui_changeCAN(+1); break; + case M_CAN_RX: state.settings.m17_can_rx = !state.settings.m17_can_rx; break; + default: break; + } + } + else if(msg.keys & KEY_ENTER) + { + switch(ui_state.menu_selected) + { + case M_CALLSIGN: + ui_state.edit_mode = true; + _ui_textInputReset(ui_state.new_callsign); + break; + case M_METATEXT: + ui_state.edit_message = true; + _ui_textInputReset(ui_state.new_message); + break; + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(m17_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(m17_num); + else if(msg.keys & KEY_ESC) + { + *gSyncRtx = true; + nvm_writeSettings(&state.settings); + _ui_menuBack(&menuSettingsScreen); + } + } + return true; +} + +bool SettingsModule17Screen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(msg.keys & KEY_LEFT) + { + switch(ui_state.menu_selected) + { + case D_TXWIPER: _ui_changeWiper(&mod17CalData.tx_wiper, -1); break; + case D_RXWIPER: _ui_changeWiper(&mod17CalData.rx_wiper, -1); break; + case D_TXINVERT: mod17CalData.bb_tx_invert -= 1; break; + case D_RXINVERT: mod17CalData.bb_rx_invert -= 1; break; + case D_MICGAIN: _ui_changeMicGain(-1); break; + case D_PTTINLEVEL: mod17CalData.ptt_in_level -= 1; break; + case D_PTTOUTLEVEL:mod17CalData.ptt_out_level -= 1; break; + default: break; + } + } + else if(msg.keys & KEY_RIGHT) + { + switch(ui_state.menu_selected) + { + case D_TXWIPER: _ui_changeWiper(&mod17CalData.tx_wiper, +1); break; + case D_RXWIPER: _ui_changeWiper(&mod17CalData.rx_wiper, +1); break; + case D_TXINVERT: mod17CalData.bb_tx_invert += 1; break; + case D_RXINVERT: mod17CalData.bb_rx_invert += 1; break; + case D_MICGAIN: _ui_changeMicGain(+1); break; + case D_PTTINLEVEL: mod17CalData.ptt_in_level += 1; break; + case D_PTTOUTLEVEL:mod17CalData.ptt_out_level += 1; break; + default: break; + } + } + else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) + _ui_menuUp(module17_num); + else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) + _ui_menuDown(module17_num); + else if(msg.keys & KEY_ESC) + { + nvm_writeSettings(&state.settings); + _ui_menuBack(&menuSettingsScreen); + } + return true; +} + +bool SettingsReset2DefaultsScreen::handleInput(event_t ev) +{ + if(ev.type != EVENT_KBD) return true; + kbd_msg_t msg; + msg.value = ev.payload; + + if(!ui_state.edit_mode) + { + if(msg.keys & KEY_ENTER) + ui_state.edit_mode = true; + else if(msg.keys & KEY_ESC) + _ui_menuBack(&menuSettingsScreen); + } + else + { + if(msg.keys & KEY_ENTER) + { + ui_state.edit_mode = false; + mod17CalData.tx_wiper = 0x080; + mod17CalData.rx_wiper = 0x080; + mod17CalData.bb_tx_invert = 0; + mod17CalData.bb_rx_invert = 0; + mod17CalData.mic_gain = 0; + state_resetSettingsAndVfo(); + nvm_writeSettings(&state.settings); + _ui_menuBack(&menuSettingsScreen); + } + else if(msg.keys & KEY_ESC) + { + ui_state.edit_mode = false; + _ui_menuBack(&menuSettingsScreen); + } + } + return true; +} + +// ---- end Screen subclasses ---- void ui_init() { layout = _ui_calculateLayout(); layout_ready = true; - // Initialize struct ui_state to all zeroes - // This syntax is called compound literal - // https://stackoverflow.com/questions/6891720/initialize-reset-struct-to-zero-null - ui_state = (const struct ui_state_t){ 0 }; + ui_state = ui_state_t{}; + current = &vfoScreen; + lastMainScreen = &vfoScreen; } void ui_drawSplashScreen() @@ -379,12 +803,12 @@ static void _ui_menuUp(uint8_t menu_entries) const hwInfo_t* hwinfo = platform_getHwInfo(); // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) + if((hwinfo->hw_version < 1) && (current == (Screen*)&menuTopScreen)) maxEntries -= 1; // Hide the softpot menu entries if hardware does not have them uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) + if((softpot == 0) && (current == (Screen*)&settingsModule17Screen)) maxEntries -= 2; if(ui_state.menu_selected > 0) @@ -399,12 +823,12 @@ static void _ui_menuDown(uint8_t menu_entries) const hwInfo_t* hwinfo = platform_getHwInfo(); // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) + if((hwinfo->hw_version < 1) && (current == (Screen*)&menuTopScreen)) maxEntries -= 1; // Hide the softpot menu entries if hardware does not have them uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) + if((softpot == 0) && (current == (Screen*)&settingsModule17Screen)) maxEntries -= 2; if(ui_state.menu_selected < maxEntries) @@ -413,19 +837,12 @@ static void _ui_menuDown(uint8_t menu_entries) ui_state.menu_selected = 0; } -static void _ui_menuBack(uint8_t prev_state) +static void _ui_menuBack(Screen *prev) { if(ui_state.edit_mode) - { ui_state.edit_mode = false; - } else - { - // Return to previous menu - state.ui_screen = prev_state; - // Reset menu selection - ui_state.menu_selected = 0; - } + transition(prev); } static void _ui_textInputReset(char *buf) @@ -487,385 +904,8 @@ void ui_updateFSM(bool *sync_rtx) event_t event; if(!ui_popEvent(&event)) return; - // Process pressed keys - if(event.type == EVENT_KBD) - { - kbd_msg_t msg; - msg.value = event.payload; - - switch(state.ui_screen) - { - // VFO screen - case MAIN_VFO: - if(ui_state.edit_mode) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected callsign and disable input mode - strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); - *sync_rtx = true; - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - else - _ui_textInputArrows(ui_state.new_callsign, 9, msg); - } - else if(ui_state.edit_message) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_message); - // Save selected message and disable input mode - strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); - ui_state.edit_message = false; - *sync_rtx = true; - } - else if(msg.keys & KEY_ESC) - // Discard selected message and disable input mode - ui_state.edit_message = false; - else - _ui_textInputArrows(ui_state.new_message, 52, msg); - } - else - { - if(msg.keys & KEY_ENTER) - { - // Save current main state - ui_state.last_main_state = state.ui_screen; - // Open Menu - state.ui_screen = MENU_TOP; - } - else if (msg.keys & KEY_RIGHT) - { - ui_state.edit_mode = true; - } - } - break; - - // Top menu screen - case MENU_TOP: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(menu_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(menu_num); - else if(msg.keys & KEY_ENTER) - { - switch(ui_state.menu_selected) - { - case M_SETTINGS: - state.ui_screen = MENU_SETTINGS; - break; - case M_INFO: - state.ui_screen = MENU_INFO; - break; - case M_ABOUT: - state.ui_screen = MENU_ABOUT; - break; - case M_SHUTDOWN: - state.devStatus = SHUTDOWN; - break; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(ui_state.last_main_state); - break; - - // Settings menu screen - case MENU_SETTINGS: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_num); - else if(msg.keys & KEY_ENTER) - { - - switch(ui_state.menu_selected) - { - case S_DISPLAY: - state.ui_screen = SETTINGS_DISPLAY; - break; - case S_M17: - state.ui_screen = SETTINGS_M17; - break; - case S_MOD17: - state.ui_screen = SETTINGS_MODULE17; - break; - case S_RESET2DEFAULTS: - state.ui_screen = SETTINGS_RESET2DEFAULTS; - break; - default: - state.ui_screen = MENU_SETTINGS; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // Info menu screen - case MENU_INFO: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(info_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(info_num); - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // About screen, scroll without rollover - case MENU_ABOUT: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - { - if(ui_state.menu_selected > 0) - ui_state.menu_selected -= 1; - } - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - ui_state.menu_selected += 1; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - - case SETTINGS_DISPLAY: - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case D_BRIGHTNESS: - _ui_changeBrightness(-5); - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case D_BRIGHTNESS: - _ui_changeBrightness(+5); - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(display_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(display_num); - else if(msg.keys & KEY_ESC) - { - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - break; - - // M17 Settings - case SETTINGS_M17: - - if(ui_state.edit_mode) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected callsign and disable input mode - strncpy(state.settings.callsign, ui_state.new_callsign, 10); - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - else - _ui_textInputArrows(ui_state.new_callsign, 9, msg); - } - else if(ui_state.edit_message) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_message); - // Save selected message and disable input mode - strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); - ui_state.edit_message = false; - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - // Discard selected message and disable input mode - ui_state.edit_message = false; - else - _ui_textInputArrows(ui_state.new_message, 52, msg); - } - else - { - // Not in edit mode: handle CAN setting - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case M_CAN: - _ui_changeCAN(-1); - break; - case M_CAN_RX: - state.settings.m17_can_rx = !state.settings.m17_can_rx; - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case M_CAN: - _ui_changeCAN(+1); - break; - case M_CAN_RX: - state.settings.m17_can_rx = !state.settings.m17_can_rx; - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_ENTER) - { - switch(ui_state.menu_selected) - { - // Enable callsign input - case M_CALLSIGN: - ui_state.edit_mode = true; - _ui_textInputReset(ui_state.new_callsign); - break; - // Enable meta text input - case M_METATEXT: - ui_state.edit_message = true; - _ui_textInputReset(ui_state.new_message); - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(m17_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(m17_num); - else if(msg.keys & KEY_ESC) - { - *sync_rtx = true; - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - } - break; - case SETTINGS_RESET2DEFAULTS: - if(! ui_state.edit_mode) - { - //require a confirmation ENTER, then another - //edit_mode is slightly misused to allow for this - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = true; - } - else if(msg.keys & KEY_ESC) - { - _ui_menuBack(MENU_SETTINGS); - } - } - else - { - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = false; - - // Reset calibration values - mod17CalData.tx_wiper = 0x080; - mod17CalData.rx_wiper = 0x080; - mod17CalData.bb_tx_invert = 0; - mod17CalData.bb_rx_invert = 0; - mod17CalData.mic_gain = 0; - - state_resetSettingsAndVfo(); - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - else if(msg.keys & KEY_ESC) - { - ui_state.edit_mode = false; - _ui_menuBack(MENU_SETTINGS); - } - } - break; - // Module17 Settings - case SETTINGS_MODULE17: - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case D_TXWIPER: - _ui_changeWiper(&mod17CalData.tx_wiper, -1); - break; - case D_RXWIPER: - _ui_changeWiper(&mod17CalData.rx_wiper, -1); - break; - case D_TXINVERT: - mod17CalData.bb_tx_invert -= 1; - break; - case D_RXINVERT: - mod17CalData.bb_rx_invert -= 1; - break; - case D_MICGAIN: - _ui_changeMicGain(-1); - break; - case D_PTTINLEVEL: - mod17CalData.ptt_in_level -= 1; - break; - case D_PTTOUTLEVEL: - mod17CalData.ptt_out_level -= 1; - break; - default: - state.ui_screen = SETTINGS_MODULE17; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case D_TXWIPER: - _ui_changeWiper(&mod17CalData.tx_wiper, +1); - break; - case D_RXWIPER: - _ui_changeWiper(&mod17CalData.rx_wiper, +1); - break; - case D_TXINVERT: - mod17CalData.bb_tx_invert += 1; - break; - case D_RXINVERT: - mod17CalData.bb_rx_invert += 1; - break; - case D_MICGAIN: - _ui_changeMicGain(+1); - break; - case D_PTTINLEVEL: - mod17CalData.ptt_in_level += 1; - break; - case D_PTTOUTLEVEL: - mod17CalData.ptt_out_level += 1; - break; - default: - state.ui_screen = SETTINGS_MODULE17; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(module17_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(module17_num); - else if(msg.keys & KEY_ESC) - { - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - break; - } - } + gSyncRtx = sync_rtx; + current->handleInput(event); } bool ui_updateGUI() @@ -875,46 +915,7 @@ bool ui_updateGUI() layout = _ui_calculateLayout(); layout_ready = true; } - // Draw current GUI page - switch(last_state.ui_screen) - { - // VFO main screen - case MAIN_VFO: - _ui_drawMainVFO(&ui_state); - break; - // Top menu screen - case MENU_TOP: - _ui_drawMenuTop(&ui_state); - break; - // Settings menu screen - case MENU_SETTINGS: - _ui_drawMenuSettings(&ui_state); - break; - // Info menu screen - case MENU_INFO: - _ui_drawMenuInfo(&ui_state); - break; - // About menu screen - case MENU_ABOUT: - _ui_drawMenuAbout(&ui_state); - break; - // Display settings screen - case SETTINGS_DISPLAY: - _ui_drawSettingsDisplay(&ui_state); - break; - // M17 settings screen - case SETTINGS_M17: - _ui_drawSettingsM17(&ui_state); - break; - // Module 17 settings screen - case SETTINGS_MODULE17: - _ui_drawSettingsModule17(&ui_state); - break; - // Screen to support resetting Settings and VFO to defaults - case SETTINGS_RESET2DEFAULTS: - _ui_drawSettingsReset2Defaults(&ui_state); - break; - } + current->render(); return true; } From e0f439d7405f0e83fabec032472a048ae21e0063 Mon Sep 17 00:00:00 2001 From: Joshua Murphy Date: Sat, 18 Apr 2026 18:27:12 -0230 Subject: [PATCH 3/4] phase 3 --- openrtx/include/ui/ui_default.h | 2 +- openrtx/include/ui/ui_draw.h | 61 +++++++++ openrtx/include/ui/ui_mod17.h | 2 +- openrtx/src/ui/default/ui.cpp | 223 ++++++++++++++------------------ openrtx/src/ui/module17/ui.cpp | 45 +------ 5 files changed, 166 insertions(+), 167 deletions(-) create mode 100644 openrtx/include/ui/ui_draw.h diff --git a/openrtx/include/ui/ui_default.h b/openrtx/include/ui/ui_default.h index 5bc9750908..3a718b2c26 100644 --- a/openrtx/include/ui/ui_default.h +++ b/openrtx/include/ui/ui_default.h @@ -214,7 +214,7 @@ typedef struct ui_state_t } ui_state_t; -extern layout_t layout; +extern const layout_t layout; extern state_t last_state; extern bool macro_latched; extern const char *menu_items[]; diff --git a/openrtx/include/ui/ui_draw.h b/openrtx/include/ui/ui_draw.h new file mode 100644 index 0000000000..2749f92948 --- /dev/null +++ b/openrtx/include/ui/ui_draw.h @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef UI_DRAW_H +#define UI_DRAW_H + +/* + * Forward declarations for all _ui_draw* functions implemented in ui_main.c + * and ui_menu.c. These are C functions called from C++ translation units, so + * the extern "C" linkage specification is mandatory. + * + * Each variant (default, module17) includes this header and declares only its + * variant-specific draw functions in its own extern "C" block. + */ + +// ui_state_t is defined in the variant header (ui_default.h or ui_mod17.h). +// A forward declaration is sufficient here since all parameters are pointers. +struct ui_state_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---- ui_main.c ---- */ +void _ui_drawMainBackground(); +void _ui_drawVFOMiddle(); +void _ui_drawMEMMiddle(); +void _ui_drawVFOBottom(); +void _ui_drawMEMBottom(); +void _ui_drawMainVFO(ui_state_t *ui_state); +void _ui_drawMainVFOInput(ui_state_t *ui_state); +void _ui_drawMainMEM(ui_state_t *ui_state); + +/* ---- ui_menu.c ---- */ +void _ui_drawMenuTop(ui_state_t *ui_state); +void _ui_drawMenuSettings(ui_state_t *ui_state); +void _ui_drawMenuInfo(ui_state_t *ui_state); +void _ui_drawMenuAbout(ui_state_t *ui_state); +void _ui_drawSettingsDisplay(ui_state_t *ui_state); +void _ui_drawSettingsM17(ui_state_t *ui_state); +void _ui_drawSettingsReset2Defaults(ui_state_t *ui_state); +bool _ui_drawMacroMenu(ui_state_t *ui_state); + +#ifdef CONFIG_GPS +void _ui_drawMenuGPS(); +void _ui_drawSettingsGPS(ui_state_t *ui_state); +#endif + +#ifdef CONFIG_RTC +void _ui_drawSettingsTimeDate(); +void _ui_drawSettingsTimeDateSet(ui_state_t *ui_state); +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* UI_DRAW_H */ diff --git a/openrtx/include/ui/ui_mod17.h b/openrtx/include/ui/ui_mod17.h index 5203670eaa..08a08e331c 100644 --- a/openrtx/include/ui/ui_mod17.h +++ b/openrtx/include/ui/ui_mod17.h @@ -187,7 +187,7 @@ typedef struct ui_state_t } ui_state_t; -extern layout_t layout; +extern const layout_t layout; // Copy of the radio state extern state_t last_state; extern const char *menu_items[]; diff --git a/openrtx/src/ui/default/ui.cpp b/openrtx/src/ui/default/ui.cpp index 771ba65b83..cb4c23511c 100644 --- a/openrtx/src/ui/default/ui.cpp +++ b/openrtx/src/ui/default/ui.cpp @@ -67,47 +67,23 @@ #include "core/beeps.h" #include "ui/ui_events.h" #include "ui/Screen.hpp" +#include "ui/ui_draw.h" +// default-variant-specific draw functions extern "C" { -/* UI main screen functions, their implementation is in "ui_main.c" */ -void _ui_drawMainBackground(); void _ui_drawMainTop(ui_state_t* ui_state); -void _ui_drawVFOMiddle(); -void _ui_drawMEMMiddle(); -void _ui_drawVFOBottom(); -void _ui_drawMEMBottom(); -void _ui_drawMainVFO(ui_state_t* ui_state); -void _ui_drawMainVFOInput(ui_state_t* ui_state); -void _ui_drawMainMEM(ui_state_t* ui_state); -/* UI menu functions, their implementation is in "ui_menu.c" */ -void _ui_drawMenuTop(ui_state_t* ui_state); void _ui_drawMenuBank(ui_state_t* ui_state); void _ui_drawMenuChannel(ui_state_t* ui_state); void _ui_drawMenuContacts(ui_state_t* ui_state); -#ifdef CONFIG_GPS -void _ui_drawMenuGPS(); -void _ui_drawSettingsGPS(ui_state_t* ui_state); -#endif void _ui_drawSettingsAccessibility(ui_state_t* ui_state); -void _ui_drawMenuSettings(ui_state_t* ui_state); void _ui_drawMenuBackupRestore(ui_state_t* ui_state); void _ui_drawMenuBackup(ui_state_t* ui_state); void _ui_drawMenuRestore(ui_state_t* ui_state); -void _ui_drawMenuInfo(ui_state_t* ui_state); -void _ui_drawMenuAbout(ui_state_t* ui_state); -#ifdef CONFIG_RTC -void _ui_drawSettingsTimeDate(); -void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); -#endif -void _ui_drawSettingsDisplay(ui_state_t* ui_state); -void _ui_drawSettingsM17(ui_state_t* ui_state); void _ui_drawSettingsFM(ui_state_t* ui_state); void _ui_drawSettingsVoicePrompts(ui_state_t* ui_state); -void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); void _ui_drawSettingsRadio(ui_state_t* ui_state); -bool _ui_drawMacroMenu(ui_state_t* ui_state); void _ui_reset_menu_anouncement_tracking(); -} // extern "C" ui_main / ui_menu +} // extern "C" default-specific // TODO: get these from ui strings / currentLanguage const char *menu_items[] = { @@ -279,157 +255,155 @@ const color_t color_grey = {60, 60, 60, 255}; const color_t color_white = {255, 255, 255, 255}; const color_t yellow_fab413 = {250, 180, 19, 255}; -layout_t layout; state_t last_state; bool macro_latched; static ui_state_t ui_state; static bool macro_menu = false; -static bool layout_ready = false; static bool redraw_needed = true; static bool standby = false; static long long last_event_tick = 0; -static void _ui_calculateLayout(layout_t *layout) +static constexpr layout_t _ui_calculateLayout() { // Horizontal line height - static const uint16_t hline_h = 1; + const uint16_t hline_h = 1; // Compensate for fonts printing below the start position - static const uint16_t text_v_offset = 1; + const uint16_t text_v_offset = 1; // Calculate UI layout depending on vertical resolution // Tytera MD380, MD-UV380 #if CONFIG_SCREEN_HEIGHT > 127 // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 16; - static const uint16_t top_pad = 4; - static const uint16_t line1_h = 20; - static const uint16_t line2_h = 20; - static const uint16_t line3_h = 20; - static const uint16_t line3_large_h = 40; - static const uint16_t line4_h = 20; - static const uint16_t menu_h = 16; - static const uint16_t bottom_h = 23; - static const uint16_t bottom_pad = top_pad; - static const uint16_t status_v_pad = 2; - static const uint16_t small_line_v_pad = 2; - static const uint16_t big_line_v_pad = 6; - static const uint16_t horizontal_pad = 4; + const uint16_t top_h = 16; + const uint16_t top_pad = 4; + const uint16_t line1_h = 20; + const uint16_t line2_h = 20; + const uint16_t line3_h = 20; + const uint16_t line3_large_h = 40; + const uint16_t line4_h = 20; + const uint16_t menu_h = 16; + const uint16_t bottom_h = 23; + const uint16_t bottom_pad = top_pad; + const uint16_t status_v_pad = 2; + const uint16_t small_line_v_pad = 2; + const uint16_t big_line_v_pad = 6; + const uint16_t horizontal_pad = 4; // Top bar font: 8 pt - static const fontSize_t top_font = FONT_SIZE_8PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_8PT; + const fontSize_t top_font = FONT_SIZE_8PT; + const symbolSize_t top_symbol_size = SYMBOLS_SIZE_8PT; // Text line font: 8 pt - static const fontSize_t line1_font = FONT_SIZE_8PT; - static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line2_font = FONT_SIZE_8PT; - static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line3_font = FONT_SIZE_8PT; - static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line4_font = FONT_SIZE_8PT; - static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_8PT; + const fontSize_t line1_font = FONT_SIZE_8PT; + const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_8PT; + const fontSize_t line2_font = FONT_SIZE_8PT; + const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_8PT; + const fontSize_t line3_font = FONT_SIZE_8PT; + const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_8PT; + const fontSize_t line4_font = FONT_SIZE_8PT; + const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_8PT; // Message font const fontSize_t message_font = FONT_SIZE_6PT; // Frequency line font: 16 pt - static const fontSize_t line3_large_font = FONT_SIZE_16PT; + const fontSize_t line3_large_font = FONT_SIZE_16PT; // Bottom bar font: 8 pt - static const fontSize_t bottom_font = FONT_SIZE_8PT; + const fontSize_t bottom_font = FONT_SIZE_8PT; // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_12PT; + const fontSize_t input_font = FONT_SIZE_12PT; // Menu font - static const fontSize_t menu_font = FONT_SIZE_8PT; + const fontSize_t menu_font = FONT_SIZE_8PT; // Radioddity GD-77 #elif CONFIG_SCREEN_HEIGHT > 63 // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 11; - static const uint16_t top_pad = 1; - static const uint16_t line1_h = 10; - static const uint16_t line2_h = 10; - static const uint16_t line3_h = 10; - static const uint16_t line3_large_h = 16; - static const uint16_t line4_h = 10; - static const uint16_t menu_h = 10; - static const uint16_t bottom_h = 15; - static const uint16_t bottom_pad = 0; - static const uint16_t status_v_pad = 1; - static const uint16_t small_line_v_pad = 1; - static const uint16_t big_line_v_pad = 0; - static const uint16_t horizontal_pad = 4; + const uint16_t top_h = 11; + const uint16_t top_pad = 1; + const uint16_t line1_h = 10; + const uint16_t line2_h = 10; + const uint16_t line3_h = 10; + const uint16_t line3_large_h = 16; + const uint16_t line4_h = 10; + const uint16_t menu_h = 10; + const uint16_t bottom_h = 15; + const uint16_t bottom_pad = 0; + const uint16_t status_v_pad = 1; + const uint16_t small_line_v_pad = 1; + const uint16_t big_line_v_pad = 0; + const uint16_t horizontal_pad = 4; // Top bar font: 6 pt - static const fontSize_t top_font = FONT_SIZE_6PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t top_font = FONT_SIZE_6PT; + const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; // Middle line fonts: 5, 8, 8 pt - static const fontSize_t line1_font = FONT_SIZE_6PT; - static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line2_font = FONT_SIZE_6PT; - static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line3_font = FONT_SIZE_6PT; - static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line3_large_font = FONT_SIZE_10PT; - static const fontSize_t line4_font = FONT_SIZE_6PT; - static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line1_font = FONT_SIZE_6PT; + const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line2_font = FONT_SIZE_6PT; + const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line3_font = FONT_SIZE_6PT; + const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t line3_large_font = FONT_SIZE_10PT; + const fontSize_t line4_font = FONT_SIZE_6PT; + const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; // Message font const fontSize_t message_font = FONT_SIZE_6PT; // Bottom bar font: 6 pt - static const fontSize_t bottom_font = FONT_SIZE_6PT; + const fontSize_t bottom_font = FONT_SIZE_6PT; // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_8PT; + const fontSize_t input_font = FONT_SIZE_8PT; // Menu font - static const fontSize_t menu_font = FONT_SIZE_6PT; + const fontSize_t menu_font = FONT_SIZE_6PT; // Radioddity RD-5R #elif CONFIG_SCREEN_HEIGHT > 47 // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 11; - static const uint16_t top_pad = 1; - static const uint16_t line1_h = 0; - static const uint16_t line2_h = 10; - static const uint16_t line3_h = 10; - static const uint16_t line3_large_h = 18; - static const uint16_t line4_h = 10; - static const uint16_t menu_h = 10; - static const uint16_t bottom_h = 0; - static const uint16_t bottom_pad = 0; - static const uint16_t status_v_pad = 1; - static const uint16_t small_line_v_pad = 1; - static const uint16_t big_line_v_pad = 0; - static const uint16_t horizontal_pad = 4; + const uint16_t top_h = 11; + const uint16_t top_pad = 1; + const uint16_t line1_h = 0; + const uint16_t line2_h = 10; + const uint16_t line3_h = 10; + const uint16_t line3_large_h = 18; + const uint16_t line4_h = 10; + const uint16_t menu_h = 10; + const uint16_t bottom_h = 0; + const uint16_t bottom_pad = 0; + const uint16_t status_v_pad = 1; + const uint16_t small_line_v_pad = 1; + const uint16_t big_line_v_pad = 0; + const uint16_t horizontal_pad = 4; // Top bar font: 6 pt - static const fontSize_t top_font = FONT_SIZE_6PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; + const fontSize_t top_font = FONT_SIZE_6PT; + const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; // Middle line fonts: 16, 16 - static const fontSize_t line2_font = FONT_SIZE_6PT; - static const fontSize_t line3_font = FONT_SIZE_6PT; - static const fontSize_t line4_font = FONT_SIZE_6PT; - static const fontSize_t line3_large_font = FONT_SIZE_12PT; + const fontSize_t line2_font = FONT_SIZE_6PT; + const fontSize_t line3_font = FONT_SIZE_6PT; + const fontSize_t line4_font = FONT_SIZE_6PT; + const fontSize_t line3_large_font = FONT_SIZE_12PT; // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_8PT; + const fontSize_t input_font = FONT_SIZE_8PT; // Menu font - static const fontSize_t menu_font = FONT_SIZE_6PT; + const fontSize_t menu_font = FONT_SIZE_6PT; // Message font const fontSize_t message_font = FONT_SIZE_6PT; // Not present on this resolution - static const fontSize_t line1_font = 0; - static const fontSize_t bottom_font = 0; + const fontSize_t line1_font = 0; + const fontSize_t bottom_font = 0; #else #error Unsupported vertical resolution! #endif // Calculate printing positions - static const uint16_t top_pos = top_h - status_v_pad - text_v_offset; - static const uint16_t line1_pos = top_h + top_pad + line1_h - small_line_v_pad - text_v_offset; - static const uint16_t line2_pos = top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset; - static const uint16_t line3_pos = top_h + top_pad + line1_h + line2_h + line3_h - small_line_v_pad - text_v_offset; - static const uint16_t line4_pos = top_h + top_pad + line1_h + line2_h + line3_h + line4_h - small_line_v_pad - text_v_offset; - static const uint16_t line3_large_pos = top_h + top_pad + line1_h + line2_h + line3_large_h - big_line_v_pad - text_v_offset; - static const uint16_t bottom_pos = CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset; + const uint16_t top_pos = top_h - status_v_pad - text_v_offset; + const uint16_t line1_pos = top_h + top_pad + line1_h - small_line_v_pad - text_v_offset; + const uint16_t line2_pos = top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset; + const uint16_t line3_pos = top_h + top_pad + line1_h + line2_h + line3_h - small_line_v_pad - text_v_offset; + const uint16_t line4_pos = top_h + top_pad + line1_h + line2_h + line3_h + line4_h - small_line_v_pad - text_v_offset; + const uint16_t line3_large_pos = top_h + top_pad + line1_h + line2_h + line3_large_h - big_line_v_pad - text_v_offset; + const uint16_t bottom_pos = CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset; layout_t new_layout = { @@ -470,9 +444,11 @@ static void _ui_calculateLayout(layout_t *layout) message_font }; - memcpy(layout, &new_layout, sizeof(layout_t)); + return new_layout; } +const layout_t layout = _ui_calculateLayout(); + static void _ui_drawLowBatteryScreen() { gfx_clearScreen(); @@ -2408,8 +2384,6 @@ void ui_init() { last_event_tick = getTick(); redraw_needed = true; - _ui_calculateLayout(&layout); - layout_ready = true; ui_state = ui_state_t{}; } @@ -2643,11 +2617,6 @@ bool ui_updateGUI() if(redraw_needed == false) return false; - if(!layout_ready) - { - _ui_calculateLayout(&layout); - layout_ready = true; - } current->render(); // If MACRO menu is active draw it diff --git a/openrtx/src/ui/module17/ui.cpp b/openrtx/src/ui/module17/ui.cpp index 50892a9dd4..ee7894d82d 100644 --- a/openrtx/src/ui/module17/ui.cpp +++ b/openrtx/src/ui/module17/ui.cpp @@ -19,38 +19,14 @@ #include "hwconfig.h" #include "ui/ui_events.h" #include "ui/Screen.hpp" +#include "ui/ui_draw.h" +// module17-variant-specific draw functions and helpers extern "C" { -/* UI main screen functions, their implementation is in "ui_main.c" */ -void _ui_drawMainBackground(); -void _ui_drawMainTop(); -void _ui_drawVFOMiddle(); -void _ui_drawMEMMiddle(); -void _ui_drawVFOBottom(); -void _ui_drawMEMBottom(); -void _ui_drawMainVFO(ui_state_t* ui_state); -void _ui_drawMainVFOInput(ui_state_t* ui_state); -void _ui_drawMainMEM(ui_state_t* ui_state); -/* UI menu functions, their implementation is in "ui_menu.c" */ -void _ui_drawMenuTop(ui_state_t* ui_state); -#ifdef CONFIG_GPS -void _ui_drawMenuGPS(); -void _ui_drawSettingsGPS(ui_state_t* ui_state); -#endif -void _ui_drawMenuSettings(ui_state_t* ui_state); -void _ui_drawMenuInfo(ui_state_t* ui_state); -void _ui_drawMenuAbout(ui_state_t* ui_state); -#ifdef CONFIG_RTC -void _ui_drawSettingsTimeDate(); -void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); -#endif -void _ui_drawSettingsDisplay(ui_state_t* ui_state); -void _ui_drawSettingsM17(ui_state_t* ui_state); +void _ui_drawMainTop(); // no-arg variant for Module17 void _ui_drawSettingsModule17(ui_state_t* ui_state); -void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); -bool _ui_drawMacroMenu(ui_state_t* ui_state); void state_resetSettingsAndVfo(); -} // extern "C" ui_main / ui_menu +} // extern "C" module17-specific const char *menu_items[] = { @@ -148,10 +124,8 @@ const color_t color_grey = {60, 60, 60, 255}; const color_t color_white = {255, 255, 255, 255}; const color_t yellow_fab413 = {250, 180, 19, 255}; -layout_t layout; state_t last_state; static ui_state_t ui_state; -static bool layout_ready = false; static Screen *current = nullptr; static Screen *lastMainScreen = nullptr; static bool *gSyncRtx = nullptr; @@ -170,7 +144,7 @@ static void _ui_textInputReset(char *buf); static void _ui_textInputArrows(char *buf, uint8_t max_len, kbd_msg_t msg); static void _ui_textInputConfirm(char *buf); -static layout_t _ui_calculateLayout() +static constexpr layout_t _ui_calculateLayout() { // Horizontal line height const uint16_t hline_h = 1; @@ -275,6 +249,8 @@ static layout_t _ui_calculateLayout() return new_layout; } +const layout_t layout = _ui_calculateLayout(); + // ---- Screen subclasses ---- class VfoScreen : public Screen { @@ -682,8 +658,6 @@ bool SettingsReset2DefaultsScreen::handleInput(event_t ev) void ui_init() { - layout = _ui_calculateLayout(); - layout_ready = true; ui_state = ui_state_t{}; current = &vfoScreen; lastMainScreen = &vfoScreen; @@ -910,11 +884,6 @@ void ui_updateFSM(bool *sync_rtx) bool ui_updateGUI() { - if(!layout_ready) - { - layout = _ui_calculateLayout(); - layout_ready = true; - } current->render(); return true; From 944cf4084488ede5f1ea70acdb7459b2dc305048 Mon Sep 17 00:00:00 2001 From: Joshua Murphy Date: Sat, 18 Apr 2026 19:25:10 -0230 Subject: [PATCH 4/4] finish --- meson.build | 7 +- openrtx/include/ui/ui_nav.h | 25 + openrtx/src/ui/common/ui_events.cpp | 47 +- openrtx/src/ui/common/ui_nav.cpp | 34 + openrtx/src/ui/default/ui.c | 2750 --------------------------- openrtx/src/ui/default/ui.cpp | 230 ++- openrtx/src/ui/module17/ui.c | 924 --------- openrtx/src/ui/module17/ui.cpp | 72 +- 8 files changed, 309 insertions(+), 3780 deletions(-) create mode 100644 openrtx/include/ui/ui_nav.h create mode 100644 openrtx/src/ui/common/ui_nav.cpp delete mode 100644 openrtx/src/ui/default/ui.c delete mode 100644 openrtx/src/ui/module17/ui.c diff --git a/meson.build b/meson.build index 8db4cae20d..6786d861e4 100644 --- a/meson.build +++ b/meson.build @@ -75,12 +75,13 @@ openrtx_inc = ['openrtx/include', 'platform'] ## OpenRTX UI sources ## -ui_src_common = ['openrtx/src/ui/common/ui_events.cpp'] +ui_src_common = ['openrtx/src/ui/common/ui_events.cpp', + 'openrtx/src/ui/common/ui_nav.cpp', + 'openrtx/src/ui/default/ui_strings.c'] ui_src_default = ui_src_common + ['openrtx/src/ui/default/ui.cpp', 'openrtx/src/ui/default/ui_main.c', - 'openrtx/src/ui/default/ui_menu.c', - 'openrtx/src/ui/default/ui_strings.c'] + 'openrtx/src/ui/default/ui_menu.c'] ui_src_module17 = ui_src_common + ['openrtx/src/ui/module17/ui.cpp', 'openrtx/src/ui/module17/ui_main.c', diff --git a/openrtx/include/ui/ui_nav.h b/openrtx/include/ui/ui_nav.h new file mode 100644 index 0000000000..4d9b647eae --- /dev/null +++ b/openrtx/include/ui/ui_nav.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef UI_NAV_H +#define UI_NAV_H + +#include "ui/Screen.hpp" + +/* + * Shared navigation state for both UI variants (default and module17). + * Holds the active screen pointer and the "last main screen" used for + * back-navigation from menus. Both variants call ui_nav_init() at startup + * and use ui_nav_transition() instead of a file-static transition() helper. + */ + +void ui_nav_init(Screen *initial); +void ui_nav_transition(Screen *next); +void ui_nav_setLastMain(Screen *s); +Screen* ui_nav_current(); +Screen* ui_nav_lastMain(); + +#endif /* UI_NAV_H */ diff --git a/openrtx/src/ui/common/ui_events.cpp b/openrtx/src/ui/common/ui_events.cpp index d6144cff3c..727dcb5fce 100644 --- a/openrtx/src/ui/common/ui_events.cpp +++ b/openrtx/src/ui/common/ui_events.cpp @@ -5,6 +5,7 @@ */ #include +#include #include "core/event.h" #include "core/ui.h" #include "ui/ui_common.h" @@ -14,35 +15,45 @@ namespace { uint8_t evQueue_rdPos = 0; uint8_t evQueue_wrPos = 0; event_t evQueue[MAX_NUM_EVENTS]; + pthread_mutex_t evQueue_mutex = PTHREAD_MUTEX_INITIALIZER; } extern "C" { bool ui_pushEvent(const uint8_t type, const uint32_t data) { - uint8_t newHead = (evQueue_wrPos + 1) % MAX_NUM_EVENTS; - - if(newHead == evQueue_rdPos) return false; - - event_t event; - event.type = type; - event.payload = data; - - evQueue[evQueue_wrPos] = event; - evQueue_wrPos = newHead; + pthread_mutex_lock(&evQueue_mutex); - return true; + uint8_t newHead = (evQueue_wrPos + 1) % MAX_NUM_EVENTS; + bool ok = (newHead != evQueue_rdPos); + + if(ok) + { + event_t event; + event.type = type; + event.payload = data; + evQueue[evQueue_wrPos] = event; + evQueue_wrPos = newHead; + } + + pthread_mutex_unlock(&evQueue_mutex); + return ok; } bool ui_popEvent(event_t *ev) { - if(evQueue_wrPos == evQueue_rdPos) return false; - - uint8_t newTail = (evQueue_rdPos + 1) % MAX_NUM_EVENTS; - *ev = evQueue[evQueue_rdPos]; - evQueue_rdPos = newTail; - - return true; + pthread_mutex_lock(&evQueue_mutex); + + bool ok = (evQueue_wrPos != evQueue_rdPos); + if(ok) + { + uint8_t newTail = (evQueue_rdPos + 1) % MAX_NUM_EVENTS; + *ev = evQueue[evQueue_rdPos]; + evQueue_rdPos = newTail; + } + + pthread_mutex_unlock(&evQueue_mutex); + return ok; } } // extern "C" diff --git a/openrtx/src/ui/common/ui_nav.cpp b/openrtx/src/ui/common/ui_nav.cpp new file mode 100644 index 0000000000..e4e774a6cf --- /dev/null +++ b/openrtx/src/ui/common/ui_nav.cpp @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include "ui/ui_nav.h" + +static Screen *navCurrent = nullptr; +static Screen *navLastMain = nullptr; + +void ui_nav_init(Screen *initial) +{ + navCurrent = initial; + navLastMain = initial; +} + +void ui_nav_transition(Screen *next) +{ + navCurrent->exit(); + next->enter(); + navCurrent = next; +} + +void ui_nav_setLastMain(Screen *s) { navLastMain = s; } +Screen* ui_nav_current() { return navCurrent; } +Screen* ui_nav_lastMain() { return navLastMain; } + +extern "C" void ui_terminate() +{ + if(navCurrent) + navCurrent->exit(); +} diff --git a/openrtx/src/ui/default/ui.c b/openrtx/src/ui/default/ui.c deleted file mode 100644 index 7e926cc49e..0000000000 --- a/openrtx/src/ui/default/ui.c +++ /dev/null @@ -1,2750 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* - * The graphical user interface (GUI) works by splitting the screen in - * horizontal rows, with row height depending on vertical resolution. - * - * The general screen layout is composed by an upper status bar at the - * top of the screen and a lower status bar at the bottom. - * The central portion of the screen is filled by two big text/number rows - * And a small row. - * - * Below is shown the row height for two common display densities. - * - * 160x128 display (MD380) Recommended font size - * ┌─────────────────────────┐ - * │ top_status_bar (16px) │ 8 pt (11 px) font with 2 px vertical padding - * │ top_pad (4px) │ 4 px padding - * │ Line 1 (20px) │ 8 pt (11 px) font with 4 px vertical padding - * │ Line 2 (20px) │ 8 pt (11 px) font with 4 px vertical padding - * │ │ - * │ Line 3 (40px) │ 16 pt (xx px) font with 6 px vertical padding - * │ RSSI+squelch bar (20px) │ 20 px - * │ bottom_pad (4px) │ 4 px padding - * └─────────────────────────┘ - * - * 128x64 display (GD-77) - * ┌─────────────────────────┐ - * │ top_status_bar (11 px) │ 6 pt (9 px) font with 1 px vertical padding - * │ top_pad (1px) │ 1 px padding - * │ Line 1 (10px) │ 6 pt (9 px) font without vertical padding - * │ Line 2 (10px) │ 6 pt (9 px) font with 2 px vertical padding - * │ Line 3 (18px) │ 12 pt (xx px) font with 0 px vertical padding - * │ RSSI+squelch bar (11px) │ 11 px - * │ bottom_pad (1px) │ 1 px padding - * └─────────────────────────┘ - * - * 128x48 display (RD-5R) - * ┌─────────────────────────┐ - * │ top_status_bar (11 px) │ 6 pt (9 px) font with 1 px vertical padding - * ├─────────────────────────┤ 1 px line - * │ Line 2 (10px) │ 8 pt (11 px) font with 4 px vertical padding - * │ Line 3 (18px) │ 8 pt (11 px) font with 4 px vertical padding - * └─────────────────────────┘ - */ - -#include -#include -#include -#include -#include "ui/ui_default.h" -#include "rtx/rtx.h" -#include "interfaces/platform.h" -#include "interfaces/display.h" -#include "interfaces/cps_io.h" -#include "interfaces/nvmem.h" -#include "interfaces/delays.h" -#include -#include "core/battery.h" -#include "core/input.h" -#include "core/utils.h" -#include "hwconfig.h" -#include "core/voicePromptUtils.h" -#include "core/beeps.h" -#include "ui/ui_events.h" - -/* UI main screen functions, their implementation is in "ui_main.c" */ -extern void _ui_drawMainBackground(); -extern void _ui_drawMainTop(ui_state_t* ui_state); -extern void _ui_drawVFOMiddle(); -extern void _ui_drawMEMMiddle(); -extern void _ui_drawVFOBottom(); -extern void _ui_drawMEMBottom(); -extern void _ui_drawMainVFO(ui_state_t* ui_state); -extern void _ui_drawMainVFOInput(ui_state_t* ui_state); -extern void _ui_drawMainMEM(ui_state_t* ui_state); -/* UI menu functions, their implementation is in "ui_menu.c" */ -extern void _ui_drawMenuTop(ui_state_t* ui_state); -extern void _ui_drawMenuBank(ui_state_t* ui_state); -extern void _ui_drawMenuChannel(ui_state_t* ui_state); -extern void _ui_drawMenuContacts(ui_state_t* ui_state); -#ifdef CONFIG_GPS -extern void _ui_drawMenuGPS(); -extern void _ui_drawSettingsGPS(ui_state_t* ui_state); -#endif -extern void _ui_drawSettingsAccessibility(ui_state_t* ui_state); -extern void _ui_drawMenuSettings(ui_state_t* ui_state); -extern void _ui_drawMenuBackupRestore(ui_state_t* ui_state); -extern void _ui_drawMenuBackup(ui_state_t* ui_state); -extern void _ui_drawMenuRestore(ui_state_t* ui_state); -extern void _ui_drawMenuInfo(ui_state_t* ui_state); -extern void _ui_drawMenuAbout(ui_state_t* ui_state); -#ifdef CONFIG_RTC -extern void _ui_drawSettingsTimeDate(); -extern void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); -#endif -extern void _ui_drawSettingsDisplay(ui_state_t* ui_state); -extern void _ui_drawSettingsM17(ui_state_t* ui_state); -extern void _ui_drawSettingsFM(ui_state_t* ui_state); -extern void _ui_drawSettingsVoicePrompts(ui_state_t* ui_state); -extern void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); -extern void _ui_drawSettingsRadio(ui_state_t* ui_state); -extern bool _ui_drawMacroMenu(ui_state_t* ui_state); -extern void _ui_reset_menu_anouncement_tracking(); -// TODO: get these from ui strings / currentLanguage -const char *menu_items[] = -{ - "Banks", - "Channels", - "Contacts", -#ifdef CONFIG_GPS - "GPS", -#endif - "Settings", - "Info", - "About" -}; - -const char *settings_items[] = -{ - "Display", -#ifdef CONFIG_RTC - "Time & Date", -#endif -#ifdef CONFIG_GPS - "GPS", -#endif - "Radio", -#ifdef CONFIG_M17 - "M17", -#endif - "FM", - "Accessibility", - "Default Settings" -}; - -const char *display_items[] = -{ -#ifdef CONFIG_SCREEN_BRIGHTNESS - "Brightness", -#endif -#ifdef CONFIG_SCREEN_CONTRAST - "Contrast", -#endif - "Timer", - "Battery Icon" -}; - -#ifdef CONFIG_GPS -const char *settings_gps_items[] = -{ - "GPS Enabled", -#ifdef CONFIG_RTC - "GPS Set Time", - "UTC Timezone" -#endif -}; -#endif - -const char *settings_radio_items[] = -{ - "Offset", - "Direction", - "Step", -}; - -const char * settings_m17_items[] = -{ - "Callsign", - "Meta Txt", - "CAN", - "CAN RX Check" -}; - -const char* settings_fm_items[] = -{ - "CTCSS Tone", - "CTCSS En." -}; - -const char * settings_accessibility_items[] = -{ - "Macro Latch", - "Voice", - "Phonetic" -}; - -const char *backup_restore_items[] = -{ - "Backup", - "Restore" -}; - -const char *info_items[] = -{ - "", - "Bat. Voltage", - "Bat. Charge", - "RSSI", - "Used heap", - "Band", - "VHF", - "UHF", - "Hw Version", -#ifdef PLATFORM_TTWRPLUS - "Radio", - "Radio FW", -#endif -}; - -const char *authors[] = -{ - "Niccolo' IU2KIN", - "Silvano IU2KWO", - "Federico IU2NUO", - "Fred IU2NRO", - "Joseph VK7JS", - "Morgan ON4MOD", - "Marco DM4RCO" -}; - -static const char *symbols_ITU_T_E161[] = -{ - " 0", - ",.?1", - "abc2ABC", - "def3DEF", - "ghi4GHI", - "jkl5JKL", - "mno6MNO", - "pqrs7PQRS", - "tuv8TUV", - "wxyz9WXYZ", - "-/*", - "#" -}; - -static const char *symbols_ITU_T_E161_callsign[] = -{ - "0 ", - "1", - "ABC2", - "DEF3", - "GHI4", - "JKL5", - "MNO6", - "PQRS7", - "TUV8", - "WXYZ9", - "-/", - "" -}; - -// Calculate number of menu entries -const uint8_t menu_num = sizeof(menu_items)/sizeof(menu_items[0]); -const uint8_t settings_num = sizeof(settings_items)/sizeof(settings_items[0]); -const uint8_t display_num = sizeof(display_items)/sizeof(display_items[0]); -#ifdef CONFIG_GPS -const uint8_t settings_gps_num = sizeof(settings_gps_items)/sizeof(settings_gps_items[0]); -#endif -const uint8_t settings_radio_num = sizeof(settings_radio_items)/sizeof(settings_radio_items[0]); -#ifdef CONFIG_M17 -const uint8_t settings_m17_num = sizeof(settings_m17_items)/sizeof(settings_m17_items[0]); -#endif -const uint8_t settings_fm_num = sizeof(settings_fm_items) / sizeof(settings_fm_items[0]); -const uint8_t settings_accessibility_num = sizeof(settings_accessibility_items)/sizeof(settings_accessibility_items[0]); -const uint8_t backup_restore_num = sizeof(backup_restore_items)/sizeof(backup_restore_items[0]); -const uint8_t info_num = sizeof(info_items)/sizeof(info_items[0]); -const uint8_t author_num = sizeof(authors)/sizeof(authors[0]); - -const color_t color_black = {0, 0, 0, 255}; -const color_t color_grey = {60, 60, 60, 255}; -const color_t color_white = {255, 255, 255, 255}; -const color_t yellow_fab413 = {250, 180, 19, 255}; - -layout_t layout; -state_t last_state; -bool macro_latched; -static ui_state_t ui_state; -static bool macro_menu = false; -static bool layout_ready = false; -static bool redraw_needed = true; - -static bool standby = false; -static long long last_event_tick = 0; - -static void _ui_calculateLayout(layout_t *layout) -{ - // Horizontal line height - static const uint16_t hline_h = 1; - // Compensate for fonts printing below the start position - static const uint16_t text_v_offset = 1; - - // Calculate UI layout depending on vertical resolution - // Tytera MD380, MD-UV380 - #if CONFIG_SCREEN_HEIGHT > 127 - - // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 16; - static const uint16_t top_pad = 4; - static const uint16_t line1_h = 20; - static const uint16_t line2_h = 20; - static const uint16_t line3_h = 20; - static const uint16_t line3_large_h = 40; - static const uint16_t line4_h = 20; - static const uint16_t menu_h = 16; - static const uint16_t bottom_h = 23; - static const uint16_t bottom_pad = top_pad; - static const uint16_t status_v_pad = 2; - static const uint16_t small_line_v_pad = 2; - static const uint16_t big_line_v_pad = 6; - static const uint16_t horizontal_pad = 4; - - // Top bar font: 8 pt - static const fontSize_t top_font = FONT_SIZE_8PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_8PT; - // Text line font: 8 pt - static const fontSize_t line1_font = FONT_SIZE_8PT; - static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line2_font = FONT_SIZE_8PT; - static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line3_font = FONT_SIZE_8PT; - static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_8PT; - static const fontSize_t line4_font = FONT_SIZE_8PT; - static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_8PT; - // Message font - const fontSize_t message_font = FONT_SIZE_6PT; - // Frequency line font: 16 pt - static const fontSize_t line3_large_font = FONT_SIZE_16PT; - // Bottom bar font: 8 pt - static const fontSize_t bottom_font = FONT_SIZE_8PT; - // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_12PT; - // Menu font - static const fontSize_t menu_font = FONT_SIZE_8PT; - - // Radioddity GD-77 - #elif CONFIG_SCREEN_HEIGHT > 63 - - // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 11; - static const uint16_t top_pad = 1; - static const uint16_t line1_h = 10; - static const uint16_t line2_h = 10; - static const uint16_t line3_h = 10; - static const uint16_t line3_large_h = 16; - static const uint16_t line4_h = 10; - static const uint16_t menu_h = 10; - static const uint16_t bottom_h = 15; - static const uint16_t bottom_pad = 0; - static const uint16_t status_v_pad = 1; - static const uint16_t small_line_v_pad = 1; - static const uint16_t big_line_v_pad = 0; - static const uint16_t horizontal_pad = 4; - - // Top bar font: 6 pt - static const fontSize_t top_font = FONT_SIZE_6PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; - // Middle line fonts: 5, 8, 8 pt - static const fontSize_t line1_font = FONT_SIZE_6PT; - static const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line2_font = FONT_SIZE_6PT; - static const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line3_font = FONT_SIZE_6PT; - static const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; - static const fontSize_t line3_large_font = FONT_SIZE_10PT; - static const fontSize_t line4_font = FONT_SIZE_6PT; - static const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; - // Message font - const fontSize_t message_font = FONT_SIZE_6PT; - // Bottom bar font: 6 pt - static const fontSize_t bottom_font = FONT_SIZE_6PT; - // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_8PT; - // Menu font - static const fontSize_t menu_font = FONT_SIZE_6PT; - - // Radioddity RD-5R - #elif CONFIG_SCREEN_HEIGHT > 47 - - // Height and padding shown in diagram at beginning of file - static const uint16_t top_h = 11; - static const uint16_t top_pad = 1; - static const uint16_t line1_h = 0; - static const uint16_t line2_h = 10; - static const uint16_t line3_h = 10; - static const uint16_t line3_large_h = 18; - static const uint16_t line4_h = 10; - static const uint16_t menu_h = 10; - static const uint16_t bottom_h = 0; - static const uint16_t bottom_pad = 0; - static const uint16_t status_v_pad = 1; - static const uint16_t small_line_v_pad = 1; - static const uint16_t big_line_v_pad = 0; - static const uint16_t horizontal_pad = 4; - - // Top bar font: 6 pt - static const fontSize_t top_font = FONT_SIZE_6PT; - static const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; - // Middle line fonts: 16, 16 - static const fontSize_t line2_font = FONT_SIZE_6PT; - static const fontSize_t line3_font = FONT_SIZE_6PT; - static const fontSize_t line4_font = FONT_SIZE_6PT; - static const fontSize_t line3_large_font = FONT_SIZE_12PT; - // TimeDate/Frequency input font - static const fontSize_t input_font = FONT_SIZE_8PT; - // Menu font - static const fontSize_t menu_font = FONT_SIZE_6PT; - // Message font - const fontSize_t message_font = FONT_SIZE_6PT; - // Not present on this resolution - static const fontSize_t line1_font = 0; - static const fontSize_t bottom_font = 0; - - #else - #error Unsupported vertical resolution! - #endif - - // Calculate printing positions - static const uint16_t top_pos = top_h - status_v_pad - text_v_offset; - static const uint16_t line1_pos = top_h + top_pad + line1_h - small_line_v_pad - text_v_offset; - static const uint16_t line2_pos = top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset; - static const uint16_t line3_pos = top_h + top_pad + line1_h + line2_h + line3_h - small_line_v_pad - text_v_offset; - static const uint16_t line4_pos = top_h + top_pad + line1_h + line2_h + line3_h + line4_h - small_line_v_pad - text_v_offset; - static const uint16_t line3_large_pos = top_h + top_pad + line1_h + line2_h + line3_large_h - big_line_v_pad - text_v_offset; - static const uint16_t bottom_pos = CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset; - - layout_t new_layout = - { - hline_h, - top_h, - line1_h, - line2_h, - line3_h, - line3_large_h, - line4_h, - menu_h, - bottom_h, - bottom_pad, - status_v_pad, - horizontal_pad, - text_v_offset, - {horizontal_pad, top_pos}, - {horizontal_pad, line1_pos}, - {horizontal_pad, line2_pos}, - {horizontal_pad, line3_pos}, - {horizontal_pad, line3_large_pos}, - {horizontal_pad, line4_pos}, - {horizontal_pad, bottom_pos}, - top_font, - top_symbol_size, - line1_font, - line1_symbol_size, - line2_font, - line2_symbol_size, - line3_font, - line3_symbol_size, - line3_large_font, - line4_font, - line4_symbol_size, - bottom_font, - input_font, - menu_font, - message_font - }; - - memcpy(layout, &new_layout, sizeof(layout_t)); -} - -static void _ui_drawLowBatteryScreen() -{ - gfx_clearScreen(); - uint16_t bat_width = CONFIG_SCREEN_WIDTH / 2; - uint16_t bat_height = CONFIG_SCREEN_HEIGHT / 3; - point_t bat_pos = {CONFIG_SCREEN_WIDTH / 4, CONFIG_SCREEN_HEIGHT / 8}; - gfx_drawBattery(bat_pos, bat_width, bat_height, 10); - point_t text_pos_1 = {0, CONFIG_SCREEN_HEIGHT * 2 / 3}; - point_t text_pos_2 = {0, CONFIG_SCREEN_HEIGHT * 2 / 3 + 16}; - - gfx_print(text_pos_1, - FONT_SIZE_6PT, - TEXT_ALIGN_CENTER, - color_white, - currentLanguage->forEmergencyUse); - gfx_print(text_pos_2, - FONT_SIZE_6PT, - TEXT_ALIGN_CENTER, - color_white, - currentLanguage->pressAnyButton); -} - -static freq_t _ui_freq_add_digit(freq_t freq, uint8_t pos, uint8_t number) -{ - freq_t coefficient = 100; - for(uint8_t i=0; i < FREQ_DIGITS - pos; i++) - { - coefficient *= 10; - } - return freq += number * coefficient; -} - -#ifdef CONFIG_RTC -static void _ui_timedate_add_digit(datetime_t *timedate, uint8_t pos, - uint8_t number) -{ - vp_flush(); - vp_queueInteger(number); - if (pos == 2 || pos == 4) - vp_queuePrompt(PROMPT_SLASH); - // just indicates separation of date and time. - if (pos==6) // start of time. - vp_queueString("hh:mm", vpAnnounceCommonSymbols|vpAnnounceLessCommonSymbols); - if (pos == 8) - vp_queuePrompt(PROMPT_COLON); - vp_play(); - - switch(pos) - { - // Set date - case 1: - timedate->date += number * 10; - break; - case 2: - timedate->date += number; - break; - // Set month - case 3: - timedate->month += number * 10; - break; - case 4: - timedate->month += number; - break; - // Set year - case 5: - timedate->year += number * 10; - break; - case 6: - timedate->year += number; - break; - // Set hour - case 7: - timedate->hour += number * 10; - break; - case 8: - timedate->hour += number; - break; - // Set minute - case 9: - timedate->minute += number * 10; - break; - case 10: - timedate->minute += number; - break; - } -} -#endif - -static bool _ui_freq_check_limits(freq_t freq) -{ - bool valid = false; - const hwInfo_t* hwinfo = platform_getHwInfo(); - if(hwinfo->vhf_band) - { - // hwInfo_t frequencies are in MHz - if(freq >= (hwinfo->vhf_minFreq * 1000000) && - freq <= (hwinfo->vhf_maxFreq * 1000000)) - valid = true; - } - if(hwinfo->uhf_band) - { - // hwInfo_t frequencies are in MHz - if(freq >= (hwinfo->uhf_minFreq * 1000000) && - freq <= (hwinfo->uhf_maxFreq * 1000000)) - valid = true; - } - return valid; -} - -static bool _ui_channel_valid(channel_t* channel) -{ -return _ui_freq_check_limits(channel->rx_frequency) && - _ui_freq_check_limits(channel->tx_frequency); -} - -static bool _ui_drawDarkOverlay() -{ - color_t alpha_grey = {0, 0, 0, 255}; - point_t origin = {0, 0}; - gfx_drawRect(origin, CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_HEIGHT, alpha_grey, true); - return true; -} - -static int _ui_fsm_loadChannel(int16_t channel_index, bool *sync_rtx) -{ - channel_t channel; - int32_t selected_channel = channel_index; - // If a bank is active, get index from current bank - if(state.bank_enabled) - { - bankHdr_t bank = { 0 }; - cps_readBankHeader(&bank, state.bank); - if((channel_index < 0) || (channel_index >= bank.ch_count)) - return -1; - channel_index = cps_readBankData(state.bank, channel_index); - } - - int result = cps_readChannel(&channel, channel_index); - // Read successful and channel is valid - if((result != -1) && _ui_channel_valid(&channel)) - { - // Set new channel index - state.channel_index = selected_channel; - // Copy channel read to state - state.channel = channel; - *sync_rtx = true; - } - - return result; -} - -static void _ui_fsm_confirmVFOInput(bool *sync_rtx) -{ - vp_flush(); - // Switch to TX input - if(ui_state.input_set == SET_RX) - { - ui_state.input_set = SET_TX; - // Reset input position - ui_state.input_position = 0; - // announce the rx frequency just confirmed with Enter. - vp_queueFrequency(ui_state.new_rx_frequency); - // defer playing till the end. - // indicate that the user has moved to the tx freq field. - vp_announceInputReceiveOrTransmit(true, vpqDefault); - } - else if(ui_state.input_set == SET_TX) - { - // Save new frequency setting - // If TX frequency was not set, TX = RX - if(ui_state.new_tx_frequency == 0) - { - ui_state.new_tx_frequency = ui_state.new_rx_frequency; - } - // Apply new frequencies if they are valid - if(_ui_freq_check_limits(ui_state.new_rx_frequency) && - _ui_freq_check_limits(ui_state.new_tx_frequency)) - { - state.channel.rx_frequency = ui_state.new_rx_frequency; - state.channel.tx_frequency = ui_state.new_tx_frequency; - *sync_rtx = true; - // force init to clear any prompts in progress. - // defer play because play is called at the end of the function - //due to above freq queuing. - vp_announceFrequencies(state.channel.rx_frequency, - state.channel.tx_frequency, vpqInit); - } - else - { - vp_announceError(vpqInit); - } - - state.ui_screen = MAIN_VFO; - } - - vp_play(); -} - -static void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) -{ - // Advance input position - ui_state.input_position += 1; - // clear any prompts in progress. - vp_flush(); - // Save pressed number to calculate frequency and show in GUI - ui_state.input_number = input_getPressedNumber(msg); - // queue the digit just pressed. - vp_queueInteger(ui_state.input_number); - // queue point if user has entered three digits. - if (ui_state.input_position == 3) - vp_queuePrompt(PROMPT_POINT); - - if(ui_state.input_set == SET_RX) - { - if(ui_state.input_position == 1) - ui_state.new_rx_frequency = 0; - // Calculate portion of the new RX frequency - ui_state.new_rx_frequency = _ui_freq_add_digit(ui_state.new_rx_frequency, - ui_state.input_position, - ui_state.input_number); - if(ui_state.input_position >= FREQ_DIGITS) - {// queue the rx freq just completed. - vp_queueFrequency(ui_state.new_rx_frequency); - /// now queue tx as user has changed fields. - vp_queuePrompt(PROMPT_TRANSMIT); - // Switch to TX input - ui_state.input_set = SET_TX; - // Reset input position - ui_state.input_position = 0; - // Reset TX frequency - ui_state.new_tx_frequency = 0; - } - } - else if(ui_state.input_set == SET_TX) - { - if(ui_state.input_position == 1) - ui_state.new_tx_frequency = 0; - // Calculate portion of the new TX frequency - ui_state.new_tx_frequency = _ui_freq_add_digit(ui_state.new_tx_frequency, - ui_state.input_position, - ui_state.input_number); - if(ui_state.input_position >= FREQ_DIGITS) - { - // Save both inserted frequencies - if(_ui_freq_check_limits(ui_state.new_rx_frequency) && - _ui_freq_check_limits(ui_state.new_tx_frequency)) - { - state.channel.rx_frequency = ui_state.new_rx_frequency; - state.channel.tx_frequency = ui_state.new_tx_frequency; - *sync_rtx = true; - // play is called at end. - vp_announceFrequencies(state.channel.rx_frequency, - state.channel.tx_frequency, vpqInit); - } - - state.ui_screen = MAIN_VFO; - } - } - - vp_play(); -} - -#ifdef CONFIG_SCREEN_BRIGHTNESS -static void _ui_changeBrightness(int variation) -{ - state.settings.brightness += variation; - - // Max value for brightness is 100, min value is set to 5 to avoid complete - // display shutdown. - if(state.settings.brightness > 100) state.settings.brightness = 100; - if(state.settings.brightness < 5) state.settings.brightness = 5; - - display_setBacklightLevel(state.settings.brightness); -} -#endif - -#ifdef CONFIG_SCREEN_CONTRAST -static void _ui_changeContrast(int variation) -{ - if(variation >= 0) - state.settings.contrast = - (255 - state.settings.contrast < variation) ? 255 : state.settings.contrast + variation; - else - state.settings.contrast = - (state.settings.contrast < -variation) ? 0 : state.settings.contrast + variation; - - display_setContrast(state.settings.contrast); -} -#endif - -static void _ui_changeTimer(int variation) -{ - if ((state.settings.display_timer == TIMER_OFF && variation < 0) || - (state.settings.display_timer == TIMER_1H && variation > 0)) - { - return; - } - - state.settings.display_timer += variation; -} - -static void _ui_changeMacroLatch(bool newVal) -{ - state.settings.macroMenuLatch = newVal ? 1 : 0; - vp_announceSettingsOnOffToggle(¤tLanguage->macroLatching, - vp_getVoiceLevelQueueFlags(), - state.settings.macroMenuLatch); -} - -#ifdef CONFIG_M17 -static inline void _ui_changeM17Can(int variation) -{ - uint8_t can = state.settings.m17_can; - state.settings.m17_can = (can + variation) % 16; -} -#endif - -static void _ui_changeVoiceLevel(int variation) -{ - if ((state.settings.vpLevel == vpNone && variation < 0) || - (state.settings.vpLevel == vpHigh && variation > 0)) - { - return; - } - - state.settings.vpLevel += variation; - - // Force these flags to ensure the changes are spoken for levels 1 through 3. - vpQueueFlags_t flags = vpqInit - | vpqAddSeparatingSilence - | vpqPlayImmediately; - - if (!vp_isPlaying()) - { - flags |= vpqIncludeDescriptions; - } - - vp_announceSettingsVoiceLevel(flags); -} - -static void _ui_changePhoneticSpell(bool newVal) -{ - state.settings.vpPhoneticSpell = newVal ? 1 : 0; - - vp_announceSettingsOnOffToggle(¤tLanguage->phonetic, - vp_getVoiceLevelQueueFlags(), - state.settings.vpPhoneticSpell); -} - -bool _ui_checkStandby(long long time_since_last_event) -{ - if (standby) - { - return false; - } - - switch (state.settings.display_timer) - { - case TIMER_OFF: - return false; - case TIMER_5S: - case TIMER_10S: - case TIMER_15S: - case TIMER_20S: - case TIMER_25S: - case TIMER_30S: - return time_since_last_event >= (5000 * state.settings.display_timer); - case TIMER_1M: - case TIMER_2M: - case TIMER_3M: - case TIMER_4M: - case TIMER_5M: - return time_since_last_event >= - (60000 * (state.settings.display_timer - (TIMER_1M - 1))); - case TIMER_15M: - case TIMER_30M: - case TIMER_45M: - return time_since_last_event >= - (60000 * 15 * (state.settings.display_timer - (TIMER_15M - 1))); - case TIMER_1H: - return time_since_last_event >= 60 * 60 * 1000; - } - - // unreachable code - return false; -} - -static void _ui_enterStandby() -{ - if(standby) - return; - - standby = true; - redraw_needed = false; - display_setBacklightLevel(0); -} - -static bool _ui_exitStandby(long long now) -{ - last_event_tick = now; - - if(!standby) - return false; - - standby = false; - redraw_needed = true; - display_setBacklightLevel(state.settings.brightness); - - return true; -} - -// TODO: find a better home for this function -int _ui_handleToneSelectScroll(bool direction_up) -{ - bool tone_tx_enable = state.channel.fm.txToneEn; - bool tone_rx_enable = state.channel.fm.rxToneEn; - uint8_t tone_flags = tone_tx_enable << 1 | tone_rx_enable; - - if(direction_up) - tone_flags++; - else - tone_flags--; - - tone_flags %= 4; - tone_tx_enable = tone_flags >> 1; - tone_rx_enable = tone_flags & 1; - state.channel.fm.txToneEn = tone_tx_enable; - state.channel.fm.rxToneEn = tone_rx_enable; - - return 1; -} - -static void _ui_fsm_menuMacro(kbd_msg_t msg, bool *sync_rtx) -{ - // If there is no keyboard left and right select the menu entry to edit -#if defined(CONFIG_UI_NO_KEYBOARD) - if (msg.keys & KNOB_LEFT) - { - ui_state.macro_menu_selected--; - ui_state.macro_menu_selected += 9; - ui_state.macro_menu_selected %= 9; - } - if (msg.keys & KNOB_RIGHT) - { - ui_state.macro_menu_selected++; - ui_state.macro_menu_selected %= 9; - } - if ((msg.keys & KEY_ENTER) && !msg.long_press) - ui_state.input_number = ui_state.macro_menu_selected + 1; - else - ui_state.input_number = 0; -#else // CONFIG_UI_NO_KEYBOARD - ui_state.input_number = input_getPressedNumber(msg); -#endif // CONFIG_UI_NO_KEYBOARD - // CTCSS Encode/Decode Selection - vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); - - switch(ui_state.input_number) - { - case 1: - if(state.channel.mode == OPMODE_FM) - { - _ui_handleToneSelectScroll(true); - *sync_rtx = true; - vp_announceCTCSS( - state.channel.fm.rxToneEn, state.channel.fm.rxTone, - state.channel.fm.txToneEn, state.channel.fm.txTone, - queueFlags | vpqIncludeDescriptions); - } - break; - case 2: - if (state.channel.mode == OPMODE_FM) - { - if (state.channel.fm.txTone == 0) - { - state.channel.fm.txTone = CTCSS_FREQ_NUM-1; - } - else - { - state.channel.fm.txTone--; - } - - state.channel.fm.txTone %= CTCSS_FREQ_NUM; - state.channel.fm.rxTone = state.channel.fm.txTone; - *sync_rtx = true; - vp_announceCTCSS(state.channel.fm.rxToneEn, - state.channel.fm.rxTone, - state.channel.fm.txToneEn, - state.channel.fm.txTone, - queueFlags); - } - break; - - case 3: - if(state.channel.mode == OPMODE_FM) - { - state.channel.fm.txTone++; - state.channel.fm.txTone %= CTCSS_FREQ_NUM; - state.channel.fm.rxTone = state.channel.fm.txTone; - *sync_rtx = true; - vp_announceCTCSS(state.channel.fm.rxToneEn, - state.channel.fm.rxTone, - state.channel.fm.txToneEn, - state.channel.fm.txTone, - queueFlags |vpqIncludeDescriptions); - } - break; - case 4: - if(state.channel.mode == OPMODE_FM) - { - state.channel.bandwidth++; - state.channel.bandwidth %= 2; - *sync_rtx = true; - vp_announceBandwidth(state.channel.bandwidth, queueFlags); - } - break; - case 5: - // Cycle through radio modes - #ifdef CONFIG_M17 - if(state.channel.mode == OPMODE_FM) - state.channel.mode = OPMODE_M17; - else if(state.channel.mode == OPMODE_M17) - state.channel.mode = OPMODE_FM; - else //catch any invalid states so they don't get locked out - #endif - state.channel.mode = OPMODE_FM; - *sync_rtx = true; - vp_announceRadioMode(state.channel.mode, queueFlags); - break; - case 6: - - switch(state.channel.power) - { - case 1000: - state.channel.power = 2500; - break; - - case 2500: - state.channel.power = 5000; - break; - - default: - state.channel.power = 1000; - } - - *sync_rtx = true; - vp_announcePower(state.channel.power, queueFlags); - break; -#ifdef CONFIG_SCREEN_BRIGHTNESS - case 7: - _ui_changeBrightness(-5); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.brightness); - break; - case 8: - _ui_changeBrightness(+5); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.brightness); - break; -#endif - case 9: - if (!ui_state.input_locked) - ui_state.input_locked = true; - else - ui_state.input_locked = false; - break; - } - -#if defined(PLATFORM_TTWRPLUS) - if(msg.keys & KEY_VOLDOWN) -#else - if(msg.keys & KEY_LEFT || msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) -#endif // PLATFORM_TTWRPLUS - { -#ifdef CONFIG_KNOB_ABSOLUTE // If the radio has an absolute position knob - state.settings.sqlLevel = platform_getChSelector() - 1; -#endif // CONFIG_KNOB_ABSOLUTE - if(state.settings.sqlLevel > 0) - { - state.settings.sqlLevel -= 1; - *sync_rtx = true; - vp_announceSquelch(state.settings.sqlLevel, queueFlags); - } - } - -#if defined(PLATFORM_TTWRPLUS) - else if(msg.keys & KEY_VOLUP) -#else - else if(msg.keys & KEY_RIGHT || msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) -#endif // PLATFORM_TTWRPLUS - { -#ifdef CONFIG_KNOB_ABSOLUTE - state.settings.sqlLevel = platform_getChSelector() - 1; -#endif - if(state.settings.sqlLevel < 15) - { - state.settings.sqlLevel += 1; - *sync_rtx = true; - vp_announceSquelch(state.settings.sqlLevel, queueFlags); - } - } -} - -static void _ui_menuUp(uint8_t menu_entries) -{ - if(ui_state.menu_selected > 0) - ui_state.menu_selected -= 1; - else - ui_state.menu_selected = menu_entries - 1; - vp_playMenuBeepIfNeeded(ui_state.menu_selected==0); -} - -static void _ui_menuDown(uint8_t menu_entries) -{ - if(ui_state.menu_selected < menu_entries - 1) - ui_state.menu_selected += 1; - else - ui_state.menu_selected = 0; - vp_playMenuBeepIfNeeded(ui_state.menu_selected==0); -} - -static void _ui_menuBack(uint8_t prev_state) -{ - if(ui_state.edit_mode) - { - ui_state.edit_mode = false; - } - else - { - // Return to previous menu - state.ui_screen = prev_state; - // Reset menu selection - ui_state.menu_selected = 0; - vp_playMenuBeepIfNeeded(true); - } -} - -static void _ui_textInputReset(char *buf) -{ - ui_state.input_number = 0; - ui_state.input_position = 0; - ui_state.input_set = 0; - ui_state.last_keypress = 0; - memset(buf, 0, 9); - buf[0] = '_'; -} - -static void _ui_textInputKeypad(char *buf, uint8_t max_len, kbd_msg_t msg, - bool callsign) -{ - long long now = getTick(); - // Get currently pressed number key - uint8_t num_key = input_getPressedChar(msg); - - bool key_timeout = ((now - ui_state.last_keypress) >= input_longPressTimeout); - bool same_key = ui_state.input_number == num_key; - // Get number of symbols related to currently pressed key - uint8_t num_symbols = 0; - if(callsign) - { - num_symbols = strlen(symbols_ITU_T_E161_callsign[num_key]); - if(num_symbols == 0) - return; - } - else - num_symbols = strlen(symbols_ITU_T_E161[num_key]); - - // Return if max length is reached or finished editing last character - if((ui_state.input_position >= max_len) || ((ui_state.input_position == (max_len-1)) && (key_timeout || !same_key))) - return; - - // Skip keypad logic for first keypress - if(ui_state.last_keypress != 0) - { - // Same key pressed and timeout not expired: cycle over chars of current key - if(same_key && !key_timeout) - { - ui_state.input_set = (ui_state.input_set + 1) % num_symbols; - } - // Different key pressed: save current char and change key - else - { - ui_state.input_position += 1; - ui_state.input_set = 0; - } - } - // Show current character on buffer - if(callsign) - buf[ui_state.input_position] = symbols_ITU_T_E161_callsign[num_key][ui_state.input_set]; - else - { - buf[ui_state.input_position] = symbols_ITU_T_E161[num_key][ui_state.input_set]; - } - // Announce the character - vp_announceInputChar(buf[ui_state.input_position]); - // Update reference values - ui_state.input_number = num_key; - ui_state.last_keypress = now; -} - -static void _ui_textInputConfirm(char *buf) -{ - buf[ui_state.input_position + 1] = '\0'; -} - -static void _ui_textInputDel(char *buf) -{ - // announce the char about to be backspaced. - // Note this assumes editing callsign. - // If we edit a different buffer which allows the underline char, we may - // not want to exclude it, but when editing callsign, we do not want to say - // underline since it means the field is empty. - if(buf[ui_state.input_position] - && buf[ui_state.input_position]!='_') - vp_announceInputChar(buf[ui_state.input_position]); - - buf[ui_state.input_position] = '\0'; - // Move back input cursor - if(ui_state.input_position > 0) - { - ui_state.input_position--; - // If we deleted the initial character, reset starting condition - } - else - ui_state.last_keypress = 0; - ui_state.input_set = 0; -} - -static void _ui_numberInputKeypad(uint32_t *num, kbd_msg_t msg) -{ - long long now = getTick(); - -#ifdef CONFIG_UI_NO_KEYBOARD - // If knob is turned, increment or Decrement - if (msg.keys & KNOB_LEFT) - { - *num = *num + 1; - if (*num % 10 == 0) - *num = *num - 10; - } - - if (msg.keys & KNOB_RIGHT) - { - if (*num == 0) - *num = 9; - else - { - *num = *num - 1; - if (*num % 10 == 9) - *num = *num + 10; - } - } - - // If enter is pressed, advance to the next digit - if (msg.keys & KEY_ENTER) - *num *= 10; - - // Announce the character - vp_announceInputChar('0' + *num % 10); - - // Update reference values - ui_state.input_number = *num % 10; -#else - // Maximum frequency len is uint32_t max value number of decimal digits - if(ui_state.input_position >= 10) - return; - - // Get currently pressed number key - uint8_t num_key = input_getPressedNumber(msg); - *num *= 10; - *num += num_key; - - // Announce the character - vp_announceInputChar('0' + num_key); - - // Update reference values - ui_state.input_number = num_key; -#endif - - ui_state.last_keypress = now; -} - -static void _ui_numberInputDel(uint32_t *num) -{ - // announce the digit about to be backspaced. - vp_announceInputChar('0' + *num % 10); - - // Move back input cursor - if(ui_state.input_position > 0) - ui_state.input_position--; - else - ui_state.last_keypress = 0; - - ui_state.input_set = 0; -} - -void ui_init() -{ - last_event_tick = getTick(); - redraw_needed = true; - _ui_calculateLayout(&layout); - layout_ready = true; - // Initialize struct ui_state to all zeroes - // This syntax is called compound literal - // https://stackoverflow.com/questions/6891720/initialize-reset-struct-to-zero-null - ui_state = (const struct ui_state_t){ 0 }; -} - -void ui_drawSplashScreen() -{ - gfx_clearScreen(); - - #if CONFIG_SCREEN_HEIGHT > 64 - static const point_t logo_orig = {0, (CONFIG_SCREEN_HEIGHT / 2) - 6}; - static const point_t call_orig = {0, CONFIG_SCREEN_HEIGHT - 8}; - static const fontSize_t logo_font = FONT_SIZE_12PT; - static const fontSize_t call_font = FONT_SIZE_8PT; - #else - static const point_t logo_orig = {0, 19}; - static const point_t call_orig = {0, CONFIG_SCREEN_HEIGHT - 8}; - static const fontSize_t logo_font = FONT_SIZE_8PT; - static const fontSize_t call_font = FONT_SIZE_6PT; - #endif - - gfx_print(logo_orig, logo_font, TEXT_ALIGN_CENTER, yellow_fab413, "O P N\nR T X"); - gfx_print(call_orig, call_font, TEXT_ALIGN_CENTER, color_white, state.settings.callsign); - - vp_announceSplashScreen(); -} - -void ui_saveState() -{ - last_state = state; -} - -#ifdef CONFIG_GPS -static uint16_t priorGPSSpeed = 0; -static int16_t priorGPSAltitude = 0; -static int16_t priorGPSDirection = 500; // impossible value init. -static uint8_t priorGPSFixQuality= 0; -static uint8_t priorGPSFixType = 0; -static uint8_t priorSatellitesInView = 0; -static uint32_t vpGPSLastUpdate = 0; - -static vpGPSInfoFlags_t GetGPSDirectionOrSpeedChanged() -{ - if (!state.settings.gps_enabled) - return vpGPSNone; - - uint32_t now = getTick(); - if (now - vpGPSLastUpdate < 8000) - return vpGPSNone; - - vpGPSInfoFlags_t whatChanged= vpGPSNone; - - if (state.gps_data.fix_quality != priorGPSFixQuality) - { - whatChanged |= vpGPSFixQuality; - priorGPSFixQuality= state.gps_data.fix_quality; - } - - if (state.gps_data.fix_type != priorGPSFixType) - { - whatChanged |= vpGPSFixType; - priorGPSFixType = state.gps_data.fix_type; - } - - if (state.gps_data.speed != priorGPSSpeed) - { - whatChanged |= vpGPSSpeed; - priorGPSSpeed = state.gps_data.speed; - } - - if (state.gps_data.altitude != priorGPSAltitude) - { - whatChanged |= vpGPSAltitude; - priorGPSAltitude = state.gps_data.altitude; - } - - if (state.gps_data.tmg_true != priorGPSDirection) - { - whatChanged |= vpGPSDirection; - priorGPSDirection = state.gps_data.tmg_true; - } - - if (state.gps_data.satellites_in_view != priorSatellitesInView) - { - whatChanged |= vpGPSSatCount; - priorSatellitesInView = state.gps_data.satellites_in_view; - } - - if (whatChanged) - vpGPSLastUpdate=now; - - return whatChanged; -} -#endif // CONFIG_GPS - -void ui_updateFSM(bool *sync_rtx) -{ - event_t event; - if(!ui_popEvent(&event)) return; - - // There is some event to process, we need an UI redraw. - // UI redraw request is cancelled if we're in standby mode. - redraw_needed = true; - if(standby) redraw_needed = false; - - // Check if battery has enough charge to operate. - // Check is skipped if there is an ongoing transmission, since the voltage - // drop caused by the RF PA power absorption causes spurious triggers of - // the low battery alert. - bool txOngoing = platform_getPttStatus(); -#if !defined(PLATFORM_TTWRPLUS) - if ((!state.emergency) && (!txOngoing) && (state.charge <= 0)) - { - state.ui_screen = LOW_BAT; - if(event.type == EVENT_KBD && event.payload) - { - state.ui_screen = MAIN_VFO; - state.emergency = true; - } - return; - } -#endif // PLATFORM_TTWRPLUS - - // Unlatch and exit from macro menu on PTT press - if(macro_latched && txOngoing) - { - macro_latched = false; - macro_menu = false; - } - - long long now = getTick(); - // Process pressed keys - if(event.type == EVENT_KBD) - { - kbd_msg_t msg; - msg.value = event.payload; - bool f1Handled = false; - vpQueueFlags_t queueFlags = vp_getVoiceLevelQueueFlags(); - // If we get out of standby, we ignore the kdb event - // unless is the MONI key for the MACRO functions - if (_ui_exitStandby(now) && !(msg.keys & KEY_MONI)) - return; - // If MONI is pressed, activate MACRO functions - bool moniPressed = msg.keys & KEY_MONI; - if(moniPressed || macro_latched) - { - macro_menu = true; - - if(state.settings.macroMenuLatch == 1) - { - // long press moni on its own latches function. - if (moniPressed && msg.long_press && !macro_latched) - { - macro_latched = true; - vp_beep(BEEP_FUNCTION_LATCH_ON, LONG_BEEP); - } - else if (moniPressed && macro_latched) - { - macro_latched = false; - vp_beep(BEEP_FUNCTION_LATCH_OFF, LONG_BEEP); - } - } - - _ui_fsm_menuMacro(msg, sync_rtx); - return; - } - else - { - macro_menu = false; - } -#if defined(PLATFORM_TTWRPLUS) - // T-TWR Plus has no KEY_MONI, using KEY_VOLDOWN long press instead - if ((msg.keys & KEY_VOLDOWN) && msg.long_press) - { - macro_menu = true; - macro_latched = true; - } -#endif // PLA%FORM_TTWRPLUS - - if(state.tone_enabled && !(msg.keys & KEY_HASH)) - { - state.tone_enabled = false; - *sync_rtx = true; - } - - int priorUIScreen = state.ui_screen; - switch(state.ui_screen) - { - // VFO screen - case MAIN_VFO: - { - // Enable Tx in MAIN_VFO mode - if (state.txDisable) - { - state.txDisable = false; - *sync_rtx = true; - } - - // Break out of the FSM if the keypad is locked but allow the - // use of the hash key in FM mode for the 1750Hz tone. - bool skipLock = (state.channel.mode == OPMODE_FM) - && (msg.keys == KEY_HASH); - - if ((ui_state.input_locked == true) && (skipLock == false)) - break; - - if(ui_state.edit_mode) - { - #ifdef CONFIG_M17 - if(state.channel.mode == OPMODE_M17) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected dst ID and disable input mode - strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); - ui_state.edit_mode = false; - *sync_rtx = true; - vp_announceM17Info(NULL, ui_state.edit_mode, - queueFlags); - } - else if(msg.keys & KEY_HASH) - { - // Save selected dst ID and disable input mode - strncpy(state.settings.m17_dest, "", 1); - ui_state.edit_mode = false; - *sync_rtx = true; - vp_announceM17Info(NULL, ui_state.edit_mode, - queueFlags); - } - else if(msg.keys & KEY_ESC) - // Discard selected dst ID and disable input mode - ui_state.edit_mode = false; - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) - _ui_textInputDel(ui_state.new_callsign); - else if(input_isCharPressed(msg)) - _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); - break; - } - #endif - } - else - { - if(msg.keys & KEY_ENTER) - { - // Save current main state - ui_state.last_main_state = state.ui_screen; - // Open Menu - state.ui_screen = MENU_TOP; - // The selected item will be announced when the item is first selected. - } - else if(msg.keys & KEY_ESC) - { - // Save VFO channel - state.vfo_channel = state.channel; - int result = _ui_fsm_loadChannel(state.channel_index, sync_rtx); - // Read successful and channel is valid - if(result != -1) - { - // Switch to MEM screen - state.ui_screen = MAIN_MEM; - // anounce the active channel name. - vp_announceChannelName(&state.channel, - state.channel_index, - queueFlags); - } - } - else if(msg.keys & KEY_HASH) - { - #ifdef CONFIG_M17 - // Only enter edit mode when using M17 - if(state.channel.mode == OPMODE_M17) - { - // Enable dst ID input - ui_state.edit_mode = true; - // Reset text input variables - _ui_textInputReset(ui_state.new_callsign); - vp_announceM17Info(NULL, ui_state.edit_mode, - queueFlags); - } - else - #endif - { - if(!state.tone_enabled) - { - state.tone_enabled = true; - *sync_rtx = true; - } - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) - { - // Increment TX and RX frequency of 12.5KHz - if(_ui_freq_check_limits(state.channel.rx_frequency + freq_steps[state.step_index]) && - _ui_freq_check_limits(state.channel.tx_frequency + freq_steps[state.step_index])) - { - state.channel.rx_frequency += freq_steps[state.step_index]; - state.channel.tx_frequency += freq_steps[state.step_index]; - *sync_rtx = true; - vp_announceFrequencies(state.channel.rx_frequency, - state.channel.tx_frequency, - queueFlags); - } - } - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) - { - // Decrement TX and RX frequency of 12.5KHz - if(_ui_freq_check_limits(state.channel.rx_frequency - freq_steps[state.step_index]) && - _ui_freq_check_limits(state.channel.tx_frequency - freq_steps[state.step_index])) - { - state.channel.rx_frequency -= freq_steps[state.step_index]; - state.channel.tx_frequency -= freq_steps[state.step_index]; - *sync_rtx = true; - vp_announceFrequencies(state.channel.rx_frequency, - state.channel.tx_frequency, - queueFlags); - } - } - else if(msg.keys & KEY_F1) - { - if (state.settings.vpLevel > vpBeep) - {// quick press repeat vp, long press summary. - if (msg.long_press) - vp_announceChannelSummary(&state.channel, 0, - state.bank, vpAllInfo); - else - vp_replayLastPrompt(); - f1Handled = true; - } - } - else if(input_isNumberPressed(msg)) - { - // Open Frequency input screen - state.ui_screen = MAIN_VFO_INPUT; - // Reset input position and selection - ui_state.input_position = 1; - ui_state.input_set = SET_RX; - // do not play because we will also announce the number just entered. - vp_announceInputReceiveOrTransmit(false, vpqInit); - vp_queueInteger(input_getPressedNumber(msg)); - vp_play(); - - ui_state.new_rx_frequency = 0; - ui_state.new_tx_frequency = 0; - // Save pressed number to calculare frequency and show in GUI - ui_state.input_number = input_getPressedNumber(msg); - // Calculate portion of the new frequency - ui_state.new_rx_frequency = _ui_freq_add_digit(ui_state.new_rx_frequency, - ui_state.input_position, - ui_state.input_number); - } - } - } - break; - // VFO frequency input screen - case MAIN_VFO_INPUT: - if(msg.keys & KEY_ENTER) - { - _ui_fsm_confirmVFOInput(sync_rtx); - } - else if(msg.keys & KEY_ESC) - { - // Cancel frequency input, return to VFO mode - state.ui_screen = MAIN_VFO; - } - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN) - { - if(ui_state.input_set == SET_RX) - { - ui_state.input_set = SET_TX; - vp_announceInputReceiveOrTransmit(true, queueFlags); - } - else if(ui_state.input_set == SET_TX) - { - ui_state.input_set = SET_RX; - vp_announceInputReceiveOrTransmit(false, queueFlags); - } - // Reset input position - ui_state.input_position = 0; - } - else if(input_isNumberPressed(msg)) - { - _ui_fsm_insertVFONumber(msg, sync_rtx); - } - break; - // MEM screen - case MAIN_MEM: - // Enable Tx in MAIN_MEM mode - if (state.txDisable) - { - state.txDisable = false; - *sync_rtx = true; - } - if (ui_state.input_locked) - break; - // M17 Destination callsign input - if(ui_state.edit_mode) - { - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected dst ID and disable input mode - strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); - ui_state.edit_mode = false; - *sync_rtx = true; - } - else if(msg.keys & KEY_HASH) - { - // Save selected dst ID and disable input mode - strncpy(state.settings.m17_dest, "", 1); - ui_state.edit_mode = false; - *sync_rtx = true; - } - else if(msg.keys & KEY_ESC) - // Discard selected dst ID and disable input mode - ui_state.edit_mode = false; - else if(msg.keys & KEY_F1) - { - if (state.settings.vpLevel > vpBeep) - { - // Quick press repeat vp, long press summary. - if (msg.long_press) - { - vp_announceChannelSummary( - &state.channel, - state.channel_index, - state.bank, - vpAllInfo); - } - else - { - vp_replayLastPrompt(); - } - - f1Handled = true; - } - } - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) - _ui_textInputDel(ui_state.new_callsign); - else if(input_isCharPressed(msg)) - _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); - break; - } - } - else - { - if(msg.keys & KEY_ENTER) - { - // Save current main state - ui_state.last_main_state = state.ui_screen; - // Open Menu - state.ui_screen = MENU_TOP; - } - else if(msg.keys & KEY_ESC) - { - // Restore VFO channel - state.channel = state.vfo_channel; - // Update RTX configuration - *sync_rtx = true; - // Switch to VFO screen - state.ui_screen = MAIN_VFO; - } - else if(msg.keys & KEY_HASH) - { - // Only enter edit mode when using M17 - if(state.channel.mode == OPMODE_M17) - { - // Enable dst ID input - ui_state.edit_mode = true; - // Reset text input variables - _ui_textInputReset(ui_state.new_callsign); - } - else - { - if(!state.tone_enabled) - { - state.tone_enabled = true; - *sync_rtx = true; - } - } - } - else if(msg.keys & KEY_F1) - { - if (state.settings.vpLevel > vpBeep) - {// quick press repeat vp, long press summary. - if (msg.long_press) - { - vp_announceChannelSummary(&state.channel, - state.channel_index+1, - state.bank, vpAllInfo); - } - else - { - vp_replayLastPrompt(); - } - - f1Handled = true; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) - { - _ui_fsm_loadChannel(state.channel_index + 1, sync_rtx); - vp_announceChannelName(&state.channel, - state.channel_index+1, - queueFlags); - } - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) - { - _ui_fsm_loadChannel(state.channel_index - 1, sync_rtx); - vp_announceChannelName(&state.channel, - state.channel_index+1, - queueFlags); - } - } - break; - // Top menu screen - case MENU_TOP: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(menu_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(menu_num); - else if(msg.keys & KEY_ENTER) - { - switch(ui_state.menu_selected) - { - case M_BANK: - state.ui_screen = MENU_BANK; - break; - case M_CHANNEL: - state.ui_screen = MENU_CHANNEL; - break; - case M_CONTACTS: - state.ui_screen = MENU_CONTACTS; - break; -#ifdef CONFIG_GPS - case M_GPS: - state.ui_screen = MENU_GPS; - break; -#endif - case M_SETTINGS: - state.ui_screen = MENU_SETTINGS; - break; - case M_INFO: - state.ui_screen = MENU_INFO; - break; - case M_ABOUT: - state.ui_screen = MENU_ABOUT; - break; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(ui_state.last_main_state); - break; - // Zone menu screen - case MENU_BANK: - // Channel menu screen - case MENU_CHANNEL: - // Contacts menu screen - case MENU_CONTACTS: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - // Using 1 as parameter disables menu wrap around - _ui_menuUp(1); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - { - if(state.ui_screen == MENU_BANK) - { - bankHdr_t bank; - // manu_selected is 0-based - // bank 0 means "All Channel" mode - // banks (1, n) are mapped to banks (0, n-1) - if(cps_readBankHeader(&bank, ui_state.menu_selected) != -1) - ui_state.menu_selected += 1; - } - else if(state.ui_screen == MENU_CHANNEL) - { - channel_t channel; - if(cps_readChannel(&channel, ui_state.menu_selected + 1) != -1) - ui_state.menu_selected += 1; - } - else if(state.ui_screen == MENU_CONTACTS) - { - contact_t contact; - if(cps_readContact(&contact, ui_state.menu_selected + 1) != -1) - ui_state.menu_selected += 1; - } - } - else if(msg.keys & KEY_ENTER) - { - if(state.ui_screen == MENU_BANK) - { - bankHdr_t newbank; - int result = 0; - // If "All channels" is selected, load default bank - if(ui_state.menu_selected == 0) - state.bank_enabled = false; - else - { - state.bank_enabled = true; - result = cps_readBankHeader(&newbank, ui_state.menu_selected - 1); - } - if(result != -1) - { - state.bank = ui_state.menu_selected - 1; - // If we were in VFO mode, save VFO channel - if(ui_state.last_main_state == MAIN_VFO) - state.vfo_channel = state.channel; - // Load bank first channel - _ui_fsm_loadChannel(0, sync_rtx); - // Switch to MEM screen - state.ui_screen = MAIN_MEM; - } - } - if(state.ui_screen == MENU_CHANNEL) - { - // If we were in VFO mode, save VFO channel - if(ui_state.last_main_state == MAIN_VFO) - state.vfo_channel = state.channel; - _ui_fsm_loadChannel(ui_state.menu_selected, sync_rtx); - // Switch to MEM screen - state.ui_screen = MAIN_MEM; - } - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; -#ifdef CONFIG_GPS - // GPS menu screen - case MENU_GPS: - if ((msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) - {// quick press repeat vp, long press summary. - if (msg.long_press) - vp_announceGPSInfo(vpGPSAll); - else - vp_replayLastPrompt(); - f1Handled = true; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; -#endif - // Settings menu screen - case MENU_SETTINGS: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_num); - else if(msg.keys & KEY_ENTER) - { - - switch(ui_state.menu_selected) - { - case S_DISPLAY: - state.ui_screen = SETTINGS_DISPLAY; - break; -#ifdef CONFIG_RTC - case S_TIMEDATE: - state.ui_screen = SETTINGS_TIMEDATE; - break; -#endif -#ifdef CONFIG_GPS - case S_GPS: - state.ui_screen = SETTINGS_GPS; - break; -#endif - case S_RADIO: - state.ui_screen = SETTINGS_RADIO; - break; -#ifdef CONFIG_M17 - case S_M17: - state.ui_screen = SETTINGS_M17; - break; -#endif - case S_FM: - state.ui_screen = SETTINGS_FM; - break; - case S_ACCESSIBILITY: - state.ui_screen = SETTINGS_ACCESSIBILITY; - break; - case S_RESET2DEFAULTS: - state.ui_screen = SETTINGS_RESET2DEFAULTS; - break; - default: - state.ui_screen = MENU_SETTINGS; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // Flash backup and restore menu screen - case MENU_BACKUP_RESTORE: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_num); - else if(msg.keys & KEY_ENTER) - { - - switch(ui_state.menu_selected) - { - case BR_BACKUP: - state.ui_screen = MENU_BACKUP; - break; - case BR_RESTORE: - state.ui_screen = MENU_RESTORE; - break; - default: - state.ui_screen = MENU_BACKUP_RESTORE; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - case MENU_BACKUP: - case MENU_RESTORE: - if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // Info menu screen - case MENU_INFO: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(info_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(info_num); - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // About screen, scroll without rollover - case MENU_ABOUT: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - { - if(ui_state.menu_selected > 0) - ui_state.menu_selected -= 1; - } - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - ui_state.menu_selected += 1; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; -#ifdef CONFIG_RTC - // Time&Date settings screen - case SETTINGS_TIMEDATE: - if(msg.keys & KEY_ENTER) - { - // Switch to set Time&Date mode - state.ui_screen = SETTINGS_TIMEDATE_SET; - // Reset input position and selection - ui_state.input_position = 0; - memset(&ui_state.new_timedate, 0, sizeof(datetime_t)); - vp_announceBuffer(¤tLanguage->timeAndDate, - true, false, "dd/mm/yy"); - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; - // Time&Date settings screen, edit mode - case SETTINGS_TIMEDATE_SET: - if(msg.keys & KEY_ENTER) - { - // Save time only if all digits have been inserted - if(ui_state.input_position < TIMEDATE_DIGITS) - break; - // Return to Time&Date menu, saving values - // NOTE: The user inserted a local time, we must save an UTC time - datetime_t utc_time = localTimeToUtc(ui_state.new_timedate, - state.settings.utc_timezone); - platform_setTime(utc_time); - state.time = utc_time; - vp_announceSettingsTimeDate(); - state.ui_screen = SETTINGS_TIMEDATE; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(SETTINGS_TIMEDATE); - else if(input_isNumberPressed(msg)) - { - // Discard excess digits - if(ui_state.input_position > TIMEDATE_DIGITS) - break; - ui_state.input_position += 1; - ui_state.input_number = input_getPressedNumber(msg); - _ui_timedate_add_digit(&ui_state.new_timedate, ui_state.input_position, - ui_state.input_number); - } - break; -#endif - case SETTINGS_DISPLAY: - if(msg.keys & KEY_LEFT || (ui_state.edit_mode && - (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT))) - { - switch(ui_state.menu_selected) - { -#ifdef CONFIG_SCREEN_BRIGHTNESS - case D_BRIGHTNESS: - _ui_changeBrightness(-5); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.brightness); - break; -#endif -#ifdef CONFIG_SCREEN_CONTRAST - case D_CONTRAST: - _ui_changeContrast(-4); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.contrast); - break; -#endif - case D_TIMER: - _ui_changeTimer(-1); - vp_announceDisplayTimer(); - break; - case D_BATTERY: - state.settings.showBatteryIcon = !state.settings.showBatteryIcon; - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_RIGHT || (ui_state.edit_mode && - (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) - { - switch(ui_state.menu_selected) - { -#ifdef CONFIG_SCREEN_BRIGHTNESS - case D_BRIGHTNESS: - _ui_changeBrightness(+5); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.brightness); - break; -#endif -#ifdef CONFIG_SCREEN_CONTRAST - case D_CONTRAST: - _ui_changeContrast(+4); - vp_announceSettingsInt(¤tLanguage->brightness, queueFlags, - state.settings.contrast); - break; -#endif - case D_TIMER: - _ui_changeTimer(+1); - vp_announceDisplayTimer(); - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(display_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(display_num); - else if(msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; -#ifdef CONFIG_GPS - case SETTINGS_GPS: - if(msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || - (ui_state.edit_mode && - (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT || - msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) - { - switch(ui_state.menu_selected) - { - case G_ENABLED: - if(state.settings.gps_enabled) - state.settings.gps_enabled = 0; - else - state.settings.gps_enabled = 1; - vp_announceSettingsOnOffToggle(¤tLanguage->gpsEnabled, - queueFlags, - state.settings.gps_enabled); - break; -#ifdef CONFIG_RTC - case G_SET_TIME: - state.settings.gpsSetTime = !state.settings.gpsSetTime; - vp_announceSettingsOnOffToggle(¤tLanguage->gpsSetTime, - queueFlags, - state.settings.gpsSetTime); - break; - case G_TIMEZONE: - if(msg.keys & KEY_LEFT || msg.keys & KEY_DOWN || - msg.keys & KNOB_LEFT) - state.settings.utc_timezone -= 1; - else if(msg.keys & KEY_RIGHT || msg.keys & KEY_UP || - msg.keys & KNOB_RIGHT) - state.settings.utc_timezone += 1; - vp_announceTimeZone(state.settings.utc_timezone, queueFlags); - break; -#endif - default: - state.ui_screen = SETTINGS_GPS; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_gps_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_gps_num); - else if(msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; -#endif - // Radio Settings - case SETTINGS_RADIO: - // If the entry is selected with enter we are in edit_mode - if (ui_state.edit_mode) - { - switch(ui_state.menu_selected) - { - case R_OFFSET: - // Handle offset frequency input -#if defined(CONFIG_UI_NO_KEYBOARD) - if(msg.long_press && msg.keys & KEY_ENTER) - { - // Long press on CONFIG_UI_NO_KEYBOARD causes digits to advance by one - ui_state.new_offset /= 10; -#else - if(msg.keys & KEY_ENTER) - { -#endif - // Apply new offset if it is within the hardware - // limits of the radio - freq_t new_freq = state.channel.rx_frequency + ui_state.new_offset; - if (_ui_freq_check_limits(new_freq)) - { - state.channel.tx_frequency = new_freq; - vp_queueStringTableEntry(¤tLanguage->offset); - vp_queueFrequency(ui_state.new_offset); - ui_state.edit_mode = false; - } - } - else - if(msg.keys & KEY_ESC) - { - // Announce old frequency offset - vp_queueStringTableEntry(¤tLanguage->offset); - vp_queueFrequency((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); - } - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) - { - _ui_numberInputDel(&ui_state.new_offset); - } -#if defined(CONFIG_UI_NO_KEYBOARD) - else if(msg.keys & KNOB_LEFT || msg.keys & KNOB_RIGHT || msg.keys & KEY_ENTER) -#else - else if(input_isNumberPressed(msg)) -#endif - { - _ui_numberInputKeypad(&ui_state.new_offset, msg); - ui_state.input_position += 1; - } - else if (msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) - { - vp_queueFrequency(ui_state.new_offset); - f1Handled=true; - } - break; - case R_DIRECTION: - if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || - msg.keys & KNOB_LEFT || msg.keys & KNOB_RIGHT) - { - // Invert frequency offset direction - if (state.channel.tx_frequency >= state.channel.rx_frequency) - state.channel.tx_frequency -= 2 * ((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); - else // Switch to positive offset - state.channel.tx_frequency -= 2 * ((int32_t)state.channel.tx_frequency - (int32_t)state.channel.rx_frequency); - } - break; - case R_STEP: - if (msg.keys & KEY_UP || msg.keys & KEY_RIGHT || msg.keys & KNOB_RIGHT) - { - // Cycle over the available frequency steps - state.step_index++; - state.step_index %= n_freq_steps; - } - else if(msg.keys & KEY_DOWN || msg.keys & KEY_LEFT || msg.keys & KNOB_LEFT) - { - state.step_index += n_freq_steps; - state.step_index--; - state.step_index %= n_freq_steps; - } - break; - default: - state.ui_screen = SETTINGS_RADIO; - } - // If ENTER or ESC are pressed, exit edit mode, R_OFFSET is managed separately - if((ui_state.menu_selected != R_OFFSET && msg.keys & KEY_ENTER) || msg.keys & KEY_ESC) - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_radio_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_radio_num); - else if(msg.keys & KEY_ENTER) { - ui_state.edit_mode = true; - // If we are entering R_OFFSET clear temp offset - if (ui_state.menu_selected == R_OFFSET) - ui_state.new_offset = 0; - // Reset input position - ui_state.input_position = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; -#ifdef CONFIG_M17 - // M17 Settings - case SETTINGS_M17: - if(ui_state.edit_mode) - { - switch (ui_state.menu_selected) - { - case M17_CALLSIGN: - // Handle text input for M17 callsign - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected callsign and disable input mode - strncpy(state.settings.callsign, ui_state.new_callsign, 10); - ui_state.edit_mode = false; - vp_announceBuffer(¤tLanguage->callsign, - false, true, state.settings.callsign); - } - else if(msg.keys & KEY_ESC) - { - // Discard selected callsign and disable input mode - ui_state.edit_mode = false; - vp_announceBuffer(¤tLanguage->callsign, - false, true, state.settings.callsign); - } - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) - { - _ui_textInputDel(ui_state.new_callsign); - } - else if(input_isCharPressed(msg)) - { - _ui_textInputKeypad(ui_state.new_callsign, 9, msg, true); - } - else if (msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) - { - vp_announceBuffer(¤tLanguage->callsign, - true, true, ui_state.new_callsign); - f1Handled=true; - } - break; - case M17_METATEXT: - // Handle text input for M17 message text - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_message); - // Save selected message and disable input mode - strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); - ui_state.edit_message = false; - ui_state.edit_mode = false; - vp_announceBuffer(¤tLanguage->metaText, - false, true, state.settings.M17_meta_text); - } - else if(msg.keys & KEY_ESC) - { - // Discard selected message and disable input mode - ui_state.edit_message = false; - ui_state.edit_mode = false; - vp_announceBuffer(¤tLanguage->metaText, - false, true, state.settings.M17_meta_text); - } - else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN || - msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT) - { - _ui_textInputDel(ui_state.new_message); - } - else if(input_isCharPressed(msg)) - { - _ui_textInputKeypad(ui_state.new_message, 52, msg, false); - } - else if (msg.long_press && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) - { - vp_announceBuffer(¤tLanguage->metaText, - true, true, ui_state.new_message); - f1Handled=true; - } - break; - case M17_CAN: - if(msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) - _ui_changeM17Can(-1); - else if(msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) - _ui_changeM17Can(+1); - else if(msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - break; - case M17_CAN_RX: - if(msg.keys & KEY_LEFT || msg.keys & KEY_RIGHT || - (ui_state.edit_mode && - (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT || - msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) - { - state.settings.m17_can_rx = - !state.settings.m17_can_rx; - } - else if(msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - } - } - else - { - if(msg.keys & KEY_ENTER) - { - // Enable edit mode - ui_state.edit_mode = true; - - // If callsign input, reset text input variables - if(ui_state.menu_selected == M17_CALLSIGN) - { - _ui_textInputReset(ui_state.new_callsign); - vp_announceBuffer(¤tLanguage->callsign, - true, true, ui_state.new_callsign); - } - // If message input, reset text input variables - if(ui_state.menu_selected == M17_METATEXT) - { - // ui_state.edit_mode = false; - ui_state.edit_message = true; - _ui_textInputReset(ui_state.new_message); - vp_announceBuffer(¤tLanguage->metaText, - true, true, ui_state.new_message); - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_m17_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_m17_num); - else if((msg.keys & KEY_RIGHT) && (ui_state.menu_selected == M17_CAN)) - _ui_changeM17Can(+1); - else if((msg.keys & KEY_LEFT) && (ui_state.menu_selected == M17_CAN)) - _ui_changeM17Can(-1); - else if(msg.keys & KEY_ESC) - { - *sync_rtx = true; - _ui_menuBack(MENU_SETTINGS); - } - } - break; -#endif - case SETTINGS_FM: - if (ui_state.edit_mode) - { - if (msg.keys & KEY_ESC) - ui_state.edit_mode = false; - - switch (ui_state.menu_selected) - { - case CTCSS_Tone: - if (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) - { - if (state.channel.fm.txTone == 0) - { - state.channel.fm.txTone = - CTCSS_FREQ_NUM - 1; - } - else - { - state.channel.fm.txTone--; - } - } - else if (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) - { - state.channel.fm.txTone++; - } else if (msg.keys & KEY_ENTER) { - ui_state.edit_mode = false; - } - state.channel.fm.txTone %= CTCSS_FREQ_NUM; - state.channel.fm.rxTone = state.channel.fm.txTone; - *sync_rtx = true; - break; - case CTCSS_Enabled: - if (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT) - { - _ui_handleToneSelectScroll(true); - } else if (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT) - { - _ui_handleToneSelectScroll(false); - } else if (msg.keys & KEY_ENTER) { - ui_state.edit_mode = false; - } - - *sync_rtx = true; - break; - } - } - else if (msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_fm_num); - else if (msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_fm_num); - else if (msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if (msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - else if (msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if (msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; - - case SETTINGS_ACCESSIBILITY: - if(msg.keys & KEY_LEFT || (ui_state.edit_mode && - (msg.keys & KEY_DOWN || msg.keys & KNOB_LEFT))) - { - switch(ui_state.menu_selected) - { - case A_MACRO_LATCH: - _ui_changeMacroLatch(false); - break; - case A_LEVEL: - _ui_changeVoiceLevel(-1); - break; - case A_PHONETIC: - _ui_changePhoneticSpell(false); - break; - default: - state.ui_screen = SETTINGS_ACCESSIBILITY; - } - } - else if(msg.keys & KEY_RIGHT || (ui_state.edit_mode && - (msg.keys & KEY_UP || msg.keys & KNOB_RIGHT))) - { - switch(ui_state.menu_selected) - { - case A_MACRO_LATCH: - _ui_changeMacroLatch(true); - break; - case A_LEVEL: - _ui_changeVoiceLevel(1); - break; - case A_PHONETIC: - _ui_changePhoneticSpell(true); - break; - default: - state.ui_screen = SETTINGS_ACCESSIBILITY; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_accessibility_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_accessibility_num); - else if(msg.keys & KEY_ENTER) - ui_state.edit_mode = !ui_state.edit_mode; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_SETTINGS); - break; - case SETTINGS_RESET2DEFAULTS: - if(! ui_state.edit_mode){ - //require a confirmation ENTER, then another - //edit_mode is slightly misused to allow for this - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = true; - } - else if(msg.keys & KEY_ESC) - { - _ui_menuBack(MENU_SETTINGS); - } - } else { - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = false; - state_resetSettingsAndVfo(); - _ui_menuBack(MENU_SETTINGS); - } - else if(msg.keys & KEY_ESC) - { - ui_state.edit_mode = false; - _ui_menuBack(MENU_SETTINGS); - } - } - break; - } - - // Enable Tx only if in MAIN_VFO or MAIN_MEM states - bool inMemOrVfo = (state.ui_screen == MAIN_VFO) || (state.ui_screen == MAIN_MEM); - if ((macro_menu == true) || ((inMemOrVfo == false) && (state.txDisable == false))) - { - state.txDisable = true; - *sync_rtx = true; - } - if (!f1Handled && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) - { - vp_replayLastPrompt(); - } - else if ((priorUIScreen!=state.ui_screen) && state.settings.vpLevel > vpLow) - { - // When we switch to VFO or Channel screen, we need to announce it. - // Likewise for information screens. - // All other cases are handled as needed. - vp_announceScreen(state.ui_screen); - } - // generic beep for any keydown if beep is enabled. - // At vp levels higher than beep, keys will generate voice so no need - // to beep or you'll get an unwanted click. - if ((msg.keys &0xffff) && (state.settings.vpLevel == vpBeep)) - vp_beep(BEEP_KEY_GENERIC, SHORT_BEEP); - // If we exit and re-enter the same menu, we want to ensure it speaks. - if (msg.keys & KEY_ESC) - _ui_reset_menu_anouncement_tracking(); - } - else if(event.type == EVENT_STATUS) - { -#ifdef CONFIG_GPS - if ((state.ui_screen == MENU_GPS) && - (!vp_isPlaying()) && - (state.settings.vpLevel > vpLow) && - (!txOngoing && !rtx_rxSquelchOpen())) - {// automatically read speed and direction changes only! - vpGPSInfoFlags_t whatChanged = GetGPSDirectionOrSpeedChanged(); - if (whatChanged != vpGPSNone) - vp_announceGPSInfo(whatChanged); - } -#endif // CONFIG_GPS - - if (txOngoing || rtx_rxSquelchOpen() || (state.volume != last_state.volume)) - { - _ui_exitStandby(now); - return; - } - - if (_ui_checkStandby(now - last_event_tick)) - { - _ui_enterStandby(); - } - } -} - -bool ui_updateGUI() -{ - if(redraw_needed == false) - return false; - - if(!layout_ready) - { - _ui_calculateLayout(&layout); - layout_ready = true; - } - // Draw current GUI page - switch(last_state.ui_screen) - { - // VFO main screen - case MAIN_VFO: - _ui_drawMainVFO(&ui_state); - break; - // VFO frequency input screen - case MAIN_VFO_INPUT: - _ui_drawMainVFOInput(&ui_state); - break; - // MEM main screen - case MAIN_MEM: - _ui_drawMainMEM(&ui_state); - break; - // Top menu screen - case MENU_TOP: - _ui_drawMenuTop(&ui_state); - break; - // Zone menu screen - case MENU_BANK: - _ui_drawMenuBank(&ui_state); - break; - // Channel menu screen - case MENU_CHANNEL: - _ui_drawMenuChannel(&ui_state); - break; - // Contacts menu screen - case MENU_CONTACTS: - _ui_drawMenuContacts(&ui_state); - break; -#ifdef CONFIG_GPS - // GPS menu screen - case MENU_GPS: - _ui_drawMenuGPS(); - break; -#endif - // Settings menu screen - case MENU_SETTINGS: - _ui_drawMenuSettings(&ui_state); - break; - // Flash backup and restore screen - case MENU_BACKUP_RESTORE: - _ui_drawMenuBackupRestore(&ui_state); - break; - // Flash backup screen - case MENU_BACKUP: - _ui_drawMenuBackup(&ui_state); - break; - // Flash restore screen - case MENU_RESTORE: - _ui_drawMenuRestore(&ui_state); - break; - // Info menu screen - case MENU_INFO: - _ui_drawMenuInfo(&ui_state); - break; - // About menu screen - case MENU_ABOUT: - _ui_drawMenuAbout(&ui_state); - break; -#ifdef CONFIG_RTC - // Time&Date settings screen - case SETTINGS_TIMEDATE: - _ui_drawSettingsTimeDate(); - break; - // Time&Date settings screen, edit mode - case SETTINGS_TIMEDATE_SET: - _ui_drawSettingsTimeDateSet(&ui_state); - break; -#endif - // Display settings screen - case SETTINGS_DISPLAY: - _ui_drawSettingsDisplay(&ui_state); - break; -#ifdef CONFIG_GPS - // GPS settings screen - case SETTINGS_GPS: - _ui_drawSettingsGPS(&ui_state); - break; -#endif -#ifdef CONFIG_M17 - // M17 settings screen - case SETTINGS_M17: - _ui_drawSettingsM17(&ui_state); - break; -#endif - // FM settings screen - case SETTINGS_FM: - _ui_drawSettingsFM(&ui_state); - break; - case SETTINGS_ACCESSIBILITY: - _ui_drawSettingsAccessibility(&ui_state); - break; - // Screen to support resetting Settings and VFO to defaults - case SETTINGS_RESET2DEFAULTS: - _ui_drawSettingsReset2Defaults(&ui_state); - break; - // Screen to set frequency offset and step - case SETTINGS_RADIO: - _ui_drawSettingsRadio(&ui_state); - break; - // Low battery screen - case LOW_BAT: - _ui_drawLowBatteryScreen(); - break; - } - - // If MACRO menu is active draw it - if(macro_menu) - { - _ui_drawDarkOverlay(); - _ui_drawMacroMenu(&ui_state); - } - - redraw_needed = false; - return true; -} - -void ui_terminate() -{ -} diff --git a/openrtx/src/ui/default/ui.cpp b/openrtx/src/ui/default/ui.cpp index cb4c23511c..974d5ede28 100644 --- a/openrtx/src/ui/default/ui.cpp +++ b/openrtx/src/ui/default/ui.cpp @@ -68,6 +68,7 @@ #include "ui/ui_events.h" #include "ui/Screen.hpp" #include "ui/ui_draw.h" +#include "ui/ui_nav.h" // default-variant-specific draw functions extern "C" { @@ -84,7 +85,7 @@ void _ui_drawSettingsVoicePrompts(ui_state_t* ui_state); void _ui_drawSettingsRadio(ui_state_t* ui_state); void _ui_reset_menu_anouncement_tracking(); } // extern "C" default-specific -// TODO: get these from ui strings / currentLanguage + const char *menu_items[] = { "Banks", @@ -860,7 +861,6 @@ static bool _ui_exitStandby(long long now) return true; } -// TODO: find a better home for this function int _ui_handleToneSelectScroll(bool direction_up) { bool tone_tx_enable = state.channel.fm.txToneEn; @@ -1070,8 +1070,6 @@ static void _ui_menuDown(uint8_t menu_entries) vp_playMenuBeepIfNeeded(ui_state.menu_selected==0); } -static void transition(Screen *next); // defined after screen instances - static void _ui_menuBack(Screen *prev) { if(ui_state.edit_mode) @@ -1080,7 +1078,7 @@ static void _ui_menuBack(Screen *prev) } else { - transition(prev); + ui_nav_transition(prev); ui_state.menu_selected = 0; vp_playMenuBeepIfNeeded(true); } @@ -1247,24 +1245,24 @@ static void _ui_numberInputDel(uint32_t *num) // ─── Screen subclass declarations ─────────────────────────────────────────── -class VfoScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class VfoScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; class VfoInputScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; -class MemScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MemScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; class MenuTopScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; class MenuBankScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; class MenuChannelScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; class MenuContactsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; #ifdef CONFIG_GPS -class MenuGpsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuGpsScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; #endif class MenuSettingsScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; class MenuBackupRestoreScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; -class MenuBackupScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; -class MenuRestoreScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuBackupScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; +class MenuRestoreScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; class MenuInfoScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; -class MenuAboutScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class MenuAboutScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; #ifdef CONFIG_RTC -class SettingsTimedateScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class SettingsTimedateScreen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; class SettingsTimedateSetScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; #endif class SettingsDisplayScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; @@ -1273,7 +1271,7 @@ class SettingsGpsScreen : public Screen { public: bool handleInput(even #endif class SettingsRadioScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; #ifdef CONFIG_M17 -class SettingsM17Screen : public Screen { public: bool handleInput(event_t) override; void render() override; }; +class SettingsM17Screen : public Screen { public: void enter() override; bool handleInput(event_t) override; void render() override; }; #endif class SettingsFmScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; class SettingsAccessibilityScreen : public Screen { public: bool handleInput(event_t) override; void render() override; }; @@ -1317,17 +1315,8 @@ static LowBatScreen lowBatScreen; // ─── Navigation state ─────────────────────────────────────────────────────── -static Screen *current = &vfoScreen; -static Screen *lastMainScreen = nullptr; -static bool *gSyncRtx = nullptr; -static bool gF1Handled = false; - -static void transition(Screen *next) -{ - current->exit(); - next->enter(); - current = next; -} +static bool *gSyncRtx = nullptr; +static bool gF1Handled = false; // ─── Render methods ───────────────────────────────────────────────────────── @@ -1364,6 +1353,42 @@ void SettingsAccessibilityScreen::render() { _ui_drawSettingsAccessibility(&ui_ void SettingsReset2DefaultsScreen::render() { _ui_drawSettingsReset2Defaults(&ui_state); } void LowBatScreen::render() { _ui_drawLowBatteryScreen(); } +// ─── enter() voice-prompt hooks ───────────────────────────────────────────── + +static const vpSummaryInfoFlags_t kVpSummaryFlags = + (vpSummaryInfoFlags_t)(vpChannelNameOrVFO | vpFrequencies | vpRadioMode); + +void VfoScreen::enter() +{ + vp_announceChannelSummary(&state.channel, 0, state.bank, kVpSummaryFlags); +} + +void MemScreen::enter() +{ + vp_announceChannelSummary(&state.channel, state.channel_index + 1, + state.bank, kVpSummaryFlags); +} + +#ifdef CONFIG_GPS +void MenuGpsScreen::enter() { vp_announceGPSInfo(vpGPSAll); } +#endif + +void MenuBackupScreen::enter() { vp_announceBackupScreen(); } +void MenuRestoreScreen::enter() { vp_announceRestoreScreen(); } +void MenuAboutScreen::enter() { vp_announceAboutScreen(); } + +#ifdef CONFIG_RTC +void SettingsTimedateScreen::enter() { vp_announceSettingsTimeDate(); } +#endif + +#ifdef CONFIG_M17 +void SettingsM17Screen::enter() +{ + vp_announceBuffer(¤tLanguage->callsign, + false, true, state.settings.callsign); +} +#endif + // ─── handleInput implementations ──────────────────────────────────────────── bool VfoScreen::handleInput(event_t ev) @@ -1411,8 +1436,8 @@ bool VfoScreen::handleInput(event_t ev) { if(msg.keys & KEY_ENTER) { - lastMainScreen = current; - transition(&menuTopScreen); + ui_nav_setLastMain(ui_nav_current()); + ui_nav_transition(&menuTopScreen); } else if(msg.keys & KEY_ESC) { @@ -1420,7 +1445,7 @@ bool VfoScreen::handleInput(event_t ev) int result = _ui_fsm_loadChannel(state.channel_index, gSyncRtx); if(result != -1) { - transition(&memScreen); + ui_nav_transition(&memScreen); vp_announceChannelName(&state.channel, state.channel_index, queueFlags); } } @@ -1474,7 +1499,7 @@ bool VfoScreen::handleInput(event_t ev) } else if(input_isNumberPressed(msg)) { - transition(&vfoInputScreen); + ui_nav_transition(&vfoInputScreen); ui_state.input_position = 1; ui_state.input_set = SET_RX; vp_announceInputReceiveOrTransmit(false, vpqInit); @@ -1501,7 +1526,7 @@ bool VfoInputScreen::handleInput(event_t ev) if(msg.keys & KEY_ENTER) _ui_fsm_confirmVFOInput(gSyncRtx); else if(msg.keys & KEY_ESC) - transition(&vfoScreen); + ui_nav_transition(&vfoScreen); else if(msg.keys & KEY_UP || msg.keys & KEY_DOWN) { if(ui_state.input_set == SET_RX) @@ -1569,14 +1594,14 @@ bool MemScreen::handleInput(event_t ev) { if(msg.keys & KEY_ENTER) { - lastMainScreen = current; - transition(&menuTopScreen); + ui_nav_setLastMain(ui_nav_current()); + ui_nav_transition(&menuTopScreen); } else if(msg.keys & KEY_ESC) { state.channel = state.vfo_channel; *gSyncRtx = true; - transition(&vfoScreen); + ui_nav_transition(&vfoScreen); } else if(msg.keys & KEY_HASH) { @@ -1629,20 +1654,20 @@ bool MenuTopScreen::handleInput(event_t ev) { switch(ui_state.menu_selected) { - case M_BANK: transition(&menuBankScreen); break; - case M_CHANNEL: transition(&menuChannelScreen); break; - case M_CONTACTS: transition(&menuContactsScreen); break; + case M_BANK: ui_nav_transition(&menuBankScreen); break; + case M_CHANNEL: ui_nav_transition(&menuChannelScreen); break; + case M_CONTACTS: ui_nav_transition(&menuContactsScreen); break; #ifdef CONFIG_GPS - case M_GPS: transition(&menuGpsScreen); break; + case M_GPS: ui_nav_transition(&menuGpsScreen); break; #endif - case M_SETTINGS: transition(&menuSettingsScreen); break; - case M_INFO: transition(&menuInfoScreen); break; - case M_ABOUT: transition(&menuAboutScreen); break; + case M_SETTINGS: ui_nav_transition(&menuSettingsScreen); break; + case M_INFO: ui_nav_transition(&menuInfoScreen); break; + case M_ABOUT: ui_nav_transition(&menuAboutScreen); break; } ui_state.menu_selected = 0; } else if(msg.keys & KEY_ESC) - _ui_menuBack(lastMainScreen); + _ui_menuBack(ui_nav_lastMain()); return true; } @@ -1674,10 +1699,10 @@ bool MenuBankScreen::handleInput(event_t ev) if(result != -1) { state.bank = ui_state.menu_selected - 1; - if(lastMainScreen == &vfoScreen) + if(ui_nav_lastMain() == &vfoScreen) state.vfo_channel = state.channel; _ui_fsm_loadChannel(0, gSyncRtx); - transition(&memScreen); + ui_nav_transition(&memScreen); } } else if(msg.keys & KEY_ESC) @@ -1701,10 +1726,10 @@ bool MenuChannelScreen::handleInput(event_t ev) } else if(msg.keys & KEY_ENTER) { - if(lastMainScreen == &vfoScreen) + if(ui_nav_lastMain() == &vfoScreen) state.vfo_channel = state.channel; _ui_fsm_loadChannel(ui_state.menu_selected, gSyncRtx); - transition(&memScreen); + ui_nav_transition(&memScreen); } else if(msg.keys & KEY_ESC) _ui_menuBack(&menuTopScreen); @@ -1765,20 +1790,20 @@ bool MenuSettingsScreen::handleInput(event_t ev) { switch(ui_state.menu_selected) { - case S_DISPLAY: transition(&settingsDisplayScreen); break; + case S_DISPLAY: ui_nav_transition(&settingsDisplayScreen); break; #ifdef CONFIG_RTC - case S_TIMEDATE: transition(&settingsTimedateScreen); break; + case S_TIMEDATE: ui_nav_transition(&settingsTimedateScreen); break; #endif #ifdef CONFIG_GPS - case S_GPS: transition(&settingsGpsScreen); break; + case S_GPS: ui_nav_transition(&settingsGpsScreen); break; #endif - case S_RADIO: transition(&settingsRadioScreen); break; + case S_RADIO: ui_nav_transition(&settingsRadioScreen); break; #ifdef CONFIG_M17 - case S_M17: transition(&settingsM17Screen); break; + case S_M17: ui_nav_transition(&settingsM17Screen); break; #endif - case S_FM: transition(&settingsFmScreen); break; - case S_ACCESSIBILITY: transition(&settingsAccessibilityScreen); break; - case S_RESET2DEFAULTS: transition(&settingsReset2DefaultsScreen); break; + case S_FM: ui_nav_transition(&settingsFmScreen); break; + case S_ACCESSIBILITY: ui_nav_transition(&settingsAccessibilityScreen); break; + case S_RESET2DEFAULTS: ui_nav_transition(&settingsReset2DefaultsScreen); break; default: break; } ui_state.menu_selected = 0; @@ -1802,8 +1827,8 @@ bool MenuBackupRestoreScreen::handleInput(event_t ev) { switch(ui_state.menu_selected) { - case BR_BACKUP: transition(&menuBackupScreen); break; - case BR_RESTORE: transition(&menuRestoreScreen); break; + case BR_BACKUP: ui_nav_transition(&menuBackupScreen); break; + case BR_RESTORE: ui_nav_transition(&menuRestoreScreen); break; default: break; } ui_state.menu_selected = 0; @@ -1872,7 +1897,7 @@ bool SettingsTimedateScreen::handleInput(event_t ev) if(msg.keys & KEY_ENTER) { - transition(&settingsTimedateSetScreen); + ui_nav_transition(&settingsTimedateSetScreen); ui_state.input_position = 0; memset(&ui_state.new_timedate, 0, sizeof(datetime_t)); vp_announceBuffer(¤tLanguage->timeAndDate, true, false, "dd/mm/yy"); @@ -1895,7 +1920,7 @@ bool SettingsTimedateSetScreen::handleInput(event_t ev) platform_setTime(utc_time); state.time = utc_time; vp_announceSettingsTimeDate(); - transition(&settingsTimedateScreen); + ui_nav_transition(&settingsTimedateScreen); } else if(msg.keys & KEY_ESC) _ui_menuBack(&settingsTimedateScreen); @@ -2372,7 +2397,7 @@ bool LowBatScreen::handleInput(event_t ev) msg.value = ev.payload; if(msg.keys) { - transition(&vfoScreen); + ui_nav_transition(&vfoScreen); state.emergency = true; } return true; @@ -2385,6 +2410,85 @@ void ui_init() last_event_tick = getTick(); redraw_needed = true; ui_state = ui_state_t{}; + ui_nav_init(&vfoScreen); + + // Refresh menu string arrays from the active language table. + menu_items[M_BANK] = currentLanguage->banks; + menu_items[M_CHANNEL] = currentLanguage->channels; + menu_items[M_CONTACTS] = currentLanguage->contacts; +#ifdef CONFIG_GPS + menu_items[M_GPS] = currentLanguage->gps; +#endif + menu_items[M_SETTINGS] = currentLanguage->settings; + menu_items[M_INFO] = currentLanguage->info; + menu_items[M_ABOUT] = currentLanguage->about; + + settings_items[S_DISPLAY] = currentLanguage->display; +#ifdef CONFIG_RTC + settings_items[S_TIMEDATE] = currentLanguage->timeAndDate; +#endif +#ifdef CONFIG_GPS + settings_items[S_GPS] = currentLanguage->gpsSettings; +#endif + settings_items[S_RADIO] = currentLanguage->radioSettings; +#ifdef CONFIG_M17 + settings_items[S_M17] = currentLanguage->m17settings; +#endif + settings_items[S_FM] = currentLanguage->fm; + settings_items[S_ACCESSIBILITY] = currentLanguage->accessibility; + settings_items[S_RESET2DEFAULTS] = currentLanguage->defaultSettings; + +#ifdef CONFIG_SCREEN_BRIGHTNESS + display_items[D_BRIGHTNESS] = currentLanguage->brightness; +#endif +#ifdef CONFIG_SCREEN_CONTRAST + display_items[D_CONTRAST] = currentLanguage->contrast; +#endif + display_items[D_TIMER] = currentLanguage->timer; + display_items[D_BATTERY] = currentLanguage->batteryIcon; + +#ifdef CONFIG_GPS + settings_gps_items[G_ENABLED] = currentLanguage->gpsEnabled; +# ifdef CONFIG_RTC + settings_gps_items[G_SET_TIME] = currentLanguage->gpsSetTime; + settings_gps_items[G_TIMEZONE] = currentLanguage->UTCTimeZone; +# endif +#endif + + settings_radio_items[R_OFFSET] = currentLanguage->offset; + settings_radio_items[R_DIRECTION] = currentLanguage->direction; + settings_radio_items[R_STEP] = currentLanguage->step; + +#ifdef CONFIG_M17 + settings_m17_items[M17_CALLSIGN] = currentLanguage->callsign; + settings_m17_items[M17_METATEXT] = currentLanguage->metaText; + settings_m17_items[M17_CAN] = currentLanguage->CAN; + settings_m17_items[M17_CAN_RX] = currentLanguage->canRxCheck; +#endif + + settings_fm_items[CTCSS_Tone] = currentLanguage->CTCSSTone; + settings_fm_items[CTCSS_Enabled] = currentLanguage->CTCSSEn; + + settings_accessibility_items[A_MACRO_LATCH] = currentLanguage->macroLatching; + settings_accessibility_items[A_LEVEL] = currentLanguage->voice; + settings_accessibility_items[A_PHONETIC] = currentLanguage->phonetic; + + backup_restore_items[0] = currentLanguage->backup; + backup_restore_items[1] = currentLanguage->restore; + + info_items[1] = currentLanguage->batteryVoltage; + info_items[2] = currentLanguage->batteryCharge; + info_items[3] = currentLanguage->RSSI; + info_items[4] = currentLanguage->usedHeap; + info_items[5] = currentLanguage->band; + info_items[6] = currentLanguage->VHF; + info_items[7] = currentLanguage->UHF; + + authors[0] = currentLanguage->Niccolo; + authors[1] = currentLanguage->Silvano; + authors[2] = currentLanguage->Federico; + authors[3] = currentLanguage->Fred; + authors[4] = currentLanguage->Joseph; } void ui_drawSplashScreen() @@ -2567,10 +2671,10 @@ void ui_updateFSM(bool *sync_rtx) gSyncRtx = sync_rtx; gF1Handled = false; - current->handleInput(event); + ui_nav_current()->handleInput(event); // Enable Tx only if in MAIN_VFO or MAIN_MEM states - bool inMemOrVfo = (current == &vfoScreen) || (current == &memScreen); + bool inMemOrVfo = (ui_nav_current() == &vfoScreen) || (ui_nav_current() == &memScreen); if((macro_menu == true) || ((inMemOrVfo == false) && (state.txDisable == false))) { state.txDisable = true; @@ -2578,7 +2682,6 @@ void ui_updateFSM(bool *sync_rtx) } if(!gF1Handled && (msg.keys & KEY_F1) && (state.settings.vpLevel > vpBeep)) vp_replayLastPrompt(); - // TODO Phase 5: restore screen-change voice announcement via Screen::enter() if((msg.keys & 0xffff) && (state.settings.vpLevel == vpBeep)) vp_beep(BEEP_KEY_GENERIC, SHORT_BEEP); if(msg.keys & KEY_ESC) @@ -2588,7 +2691,7 @@ void ui_updateFSM(bool *sync_rtx) else if(event.type == EVENT_STATUS) { #ifdef CONFIG_GPS - if ((current == &menuGpsScreen) && + if ((ui_nav_current() == &menuGpsScreen) && (!vp_isPlaying()) && (state.settings.vpLevel > vpLow) && (!txOngoing && !rtx_rxSquelchOpen())) @@ -2617,7 +2720,7 @@ bool ui_updateGUI() if(redraw_needed == false) return false; - current->render(); + ui_nav_current()->render(); // If MACRO menu is active draw it if(macro_menu) @@ -2630,6 +2733,3 @@ bool ui_updateGUI() return true; } -void ui_terminate() -{ -} diff --git a/openrtx/src/ui/module17/ui.c b/openrtx/src/ui/module17/ui.c deleted file mode 100644 index 3840692214..0000000000 --- a/openrtx/src/ui/module17/ui.c +++ /dev/null @@ -1,924 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2020-2026 OpenRTX Contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include "ui/ui_mod17.h" -#include "rtx/rtx.h" -#include "interfaces/platform.h" -#include "interfaces/display.h" -#include "interfaces/cps_io.h" -#include "interfaces/nvmem.h" -#include "interfaces/delays.h" -#include -#include "core/battery.h" -#include "core/input.h" -#include "hwconfig.h" -#include "ui/ui_events.h" - -/* UI main screen functions, their implementation is in "ui_main.c" */ -extern void _ui_drawMainBackground(); -extern void _ui_drawMainTop(); -extern void _ui_drawVFOMiddle(); -extern void _ui_drawMEMMiddle(); -extern void _ui_drawVFOBottom(); -extern void _ui_drawMEMBottom(); -extern void _ui_drawMainVFO(ui_state_t* ui_state); -extern void _ui_drawMainVFOInput(ui_state_t* ui_state); -extern void _ui_drawMainMEM(ui_state_t* ui_state); -/* UI menu functions, their implementation is in "ui_menu.c" */ -extern void _ui_drawMenuTop(ui_state_t* ui_state); -#ifdef CONFIG_GPS -extern void _ui_drawMenuGPS(); -extern void _ui_drawSettingsGPS(ui_state_t* ui_state); -#endif -extern void _ui_drawMenuSettings(ui_state_t* ui_state); -extern void _ui_drawMenuInfo(ui_state_t* ui_state); -extern void _ui_drawMenuAbout(ui_state_t* ui_state); -#ifdef CONFIG_RTC -extern void _ui_drawSettingsTimeDate(); -extern void _ui_drawSettingsTimeDateSet(ui_state_t* ui_state); -#endif -extern void _ui_drawSettingsDisplay(ui_state_t* ui_state); -extern void _ui_drawSettingsM17(ui_state_t* ui_state); -extern void _ui_drawSettingsModule17(ui_state_t* ui_state); -extern void _ui_drawSettingsReset2Defaults(ui_state_t* ui_state); -extern bool _ui_drawMacroMenu(ui_state_t* ui_state); - -const char *menu_items[] = -{ - "Settings", -#ifdef CONFIG_GPS - "GPS", -#endif - "Info", - "About", - "Shutdown" -}; - -const char *settings_items[] = -{ - "Display", -#ifdef CONFIG_RTC - "Time & Date", -#endif -#ifdef CONFIG_GPS - "GPS", -#endif - "M17", - "Module 17", - "Default Settings" -}; - -const char *display_items[] = -{ - "Brightness", -}; - -const char *m17_items[] = -{ - "Callsign", - "Meta Txt", - "CAN", - "CAN RX Check" -}; - -const char *module17_items[] = -{ - "Mic Gain", - "PTT In", - "PTT Out", - "TX Phase", - "RX Phase", - "TX Softpot", - "RX Softpot" -}; - -#ifdef CONFIG_GPS -const char *settings_gps_items[] = -{ - "GPS Enabled", - "GPS Set Time", - "UTC Timezone" -}; -#endif - -const char *info_items[] = -{ - "", - "Used heap", - "Hw Version", - "HMI", - "BB Tuning Pot", -}; - -const char *authors[] = -{ - "Niccolo' IU2KIN", - "Silvano IU2KWO", - "Federico IU2NUO", - "Mathis DB9MAT", - "Morgan ON4MOD", - "Marco DM4RCO" -}; - -static const char symbols_callsign[] = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890/-."; - -// Calculate number of menu entries -const uint8_t menu_num = sizeof(menu_items)/sizeof(menu_items[0]); -const uint8_t settings_num = sizeof(settings_items)/sizeof(settings_items[0]); -const uint8_t display_num = sizeof(display_items)/sizeof(display_items[0]); -#ifdef CONFIG_GPS -const uint8_t settings_gps_num = sizeof(settings_gps_items)/sizeof(settings_gps_items[0]); -#endif -const uint8_t m17_num = sizeof(m17_items)/sizeof(m17_items[0]); -const uint8_t module17_num = sizeof(module17_items)/sizeof(module17_items[0]); -const uint8_t info_num = sizeof(info_items)/sizeof(info_items[0]); -const uint8_t author_num = sizeof(authors)/sizeof(authors[0]); - -const color_t color_black = {0, 0, 0, 255}; -const color_t color_grey = {60, 60, 60, 255}; -const color_t color_white = {255, 255, 255, 255}; -const color_t yellow_fab413 = {250, 180, 19, 255}; - -layout_t layout; -state_t last_state; -static ui_state_t ui_state; -static bool layout_ready = false; - -static layout_t _ui_calculateLayout() -{ - // Horizontal line height - const uint16_t hline_h = 1; - // Compensate for fonts printing below the start position - const uint16_t text_v_offset = 1; - - // Calculate UI layout for the Module 17 - - // Height and padding shown in diagram at beginning of file - const uint16_t top_h = 11; - const uint16_t top_pad = 1; - const uint16_t line1_h = 10; - const uint16_t line2_h = 10; - const uint16_t line3_h = 10; - const uint16_t line4_h = 10; - const uint16_t line5_h = 10; - const uint16_t menu_h = 10; - const uint16_t bottom_h = 15; - const uint16_t bottom_pad = 0; - const uint16_t status_v_pad = 1; - const uint16_t small_line_v_pad = 1; - const uint16_t big_line_v_pad = 0; - const uint16_t horizontal_pad = 4; - - // Top bar font: 6 pt - const fontSize_t top_font = FONT_SIZE_6PT; - const symbolSize_t top_symbol_size = SYMBOLS_SIZE_6PT; - // Middle line fonts: 5, 8, 8 pt - const fontSize_t line1_font = FONT_SIZE_6PT; - const symbolSize_t line1_symbol_size = SYMBOLS_SIZE_6PT; - const fontSize_t line2_font = FONT_SIZE_6PT; - const symbolSize_t line2_symbol_size = SYMBOLS_SIZE_6PT; - const fontSize_t line3_font = FONT_SIZE_6PT; - const symbolSize_t line3_symbol_size = SYMBOLS_SIZE_6PT; - const fontSize_t line4_font = FONT_SIZE_6PT; - const symbolSize_t line4_symbol_size = SYMBOLS_SIZE_6PT; - const fontSize_t line5_font = FONT_SIZE_6PT; - const symbolSize_t line5_symbol_size = SYMBOLS_SIZE_6PT; - // Bottom bar font: 6 pt - const fontSize_t bottom_font = FONT_SIZE_6PT; - // TimeDate/Frequency input font - const fontSize_t input_font = FONT_SIZE_8PT; - // Menu font - const fontSize_t menu_font = FONT_SIZE_6PT; - // Message font - const fontSize_t message_font = FONT_SIZE_6PT; - // Mode screen frequency font: 9 pt - const fontSize_t mode_font_big = FONT_SIZE_9PT; - // Mode screen details font: 6 pt - const fontSize_t mode_font_small = FONT_SIZE_6PT; - - // Calculate printing positions - point_t top_pos = {horizontal_pad, top_h - status_v_pad - text_v_offset}; - point_t line1_pos = {horizontal_pad, top_h + top_pad + line1_h - small_line_v_pad - text_v_offset}; - point_t line2_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h - small_line_v_pad - text_v_offset}; - point_t line3_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h - big_line_v_pad - text_v_offset}; - point_t line4_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h + line4_h - big_line_v_pad - text_v_offset}; - point_t line5_pos = {horizontal_pad, top_h + top_pad + line1_h + line2_h + line3_h + line4_h + line5_h - big_line_v_pad - text_v_offset}; - point_t bottom_pos = {horizontal_pad, CONFIG_SCREEN_HEIGHT - bottom_pad - status_v_pad - text_v_offset}; - - layout_t new_layout = - { - hline_h, - top_h, - line1_h, - line2_h, - line3_h, - line4_h, - line5_h, - menu_h, - bottom_h, - bottom_pad, - status_v_pad, - horizontal_pad, - text_v_offset, - top_pos, - line1_pos, - line2_pos, - line3_pos, - line4_pos, - line5_pos, - bottom_pos, - top_font, - top_symbol_size, - line1_font, - line1_symbol_size, - line2_font, - line2_symbol_size, - line3_font, - line3_symbol_size, - line4_font, - line4_symbol_size, - line5_font, - line5_symbol_size, - bottom_font, - input_font, - menu_font, - message_font, - mode_font_big, - mode_font_small - }; - return new_layout; -} - - -void ui_init() -{ - layout = _ui_calculateLayout(); - layout_ready = true; - // Initialize struct ui_state to all zeroes - // This syntax is called compound literal - // https://stackoverflow.com/questions/6891720/initialize-reset-struct-to-zero-null - ui_state = (const struct ui_state_t){ 0 }; -} - -void ui_drawSplashScreen() -{ - gfx_clearScreen(); - - point_t origin = {0, (CONFIG_SCREEN_HEIGHT / 2) - 6}; - gfx_print(origin, FONT_SIZE_12PT, TEXT_ALIGN_CENTER, yellow_fab413, "O P N\nR T X"); -} - -#ifdef CONFIG_RTC -void _ui_timedate_add_digit(datetime_t *timedate, uint8_t pos, uint8_t number) -{ - switch(pos) - { - // Set date - case 1: - timedate->date += number * 10; - break; - case 2: - timedate->date += number; - break; - // Set month - case 3: - timedate->month += number * 10; - break; - case 4: - timedate->month += number; - break; - // Set year - case 5: - timedate->year += number * 10; - break; - case 6: - timedate->year += number; - break; - // Set hour - case 7: - timedate->hour += number * 10; - break; - case 8: - timedate->hour += number; - break; - // Set minute - case 9: - timedate->minute += number * 10; - break; - case 10: - timedate->minute += number; - break; - } -} -#endif - -static void _ui_changeBrightness(int variation) -{ - // Avoid rollover if current value is zero. - if((state.settings.brightness == 0) && (variation < 0)) - return; - - // Cap max brightness to 100 - if((state.settings.brightness == 100) && (variation > 0)) - return; - - state.settings.brightness += variation; - display_setBacklightLevel(state.settings.brightness); -} - -static void _ui_changeCAN(int variation) -{ - int8_t can = state.settings.m17_can + variation; - - // M17 CAN ranges from 0 to 15 - if(can > 15) - can = 0; - - if(can < 0) - can = 15; - - state.settings.m17_can = can; -} - -static void _ui_changeWiper(uint16_t *wiper, int variation) -{ - uint16_t value = *wiper; - value += variation; - - // Max value for softpot is 0x100, min value is set to 0x001 - if(value > 0x100) - value = 0x100; - - if(value < 0x001) - value = 0x001; - - *wiper = value; -} - -static void _ui_changeMicGain(int variation) -{ - int8_t gain = mod17CalData.mic_gain + variation; - - if(gain > 2) - gain = 0; - - if(gain < 0) - gain = 2; - - mod17CalData.mic_gain = gain; -} - -static void _ui_menuUp(uint8_t menu_entries) -{ - uint8_t maxEntries = menu_entries - 1; - const hwInfo_t* hwinfo = platform_getHwInfo(); - - // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) - maxEntries -= 1; - - // Hide the softpot menu entries if hardware does not have them - uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) - maxEntries -= 2; - - if(ui_state.menu_selected > 0) - ui_state.menu_selected -= 1; - else - ui_state.menu_selected = maxEntries; -} - -static void _ui_menuDown(uint8_t menu_entries) -{ - uint8_t maxEntries = menu_entries - 1; - const hwInfo_t* hwinfo = platform_getHwInfo(); - - // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (state.ui_screen == MENU_TOP)) - maxEntries -= 1; - - // Hide the softpot menu entries if hardware does not have them - uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (state.ui_screen == SETTINGS_MODULE17)) - maxEntries -= 2; - - if(ui_state.menu_selected < maxEntries) - ui_state.menu_selected += 1; - else - ui_state.menu_selected = 0; -} - -static void _ui_menuBack(uint8_t prev_state) -{ - if(ui_state.edit_mode) - { - ui_state.edit_mode = false; - } - else - { - // Return to previous menu - state.ui_screen = prev_state; - // Reset menu selection - ui_state.menu_selected = 0; - } -} - -static void _ui_textInputReset(char *buf) -{ - ui_state.input_number = 0; - ui_state.input_position = 0; - ui_state.input_set = 0; - ui_state.last_keypress = 0; - memset(buf, 0, 9); -} - -static void _ui_textInputArrows(char *buf, uint8_t max_len, kbd_msg_t msg) -{ - if(ui_state.input_position >= max_len) - return; - - uint8_t num_symbols = 0; - num_symbols = strlen(symbols_callsign); - - if (msg.keys & KEY_RIGHT) - { - if (ui_state.input_position < (max_len - 1)) - { - ui_state.input_position = ui_state.input_position + 1; - ui_state.input_set = 0; - } - } - else if (msg.keys & KEY_LEFT) - { - if (ui_state.input_position > 0) - { - buf[ui_state.input_position] = '\0'; - ui_state.input_position = ui_state.input_position - 1; - } - - // get index of current selected character in symbol table - ui_state.input_set = strcspn(symbols_callsign, &buf[ui_state.input_position]); - } - else if (msg.keys & KEY_UP) - ui_state.input_set = (ui_state.input_set + 1) % num_symbols; - else if (msg.keys & KEY_DOWN) - ui_state.input_set = ui_state.input_set==0 ? num_symbols-1 : ui_state.input_set-1; - - buf[ui_state.input_position] = symbols_callsign[ui_state.input_set]; -} - -static void _ui_textInputConfirm(char *buf) -{ - buf[ui_state.input_position + 1] = '\0'; -} - -void ui_saveState() -{ - last_state = state; -} - -void ui_updateFSM(bool *sync_rtx) -{ - event_t event; - if(!ui_popEvent(&event)) return; - - // Process pressed keys - if(event.type == EVENT_KBD) - { - kbd_msg_t msg; - msg.value = event.payload; - - switch(state.ui_screen) - { - // VFO screen - case MAIN_VFO: - if(ui_state.edit_mode) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected callsign and disable input mode - strncpy(state.settings.m17_dest, ui_state.new_callsign, 10); - *sync_rtx = true; - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - else - _ui_textInputArrows(ui_state.new_callsign, 9, msg); - } - else if(ui_state.edit_message) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_message); - // Save selected message and disable input mode - strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); - ui_state.edit_message = false; - *sync_rtx = true; - } - else if(msg.keys & KEY_ESC) - // Discard selected message and disable input mode - ui_state.edit_message = false; - else - _ui_textInputArrows(ui_state.new_message, 52, msg); - } - else - { - if(msg.keys & KEY_ENTER) - { - // Save current main state - ui_state.last_main_state = state.ui_screen; - // Open Menu - state.ui_screen = MENU_TOP; - } - else if (msg.keys & KEY_RIGHT) - { - ui_state.edit_mode = true; - } - } - break; - - // Top menu screen - case MENU_TOP: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(menu_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(menu_num); - else if(msg.keys & KEY_ENTER) - { - switch(ui_state.menu_selected) - { - case M_SETTINGS: - state.ui_screen = MENU_SETTINGS; - break; - case M_INFO: - state.ui_screen = MENU_INFO; - break; - case M_ABOUT: - state.ui_screen = MENU_ABOUT; - break; - case M_SHUTDOWN: - state.devStatus = SHUTDOWN; - break; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(ui_state.last_main_state); - break; - - // Settings menu screen - case MENU_SETTINGS: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(settings_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(settings_num); - else if(msg.keys & KEY_ENTER) - { - - switch(ui_state.menu_selected) - { - case S_DISPLAY: - state.ui_screen = SETTINGS_DISPLAY; - break; - case S_M17: - state.ui_screen = SETTINGS_M17; - break; - case S_MOD17: - state.ui_screen = SETTINGS_MODULE17; - break; - case S_RESET2DEFAULTS: - state.ui_screen = SETTINGS_RESET2DEFAULTS; - break; - default: - state.ui_screen = MENU_SETTINGS; - } - // Reset menu selection - ui_state.menu_selected = 0; - } - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // Info menu screen - case MENU_INFO: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(info_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(info_num); - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - // About screen, scroll without rollover - case MENU_ABOUT: - if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - { - if(ui_state.menu_selected > 0) - ui_state.menu_selected -= 1; - } - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - ui_state.menu_selected += 1; - else if(msg.keys & KEY_ESC) - _ui_menuBack(MENU_TOP); - break; - - case SETTINGS_DISPLAY: - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case D_BRIGHTNESS: - _ui_changeBrightness(-5); - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case D_BRIGHTNESS: - _ui_changeBrightness(+5); - break; - default: - state.ui_screen = SETTINGS_DISPLAY; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(display_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(display_num); - else if(msg.keys & KEY_ESC) - { - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - break; - - // M17 Settings - case SETTINGS_M17: - - if(ui_state.edit_mode) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_callsign); - // Save selected callsign and disable input mode - strncpy(state.settings.callsign, ui_state.new_callsign, 10); - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - ui_state.edit_mode = false; - else - _ui_textInputArrows(ui_state.new_callsign, 9, msg); - } - else if(ui_state.edit_message) - { - if(msg.keys & KEY_ENTER) - { - _ui_textInputConfirm(ui_state.new_message); - // Save selected message and disable input mode - strncpy(state.settings.M17_meta_text, ui_state.new_message, 52); - ui_state.edit_message = false; - ui_state.edit_mode = false; - } - else if(msg.keys & KEY_ESC) - // Discard selected message and disable input mode - ui_state.edit_message = false; - else - _ui_textInputArrows(ui_state.new_message, 52, msg); - } - else - { - // Not in edit mode: handle CAN setting - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case M_CAN: - _ui_changeCAN(-1); - break; - case M_CAN_RX: - state.settings.m17_can_rx = !state.settings.m17_can_rx; - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case M_CAN: - _ui_changeCAN(+1); - break; - case M_CAN_RX: - state.settings.m17_can_rx = !state.settings.m17_can_rx; - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_ENTER) - { - switch(ui_state.menu_selected) - { - // Enable callsign input - case M_CALLSIGN: - ui_state.edit_mode = true; - _ui_textInputReset(ui_state.new_callsign); - break; - // Enable meta text input - case M_METATEXT: - ui_state.edit_message = true; - _ui_textInputReset(ui_state.new_message); - break; - default: - state.ui_screen = SETTINGS_M17; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(m17_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(m17_num); - else if(msg.keys & KEY_ESC) - { - *sync_rtx = true; - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - } - break; - case SETTINGS_RESET2DEFAULTS: - if(! ui_state.edit_mode) - { - //require a confirmation ENTER, then another - //edit_mode is slightly misused to allow for this - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = true; - } - else if(msg.keys & KEY_ESC) - { - _ui_menuBack(MENU_SETTINGS); - } - } - else - { - if(msg.keys & KEY_ENTER) - { - ui_state.edit_mode = false; - - // Reset calibration values - mod17CalData.tx_wiper = 0x080; - mod17CalData.rx_wiper = 0x080; - mod17CalData.bb_tx_invert = 0; - mod17CalData.bb_rx_invert = 0; - mod17CalData.mic_gain = 0; - - state_resetSettingsAndVfo(); - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - else if(msg.keys & KEY_ESC) - { - ui_state.edit_mode = false; - _ui_menuBack(MENU_SETTINGS); - } - } - break; - // Module17 Settings - case SETTINGS_MODULE17: - if(msg.keys & KEY_LEFT) - { - switch(ui_state.menu_selected) - { - case D_TXWIPER: - _ui_changeWiper(&mod17CalData.tx_wiper, -1); - break; - case D_RXWIPER: - _ui_changeWiper(&mod17CalData.rx_wiper, -1); - break; - case D_TXINVERT: - mod17CalData.bb_tx_invert -= 1; - break; - case D_RXINVERT: - mod17CalData.bb_rx_invert -= 1; - break; - case D_MICGAIN: - _ui_changeMicGain(-1); - break; - case D_PTTINLEVEL: - mod17CalData.ptt_in_level -= 1; - break; - case D_PTTOUTLEVEL: - mod17CalData.ptt_out_level -= 1; - break; - default: - state.ui_screen = SETTINGS_MODULE17; - } - } - else if(msg.keys & KEY_RIGHT) - { - switch(ui_state.menu_selected) - { - case D_TXWIPER: - _ui_changeWiper(&mod17CalData.tx_wiper, +1); - break; - case D_RXWIPER: - _ui_changeWiper(&mod17CalData.rx_wiper, +1); - break; - case D_TXINVERT: - mod17CalData.bb_tx_invert += 1; - break; - case D_RXINVERT: - mod17CalData.bb_rx_invert += 1; - break; - case D_MICGAIN: - _ui_changeMicGain(+1); - break; - case D_PTTINLEVEL: - mod17CalData.ptt_in_level += 1; - break; - case D_PTTOUTLEVEL: - mod17CalData.ptt_out_level += 1; - break; - default: - state.ui_screen = SETTINGS_MODULE17; - } - } - else if(msg.keys & KEY_UP || msg.keys & KNOB_LEFT) - _ui_menuUp(module17_num); - else if(msg.keys & KEY_DOWN || msg.keys & KNOB_RIGHT) - _ui_menuDown(module17_num); - else if(msg.keys & KEY_ESC) - { - nvm_writeSettings(&state.settings); - _ui_menuBack(MENU_SETTINGS); - } - break; - } - } -} - -bool ui_updateGUI() -{ - if(!layout_ready) - { - layout = _ui_calculateLayout(); - layout_ready = true; - } - // Draw current GUI page - switch(last_state.ui_screen) - { - // VFO main screen - case MAIN_VFO: - _ui_drawMainVFO(&ui_state); - break; - // Top menu screen - case MENU_TOP: - _ui_drawMenuTop(&ui_state); - break; - // Settings menu screen - case MENU_SETTINGS: - _ui_drawMenuSettings(&ui_state); - break; - // Info menu screen - case MENU_INFO: - _ui_drawMenuInfo(&ui_state); - break; - // About menu screen - case MENU_ABOUT: - _ui_drawMenuAbout(&ui_state); - break; - // Display settings screen - case SETTINGS_DISPLAY: - _ui_drawSettingsDisplay(&ui_state); - break; - // M17 settings screen - case SETTINGS_M17: - _ui_drawSettingsM17(&ui_state); - break; - // Module 17 settings screen - case SETTINGS_MODULE17: - _ui_drawSettingsModule17(&ui_state); - break; - // Screen to support resetting Settings and VFO to defaults - case SETTINGS_RESET2DEFAULTS: - _ui_drawSettingsReset2Defaults(&ui_state); - break; - } - - return true; -} - -void ui_terminate() -{ -} diff --git a/openrtx/src/ui/module17/ui.cpp b/openrtx/src/ui/module17/ui.cpp index ee7894d82d..34998e58ad 100644 --- a/openrtx/src/ui/module17/ui.cpp +++ b/openrtx/src/ui/module17/ui.cpp @@ -18,8 +18,10 @@ #include "core/input.h" #include "hwconfig.h" #include "ui/ui_events.h" +#include "ui/ui_strings.h" #include "ui/Screen.hpp" #include "ui/ui_draw.h" +#include "ui/ui_nav.h" // module17-variant-specific draw functions and helpers extern "C" { @@ -126,11 +128,7 @@ const color_t yellow_fab413 = {250, 180, 19, 255}; state_t last_state; static ui_state_t ui_state; -static Screen *current = nullptr; -static Screen *lastMainScreen = nullptr; -static bool *gSyncRtx = nullptr; - -static void transition(Screen *next); +static bool *gSyncRtx = nullptr; // Forward declarations of static helpers used in Screen::handleInput bodies static void _ui_changeBrightness(int variation); @@ -319,7 +317,7 @@ static SettingsReset2DefaultsScreen settingsReset2DefaultsScreen; static void transition(Screen *next) { - current = next; + ui_nav_transition(next); ui_state.menu_selected = 0; } @@ -363,7 +361,7 @@ bool VfoScreen::handleInput(event_t ev) { if(msg.keys & KEY_ENTER) { - lastMainScreen = current; + ui_nav_setLastMain(ui_nav_current()); transition(&menuTopScreen); } else if(msg.keys & KEY_RIGHT) @@ -393,7 +391,7 @@ bool MenuTopScreen::handleInput(event_t ev) } } else if(msg.keys & KEY_ESC) - _ui_menuBack(lastMainScreen); + _ui_menuBack(ui_nav_lastMain()); return true; } @@ -658,9 +656,46 @@ bool SettingsReset2DefaultsScreen::handleInput(event_t ev) void ui_init() { - ui_state = ui_state_t{}; - current = &vfoScreen; - lastMainScreen = &vfoScreen; + ui_state = ui_state_t{}; + ui_nav_init(&vfoScreen); + + // Refresh translatable menu strings from the active language table. + // Hardware-specific strings ("Shutdown", "Module 17", etc.) stay hardcoded. + menu_items[M_SETTINGS] = currentLanguage->settings; +#ifdef CONFIG_GPS + menu_items[M_GPS] = currentLanguage->gps; +#endif + menu_items[M_INFO] = currentLanguage->info; + menu_items[M_ABOUT] = currentLanguage->about; + + settings_items[S_DISPLAY] = currentLanguage->display; +#ifdef CONFIG_RTC + settings_items[S_TIMEDATE] = currentLanguage->timeAndDate; +#endif +#ifdef CONFIG_GPS + settings_items[S_GPS] = currentLanguage->gpsSettings; +#endif + settings_items[S_M17] = currentLanguage->m17; + settings_items[S_RESET2DEFAULTS] = currentLanguage->defaultSettings; + + display_items[D_BRIGHTNESS] = currentLanguage->brightness; + +#ifdef CONFIG_GPS + settings_gps_items[G_ENABLED] = currentLanguage->gpsEnabled; + settings_gps_items[G_SET_TIME] = currentLanguage->gpsSetTime; + settings_gps_items[G_TIMEZONE] = currentLanguage->UTCTimeZone; +#endif + + m17_items[M_CALLSIGN] = currentLanguage->callsign; + m17_items[M_METATEXT] = currentLanguage->metaText; + m17_items[M_CAN] = currentLanguage->CAN; + m17_items[M_CAN_RX] = currentLanguage->canRxCheck; + + info_items[1] = currentLanguage->usedHeap; + + authors[0] = currentLanguage->Niccolo; + authors[1] = currentLanguage->Silvano; + authors[2] = currentLanguage->Federico; } void ui_drawSplashScreen() @@ -777,12 +812,12 @@ static void _ui_menuUp(uint8_t menu_entries) const hwInfo_t* hwinfo = platform_getHwInfo(); // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (current == (Screen*)&menuTopScreen)) + if((hwinfo->hw_version < 1) && (ui_nav_current() == (Screen*)&menuTopScreen)) maxEntries -= 1; // Hide the softpot menu entries if hardware does not have them uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (current == (Screen*)&settingsModule17Screen)) + if((softpot == 0) && (ui_nav_current() == (Screen*)&settingsModule17Screen)) maxEntries -= 2; if(ui_state.menu_selected > 0) @@ -797,12 +832,12 @@ static void _ui_menuDown(uint8_t menu_entries) const hwInfo_t* hwinfo = platform_getHwInfo(); // Hide the "shutdown" main menu entry for versions lower than 0.1e - if((hwinfo->hw_version < 1) && (current == (Screen*)&menuTopScreen)) + if((hwinfo->hw_version < 1) && (ui_nav_current() == (Screen*)&menuTopScreen)) maxEntries -= 1; // Hide the softpot menu entries if hardware does not have them uint8_t softpot = hwinfo->flags & MOD17_FLAGS_SOFTPOT; - if((softpot == 0) && (current == (Screen*)&settingsModule17Screen)) + if((softpot == 0) && (ui_nav_current() == (Screen*)&settingsModule17Screen)) maxEntries -= 2; if(ui_state.menu_selected < maxEntries) @@ -879,16 +914,13 @@ void ui_updateFSM(bool *sync_rtx) if(!ui_popEvent(&event)) return; gSyncRtx = sync_rtx; - current->handleInput(event); + ui_nav_current()->handleInput(event); } bool ui_updateGUI() { - current->render(); + ui_nav_current()->render(); return true; } -void ui_terminate() -{ -}