diff options
Diffstat (limited to 'apps/radio/presets.c')
-rw-r--r-- | apps/radio/presets.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/apps/radio/presets.c b/apps/radio/presets.c new file mode 100644 index 0000000000..b996e68443 --- /dev/null +++ b/apps/radio/presets.c | |||
@@ -0,0 +1,614 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id: radio.c -1 $ | ||
9 | * | ||
10 | * Copyright (C) 2003 Linus Nielsen Feltzing | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "config.h" | ||
23 | #include <stdio.h> | ||
24 | #include <stdbool.h> | ||
25 | #include <stdlib.h> | ||
26 | #include "settings.h" | ||
27 | #include "general.h" | ||
28 | #include "radio.h" | ||
29 | #include "tuner.h" | ||
30 | #include "file.h" | ||
31 | #include "string-extra.h" | ||
32 | #include "misc.h" | ||
33 | #include "lang.h" | ||
34 | #include "action.h" | ||
35 | #include "list.h" | ||
36 | #include "splash.h" | ||
37 | #include "menu.h" | ||
38 | #include "yesno.h" | ||
39 | #include "keyboard.h" | ||
40 | #include "talk.h" | ||
41 | #include "filetree.h" | ||
42 | #include "dir.h" | ||
43 | |||
44 | static int curr_preset = -1; | ||
45 | |||
46 | extern int curr_freq; /* from radio.c.. naughty but meh */ | ||
47 | extern int radio_mode; | ||
48 | int snap_freq_to_grid(int freq); | ||
49 | void remember_frequency(void); | ||
50 | void talk_freq(int freq, bool enqueue); | ||
51 | |||
52 | #define MAX_PRESETS 64 | ||
53 | static bool presets_loaded = false, presets_changed = false; | ||
54 | static struct fmstation presets[MAX_PRESETS]; | ||
55 | |||
56 | static char filepreset[MAX_PATH]; /* preset filename variable */ | ||
57 | |||
58 | static int num_presets = 0; /* The number of presets in the preset list */ | ||
59 | |||
60 | bool yesno_pop(const char* text); /* radio.c */ | ||
61 | |||
62 | int radio_current_preset(void) | ||
63 | { | ||
64 | return curr_preset; | ||
65 | } | ||
66 | int radio_preset_count(void) | ||
67 | { | ||
68 | return num_presets; | ||
69 | } | ||
70 | const struct fmstation *radio_get_preset(int preset) | ||
71 | { | ||
72 | return &presets[preset]; | ||
73 | } | ||
74 | |||
75 | bool has_presets_changed(void) | ||
76 | { | ||
77 | return presets_changed; | ||
78 | } | ||
79 | |||
80 | |||
81 | /* Find a matching preset to freq */ | ||
82 | int find_preset(int freq) | ||
83 | { | ||
84 | int i; | ||
85 | if(num_presets < 1) | ||
86 | return -1; | ||
87 | for(i = 0;i < MAX_PRESETS;i++) | ||
88 | { | ||
89 | if(freq == presets[i].frequency) | ||
90 | return i; | ||
91 | } | ||
92 | |||
93 | return -1; | ||
94 | } | ||
95 | |||
96 | /* Return the closest preset encountered in the search direction with | ||
97 | wraparound. */ | ||
98 | int find_closest_preset(int freq, int direction) | ||
99 | { | ||
100 | int i; | ||
101 | int lowpreset = 0; | ||
102 | int highpreset = 0; | ||
103 | int closest = -1; | ||
104 | |||
105 | if (direction == 0) /* direction == 0 isn't really used */ | ||
106 | return 0; | ||
107 | |||
108 | for (i = 0; i < num_presets; i++) | ||
109 | { | ||
110 | int f = presets[i].frequency; | ||
111 | if (f == freq) | ||
112 | return i; /* Exact match = stop */ | ||
113 | |||
114 | /* remember the highest and lowest presets for wraparound */ | ||
115 | if (f < presets[lowpreset].frequency) | ||
116 | lowpreset = i; | ||
117 | if (f > presets[highpreset].frequency) | ||
118 | highpreset = i; | ||
119 | |||
120 | /* find the closest preset in the given direction */ | ||
121 | if (direction > 0 && f > freq) | ||
122 | { | ||
123 | if (closest < 0 || f < presets[closest].frequency) | ||
124 | closest = i; | ||
125 | } | ||
126 | else if (direction < 0 && f < freq) | ||
127 | { | ||
128 | if (closest < 0 || f > presets[closest].frequency) | ||
129 | closest = i; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | if (closest < 0) | ||
134 | { | ||
135 | /* no presets in the given direction */ | ||
136 | /* wrap around depending on direction */ | ||
137 | if (direction < 0) | ||
138 | closest = highpreset; | ||
139 | else | ||
140 | closest = lowpreset; | ||
141 | } | ||
142 | |||
143 | return closest; | ||
144 | } | ||
145 | |||
146 | void next_preset(int direction) | ||
147 | { | ||
148 | if (num_presets < 1) | ||
149 | return; | ||
150 | |||
151 | if (curr_preset == -1) | ||
152 | curr_preset = find_closest_preset(curr_freq, direction); | ||
153 | else | ||
154 | curr_preset = (curr_preset + direction + num_presets) % num_presets; | ||
155 | |||
156 | /* Must stay on the current grid for the region */ | ||
157 | curr_freq = snap_freq_to_grid(presets[curr_preset].frequency); | ||
158 | |||
159 | tuner_set(RADIO_FREQUENCY, curr_freq); | ||
160 | remember_frequency(); | ||
161 | } | ||
162 | |||
163 | void set_current_preset(int preset) | ||
164 | { | ||
165 | curr_preset = preset; | ||
166 | } | ||
167 | |||
168 | /* Speak a preset by number or by spelling its name, depending on settings. */ | ||
169 | void talk_preset(int preset, bool fallback, bool enqueue) | ||
170 | { | ||
171 | if (global_settings.talk_file == 1) /* number */ | ||
172 | talk_number(preset + 1, enqueue); | ||
173 | else | ||
174 | { /* spell */ | ||
175 | if(presets[preset].name[0]) | ||
176 | talk_spell(presets[preset].name, enqueue); | ||
177 | else if(fallback) | ||
178 | talk_freq(presets[preset].frequency, enqueue); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | |||
183 | void radio_save_presets(void) | ||
184 | { | ||
185 | int fd; | ||
186 | int i; | ||
187 | |||
188 | fd = creat(filepreset, 0666); | ||
189 | if(fd >= 0) | ||
190 | { | ||
191 | for(i = 0;i < num_presets;i++) | ||
192 | { | ||
193 | fdprintf(fd, "%d:%s\n", presets[i].frequency, presets[i].name); | ||
194 | } | ||
195 | close(fd); | ||
196 | |||
197 | if(!strncasecmp(FMPRESET_PATH, filepreset, strlen(FMPRESET_PATH))) | ||
198 | set_file(filepreset, global_settings.fmr_file, MAX_FILENAME); | ||
199 | presets_changed = false; | ||
200 | } | ||
201 | else | ||
202 | { | ||
203 | splash(HZ, ID2P(LANG_FM_PRESET_SAVE_FAILED)); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | void radio_load_presets(char *filename) | ||
208 | { | ||
209 | int fd; | ||
210 | int rc; | ||
211 | char buf[128]; | ||
212 | char *freq; | ||
213 | char *name; | ||
214 | bool done = false; | ||
215 | int f; | ||
216 | |||
217 | memset(presets, 0, sizeof(presets)); | ||
218 | num_presets = 0; | ||
219 | |||
220 | /* No Preset in configuration. */ | ||
221 | if(filename[0] == '\0') | ||
222 | { | ||
223 | filepreset[0] = '\0'; | ||
224 | return; | ||
225 | } | ||
226 | /* Temporary preset, loaded until player shuts down. */ | ||
227 | else if(filename[0] == '/') | ||
228 | strlcpy(filepreset, filename, sizeof(filepreset)); | ||
229 | /* Preset from default directory. */ | ||
230 | else | ||
231 | snprintf(filepreset, sizeof(filepreset), "%s/%s.fmr", | ||
232 | FMPRESET_PATH, filename); | ||
233 | |||
234 | fd = open_utf8(filepreset, O_RDONLY); | ||
235 | if(fd >= 0) | ||
236 | { | ||
237 | while(!done && num_presets < MAX_PRESETS) | ||
238 | { | ||
239 | rc = read_line(fd, buf, 128); | ||
240 | if(rc > 0) | ||
241 | { | ||
242 | if(settings_parseline(buf, &freq, &name)) | ||
243 | { | ||
244 | f = atoi(freq); | ||
245 | if(f) /* For backwards compatibility */ | ||
246 | { | ||
247 | struct fmstation * const fms = &presets[num_presets]; | ||
248 | fms->frequency = f; | ||
249 | strlcpy(fms->name, name, MAX_FMPRESET_LEN+1); | ||
250 | num_presets++; | ||
251 | } | ||
252 | } | ||
253 | } | ||
254 | else | ||
255 | done = true; | ||
256 | } | ||
257 | close(fd); | ||
258 | } | ||
259 | else /* invalid file name? */ | ||
260 | filepreset[0] = '\0'; | ||
261 | |||
262 | presets_loaded = num_presets > 0; | ||
263 | presets_changed = false; | ||
264 | } | ||
265 | |||
266 | const char* radio_get_preset_name(int preset) | ||
267 | { | ||
268 | if (preset < num_presets) | ||
269 | return presets[preset].name; | ||
270 | return NULL; | ||
271 | } | ||
272 | |||
273 | int radio_add_preset(void) | ||
274 | { | ||
275 | char buf[MAX_FMPRESET_LEN + 1]; | ||
276 | |||
277 | if(num_presets < MAX_PRESETS) | ||
278 | { | ||
279 | buf[0] = '\0'; | ||
280 | |||
281 | if (!kbd_input(buf, MAX_FMPRESET_LEN + 1)) | ||
282 | { | ||
283 | struct fmstation * const fms = &presets[num_presets]; | ||
284 | strcpy(fms->name, buf); | ||
285 | fms->frequency = curr_freq; | ||
286 | num_presets++; | ||
287 | presets_changed = true; | ||
288 | presets_loaded = num_presets > 0; | ||
289 | return true; | ||
290 | } | ||
291 | } | ||
292 | else | ||
293 | { | ||
294 | splash(HZ, ID2P(LANG_FM_NO_FREE_PRESETS)); | ||
295 | } | ||
296 | return false; | ||
297 | } | ||
298 | |||
299 | /* needed to know which preset we are edit/delete-ing */ | ||
300 | static int selected_preset = -1; | ||
301 | static int radio_edit_preset(void) | ||
302 | { | ||
303 | char buf[MAX_FMPRESET_LEN + 1]; | ||
304 | |||
305 | if (num_presets > 0) | ||
306 | { | ||
307 | struct fmstation * const fms = &presets[selected_preset]; | ||
308 | |||
309 | strcpy(buf, fms->name); | ||
310 | |||
311 | if (!kbd_input(buf, MAX_FMPRESET_LEN + 1)) | ||
312 | { | ||
313 | strcpy(fms->name, buf); | ||
314 | presets_changed = true; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | return 1; | ||
319 | } | ||
320 | |||
321 | static int radio_delete_preset(void) | ||
322 | { | ||
323 | if (num_presets > 0) | ||
324 | { | ||
325 | struct fmstation * const fms = &presets[selected_preset]; | ||
326 | |||
327 | if (selected_preset >= --num_presets) | ||
328 | selected_preset = num_presets - 1; | ||
329 | |||
330 | memmove(fms, fms + 1, (uintptr_t)(fms + num_presets) - | ||
331 | (uintptr_t)fms); | ||
332 | |||
333 | if (curr_preset >= num_presets) | ||
334 | --curr_preset; | ||
335 | } | ||
336 | |||
337 | /* Don't ask to save when all presets are deleted. */ | ||
338 | presets_changed = num_presets > 0; | ||
339 | |||
340 | if (!presets_changed) | ||
341 | { | ||
342 | /* The preset list will be cleared, switch to Scan Mode. */ | ||
343 | radio_mode = RADIO_SCAN_MODE; | ||
344 | curr_preset = -1; | ||
345 | presets_loaded = false; | ||
346 | } | ||
347 | |||
348 | return 1; | ||
349 | } | ||
350 | |||
351 | int load_preset_list(void) | ||
352 | { | ||
353 | return !rockbox_browse(FMPRESET_PATH, SHOW_FMR); | ||
354 | } | ||
355 | |||
356 | int save_preset_list(void) | ||
357 | { | ||
358 | if(num_presets > 0) | ||
359 | { | ||
360 | bool bad_file_name = true; | ||
361 | |||
362 | if(!dir_exists(FMPRESET_PATH)) /* Check if there is preset folder */ | ||
363 | mkdir(FMPRESET_PATH); | ||
364 | |||
365 | create_numbered_filename(filepreset, FMPRESET_PATH, "preset", | ||
366 | ".fmr", 2 IF_CNFN_NUM_(, NULL)); | ||
367 | |||
368 | while(bad_file_name) | ||
369 | { | ||
370 | if(!kbd_input(filepreset, sizeof(filepreset))) | ||
371 | { | ||
372 | /* check the name: max MAX_FILENAME (20) chars */ | ||
373 | char* p2; | ||
374 | char* p1; | ||
375 | int len; | ||
376 | p1 = strrchr(filepreset, '/'); | ||
377 | p2 = p1; | ||
378 | while((p1) && (*p2) && (*p2 != '.')) | ||
379 | p2++; | ||
380 | len = (int)(p2-p1) - 1; | ||
381 | if((!p1) || (len > MAX_FILENAME) || (len == 0)) | ||
382 | { | ||
383 | /* no slash, too long or too short */ | ||
384 | splash(HZ, ID2P(LANG_INVALID_FILENAME)); | ||
385 | } | ||
386 | else | ||
387 | { | ||
388 | /* add correct extension (easier to always write) | ||
389 | at this point, p2 points to 0 or the extension dot */ | ||
390 | *p2 = '\0'; | ||
391 | strcat(filepreset,".fmr"); | ||
392 | bad_file_name = false; | ||
393 | radio_save_presets(); | ||
394 | } | ||
395 | } | ||
396 | else | ||
397 | { | ||
398 | /* user aborted */ | ||
399 | return false; | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | else | ||
404 | splash(HZ, ID2P(LANG_FM_NO_PRESETS)); | ||
405 | |||
406 | return true; | ||
407 | } | ||
408 | |||
409 | int clear_preset_list(void) | ||
410 | { | ||
411 | /* Clear all the preset entries */ | ||
412 | memset(presets, 0, sizeof (presets)); | ||
413 | |||
414 | num_presets = 0; | ||
415 | presets_loaded = false; | ||
416 | /* The preset list will be cleared switch to Scan Mode. */ | ||
417 | radio_mode = RADIO_SCAN_MODE; | ||
418 | curr_preset = -1; | ||
419 | presets_changed = false; /* Don't ask to save when clearing the list. */ | ||
420 | |||
421 | return true; | ||
422 | } | ||
423 | |||
424 | MENUITEM_FUNCTION(radio_edit_preset_item, MENU_FUNC_CHECK_RETVAL, | ||
425 | ID2P(LANG_FM_EDIT_PRESET), | ||
426 | radio_edit_preset, NULL, NULL, Icon_NOICON); | ||
427 | MENUITEM_FUNCTION(radio_delete_preset_item, MENU_FUNC_CHECK_RETVAL, | ||
428 | ID2P(LANG_FM_DELETE_PRESET), | ||
429 | radio_delete_preset, NULL, NULL, Icon_NOICON); | ||
430 | static int radio_preset_callback(int action, | ||
431 | const struct menu_item_ex *this_item) | ||
432 | { | ||
433 | if (action == ACTION_STD_OK) | ||
434 | action = ACTION_EXIT_AFTER_THIS_MENUITEM; | ||
435 | return action; | ||
436 | (void)this_item; | ||
437 | } | ||
438 | MAKE_MENU(handle_radio_preset_menu, ID2P(LANG_PRESET), | ||
439 | radio_preset_callback, Icon_NOICON, &radio_edit_preset_item, | ||
440 | &radio_delete_preset_item); | ||
441 | /* present a list of preset stations */ | ||
442 | static const char* presets_get_name(int selected_item, void *data, | ||
443 | char *buffer, size_t buffer_len) | ||
444 | { | ||
445 | (void)data; | ||
446 | struct fmstation *p = &presets[selected_item]; | ||
447 | if(p->name[0]) | ||
448 | return p->name; | ||
449 | int freq = p->frequency / 10000; | ||
450 | int frac = freq % 100; | ||
451 | freq /= 100; | ||
452 | snprintf(buffer, buffer_len, | ||
453 | str(LANG_FM_DEFAULT_PRESET_NAME), freq, frac); | ||
454 | return buffer; | ||
455 | } | ||
456 | |||
457 | static int presets_speak_name(int selected_item, void * data) | ||
458 | { | ||
459 | (void)data; | ||
460 | talk_preset(selected_item, true, false); | ||
461 | return 0; | ||
462 | } | ||
463 | |||
464 | int handle_radio_presets(void) | ||
465 | { | ||
466 | struct gui_synclist lists; | ||
467 | int result = 0; | ||
468 | int action = ACTION_NONE; | ||
469 | #ifdef HAVE_BUTTONBAR | ||
470 | struct gui_buttonbar buttonbar; | ||
471 | #endif | ||
472 | |||
473 | if(presets_loaded == false) | ||
474 | return result; | ||
475 | |||
476 | #ifdef HAVE_BUTTONBAR | ||
477 | gui_buttonbar_init(&buttonbar); | ||
478 | gui_buttonbar_set_display(&buttonbar, &(screens[SCREEN_MAIN]) ); | ||
479 | gui_buttonbar_set(&buttonbar, str(LANG_FM_BUTTONBAR_ADD), | ||
480 | str(LANG_FM_BUTTONBAR_EXIT), | ||
481 | str(LANG_FM_BUTTONBAR_ACTION)); | ||
482 | gui_buttonbar_draw(&buttonbar); | ||
483 | #endif | ||
484 | gui_synclist_init(&lists, presets_get_name, NULL, false, 1, NULL); | ||
485 | gui_synclist_set_title(&lists, str(LANG_PRESET), NOICON); | ||
486 | gui_synclist_set_icon_callback(&lists, NULL); | ||
487 | if(global_settings.talk_file) | ||
488 | gui_synclist_set_voice_callback(&lists, presets_speak_name); | ||
489 | gui_synclist_set_nb_items(&lists, num_presets); | ||
490 | gui_synclist_select_item(&lists, curr_preset<0 ? 0 : curr_preset); | ||
491 | gui_synclist_speak_item(&lists); | ||
492 | |||
493 | while (result == 0) | ||
494 | { | ||
495 | gui_synclist_draw(&lists); | ||
496 | list_do_action(CONTEXT_STD, TIMEOUT_BLOCK, | ||
497 | &lists, &action, LIST_WRAP_UNLESS_HELD); | ||
498 | switch (action) | ||
499 | { | ||
500 | case ACTION_STD_MENU: | ||
501 | if (radio_add_preset()) | ||
502 | { | ||
503 | gui_synclist_set_nb_items(&lists, num_presets); | ||
504 | gui_synclist_select_item(&lists, num_presets - 1); | ||
505 | } | ||
506 | break; | ||
507 | case ACTION_STD_CANCEL: | ||
508 | result = 1; | ||
509 | break; | ||
510 | case ACTION_STD_OK: | ||
511 | curr_preset = gui_synclist_get_sel_pos(&lists); | ||
512 | curr_freq = presets[curr_preset].frequency; | ||
513 | next_station(0); | ||
514 | remember_frequency(); | ||
515 | result = 1; | ||
516 | break; | ||
517 | case ACTION_F3: | ||
518 | case ACTION_STD_CONTEXT: | ||
519 | selected_preset = gui_synclist_get_sel_pos(&lists); | ||
520 | do_menu(&handle_radio_preset_menu, NULL, NULL, false); | ||
521 | gui_synclist_set_nb_items(&lists, num_presets); | ||
522 | gui_synclist_select_item(&lists, selected_preset); | ||
523 | gui_synclist_speak_item(&lists); | ||
524 | break; | ||
525 | default: | ||
526 | if(default_event_handler(action) == SYS_USB_CONNECTED) | ||
527 | result = 2; | ||
528 | } | ||
529 | } | ||
530 | return result - 1; | ||
531 | } | ||
532 | |||
533 | |||
534 | int scan_presets(void *viewports) | ||
535 | { | ||
536 | bool do_scan = true; | ||
537 | int i; | ||
538 | struct viewport *vp = (struct viewport *)viewports; | ||
539 | |||
540 | FOR_NB_SCREENS(i) | ||
541 | screens[i].set_viewport(vp?&vp[i]:NULL); | ||
542 | if(num_presets > 0) /* Do that to avoid 2 questions. */ | ||
543 | do_scan = yesno_pop(ID2P(LANG_FM_CLEAR_PRESETS)); | ||
544 | |||
545 | if(do_scan) | ||
546 | { | ||
547 | const struct fm_region_data * const fmr = | ||
548 | &fm_region_data[global_settings.fm_region]; | ||
549 | |||
550 | curr_freq = fmr->freq_min; | ||
551 | num_presets = 0; | ||
552 | memset(presets, 0, sizeof(presets)); | ||
553 | |||
554 | tuner_set(RADIO_MUTE, 1); | ||
555 | |||
556 | while(curr_freq <= fmr->freq_max) | ||
557 | { | ||
558 | int freq, frac; | ||
559 | if(num_presets >= MAX_PRESETS || action_userabort(TIMEOUT_NOBLOCK)) | ||
560 | break; | ||
561 | |||
562 | freq = curr_freq / 10000; | ||
563 | frac = freq % 100; | ||
564 | freq /= 100; | ||
565 | |||
566 | splashf(0, str(LANG_FM_SCANNING), freq, frac); | ||
567 | |||
568 | if(tuner_set(RADIO_SCAN_FREQUENCY, curr_freq)) | ||
569 | { | ||
570 | /* add preset */ | ||
571 | presets[num_presets].name[0] = '\0'; | ||
572 | presets[num_presets].frequency = curr_freq; | ||
573 | num_presets++; | ||
574 | } | ||
575 | |||
576 | curr_freq += fmr->freq_step; | ||
577 | } | ||
578 | |||
579 | if (get_radio_status() == FMRADIO_PLAYING) | ||
580 | tuner_set(RADIO_MUTE, 0); | ||
581 | |||
582 | presets_changed = true; | ||
583 | |||
584 | FOR_NB_SCREENS(i) | ||
585 | { | ||
586 | screens[i].clear_viewport(); | ||
587 | screens[i].update_viewport(); | ||
588 | } | ||
589 | |||
590 | if(num_presets > 0) | ||
591 | { | ||
592 | curr_freq = presets[0].frequency; | ||
593 | radio_mode = RADIO_PRESET_MODE; | ||
594 | presets_loaded = true; | ||
595 | next_station(0); | ||
596 | } | ||
597 | else | ||
598 | { | ||
599 | /* Wrap it to beginning or we'll be past end of band */ | ||
600 | presets_loaded = false; | ||
601 | next_station(1); | ||
602 | } | ||
603 | } | ||
604 | return true; | ||
605 | } | ||
606 | |||
607 | |||
608 | void presets_save(void) | ||
609 | { | ||
610 | if(filepreset[0] == '\0') | ||
611 | save_preset_list(); | ||
612 | else | ||
613 | radio_save_presets(); | ||
614 | } | ||