diff --git a/companion/src/firmwares/opentx/opentxGruvin9xsimulator.cpp b/companion/src/firmwares/opentx/opentxGruvin9xsimulator.cpp index d3b6f8c8e..efe1f2849 100644 --- a/companion/src/firmwares/opentx/opentxGruvin9xsimulator.cpp +++ b/companion/src/firmwares/opentx/opentxGruvin9xsimulator.cpp @@ -75,6 +75,7 @@ namespace Open9xGruvin9x { #include "radio/src/vario.cpp" #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_curves.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" diff --git a/companion/src/firmwares/opentx/opentxM128simulator.cpp b/companion/src/firmwares/opentx/opentxM128simulator.cpp index caebb982f..d78fd2312 100644 --- a/companion/src/firmwares/opentx/opentxM128simulator.cpp +++ b/companion/src/firmwares/opentx/opentxM128simulator.cpp @@ -77,6 +77,7 @@ namespace OpenTxM128 { #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" #include "radio/src/gui/menu_model_curves.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" #include "radio/src/gui/menu_general.cpp" diff --git a/companion/src/firmwares/opentx/opentxM64simulator.cpp b/companion/src/firmwares/opentx/opentxM64simulator.cpp index 4eb8e551a..be46c1434 100644 --- a/companion/src/firmwares/opentx/opentxM64simulator.cpp +++ b/companion/src/firmwares/opentx/opentxM64simulator.cpp @@ -82,6 +82,7 @@ namespace OpenTxM64 { #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" #include "radio/src/gui/menu_model_curves.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" #include "radio/src/gui/menu_general.cpp" diff --git a/companion/src/firmwares/opentx/opentxSky9xsimulator.cpp b/companion/src/firmwares/opentx/opentxSky9xsimulator.cpp index cfa17035c..cdc23d3c9 100644 --- a/companion/src/firmwares/opentx/opentxSky9xsimulator.cpp +++ b/companion/src/firmwares/opentx/opentxSky9xsimulator.cpp @@ -91,6 +91,7 @@ namespace Open9xSky9x { #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" #include "radio/src/gui/menu_model_curves.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" #include "radio/src/gui/menu_general.cpp" diff --git a/companion/src/firmwares/opentx/opentxTaranisSimulator.cpp b/companion/src/firmwares/opentx/opentxTaranisSimulator.cpp index e78c4edd8..682c7eef1 100644 --- a/companion/src/firmwares/opentx/opentxTaranisSimulator.cpp +++ b/companion/src/firmwares/opentx/opentxTaranisSimulator.cpp @@ -97,6 +97,7 @@ inline int geteepromsize() { #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" #include "radio/src/gui/menu_model_curves.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_custom_scripts.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" diff --git a/companion/src/firmwares/opentx/opentxTaranisX9ESimulator.cpp b/companion/src/firmwares/opentx/opentxTaranisX9ESimulator.cpp index 2fbd80be1..cfc7a0675 100755 --- a/companion/src/firmwares/opentx/opentxTaranisX9ESimulator.cpp +++ b/companion/src/firmwares/opentx/opentxTaranisX9ESimulator.cpp @@ -99,6 +99,7 @@ inline int geteepromsize() { #include "radio/src/gui/menus.cpp" #include "radio/src/gui/menu_model.cpp" #include "radio/src/gui/menu_model_curves.cpp" +#include "radio/src/gui/menu_model_custom_functions.cpp" #include "radio/src/gui/menu_model_custom_scripts.cpp" #include "radio/src/gui/menu_model_limits.cpp" #include "radio/src/gui/menu_model_telemetry.cpp" diff --git a/radio/src/Makefile b/radio/src/Makefile index 76c447cff..5fbf1eb63 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -365,7 +365,7 @@ M128_VARIANT = +32768 M2561_VARIANT = +16384 EEPROM_VARIANT = 0 -GUIMODELSRC = gui/menu_model.cpp gui/menu_model_limits.cpp gui/menu_model_telemetry.cpp +GUIMODELSRC = gui/menu_model.cpp gui/menu_model_limits.cpp gui/menu_model_custom_functions.cpp gui/menu_model_telemetry.cpp ifeq ($(PCB), $(filter $(PCB), STD 9X 9XR)) # 9x/9xr radio diff --git a/radio/src/gui/menu_model.cpp b/radio/src/gui/menu_model.cpp index 6665128ff..7bce8d4da 100644 --- a/radio/src/gui/menu_model.cpp +++ b/radio/src/gui/menu_model.cpp @@ -4308,458 +4308,6 @@ void menuModelLogicalSwitches(uint8_t event) } #endif -#if LCD_W >= 212 - #define MODEL_CUSTOM_FUNC_1ST_COLUMN (4*FW+2) - #define MODEL_CUSTOM_FUNC_2ND_COLUMN (8*FW+2) - #define MODEL_CUSTOM_FUNC_3RD_COLUMN (21*FW) - #define MODEL_CUSTOM_FUNC_4TH_COLUMN (33*FW-3) - #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (34*FW-3) -#else - #define MODEL_CUSTOM_FUNC_1ST_COLUMN (3) - #define MODEL_CUSTOM_FUNC_2ND_COLUMN (5*FW-2) - #define MODEL_CUSTOM_FUNC_3RD_COLUMN (15*FW+2) - #define MODEL_CUSTOM_FUNC_4TH_COLUMN (20*FW) - #if defined(GRAPHICS) - #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (20*FW) - #else - #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (18*FW+2) - #endif -#endif - -#if defined(CPUARM) && defined(SDCARD) -void onCustomFunctionsFileSelectionMenu(const char *result) -{ - int8_t sub = m_posVert - 1; - CustomFunctionData * cf = &g_model.customFn[sub]; - uint8_t func = CFN_FUNC(cf); - - if (result == STR_UPDATE_LIST) { - char directory[256]; - if (func == FUNC_PLAY_SCRIPT) { - strcpy(directory, SCRIPTS_FUNCS_PATH); - } - else { - strcpy(directory, SOUNDS_PATH); - strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); - } - if (!listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cf->play.name), NULL)) { - POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); - s_menu_flags = 0; - } - } - else { - // The user choosed a file in the list - memcpy(cf->play.name, result, sizeof(cf->play.name)); - eeDirty(EE_MODEL); - if (func == FUNC_PLAY_SCRIPT) { - LUA_LOAD_MODEL_SCRIPTS(); - } - } -} -#endif - -#if defined(PCBTARANIS) -void onCustomFunctionsMenu(const char *result) -{ - int8_t sub = m_posVert-1; - CustomFunctionData * cfn; - uint8_t eeFlags; - - if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) { - cfn = &g_model.customFn[sub]; - eeFlags = EE_MODEL; - } - else { - cfn = &g_eeGeneral.customFn[sub]; - eeFlags = EE_GENERAL; - } - - if (result == STR_COPY) { - clipboard.type = CLIPBOARD_TYPE_CUSTOM_FUNCTION; - clipboard.data.cfn = *cfn; - } - else if (result == STR_PASTE) { - *cfn = clipboard.data.cfn; - eeDirty(eeFlags); - } - else if (result == STR_CLEAR) { - memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); - } - else if (result == STR_INSERT) { - memmove(cfn+1, cfn, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); - memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); - } - else if (result == STR_DELETE) { - memmove(cfn, cfn+1, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); - memset(&g_model.customFn[NUM_CFN-1], 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); - } -} -#endif - -void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext) -{ - int8_t sub = m_posVert - 1; - -#if defined(CPUARM) - uint8_t eeFlags = (functions == g_model.customFn) ? EE_MODEL : EE_GENERAL; -#elif !defined(CPUM64) || defined(AUTOSWITCH) - uint8_t eeFlags = EE_MODEL; -#endif - -#if defined(PCBTARANIS) - if (sub>=0 && m_posHorz<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { - killEvents(event); - CustomFunctionData *cfn = &functions[sub]; - if (!CFN_EMPTY(cfn)) - MENU_ADD_ITEM(STR_COPY); - if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) - MENU_ADD_ITEM(STR_PASTE); - if (!CFN_EMPTY(cfn) && CFN_EMPTY(&functions[NUM_CFN-1])) - MENU_ADD_ITEM(STR_INSERT); - if (!CFN_EMPTY(cfn)) - MENU_ADD_ITEM(STR_CLEAR); - for (int i=sub+1; i= 212 - putsStrIdx(0, y, functions == g_model.customFn ? STR_SF : STR_GF, k+1, (sub==k && m_posHorz<0) ? INVERS : 0); -#endif - - CustomFunctionData *cfn = &functions[k]; - uint8_t func = CFN_FUNC(cfn); - for (uint8_t j=0; j<5; j++) { - uint8_t attr = ((sub==k && m_posHorz==j) ? ((s_editMode>0) ? BLINK|INVERS : INVERS) : 0); - uint8_t active = (attr && (s_editMode>0 || p1valdiff)); - switch (j) { - case 0: - putsSwitches(MODEL_CUSTOM_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); - if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); - break; - - case 1: - if (CFN_SWITCH(cfn)) { -#if defined(CPUARM) - if (func == FUNC_OVERRIDE_CHANNEL) { - func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); - } -#endif - lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); - if (active) { -#if defined(CPUARM) - CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); -#else - CHECK_INCDEC_MODELVAR_ZERO(event, CFN_FUNC(cfn), FUNC_MAX-1); -#endif - if (checkIncDec_Ret) CFN_RESET(cfn); - } - } - else { - j = 4; // skip other fields - if (sub==k && m_posHorz > 0) { - REPEAT_LAST_CURSOR_MOVE(); - } - } - break; - - case 2: - { - int8_t maxParam = NUM_CHNOUT-1; -#if defined(OVERRIDE_CHANNEL_FUNCTION) - if (func == FUNC_OVERRIDE_CHANNEL) { - putsChn(lcdNextPos, y, CFN_CH_INDEX(cfn)+1, attr); - } - else -#endif - if (func == FUNC_TRAINER) { - maxParam = 4; -#if defined(CPUARM) - putsMixerSource(lcdNextPos, y, CFN_CH_INDEX(cfn)==0 ? 0 : MIXSRC_Rud+CFN_CH_INDEX(cfn)-1, attr); -#else - putsMixerSource(lcdNextPos, y, MIXSRC_Rud+CFN_CH_INDEX(cfn)-1, attr); -#endif - } -#if defined(GVARS) - else if (func == FUNC_ADJUST_GVAR) { - maxParam = MAX_GVARS-1; - putsStrIdx(lcdNextPos, y, STR_GV, CFN_GVAR_INDEX(cfn)+1, attr); -#if defined(CPUARM) - if (active) CFN_GVAR_INDEX(cfn) = checkIncDec(event, CFN_GVAR_INDEX(cfn), 0, maxParam, eeFlags); -#else - if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_GVAR_INDEX(cfn), maxParam); -#endif - break; - } -#endif -#if defined(CPUARM) - else if (func == FUNC_SET_TIMER) { - maxParam = MAX_TIMERS-1; - putsStrIdx(lcdNextPos, y, STR_TIMER, CFN_TIMER_INDEX(cfn)+1, attr); - if (active) CFN_TIMER_INDEX(cfn) = checkIncDec(event, CFN_TIMER_INDEX(cfn), 0, maxParam, eeFlags); - break; - } -#endif - else if (attr) { - REPEAT_LAST_CURSOR_MOVE(); - } - if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_CH_INDEX(cfn), maxParam); - break; - } - - case 3: - { - INCDEC_DECLARE_VARS(eeFlags); - int16_t val_displayed = CFN_PARAM(cfn); -#if defined(CPUARM) - int16_t val_min = 0; - int16_t val_max = 255; -#else - int8_t val_min = 0; - uint8_t val_max = 255; -#endif - if (func == FUNC_RESET) { - val_max = FUNC_RESET_PARAM_LAST; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); - } -#if defined(OVERRIDE_CHANNEL_FUNCTION) - else if (func == FUNC_OVERRIDE_CHANNEL) { -#if !defined(CPUARM) - val_displayed = (int8_t)CFN_PARAM(cfn); -#endif - val_min = -LIMIT_EXT_PERCENT; val_max = +LIMIT_EXT_PERCENT; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); - } -#endif -#if defined(DANGEROUS_MODULE_FUNCTIONS) - else if (func >= FUNC_RANGECHECK && func <= FUNC_MODULE_OFF) { - val_max = NUM_MODULES-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); - } -#endif -#if defined(CPUARM) - else if (func == FUNC_SET_TIMER) { - val_max = 59*60+59; - putsTimer(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT, attr); - } -#endif -#if defined(AUDIO) - else if (func == FUNC_PLAY_SOUND) { - val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); - } -#endif -#if defined(HAPTIC) - else if (func == FUNC_HAPTIC) { - val_max = 3; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); - } -#endif -#if defined(CPUARM) && defined(SDCARD) - else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { -#if LCD_W >= 212 - coord_t x = MODEL_CUSTOM_FUNC_3RD_COLUMN; -#else - coord_t x = (func == FUNC_PLAY_TRACK ? MODEL_CUSTOM_FUNC_2ND_COLUMN + FW + FW*strlen(TR_PLAY_TRACK) : MODEL_CUSTOM_FUNC_3RD_COLUMN); -#endif - if (ZEXIST(cfn->play.name)) - lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); - else - lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); - if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { - s_editMode = 0; - char directory[256]; - if (func==FUNC_PLAY_SCRIPT) { - strcpy(directory, SCRIPTS_FUNCS_PATH); - } - else { - strcpy(directory, SOUNDS_PATH); - strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); - } - if (listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { - menuHandler = onCustomFunctionsFileSelectionMenu; - } - else { - POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); - s_menu_flags = 0; - } - } - break; - } - else if (func == FUNC_PLAY_VALUE) { - val_max = MIXSRC_LAST_TELEM; - putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); - INCDEC_ENABLE_CHECK(isSourceAvailable); - } -#endif -#if defined(CPUARM) - else if (func == FUNC_VOLUME) { - val_max = MIXSRC_LAST_CH; - putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); - INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); - INCDEC_ENABLE_CHECK(isSourceAvailable); - } -#elif defined(VOICE) - else if (func == FUNC_PLAY_TRACK) { -#if defined(GVARS) - if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { - killEvents(event); - s_editMode = !s_editMode; - active = true; - val_displayed = (val_displayed > 250 ? 0 : 251); - } - if (val_displayed > 250) { - putsStrIdx(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_GV, val_displayed-250, attr); - } - else { - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed+PROMPT_CUSTOM_BASE, attr|LEFT); - } -#else - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed+PROMPT_CUSTOM_BASE, attr|LEFT); -#endif - } - else if (func == FUNC_PLAY_BOTH) { - lcd_putcAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+3*FWNUM, y, '|', attr); - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+3*FWNUM, y, val_displayed+PROMPT_CUSTOM_BASE, attr); - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+2+3*FWNUM, y, (val_displayed+PROMPT_CUSTOM_BASE+1)%10, attr|LEFT); - } - else if (func == FUNC_PLAY_VALUE) { - val_max = MIXSRC_FIRST_TELEM + TELEM_DISPLAY_MAX - 1; - putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); - INCDEC_ENABLE_CHECK(isSourceAvailable); - } -#endif -#if defined(SDCARD) - else if (func == FUNC_LOGS) { - if (val_displayed) { - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT); - lcd_putc(lcdLastPos, y, 's'); - } - else { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); - } - } -#endif -#if defined(PCBTARANIS) && defined(REVPLUS) - else if (func == FUNC_BACKLIGHT) { - displaySlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); - INCDEC_SET_FLAG(eeFlags | NO_INCDEC_MARKS); - val_min = 0; - val_max = 100; - } -#endif -#if defined(GVARS) - else if (func == FUNC_ADJUST_GVAR) { - switch (CFN_GVAR_MODE(cfn)) { - case FUNC_ADJUST_GVAR_CONSTANT: - val_displayed = (int16_t)CFN_PARAM(cfn); - val_min = -CFN_GVAR_CST_MAX; val_max = +CFN_GVAR_CST_MAX; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); - break; - case FUNC_ADJUST_GVAR_SOURCE: - val_max = MIXSRC_LAST_CH; - putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); - INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); - INCDEC_ENABLE_CHECK(isSourceAvailable); - break; - case FUNC_ADJUST_GVAR_GVAR: - val_max = MAX_GVARS-1; - putsStrIdx(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_GV, val_displayed+1, attr); - break; - default: // FUNC_ADJUST_GVAR_INC - val_max = 1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); - break; - } - - if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { - killEvents(event); - s_editMode = !s_editMode; - active = true; - CFN_GVAR_MODE(cfn) += 1; -#if defined(CPUARM) - CFN_GVAR_MODE(cfn) &= 0x03; -#endif - val_displayed = 0; - } - } -#endif - else if (attr) { - REPEAT_LAST_CURSOR_MOVE(); - } - - if (active) { - CFN_PARAM(cfn) = CHECK_INCDEC_PARAM(event, val_displayed, val_min, val_max); - } - break; - } - - case 4: - if (HAS_ENABLE_PARAM(func)) { - menu_lcd_onoff(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); -#if defined(CPUARM) - if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); -#else - if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_ACTIVE(cfn), 1); -#endif - } - else if (HAS_REPEAT_PARAM(func)) { - if (CFN_PLAY_REPEAT(cfn) == 0) { -#if LCD_W >= 212 - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); -#else - lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF+3, y, '-', attr); -#endif - } -#if defined(CPUARM) - else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { -#if LCD_W >= 212 - lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, '!', attr); - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); -#else - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, "!-", attr); -#endif - } -#endif - else { - lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr); -#if LCD_W >= 212 - lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, 's', attr); -#endif - } -#if defined(CPUARM) - if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); -#else - if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_PLAY_REPEAT(cfn), 60/CFN_PLAY_REPEAT_MUL); -#endif - } - else if (attr) { - REPEAT_LAST_CURSOR_MOVE(); - } - break; - } - } - } -} - -void menuModelCustomFunctions(uint8_t event) -{ - MENU(STR_MENUCUSTOMFUNC, menuTabModel, e_CustomFunctions, NUM_CFN+1, {0, NAVIGATION_LINE_BY_LINE|4/*repeated*/}); - return menuCustomFunctions(event, g_model.customFn, modelFunctionsContext); -} - #if defined(TEMPLATES) void menuModelTemplates(uint8_t event) { diff --git a/radio/src/gui/menu_model_custom_functions.cpp b/radio/src/gui/menu_model_custom_functions.cpp new file mode 100755 index 000000000..18446395b --- /dev/null +++ b/radio/src/gui/menu_model_custom_functions.cpp @@ -0,0 +1,488 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "../opentx.h" + +#if LCD_W >= 212 + #define MODEL_CUSTOM_FUNC_1ST_COLUMN (4*FW+2) + #define MODEL_CUSTOM_FUNC_2ND_COLUMN (8*FW+2) + #define MODEL_CUSTOM_FUNC_3RD_COLUMN (21*FW) + #define MODEL_CUSTOM_FUNC_4TH_COLUMN (33*FW-3) + #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (34*FW-3) +#else + #define MODEL_CUSTOM_FUNC_1ST_COLUMN (3) + #define MODEL_CUSTOM_FUNC_2ND_COLUMN (5*FW-2) + #define MODEL_CUSTOM_FUNC_3RD_COLUMN (15*FW+2) + #define MODEL_CUSTOM_FUNC_4TH_COLUMN (20*FW) + #if defined(GRAPHICS) + #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (20*FW) + #else + #define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF (18*FW+2) + #endif +#endif + +#if defined(CPUARM) && defined(SDCARD) +void onCustomFunctionsFileSelectionMenu(const char *result) +{ + int8_t sub = m_posVert - 1; + CustomFunctionData * cf = &g_model.customFn[sub]; + uint8_t func = CFN_FUNC(cf); + + if (result == STR_UPDATE_LIST) { + char directory[256]; + if (func == FUNC_PLAY_SCRIPT) { + strcpy(directory, SCRIPTS_FUNCS_PATH); + } + else { + strcpy(directory, SOUNDS_PATH); + strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); + } + if (!listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cf->play.name), NULL)) { + POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); + s_menu_flags = 0; + } + } + else { + // The user choosed a file in the list + memcpy(cf->play.name, result, sizeof(cf->play.name)); + eeDirty(EE_MODEL); + if (func == FUNC_PLAY_SCRIPT) { + LUA_LOAD_MODEL_SCRIPTS(); + } + } +} +#endif + +#if defined(PCBTARANIS) +void onCustomFunctionsMenu(const char *result) +{ + int8_t sub = m_posVert-1; + CustomFunctionData * cfn; + uint8_t eeFlags; + + if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) { + cfn = &g_model.customFn[sub]; + eeFlags = EE_MODEL; + } + else { + cfn = &g_eeGeneral.customFn[sub]; + eeFlags = EE_GENERAL; + } + + if (result == STR_COPY) { + clipboard.type = CLIPBOARD_TYPE_CUSTOM_FUNCTION; + clipboard.data.cfn = *cfn; + } + else if (result == STR_PASTE) { + *cfn = clipboard.data.cfn; + eeDirty(eeFlags); + } + else if (result == STR_CLEAR) { + memset(cfn, 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } + else if (result == STR_INSERT) { + memmove(cfn+1, cfn, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); + memset(cfn, 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } + else if (result == STR_DELETE) { + memmove(cfn, cfn+1, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); + memset(&g_model.customFn[NUM_CFN-1], 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } +} +#endif + +void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext) +{ + int8_t sub = m_posVert - 1; + +#if defined(CPUARM) + uint8_t eeFlags = (functions == g_model.customFn) ? EE_MODEL : EE_GENERAL; +#elif !defined(CPUM64) || defined(AUTOSWITCH) + uint8_t eeFlags = EE_MODEL; +#endif + +#if defined(PCBTARANIS) + if (sub>=0 && m_posHorz<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { + killEvents(event); + CustomFunctionData *cfn = &functions[sub]; + if (!CFN_EMPTY(cfn)) + MENU_ADD_ITEM(STR_COPY); + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) + MENU_ADD_ITEM(STR_PASTE); + if (!CFN_EMPTY(cfn) && CFN_EMPTY(&functions[NUM_CFN-1])) + MENU_ADD_ITEM(STR_INSERT); + if (!CFN_EMPTY(cfn)) + MENU_ADD_ITEM(STR_CLEAR); + for (int i=sub+1; i= 212 + putsStrIdx(0, y, functions == g_model.customFn ? STR_SF : STR_GF, k+1, (sub==k && m_posHorz<0) ? INVERS : 0); +#endif + + CustomFunctionData *cfn = &functions[k]; + uint8_t func = CFN_FUNC(cfn); + for (uint8_t j=0; j<5; j++) { + uint8_t attr = ((sub==k && m_posHorz==j) ? ((s_editMode>0) ? BLINK|INVERS : INVERS) : 0); + uint8_t active = (attr && (s_editMode>0 || p1valdiff)); + switch (j) { + case 0: + putsSwitches(MODEL_CUSTOM_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); + if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); + break; + + case 1: + if (CFN_SWITCH(cfn)) { +#if defined(CPUARM) + if (func == FUNC_OVERRIDE_CHANNEL) { + func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); + } +#endif + lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); + if (active) { +#if defined(CPUARM) + CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); +#else + CHECK_INCDEC_MODELVAR_ZERO(event, CFN_FUNC(cfn), FUNC_MAX-1); +#endif + if (checkIncDec_Ret) CFN_RESET(cfn); + } + } + else { + j = 4; // skip other fields + if (sub==k && m_posHorz > 0) { + REPEAT_LAST_CURSOR_MOVE(); + } + } + break; + + case 2: + { + int8_t maxParam = NUM_CHNOUT-1; +#if defined(OVERRIDE_CHANNEL_FUNCTION) + if (func == FUNC_OVERRIDE_CHANNEL) { + putsChn(lcdNextPos, y, CFN_CH_INDEX(cfn)+1, attr); + } + else +#endif + if (func == FUNC_TRAINER) { + maxParam = 4; +#if defined(CPUARM) + putsMixerSource(lcdNextPos, y, CFN_CH_INDEX(cfn)==0 ? 0 : MIXSRC_Rud+CFN_CH_INDEX(cfn)-1, attr); +#else + putsMixerSource(lcdNextPos, y, MIXSRC_Rud+CFN_CH_INDEX(cfn)-1, attr); +#endif + } +#if defined(GVARS) + else if (func == FUNC_ADJUST_GVAR) { + maxParam = MAX_GVARS-1; + putsStrIdx(lcdNextPos, y, STR_GV, CFN_GVAR_INDEX(cfn)+1, attr); +#if defined(CPUARM) + if (active) CFN_GVAR_INDEX(cfn) = checkIncDec(event, CFN_GVAR_INDEX(cfn), 0, maxParam, eeFlags); +#else + if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_GVAR_INDEX(cfn), maxParam); +#endif + break; + } +#endif +#if defined(CPUARM) + else if (func == FUNC_SET_TIMER) { + maxParam = MAX_TIMERS-1; + putsStrIdx(lcdNextPos, y, STR_TIMER, CFN_TIMER_INDEX(cfn)+1, attr); + if (active) CFN_TIMER_INDEX(cfn) = checkIncDec(event, CFN_TIMER_INDEX(cfn), 0, maxParam, eeFlags); + break; + } +#endif + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_CH_INDEX(cfn), maxParam); + break; + } + + case 3: + { + INCDEC_DECLARE_VARS(eeFlags); + int16_t val_displayed = CFN_PARAM(cfn); +#if defined(CPUARM) + int16_t val_min = 0; + int16_t val_max = 255; +#else + int8_t val_min = 0; + uint8_t val_max = 255; +#endif + if (func == FUNC_RESET) { + val_max = FUNC_RESET_PARAM_LAST; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); + } +#if defined(OVERRIDE_CHANNEL_FUNCTION) + else if (func == FUNC_OVERRIDE_CHANNEL) { +#if !defined(CPUARM) + val_displayed = (int8_t)CFN_PARAM(cfn); +#endif + val_min = -LIMIT_EXT_PERCENT; val_max = +LIMIT_EXT_PERCENT; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + } +#endif +#if defined(DANGEROUS_MODULE_FUNCTIONS) + else if (func >= FUNC_RANGECHECK && func <= FUNC_MODULE_OFF) { + val_max = NUM_MODULES-1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); + } +#endif +#if defined(CPUARM) + else if (func == FUNC_SET_TIMER) { + val_max = 59*60+59; + putsTimer(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT, attr); + } +#endif +#if defined(AUDIO) + else if (func == FUNC_PLAY_SOUND) { + val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); + } +#endif +#if defined(HAPTIC) + else if (func == FUNC_HAPTIC) { + val_max = 3; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + } +#endif +#if defined(CPUARM) && defined(SDCARD) + else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { +#if LCD_W >= 212 + coord_t x = MODEL_CUSTOM_FUNC_3RD_COLUMN; +#else + coord_t x = (func == FUNC_PLAY_TRACK ? MODEL_CUSTOM_FUNC_2ND_COLUMN + FW + FW*strlen(TR_PLAY_TRACK) : MODEL_CUSTOM_FUNC_3RD_COLUMN); +#endif + if (ZEXIST(cfn->play.name)) + lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); + else + lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); + if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { + s_editMode = 0; + char directory[256]; + if (func==FUNC_PLAY_SCRIPT) { + strcpy(directory, SCRIPTS_FUNCS_PATH); + } + else { + strcpy(directory, SOUNDS_PATH); + strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); + } + if (listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { + menuHandler = onCustomFunctionsFileSelectionMenu; + } + else { + POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); + s_menu_flags = 0; + } + } + break; + } + else if (func == FUNC_PLAY_VALUE) { + val_max = MIXSRC_LAST_TELEM; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } +#endif +#if defined(CPUARM) + else if (func == FUNC_VOLUME) { + val_max = MIXSRC_LAST_CH; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } +#elif defined(VOICE) + else if (func == FUNC_PLAY_TRACK) { +#if defined(GVARS) + if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + s_editMode = !s_editMode; + active = true; + val_displayed = (val_displayed > 250 ? 0 : 251); + } + if (val_displayed > 250) { + putsStrIdx(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_GV, val_displayed-250, attr); + } + else { + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed+PROMPT_CUSTOM_BASE, attr|LEFT); + } +#else + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed+PROMPT_CUSTOM_BASE, attr|LEFT); +#endif + } + else if (func == FUNC_PLAY_BOTH) { + lcd_putcAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+3*FWNUM, y, '|', attr); + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+3*FWNUM, y, val_displayed+PROMPT_CUSTOM_BASE, attr); + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN+2+3*FWNUM, y, (val_displayed+PROMPT_CUSTOM_BASE+1)%10, attr|LEFT); + } + else if (func == FUNC_PLAY_VALUE) { + val_max = MIXSRC_FIRST_TELEM + TELEM_DISPLAY_MAX - 1; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } +#endif +#if defined(SDCARD) + else if (func == FUNC_LOGS) { + if (val_displayed) { + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT); + lcd_putc(lcdLastPos, y, 's'); + } + else { + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); + } + } +#endif +#if defined(PCBTARANIS) && defined(REVPLUS) + else if (func == FUNC_BACKLIGHT) { + displaySlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); + INCDEC_SET_FLAG(eeFlags | NO_INCDEC_MARKS); + val_min = 0; + val_max = 100; + } +#endif +#if defined(GVARS) + else if (func == FUNC_ADJUST_GVAR) { + switch (CFN_GVAR_MODE(cfn)) { + case FUNC_ADJUST_GVAR_CONSTANT: + val_displayed = (int16_t)CFN_PARAM(cfn); + val_min = -CFN_GVAR_CST_MAX; val_max = +CFN_GVAR_CST_MAX; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + break; + case FUNC_ADJUST_GVAR_SOURCE: + val_max = MIXSRC_LAST_CH; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailable); + break; + case FUNC_ADJUST_GVAR_GVAR: + val_max = MAX_GVARS-1; + putsStrIdx(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_GV, val_displayed+1, attr); + break; + default: // FUNC_ADJUST_GVAR_INC + val_max = 1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); + break; + } + + if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + s_editMode = !s_editMode; + active = true; + CFN_GVAR_MODE(cfn) += 1; +#if defined(CPUARM) + CFN_GVAR_MODE(cfn) &= 0x03; +#endif + val_displayed = 0; + } + } +#endif + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + + if (active) { + CFN_PARAM(cfn) = CHECK_INCDEC_PARAM(event, val_displayed, val_min, val_max); + } + break; + } + + case 4: + if (HAS_ENABLE_PARAM(func)) { + menu_lcd_onoff(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); +#if defined(CPUARM) + if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); +#else + if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_ACTIVE(cfn), 1); +#endif + } + else if (HAS_REPEAT_PARAM(func)) { + if (CFN_PLAY_REPEAT(cfn) == 0) { +#if LCD_W >= 212 + lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); +#else + lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF+3, y, '-', attr); +#endif + } +#if defined(CPUARM) + else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { +#if LCD_W >= 212 + lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, '!', attr); + lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); +#else + lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, "!-", attr); +#endif + } +#endif + else { + lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr); +#if LCD_W >= 212 + lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, 's', attr); +#endif + } +#if defined(CPUARM) + if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); +#else + if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_PLAY_REPEAT(cfn), 60/CFN_PLAY_REPEAT_MUL); +#endif + } + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + break; + } + } + } +} + +void menuModelCustomFunctions(uint8_t event) +{ + MENU(STR_MENUCUSTOMFUNC, menuTabModel, e_CustomFunctions, NUM_CFN+1, {0, NAVIGATION_LINE_BY_LINE|4/*repeated*/}); + return menuCustomFunctions(event, g_model.customFn, modelFunctionsContext); +}