Extract config file saving code to a function; Do not write the NUL character to...
[kugel-rb.git] / apps / radio / presets.c
blob9c6fedecc7b9d927752747f4f219f3205503b862
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2003 Linus Nielsen Feltzing
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
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 "filefuncs.h"
34 #include "lang.h"
35 #include "action.h"
36 #include "list.h"
37 #include "splash.h"
38 #include "menu.h"
39 #include "yesno.h"
40 #include "keyboard.h"
41 #include "talk.h"
42 #include "filetree.h"
43 #include "dir.h"
44 #include "presets.h"
46 static int curr_preset = -1;
48 extern int curr_freq; /* from radio.c.. naughty but meh */
49 extern int radio_mode;
50 int snap_freq_to_grid(int freq);
51 void remember_frequency(void);
52 void talk_freq(int freq, bool enqueue);
54 #define MAX_PRESETS 64
55 static bool presets_loaded = false;
56 static bool presets_changed = false;
57 static struct fmstation presets[MAX_PRESETS];
59 static char filepreset[MAX_PATH]; /* preset filename variable */
61 static int num_presets = 0; /* The number of presets in the preset list */
63 bool yesno_pop(const char* text); /* radio.c */
65 int radio_current_preset(void)
67 return curr_preset;
69 int radio_preset_count(void)
71 return num_presets;
73 const struct fmstation *radio_get_preset(int preset)
75 return &presets[preset];
78 bool presets_have_changed(void)
80 return presets_changed;
84 /* Find a matching preset to freq */
85 int preset_find(int freq)
87 int i;
88 if(num_presets < 1)
89 return -1;
90 for(i = 0;i < MAX_PRESETS;i++)
92 if(freq == presets[i].frequency)
93 return i;
96 return -1;
99 /* Return the closest preset encountered in the search direction with
100 wraparound. */
101 static int find_closest_preset(int freq, int direction)
103 int i;
104 int lowpreset = 0;
105 int highpreset = 0;
106 int closest = -1;
108 if (direction == 0) /* direction == 0 isn't really used */
109 return 0;
111 for (i = 0; i < num_presets; i++)
113 int f = presets[i].frequency;
114 if (f == freq)
115 return i; /* Exact match = stop */
117 /* remember the highest and lowest presets for wraparound */
118 if (f < presets[lowpreset].frequency)
119 lowpreset = i;
120 if (f > presets[highpreset].frequency)
121 highpreset = i;
123 /* find the closest preset in the given direction */
124 if (direction > 0 && f > freq)
126 if (closest < 0 || f < presets[closest].frequency)
127 closest = i;
129 else if (direction < 0 && f < freq)
131 if (closest < 0 || f > presets[closest].frequency)
132 closest = i;
136 if (closest < 0)
138 /* no presets in the given direction */
139 /* wrap around depending on direction */
140 if (direction < 0)
141 closest = highpreset;
142 else
143 closest = lowpreset;
146 return closest;
149 void preset_next(int direction)
151 if (num_presets < 1)
152 return;
154 if (curr_preset == -1)
155 curr_preset = find_closest_preset(curr_freq, direction);
156 else
157 curr_preset = (curr_preset + direction + num_presets) % num_presets;
159 /* Must stay on the current grid for the region */
160 curr_freq = snap_freq_to_grid(presets[curr_preset].frequency);
162 tuner_set(RADIO_FREQUENCY, curr_freq);
163 remember_frequency();
166 void preset_set_current(int preset)
168 curr_preset = preset;
171 /* Speak a preset by number or by spelling its name, depending on settings. */
172 void preset_talk(int preset, bool fallback, bool enqueue)
174 if (global_settings.talk_file == 1) /* number */
175 talk_number(preset + 1, enqueue);
176 else
177 { /* spell */
178 if(presets[preset].name[0])
179 talk_spell(presets[preset].name, enqueue);
180 else if(fallback)
181 talk_freq(presets[preset].frequency, enqueue);
186 void radio_save_presets(void)
188 int fd;
189 int i;
191 fd = creat(filepreset, 0666);
192 if(fd >= 0)
194 for(i = 0;i < num_presets;i++)
196 fdprintf(fd, "%d:%s\n", presets[i].frequency, presets[i].name);
198 close(fd);
200 if(!strncasecmp(FMPRESET_PATH, filepreset, strlen(FMPRESET_PATH)))
201 set_file(filepreset, global_settings.fmr_file, MAX_FILENAME);
202 presets_changed = false;
204 else
206 splash(HZ, ID2P(LANG_FM_PRESET_SAVE_FAILED));
210 void radio_load_presets(char *filename)
212 int fd;
213 int rc;
214 char buf[128];
215 char *freq;
216 char *name;
217 bool done = false;
218 int f;
220 memset(presets, 0, sizeof(presets));
221 num_presets = 0;
223 /* No Preset in configuration. */
224 if(filename[0] == '\0')
226 filepreset[0] = '\0';
227 return;
229 /* Temporary preset, loaded until player shuts down. */
230 else if(filename[0] == '/')
231 strlcpy(filepreset, filename, sizeof(filepreset));
232 /* Preset from default directory. */
233 else
234 snprintf(filepreset, sizeof(filepreset), "%s/%s.fmr",
235 FMPRESET_PATH, filename);
237 fd = open_utf8(filepreset, O_RDONLY);
238 if(fd >= 0)
240 while(!done && num_presets < MAX_PRESETS)
242 rc = read_line(fd, buf, 128);
243 if(rc > 0)
245 if(settings_parseline(buf, &freq, &name))
247 f = atoi(freq);
248 if(f) /* For backwards compatibility */
250 struct fmstation * const fms = &presets[num_presets];
251 fms->frequency = f;
252 strlcpy(fms->name, name, MAX_FMPRESET_LEN+1);
253 num_presets++;
257 else
258 done = true;
260 close(fd);
262 else /* invalid file name? */
263 filepreset[0] = '\0';
265 presets_loaded = num_presets > 0;
266 presets_changed = false;
269 const char* radio_get_preset_name(int preset)
271 if (preset < num_presets)
272 return presets[preset].name;
273 return NULL;
276 int handle_radio_add_preset(void)
278 char buf[MAX_FMPRESET_LEN + 1];
280 if(num_presets < MAX_PRESETS)
282 buf[0] = '\0';
284 if (!kbd_input(buf, MAX_FMPRESET_LEN + 1))
286 struct fmstation * const fms = &presets[num_presets];
287 strcpy(fms->name, buf);
288 fms->frequency = curr_freq;
289 num_presets++;
290 presets_changed = true;
291 presets_loaded = num_presets > 0;
292 return true;
295 else
297 splash(HZ, ID2P(LANG_FM_NO_FREE_PRESETS));
299 return false;
302 /* needed to know which preset we are edit/delete-ing */
303 static int selected_preset = -1;
304 static int radio_edit_preset(void)
306 char buf[MAX_FMPRESET_LEN + 1];
308 if (num_presets > 0)
310 struct fmstation * const fms = &presets[selected_preset];
312 strcpy(buf, fms->name);
314 if (!kbd_input(buf, MAX_FMPRESET_LEN + 1))
316 strcpy(fms->name, buf);
317 presets_changed = true;
321 return 1;
324 static int radio_delete_preset(void)
326 if (num_presets > 0)
328 struct fmstation * const fms = &presets[selected_preset];
330 if (selected_preset >= --num_presets)
331 selected_preset = num_presets - 1;
333 memmove(fms, fms + 1, (uintptr_t)(fms + num_presets) -
334 (uintptr_t)fms);
336 if (curr_preset >= num_presets)
337 --curr_preset;
340 /* Don't ask to save when all presets are deleted. */
341 presets_changed = num_presets > 0;
343 if (!presets_changed)
345 /* The preset list will be cleared, switch to Scan Mode. */
346 radio_mode = RADIO_SCAN_MODE;
347 curr_preset = -1;
348 presets_loaded = false;
351 return 1;
354 int preset_list_load(void)
356 char selected[MAX_PATH];
357 struct browse_context browse;
358 snprintf(selected, sizeof(selected), "%s.%s", global_settings.fmr_file, "fmr");
359 browse_context_init(&browse, SHOW_FMR, 0,
360 str(LANG_FM_PRESET_LOAD), NOICON,
361 FMPRESET_PATH, selected);
362 return !rockbox_browse(&browse);
365 int preset_list_save(void)
367 if(num_presets > 0)
369 bool bad_file_name = true;
371 if(!dir_exists(FMPRESET_PATH)) /* Check if there is preset folder */
372 mkdir(FMPRESET_PATH);
374 create_numbered_filename(filepreset, FMPRESET_PATH, "preset",
375 ".fmr", 2 IF_CNFN_NUM_(, NULL));
377 while(bad_file_name)
379 if(!kbd_input(filepreset, sizeof(filepreset)))
381 /* check the name: max MAX_FILENAME (20) chars */
382 char* p2;
383 char* p1;
384 int len;
385 p1 = strrchr(filepreset, '/');
386 p2 = p1;
387 while((p1) && (*p2) && (*p2 != '.'))
388 p2++;
389 len = (int)(p2-p1) - 1;
390 if((!p1) || (len > MAX_FILENAME) || (len == 0))
392 /* no slash, too long or too short */
393 splash(HZ, ID2P(LANG_INVALID_FILENAME));
395 else
397 /* add correct extension (easier to always write)
398 at this point, p2 points to 0 or the extension dot */
399 *p2 = '\0';
400 strcat(filepreset,".fmr");
401 bad_file_name = false;
402 radio_save_presets();
405 else
407 /* user aborted */
408 return false;
412 else
413 splash(HZ, ID2P(LANG_FM_NO_PRESETS));
415 return true;
418 int preset_list_clear(void)
420 /* Clear all the preset entries */
421 memset(presets, 0, sizeof (presets));
423 num_presets = 0;
424 presets_loaded = false;
425 /* The preset list will be cleared switch to Scan Mode. */
426 radio_mode = RADIO_SCAN_MODE;
427 curr_preset = -1;
428 presets_changed = false; /* Don't ask to save when clearing the list. */
430 return true;
433 MENUITEM_FUNCTION(radio_edit_preset_item, MENU_FUNC_CHECK_RETVAL,
434 ID2P(LANG_FM_EDIT_PRESET),
435 radio_edit_preset, NULL, NULL, Icon_NOICON);
436 MENUITEM_FUNCTION(radio_delete_preset_item, MENU_FUNC_CHECK_RETVAL,
437 ID2P(LANG_FM_DELETE_PRESET),
438 radio_delete_preset, NULL, NULL, Icon_NOICON);
439 static int radio_preset_callback(int action,
440 const struct menu_item_ex *this_item)
442 if (action == ACTION_STD_OK)
443 action = ACTION_EXIT_AFTER_THIS_MENUITEM;
444 return action;
445 (void)this_item;
447 MAKE_MENU(handle_radio_preset_menu, ID2P(LANG_PRESET),
448 radio_preset_callback, Icon_NOICON, &radio_edit_preset_item,
449 &radio_delete_preset_item);
450 /* present a list of preset stations */
451 static const char* presets_get_name(int selected_item, void *data,
452 char *buffer, size_t buffer_len)
454 (void)data;
455 struct fmstation *p = &presets[selected_item];
456 if(p->name[0])
457 return p->name;
458 int freq = p->frequency / 10000;
459 int frac = freq % 100;
460 freq /= 100;
461 snprintf(buffer, buffer_len,
462 str(LANG_FM_DEFAULT_PRESET_NAME), freq, frac);
463 return buffer;
466 static int presets_speak_name(int selected_item, void * data)
468 (void)data;
469 preset_talk(selected_item, true, false);
470 return 0;
473 int handle_radio_presets(void)
475 struct gui_synclist lists;
476 int result = 0;
477 int action = ACTION_NONE;
478 #ifdef HAVE_BUTTONBAR
479 struct gui_buttonbar buttonbar;
480 #endif
482 if(presets_loaded == false)
483 return result;
485 #ifdef HAVE_BUTTONBAR
486 gui_buttonbar_init(&buttonbar);
487 gui_buttonbar_set_display(&buttonbar, &(screens[SCREEN_MAIN]) );
488 gui_buttonbar_set(&buttonbar, str(LANG_FM_BUTTONBAR_ADD),
489 str(LANG_FM_BUTTONBAR_EXIT),
490 str(LANG_FM_BUTTONBAR_ACTION));
491 gui_buttonbar_draw(&buttonbar);
492 #endif
493 gui_synclist_init(&lists, presets_get_name, NULL, false, 1, NULL);
494 gui_synclist_set_title(&lists, str(LANG_PRESET), NOICON);
495 gui_synclist_set_icon_callback(&lists, NULL);
496 if(global_settings.talk_file)
497 gui_synclist_set_voice_callback(&lists, presets_speak_name);
498 gui_synclist_set_nb_items(&lists, num_presets);
499 gui_synclist_select_item(&lists, curr_preset<0 ? 0 : curr_preset);
500 gui_synclist_speak_item(&lists);
502 while (result == 0)
504 gui_synclist_draw(&lists);
505 list_do_action(CONTEXT_STD, TIMEOUT_BLOCK,
506 &lists, &action, LIST_WRAP_UNLESS_HELD);
507 switch (action)
509 case ACTION_STD_MENU:
510 if (handle_radio_add_preset())
512 gui_synclist_set_nb_items(&lists, num_presets);
513 gui_synclist_select_item(&lists, num_presets - 1);
515 break;
516 case ACTION_STD_CANCEL:
517 result = 1;
518 break;
519 case ACTION_STD_OK:
520 curr_preset = gui_synclist_get_sel_pos(&lists);
521 curr_freq = presets[curr_preset].frequency;
522 next_station(0);
523 remember_frequency();
524 result = 1;
525 break;
526 case ACTION_F3:
527 case ACTION_STD_CONTEXT:
528 selected_preset = gui_synclist_get_sel_pos(&lists);
529 do_menu(&handle_radio_preset_menu, NULL, NULL, false);
530 gui_synclist_set_nb_items(&lists, num_presets);
531 gui_synclist_select_item(&lists, selected_preset);
532 gui_synclist_speak_item(&lists);
533 break;
534 default:
535 if(default_event_handler(action) == SYS_USB_CONNECTED)
536 result = 2;
539 return result - 1;
543 int presets_scan(void *viewports)
545 bool do_scan = true;
546 int i;
547 struct viewport *vp = (struct viewport *)viewports;
549 FOR_NB_SCREENS(i)
550 screens[i].set_viewport(vp?&vp[i]:NULL);
551 if(num_presets > 0) /* Do that to avoid 2 questions. */
552 do_scan = yesno_pop(ID2P(LANG_FM_CLEAR_PRESETS));
554 if(do_scan)
556 const struct fm_region_data * const fmr =
557 &fm_region_data[global_settings.fm_region];
559 curr_freq = fmr->freq_min;
560 num_presets = 0;
561 memset(presets, 0, sizeof(presets));
563 tuner_set(RADIO_MUTE, 1);
565 while(curr_freq <= fmr->freq_max)
567 int freq, frac;
568 if(num_presets >= MAX_PRESETS || action_userabort(TIMEOUT_NOBLOCK))
569 break;
571 freq = curr_freq / 10000;
572 frac = freq % 100;
573 freq /= 100;
575 splashf(0, str(LANG_FM_SCANNING), freq, frac);
577 if(tuner_set(RADIO_SCAN_FREQUENCY, curr_freq))
579 /* add preset */
580 presets[num_presets].name[0] = '\0';
581 presets[num_presets].frequency = curr_freq;
582 num_presets++;
585 curr_freq += fmr->freq_step;
588 if (get_radio_status() == FMRADIO_PLAYING)
589 tuner_set(RADIO_MUTE, 0);
591 presets_changed = true;
593 FOR_NB_SCREENS(i)
595 screens[i].clear_viewport();
596 screens[i].update_viewport();
599 if(num_presets > 0)
601 curr_freq = presets[0].frequency;
602 radio_mode = RADIO_PRESET_MODE;
603 presets_loaded = true;
604 next_station(0);
606 else
608 /* Wrap it to beginning or we'll be past end of band */
609 presets_loaded = false;
610 next_station(1);
613 return true;
617 void presets_save(void)
619 if(filepreset[0] == '\0')
620 preset_list_save();
621 else
622 radio_save_presets();
625 #ifdef HAVE_LCD_BITMAP
626 static inline void draw_vertical_line_mark(struct screen * screen,
627 int x, int y, int h)
629 screen->set_drawmode(DRMODE_COMPLEMENT);
630 screen->vline(x, y, y+h-1);
633 /* draw the preset markers for a track of length "tracklen",
634 between (x,y) and (x+w,y) */
635 void presets_draw_markers(struct screen *screen,
636 int x, int y, int w, int h)
638 int i,xi;
639 const struct fm_region_data *region_data =
640 &(fm_region_data[global_settings.fm_region]);
641 int len = region_data->freq_max - region_data->freq_min;
642 for (i=0; i < radio_preset_count(); i++)
644 int freq = radio_get_preset(i)->frequency;
645 int diff = freq - region_data->freq_min;
646 xi = x + (w * diff)/len;
647 draw_vertical_line_mark(screen, xi, y, h);
650 #endif