5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
23 #define MODEL_SPECIAL_FUNC_1ST_COLUMN (0)
24 #define MODEL_SPECIAL_FUNC_2ND_COLUMN (4*FW-1)
25 #define MODEL_SPECIAL_FUNC_3RD_COLUMN (15*FW-3)
26 #define MODEL_SPECIAL_FUNC_4TH_COLUMN (20*FW)
28 #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (20*FW)
30 #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (18*FW+2)
34 void onCustomFunctionsFileSelectionMenu(const char * result
)
36 int sub
= menuVerticalPosition
- HEADER_LINE
;
37 CustomFunctionData
* cfn
;
40 if (menuHandlers
[menuLevel
] == menuModelSpecialFunctions
) {
41 cfn
= &g_model
.customFn
[sub
];
45 cfn
= &g_eeGeneral
.customFn
[sub
];
49 uint8_t func
= CFN_FUNC(cfn
);
51 if (result
== STR_UPDATE_LIST
) {
53 if (func
== FUNC_PLAY_SCRIPT
) {
54 strcpy(directory
, SCRIPTS_FUNCS_PATH
);
57 strcpy(directory
, SOUNDS_PATH
);
58 strncpy(directory
+SOUNDS_PATH_LNG_OFS
, currentLanguagePack
->id
, 2);
60 if (!sdListFiles(directory
, func
==FUNC_PLAY_SCRIPT
? SCRIPTS_EXT
: SOUNDS_EXT
, sizeof(cfn
->play
.name
), nullptr)) {
61 POPUP_WARNING(func
==FUNC_PLAY_SCRIPT
? STR_NO_SCRIPTS_ON_SD
: STR_NO_SOUNDS_ON_SD
);
64 else if (result
!= STR_EXIT
) {
65 // The user choosed a file in the list
66 memcpy(cfn
->play
.name
, result
, sizeof(cfn
->play
.name
));
67 storageDirty(eeFlags
);
72 #if defined(PCBTARANIS)
73 void onAdjustGvarSourceLongEnterPress(const char * result
)
75 CustomFunctionData
* cfn
= &g_model
.customFn
[menuVerticalPosition
];
77 if (result
== STR_CONSTANT
) {
78 CFN_GVAR_MODE(cfn
) = FUNC_ADJUST_GVAR_CONSTANT
;
80 storageDirty(EE_MODEL
);
82 else if (result
== STR_MIXSOURCE
) {
83 CFN_GVAR_MODE(cfn
) = FUNC_ADJUST_GVAR_SOURCE
;
85 storageDirty(EE_MODEL
);
87 else if (result
== STR_GLOBALVAR
) {
88 CFN_GVAR_MODE(cfn
) = FUNC_ADJUST_GVAR_GVAR
;
90 storageDirty(EE_MODEL
);
92 else if (result
== STR_INCDEC
) {
93 CFN_GVAR_MODE(cfn
) = FUNC_ADJUST_GVAR_INCDEC
;
95 storageDirty(EE_MODEL
);
97 else if (result
!= STR_EXIT
) {
98 onSourceLongEnterPress(result
);
102 void onCustomFunctionsMenu(const char * result
)
104 int sub
= menuVerticalPosition
- HEADER_LINE
;
105 CustomFunctionData
* cfn
;
108 if (menuHandlers
[menuLevel
] == menuModelSpecialFunctions
) {
109 cfn
= &g_model
.customFn
[sub
];
113 cfn
= &g_eeGeneral
.customFn
[sub
];
114 eeFlags
= EE_GENERAL
;
117 if (result
== STR_COPY
) {
118 clipboard
.type
= CLIPBOARD_TYPE_CUSTOM_FUNCTION
;
119 clipboard
.data
.cfn
= *cfn
;
121 else if (result
== STR_PASTE
) {
122 *cfn
= clipboard
.data
.cfn
;
123 storageDirty(eeFlags
);
125 else if (result
== STR_CLEAR
) {
126 memset(cfn
, 0, sizeof(CustomFunctionData
));
127 storageDirty(eeFlags
);
129 else if (result
== STR_INSERT
) {
130 memmove(cfn
+1, cfn
, (MAX_SPECIAL_FUNCTIONS
-sub
-1)*sizeof(CustomFunctionData
));
131 memset(cfn
, 0, sizeof(CustomFunctionData
));
132 storageDirty(eeFlags
);
134 else if (result
== STR_DELETE
) {
135 memmove(cfn
, cfn
+1, (MAX_SPECIAL_FUNCTIONS
-sub
-1)*sizeof(CustomFunctionData
));
136 memset(&g_model
.customFn
[MAX_SPECIAL_FUNCTIONS
-1], 0, sizeof(CustomFunctionData
));
137 storageDirty(eeFlags
);
142 void menuSpecialFunctions(event_t event
, CustomFunctionData
* functions
, CustomFunctionsContext
* functionsContext
)
144 int8_t sub
= menuVerticalPosition
- HEADER_LINE
;
146 uint8_t eeFlags
= (functions
== g_model
.customFn
) ? EE_MODEL
: EE_GENERAL
;
148 #if defined(PCBTARANIS)
149 #if defined(PCBXLITE)
150 // ENT LONG on xlite brings up switch type menu, so this menu is activated with SHIT + ENT LONG
151 if (menuHorizontalPosition
==0 && event
==EVT_KEY_LONG(KEY_ENTER
) && IS_SHIFT_PRESSED() && !READ_ONLY()) {
153 if (menuHorizontalPosition
<0 && event
==EVT_KEY_LONG(KEY_ENTER
) && !READ_ONLY()) {
156 CustomFunctionData
*cfn
= &functions
[sub
];
158 POPUP_MENU_ADD_ITEM(STR_COPY
);
159 if (clipboard
.type
== CLIPBOARD_TYPE_CUSTOM_FUNCTION
&& isAssignableFunctionAvailable(clipboard
.data
.cfn
.func
))
160 POPUP_MENU_ADD_ITEM(STR_PASTE
);
161 if (!CFN_EMPTY(cfn
) && CFN_EMPTY(&functions
[MAX_SPECIAL_FUNCTIONS
-1]))
162 POPUP_MENU_ADD_ITEM(STR_INSERT
);
164 POPUP_MENU_ADD_ITEM(STR_CLEAR
);
165 for (int i
=sub
+1; i
<MAX_SPECIAL_FUNCTIONS
; i
++) {
166 if (!CFN_EMPTY(&functions
[i
])) {
167 POPUP_MENU_ADD_ITEM(STR_DELETE
);
171 POPUP_MENU_START(onCustomFunctionsMenu
);
175 for (uint8_t i
=0; i
<NUM_BODY_LINES
; i
++) {
176 coord_t y
= MENU_HEADER_HEIGHT
+ 1 + i
*FH
;
177 uint8_t k
= i
+menuVerticalOffset
;
179 CustomFunctionData
* cfn
= &functions
[k
];
180 uint8_t func
= CFN_FUNC(cfn
);
181 for (uint8_t j
=0; j
<5; j
++) {
182 uint8_t attr
= ((sub
==k
&& menuHorizontalPosition
==j
) ? ((s_editMode
>0) ? BLINK
|INVERS
: INVERS
) : 0);
183 uint8_t active
= (attr
&& s_editMode
> 0);
186 if (sub
==k
&& menuHorizontalPosition
< 1 && CFN_SWITCH(cfn
) == SWSRC_NONE
) {
187 drawSwitch(MODEL_SPECIAL_FUNC_1ST_COLUMN
, y
, CFN_SWITCH(cfn
), attr
| INVERS
| ((functionsContext
->activeSwitches
& ((MASK_CFN_TYPE
)1 << k
)) ? BOLD
: 0));
188 if (active
) CHECK_INCDEC_SWITCH(event
, CFN_SWITCH(cfn
), SWSRC_FIRST
, SWSRC_LAST
, eeFlags
, isSwitchAvailableInCustomFunctions
);
191 drawSwitch(MODEL_SPECIAL_FUNC_1ST_COLUMN
, y
, CFN_SWITCH(cfn
), attr
| ((functionsContext
->activeSwitches
& ((MASK_CFN_TYPE
)1 << k
)) ? BOLD
: 0));
192 if (active
|| AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event
, CFN_SWITCH(cfn
), SWSRC_FIRST
, SWSRC_LAST
, eeFlags
, isSwitchAvailableInCustomFunctions
);
194 if (func
== FUNC_OVERRIDE_CHANNEL
&& functions
!= g_model
.customFn
) {
195 func
= CFN_FUNC(cfn
) = func
+1;
200 if (CFN_SWITCH(cfn
)) {
201 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_2ND_COLUMN
, y
, STR_VFSWFUNC
, func
, attr
);
203 CFN_FUNC(cfn
) = checkIncDec(event
, CFN_FUNC(cfn
), 0, FUNC_MAX
-1, eeFlags
, isAssignableFunctionAvailable
);
204 if (checkIncDec_Ret
) CFN_RESET(cfn
);
208 j
= 4; // skip other fields
209 if (sub
==k
&& menuHorizontalPosition
> 0) {
210 REPEAT_LAST_CURSOR_MOVE();
217 int8_t maxParam
= MAX_OUTPUT_CHANNELS
-1;
218 #if defined(OVERRIDE_CHANNEL_FUNCTION)
219 if (func
== FUNC_OVERRIDE_CHANNEL
) {
220 putsChn(lcdNextPos
, y
, CFN_CH_INDEX(cfn
)+1, attr
);
224 if (func
== FUNC_TRAINER
) {
225 maxParam
= NUM_STICKS
+ 1;
226 uint8_t param
= CFN_CH_INDEX(cfn
);
228 lcdDrawText(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_STICKS
, attr
);
229 else if (param
== NUM_STICKS
+ 1)
230 lcdDrawText(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_CHANS
, attr
);
232 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, MIXSRC_Rud
+ param
- 1, attr
);
235 else if (func
== FUNC_ADJUST_GVAR
) {
236 maxParam
= MAX_GVARS
-1;
237 drawStringWithIndex(lcdNextPos
+ 2, y
, STR_GV
, CFN_GVAR_INDEX(cfn
)+1, attr
);
238 if (active
) CFN_GVAR_INDEX(cfn
) = checkIncDec(event
, CFN_GVAR_INDEX(cfn
), 0, maxParam
, eeFlags
);
242 else if (func
== FUNC_SET_TIMER
) {
243 maxParam
= MAX_TIMERS
-1;
244 lcdDrawTextAtIndex(lcdNextPos
, y
, STR_VFSWRESET
, CFN_TIMER_INDEX(cfn
), attr
);
245 if (active
) CFN_TIMER_INDEX(cfn
) = checkIncDec(event
, CFN_TIMER_INDEX(cfn
), 0, maxParam
, eeFlags
);
249 REPEAT_LAST_CURSOR_MOVE();
251 if (active
) CHECK_INCDEC_MODELVAR_ZERO(event
, CFN_CH_INDEX(cfn
), maxParam
);
257 INCDEC_DECLARE_VARS(eeFlags
);
258 int16_t val_displayed
= CFN_PARAM(cfn
);
260 int16_t val_max
= 255;
261 if (func
== FUNC_RESET
) {
262 val_max
= FUNC_RESET_PARAM_FIRST_TELEM
+lastUsedTelemetryIndex();
263 int param
= CFN_PARAM(cfn
);
264 if (param
< FUNC_RESET_PARAM_FIRST_TELEM
) {
265 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_VFSWRESET
, param
, attr
);
268 TelemetrySensor
* sensor
= & g_model
.telemetrySensors
[param
-FUNC_RESET_PARAM_FIRST_TELEM
];
269 lcdDrawSizedText(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, sensor
->label
, TELEM_LABEL_LEN
, attr
|ZCHAR
);
271 if (active
) INCDEC_ENABLE_CHECK(functionsContext
== &globalFunctionsContext
? isSourceAvailableInGlobalResetSpecialFunction
: isSourceAvailableInResetSpecialFunction
);
273 #if defined(OVERRIDE_CHANNEL_FUNCTION)
274 else if (func
== FUNC_OVERRIDE_CHANNEL
) {
275 getMixSrcRange(MIXSRC_FIRST_CH
, val_min
, val_max
);
276 lcdDrawNumber(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
|LEFT
);
278 #endif // OVERRIDE_CHANNEL_FUNCTION
279 else if (func
>= FUNC_SET_FAILSAFE
&& func
<= FUNC_BIND
) {
280 val_max
= NUM_MODULES
-1;
281 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, "\004Int.Ext.", CFN_PARAM(cfn
), attr
);
283 else if (func
== FUNC_SET_TIMER
) {
284 getMixSrcRange(MIXSRC_FIRST_TIMER
, val_min
, val_max
);
285 drawTimer(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
|LEFT
, attr
);
288 else if (func
== FUNC_PLAY_SOUND
) {
289 val_max
= AU_SPECIAL_SOUND_LAST
-AU_SPECIAL_SOUND_FIRST
-1;
290 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_FUNCSOUNDS
, val_displayed
, attr
);
294 else if (func
== FUNC_HAPTIC
) {
296 lcdDrawNumber(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
|LEFT
);
300 else if (func
== FUNC_PLAY_TRACK
|| func
== FUNC_BACKGND_MUSIC
|| func
== FUNC_PLAY_SCRIPT
) {
301 if (ZEXIST(cfn
->play
.name
))
302 lcdDrawSizedText(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, cfn
->play
.name
, sizeof(cfn
->play
.name
), attr
);
304 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_VCSWFUNC
, 0, attr
);
305 if (active
&& event
==EVT_KEY_BREAK(KEY_ENTER
)) {
308 if (func
==FUNC_PLAY_SCRIPT
) {
309 strcpy(directory
, SCRIPTS_FUNCS_PATH
);
312 strcpy(directory
, SOUNDS_PATH
);
313 strncpy(directory
+SOUNDS_PATH_LNG_OFS
, currentLanguagePack
->id
, 2);
315 if (sdListFiles(directory
, func
==FUNC_PLAY_SCRIPT
? SCRIPTS_EXT
: SOUNDS_EXT
, sizeof(cfn
->play
.name
), cfn
->play
.name
)) {
316 POPUP_MENU_START(onCustomFunctionsFileSelectionMenu
);
319 POPUP_WARNING(func
==FUNC_PLAY_SCRIPT
? STR_NO_SCRIPTS_ON_SD
: STR_NO_SOUNDS_ON_SD
);
324 else if (func
== FUNC_PLAY_VALUE
) {
325 val_max
= MIXSRC_LAST_TELEM
;
326 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
);
328 INCDEC_SET_FLAG(eeFlags
| INCDEC_SOURCE
);
329 INCDEC_ENABLE_CHECK(functionsContext
== &globalFunctionsContext
? isSourceAvailableInGlobalFunctions
: isSourceAvailable
);
333 else if (func
== FUNC_VOLUME
) {
334 val_max
= MIXSRC_LAST_CH
;
335 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
);
337 INCDEC_SET_FLAG(eeFlags
| INCDEC_SOURCE
);
338 INCDEC_ENABLE_CHECK(isSourceAvailable
);
342 else if (func
== FUNC_LOGS
) {
344 lcdDrawNumber(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
|PREC1
|LEFT
);
345 lcdDrawChar(lcdLastRightPos
, y
, 's');
348 lcdDrawMMM(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, attr
);
353 else if (func
== FUNC_ADJUST_GVAR
) {
354 switch (CFN_GVAR_MODE(cfn
)) {
355 case FUNC_ADJUST_GVAR_CONSTANT
:
356 val_displayed
= (int16_t)CFN_PARAM(cfn
);
357 getMixSrcRange(CFN_GVAR_INDEX(cfn
) + MIXSRC_FIRST_GVAR
, val_min
, val_max
);
358 drawGVarValue(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, CFN_GVAR_INDEX(cfn
), val_displayed
, attr
|LEFT
);
360 case FUNC_ADJUST_GVAR_SOURCE
:
361 val_max
= MIXSRC_LAST_CH
;
362 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, val_displayed
, attr
);
364 INCDEC_SET_FLAG(eeFlags
| INCDEC_SOURCE
);
365 INCDEC_ENABLE_CHECK(isSourceAvailable
);
368 case FUNC_ADJUST_GVAR_GVAR
:
369 val_max
= MAX_GVARS
-1;
370 drawStringWithIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, STR_GV
, val_displayed
+1, attr
);
372 default: // FUNC_ADJUST_GVAR_INC
373 getMixSrcRange(CFN_GVAR_INDEX(cfn
) + MIXSRC_FIRST_GVAR
, val_min
, val_max
);
374 getGVarIncDecRange(val_min
, val_max
);
375 lcdDrawText(MODEL_SPECIAL_FUNC_3RD_COLUMN
, y
, (val_displayed
< 0 ? "-= " : "+= "), attr
);
376 drawGVarValue(lcdNextPos
, y
, CFN_GVAR_INDEX(cfn
), abs(val_displayed
), attr
|LEFT
);
380 if (attr
&& event
==EVT_KEY_LONG(KEY_ENTER
)) {
382 s_editMode
= !s_editMode
;
384 CFN_GVAR_MODE(cfn
) += 1;
385 CFN_GVAR_MODE(cfn
) &= 0x03;
391 REPEAT_LAST_CURSOR_MOVE();
393 #if defined(NAVIGATION_X7)
394 if (active
|| event
==EVT_KEY_LONG(KEY_ENTER
)) {
395 CFN_PARAM(cfn
) = CHECK_INCDEC_PARAM(event
, val_displayed
, val_min
, val_max
);
396 if (func
== FUNC_ADJUST_GVAR
&& attr
&& event
==EVT_KEY_LONG(KEY_ENTER
)) {
398 if (CFN_GVAR_MODE(cfn
) != FUNC_ADJUST_GVAR_CONSTANT
)
399 POPUP_MENU_ADD_ITEM(STR_CONSTANT
);
400 if (CFN_GVAR_MODE(cfn
) != FUNC_ADJUST_GVAR_SOURCE
)
401 POPUP_MENU_ADD_ITEM(STR_MIXSOURCE
);
402 if (CFN_GVAR_MODE(cfn
) != FUNC_ADJUST_GVAR_GVAR
)
403 POPUP_MENU_ADD_ITEM(STR_GLOBALVAR
);
404 if (CFN_GVAR_MODE(cfn
) != FUNC_ADJUST_GVAR_INCDEC
)
405 POPUP_MENU_ADD_ITEM(STR_INCDEC
);
406 POPUP_MENU_START(onAdjustGvarSourceLongEnterPress
);
407 s_editMode
= EDIT_MODIFY_FIELD
;
411 CFN_PARAM(cfn
) = CHECK_INCDEC_PARAM(event
, val_displayed
, val_min
, val_max
);
418 if (HAS_ENABLE_PARAM(func
)) {
419 drawCheckBox(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF
, y
, CFN_ACTIVE(cfn
), attr
);
420 if (active
) CFN_ACTIVE(cfn
) = checkIncDec(event
, CFN_ACTIVE(cfn
), 0, 1, eeFlags
);
422 else if (HAS_REPEAT_PARAM(func
)) {
423 if (CFN_PLAY_REPEAT(cfn
) == 0) {
424 lcdDrawChar(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF
+3, y
, '-', attr
);
426 else if (CFN_PLAY_REPEAT(cfn
) == CFN_PLAY_REPEAT_NOSTART
) {
427 lcdDrawText(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF
+1, y
, "!-", attr
);
430 lcdDrawNumber(MODEL_SPECIAL_FUNC_4TH_COLUMN
+2+FW
, y
, CFN_PLAY_REPEAT(cfn
)*CFN_PLAY_REPEAT_MUL
, RIGHT
| attr
);
432 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
);
435 REPEAT_LAST_CURSOR_MOVE();
440 #if defined(NAVIGATION_X7)
441 if (sub
==k
&& menuHorizontalPosition
<0 && CFN_SWITCH(cfn
)) {
448 void menuModelSpecialFunctions(event_t event
)
450 #if defined(NAVIGATION_X7)
451 const CustomFunctionData
* cfn
= &g_model
.customFn
[menuVerticalPosition
];
452 if (!CFN_SWITCH(cfn
) && menuHorizontalPosition
< 0 && event
==EVT_KEY_BREAK(KEY_ENTER
)) {
453 menuHorizontalPosition
= 0;
456 MENU(STR_MENUCUSTOMFUNC
, menuTabModel
, MENU_MODEL_SPECIAL_FUNCTIONS
, HEADER_LINE
+MAX_SPECIAL_FUNCTIONS
, { HEADER_LINE_COLUMNS NAVIGATION_LINE_BY_LINE
|4/*repeated*/ });
458 menuSpecialFunctions(event
, g_model
.customFn
, &modelFunctionsContext
);
460 #if defined(NAVIGATION_X7)
461 if (!CFN_SWITCH(cfn
) && menuHorizontalPosition
== 0 && s_editMode
<= 0) {
462 menuHorizontalPosition
= -1;