Companion: Russian UI (#7180)
[opentx.git] / radio / src / gui / 128x64 / model_special_functions.cpp
blobbfcee26c3ba06b020130da788a855b5a428e243d
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
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.
21 #include "opentx.h"
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)
27 #if defined(GRAPHICS)
28 #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (20*FW)
29 #else
30 #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (18*FW+2)
31 #endif
33 #if defined(SDCARD)
34 void onCustomFunctionsFileSelectionMenu(const char * result)
36 int sub = menuVerticalPosition - HEADER_LINE;
37 CustomFunctionData * cfn;
38 uint8_t eeFlags;
40 if (menuHandlers[menuLevel] == menuModelSpecialFunctions) {
41 cfn = &g_model.customFn[sub];
42 eeFlags = EE_MODEL;
44 else {
45 cfn = &g_eeGeneral.customFn[sub];
46 eeFlags = EE_GENERAL;
49 uint8_t func = CFN_FUNC(cfn);
51 if (result == STR_UPDATE_LIST) {
52 char directory[256];
53 if (func == FUNC_PLAY_SCRIPT) {
54 strcpy(directory, SCRIPTS_FUNCS_PATH);
56 else {
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);
70 #endif // SDCARD
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;
79 CFN_PARAM(cfn) = 0;
80 storageDirty(EE_MODEL);
82 else if (result == STR_MIXSOURCE) {
83 CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_SOURCE;
84 CFN_PARAM(cfn) = 0;
85 storageDirty(EE_MODEL);
87 else if (result == STR_GLOBALVAR) {
88 CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_GVAR;
89 CFN_PARAM(cfn) = 0;
90 storageDirty(EE_MODEL);
92 else if (result == STR_INCDEC) {
93 CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_INCDEC;
94 CFN_PARAM(cfn) = 0;
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;
106 uint8_t eeFlags;
108 if (menuHandlers[menuLevel] == menuModelSpecialFunctions) {
109 cfn = &g_model.customFn[sub];
110 eeFlags = EE_MODEL;
112 else {
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);
140 #endif // PCBTARANIS
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()) {
152 #else
153 if (menuHorizontalPosition<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) {
154 #endif
155 killEvents(event);
156 CustomFunctionData *cfn = &functions[sub];
157 if (!CFN_EMPTY(cfn))
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);
163 if (!CFN_EMPTY(cfn))
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);
168 break;
171 POPUP_MENU_START(onCustomFunctionsMenu);
173 #endif // PCBTARANIS
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);
184 switch (j) {
185 case 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);
190 else {
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;
197 break;
199 case 1:
200 if (CFN_SWITCH(cfn)) {
201 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr);
202 if (active) {
203 CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable);
204 if (checkIncDec_Ret) CFN_RESET(cfn);
207 else {
208 j = 4; // skip other fields
209 if (sub==k && menuHorizontalPosition > 0) {
210 REPEAT_LAST_CURSOR_MOVE();
213 break;
215 case 2:
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);
222 else
223 #endif
224 if (func == FUNC_TRAINER) {
225 maxParam = NUM_STICKS + 1;
226 uint8_t param = CFN_CH_INDEX(cfn);
227 if (param == 0)
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);
231 else
232 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, MIXSRC_Rud + param - 1, attr);
234 #if defined(GVARS)
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);
239 break;
241 #endif // GVARS
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);
246 break;
248 else if (attr) {
249 REPEAT_LAST_CURSOR_MOVE();
251 if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_CH_INDEX(cfn), maxParam);
252 break;
255 case 3:
257 INCDEC_DECLARE_VARS(eeFlags);
258 int16_t val_displayed = CFN_PARAM(cfn);
259 int16_t val_min = 0;
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);
267 else {
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);
287 #if defined(AUDIO)
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);
292 #endif
293 #if defined(HAPTIC)
294 else if (func == FUNC_HAPTIC) {
295 val_max = 3;
296 lcdDrawNumber(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT);
298 #endif
299 #if defined(SDCARD)
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);
303 else
304 lcdDrawTextAtIndex(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, STR_VCSWFUNC, 0, attr);
305 if (active && event==EVT_KEY_BREAK(KEY_ENTER)) {
306 s_editMode = 0;
307 char directory[256];
308 if (func==FUNC_PLAY_SCRIPT) {
309 strcpy(directory, SCRIPTS_FUNCS_PATH);
311 else {
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);
318 else {
319 POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD);
322 break;
324 else if (func == FUNC_PLAY_VALUE) {
325 val_max = MIXSRC_LAST_TELEM;
326 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, val_displayed, attr);
327 if (active) {
328 INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE);
329 INCDEC_ENABLE_CHECK(functionsContext == &globalFunctionsContext ? isSourceAvailableInGlobalFunctions : isSourceAvailable);
332 #endif // SDCARD
333 else if (func == FUNC_VOLUME) {
334 val_max = MIXSRC_LAST_CH;
335 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, val_displayed, attr);
336 if (active) {
337 INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE);
338 INCDEC_ENABLE_CHECK(isSourceAvailable);
341 #if defined(SDCARD)
342 else if (func == FUNC_LOGS) {
343 if (val_displayed) {
344 lcdDrawNumber(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT);
345 lcdDrawChar(lcdLastRightPos, y, 's');
347 else {
348 lcdDrawMMM(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, attr);
351 #endif
352 #if defined(GVARS)
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);
359 break;
360 case FUNC_ADJUST_GVAR_SOURCE:
361 val_max = MIXSRC_LAST_CH;
362 drawSource(MODEL_SPECIAL_FUNC_3RD_COLUMN, y, val_displayed, attr);
363 if (active) {
364 INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE);
365 INCDEC_ENABLE_CHECK(isSourceAvailable);
367 break;
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);
371 break;
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);
377 break;
380 if (attr && event==EVT_KEY_LONG(KEY_ENTER)) {
381 killEvents(event);
382 s_editMode = !s_editMode;
383 active = true;
384 CFN_GVAR_MODE(cfn) += 1;
385 CFN_GVAR_MODE(cfn) &= 0x03;
386 val_displayed = 0;
389 #endif // GVARS
390 else if (attr) {
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)) {
397 killEvents(event);
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;
409 #else
410 if (active) {
411 CFN_PARAM(cfn) = CHECK_INCDEC_PARAM(event, val_displayed, val_min, val_max);
412 #endif
414 break;
417 case 4:
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);
429 else {
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);
434 else if (attr) {
435 REPEAT_LAST_CURSOR_MOVE();
437 break;
440 #if defined(NAVIGATION_X7)
441 if (sub==k && menuHorizontalPosition<0 && CFN_SWITCH(cfn)) {
442 lcdInvertLine(i+1);
444 #endif
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;
455 #endif
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;
464 #endif