mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
Basically, this just replaces str(STRID) with ID2P(STRID). The voiced version of these strings cannot not have any format specifiers (enforced by the language tooling) and are instead more generic. As many of these are error conditions, it is doubly important for them to be voiced in some way. There are some places in the code that perform their own voicing for splash messages (eg the shutdown code); those are left alone. Change-Id: I7d51af351e8fa5c4beee42fbfc02719f1d6591b8
660 lines
18 KiB
C
660 lines
18 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2003 Linus Nielsen Feltzing
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include "settings.h"
|
|
#include "rbpaths.h"
|
|
#include "general.h"
|
|
#include "radio.h"
|
|
#include "tuner.h"
|
|
#include "file.h"
|
|
#include "string-extra.h"
|
|
#include "misc.h"
|
|
#include "pathfuncs.h"
|
|
#include "lang.h"
|
|
#include "action.h"
|
|
#include "list.h"
|
|
#include "splash.h"
|
|
#include "menu.h"
|
|
#include "yesno.h"
|
|
#include "keyboard.h"
|
|
#include "talk.h"
|
|
#include "filetree.h"
|
|
#include "dir.h"
|
|
#include "presets.h"
|
|
|
|
static int curr_preset = -1;
|
|
|
|
int snap_freq_to_grid(int freq);
|
|
void remember_frequency(void);
|
|
|
|
#define FREQ_DISP_DIVISOR (10000)
|
|
#define MAX_PRESETS 64
|
|
static bool presets_loaded = false;
|
|
static bool presets_changed = false;
|
|
|
|
static struct fmstation
|
|
{
|
|
int frequency; /* In Hz */
|
|
char name[MAX_FMPRESET_LEN+1];
|
|
} presets[MAX_PRESETS];
|
|
|
|
static char filepreset[MAX_PATH]; /* preset filename variable */
|
|
|
|
static int num_presets = 0; /* The number of presets in the preset list */
|
|
|
|
int radio_current_preset(void)
|
|
{
|
|
return curr_preset;
|
|
}
|
|
int radio_preset_count(void)
|
|
{
|
|
return num_presets;
|
|
}
|
|
|
|
bool presets_have_changed(void)
|
|
{
|
|
return presets_changed;
|
|
}
|
|
|
|
|
|
/* Find a matching preset to freq */
|
|
int preset_find(int freq)
|
|
{
|
|
int i;
|
|
if(num_presets < 1)
|
|
return -1;
|
|
for(i = 0;i < MAX_PRESETS;i++)
|
|
{
|
|
if(freq == presets[i].frequency)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Return the closest preset encountered in the search direction with
|
|
wraparound. */
|
|
static int find_closest_preset(int freq, int direction)
|
|
{
|
|
int i;
|
|
int lowpreset = 0;
|
|
int highpreset = 0;
|
|
int closest = -1;
|
|
|
|
if (direction == 0) /* direction == 0 isn't really used */
|
|
return 0;
|
|
|
|
for (i = 0; i < num_presets; i++)
|
|
{
|
|
int f = presets[i].frequency;
|
|
if (f == freq)
|
|
return i; /* Exact match = stop */
|
|
|
|
/* remember the highest and lowest presets for wraparound */
|
|
if (f < presets[lowpreset].frequency)
|
|
lowpreset = i;
|
|
if (f > presets[highpreset].frequency)
|
|
highpreset = i;
|
|
|
|
/* find the closest preset in the given direction */
|
|
if (direction > 0 && f > freq)
|
|
{
|
|
if (closest < 0 || f < presets[closest].frequency)
|
|
closest = i;
|
|
}
|
|
else if (direction < 0 && f < freq)
|
|
{
|
|
if (closest < 0 || f > presets[closest].frequency)
|
|
closest = i;
|
|
}
|
|
}
|
|
|
|
if (closest < 0)
|
|
{
|
|
/* no presets in the given direction */
|
|
/* wrap around depending on direction */
|
|
if (direction < 0)
|
|
closest = highpreset;
|
|
else
|
|
closest = lowpreset;
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
void preset_next(int direction)
|
|
{
|
|
if (num_presets < 1)
|
|
return;
|
|
|
|
int curr_freq = radio_get_current_frequency();
|
|
|
|
if (curr_preset == -1)
|
|
curr_preset = find_closest_preset(curr_freq, direction);
|
|
else
|
|
curr_preset = (curr_preset + direction + num_presets) % num_presets;
|
|
|
|
/* Must stay on the current grid for the region */
|
|
curr_freq = snap_freq_to_grid(presets[curr_preset].frequency);
|
|
radio_set_current_frequency(curr_freq);
|
|
tuner_set(RADIO_FREQUENCY, curr_freq);
|
|
remember_frequency();
|
|
}
|
|
|
|
void preset_set_current(int preset)
|
|
{
|
|
curr_preset = preset;
|
|
}
|
|
|
|
/* Speak a preset by number or by spelling its name, depending on settings. */
|
|
void preset_talk(int preset, bool fallback, bool enqueue)
|
|
{
|
|
if (global_settings.talk_file == 1) /* number */
|
|
talk_number(preset + 1, enqueue);
|
|
else
|
|
{ /* spell */
|
|
if(presets[preset].name[0])
|
|
talk_spell(presets[preset].name, enqueue);
|
|
else if(fallback)
|
|
talk_value_decimal(presets[preset].frequency, UNIT_INT, 6, enqueue);
|
|
}
|
|
}
|
|
|
|
void radio_save_presets(void)
|
|
{
|
|
int fd;
|
|
int i;
|
|
|
|
fd = creat(filepreset, 0666);
|
|
if(fd >= 0)
|
|
{
|
|
for(i = 0;i < num_presets;i++)
|
|
{
|
|
fdprintf(fd, "%d:%s\n", presets[i].frequency, presets[i].name);
|
|
}
|
|
close(fd);
|
|
|
|
if (strcasestr(filepreset, FMPRESET_PATH))
|
|
set_file(filepreset, global_settings.fmr_file);
|
|
presets_changed = false;
|
|
}
|
|
else
|
|
{
|
|
splash(HZ, ID2P(LANG_FM_PRESET_SAVE_FAILED));
|
|
}
|
|
}
|
|
|
|
void radio_load_presets(const char *filename)
|
|
{
|
|
int fd;
|
|
int rc;
|
|
char buf[128];
|
|
char *freq;
|
|
char *name;
|
|
bool done = false;
|
|
int f;
|
|
|
|
memset(presets, 0, sizeof(presets));
|
|
num_presets = 0;
|
|
|
|
/* No Preset in configuration. */
|
|
if(filename[0] == '\0' || filename[0] == '-')
|
|
{
|
|
filepreset[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
splash(0, ID2P(LANG_WAIT));
|
|
|
|
if(filename[0] != '/') /* Preset within radio screen */
|
|
{
|
|
snprintf(filepreset, sizeof(filepreset), "%s/%s.fmr",
|
|
FMPRESET_PATH, filename);;
|
|
}
|
|
else
|
|
{
|
|
strmemccpy(filepreset, filename, sizeof(filepreset));
|
|
}
|
|
|
|
/* Preset inside the default folder? */
|
|
if (strcasestr(filepreset, FMPRESET_PATH))
|
|
set_file(filepreset, global_settings.fmr_file);
|
|
/* else Temporary preset, loaded until player shuts down. */
|
|
|
|
fd = open_utf8(filepreset, O_RDONLY);
|
|
if(fd >= 0)
|
|
{
|
|
while(!done && num_presets < MAX_PRESETS)
|
|
{
|
|
rc = read_line(fd, buf, sizeof(buf));
|
|
if(rc > 0)
|
|
{
|
|
if(settings_parseline(buf, &freq, &name))
|
|
{
|
|
f = atoi(freq);
|
|
if(f) /* For backwards compatibility */
|
|
{
|
|
struct fmstation * const fms = &presets[num_presets];
|
|
fms->frequency = f;
|
|
strmemccpy(fms->name, name, MAX_FMPRESET_LEN+1);
|
|
num_presets++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
done = true;
|
|
}
|
|
close(fd);
|
|
}
|
|
else /* invalid file name? */
|
|
filepreset[0] = '\0';
|
|
|
|
presets_loaded = num_presets > 0;
|
|
presets_changed = false;
|
|
}
|
|
|
|
int radio_get_preset_freq(int preset)
|
|
{
|
|
if (preset < num_presets)
|
|
return presets[preset].frequency;
|
|
return -1;
|
|
}
|
|
|
|
const char* radio_get_preset_name(int preset)
|
|
{
|
|
if (preset < num_presets)
|
|
return presets[preset].name;
|
|
return NULL;
|
|
}
|
|
|
|
int handle_radio_add_preset(void)
|
|
{
|
|
char buf[MAX_FMPRESET_LEN + 1];
|
|
|
|
if(num_presets < MAX_PRESETS)
|
|
{
|
|
buf[0] = '\0';
|
|
|
|
if (!kbd_input(buf, MAX_FMPRESET_LEN + 1, NULL))
|
|
{
|
|
struct fmstation * const fms = &presets[num_presets];
|
|
strcpy(fms->name, buf);
|
|
fms->frequency = radio_get_current_frequency();
|
|
num_presets++;
|
|
presets_changed = true;
|
|
presets_loaded = num_presets > 0;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
splash(HZ, ID2P(LANG_FM_NO_FREE_PRESETS));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* needed to know which preset we are edit/delete-ing */
|
|
static int selected_preset = -1;
|
|
static int radio_edit_preset(void)
|
|
{
|
|
char buf[MAX_FMPRESET_LEN + 1];
|
|
|
|
if (num_presets > 0)
|
|
{
|
|
struct fmstation * const fms = &presets[selected_preset];
|
|
|
|
strcpy(buf, fms->name);
|
|
|
|
if (!kbd_input(buf, MAX_FMPRESET_LEN + 1, NULL))
|
|
{
|
|
strcpy(fms->name, buf);
|
|
presets_changed = true;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int radio_delete_preset(void)
|
|
{
|
|
if (num_presets > 0)
|
|
{
|
|
struct fmstation * const fms = &presets[selected_preset];
|
|
|
|
if (selected_preset >= --num_presets)
|
|
selected_preset = num_presets - 1;
|
|
|
|
memmove(fms, fms + 1, (uintptr_t)(fms + num_presets) -
|
|
(uintptr_t)fms);
|
|
|
|
if (curr_preset >= num_presets)
|
|
--curr_preset;
|
|
}
|
|
|
|
/* Don't ask to save when all presets are deleted. */
|
|
presets_changed = num_presets > 0;
|
|
|
|
if (!presets_changed)
|
|
{
|
|
/* The preset list will be cleared, switch to Scan Mode. */
|
|
radio_set_mode(RADIO_SCAN_MODE);
|
|
curr_preset = -1;
|
|
presets_loaded = false;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int preset_list_load(void)
|
|
{
|
|
char selected[MAX_PATH];
|
|
snprintf(selected, sizeof(selected), "%s.%s", global_settings.fmr_file, "fmr");
|
|
|
|
struct browse_context browse = {
|
|
.dirfilter = SHOW_FMR,
|
|
.title = str(LANG_FM_PRESET_LOAD),
|
|
.icon = Icon_NOICON,
|
|
.root = FMPRESET_PATH,
|
|
.selected = selected,
|
|
};
|
|
|
|
return !rockbox_browse(&browse);
|
|
}
|
|
|
|
int preset_list_save(void)
|
|
{
|
|
if(num_presets > 0)
|
|
{
|
|
bool bad_file_name = true;
|
|
|
|
if(!dir_exists(FMPRESET_PATH)) /* Check if there is preset folder */
|
|
mkdir(FMPRESET_PATH);
|
|
|
|
create_numbered_filename(filepreset, FMPRESET_PATH, "preset",
|
|
".fmr", 2 IF_CNFN_NUM_(, NULL));
|
|
|
|
while(bad_file_name)
|
|
{
|
|
if(!kbd_input(filepreset, sizeof(filepreset), NULL))
|
|
{
|
|
/* check the name: max MAX_FILENAME (20) chars */
|
|
char* p2;
|
|
char* p1;
|
|
int len;
|
|
p1 = strrchr(filepreset, '/');
|
|
p2 = p1;
|
|
while((p1) && (*p2) && (*p2 != '.'))
|
|
p2++;
|
|
len = (int)(p2-p1) - 1;
|
|
if((!p1) || (len > MAX_FILENAME) || (len == 0))
|
|
{
|
|
/* no slash, too long or too short */
|
|
splash(HZ, ID2P(LANG_INVALID_FILENAME));
|
|
}
|
|
else
|
|
{
|
|
/* add correct extension (easier to always write)
|
|
at this point, p2 points to 0 or the extension dot */
|
|
*p2 = '\0';
|
|
strcat(filepreset,".fmr");
|
|
bad_file_name = false;
|
|
radio_save_presets();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* user aborted */
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
splash(HZ, ID2P(LANG_FM_NO_PRESETS));
|
|
|
|
return true;
|
|
}
|
|
|
|
int preset_list_clear(void)
|
|
{
|
|
/* Clear all the preset entries */
|
|
memset(presets, 0, sizeof (presets));
|
|
|
|
num_presets = 0;
|
|
presets_loaded = false;
|
|
/* The preset list will be cleared switch to Scan Mode. */
|
|
radio_set_mode(RADIO_SCAN_MODE);
|
|
curr_preset = -1;
|
|
presets_changed = false; /* Don't ask to save when clearing the list. */
|
|
global_settings.fmr_file[0] = '-';
|
|
|
|
return true;
|
|
}
|
|
|
|
MENUITEM_FUNCTION(radio_edit_preset_item, MENU_FUNC_CHECK_RETVAL,
|
|
ID2P(LANG_FM_EDIT_PRESET),
|
|
radio_edit_preset, NULL, Icon_NOICON);
|
|
MENUITEM_FUNCTION(radio_delete_preset_item, MENU_FUNC_CHECK_RETVAL,
|
|
ID2P(LANG_FM_DELETE_PRESET),
|
|
radio_delete_preset, NULL, Icon_NOICON);
|
|
static int radio_preset_callback(int action,
|
|
const struct menu_item_ex *this_item,
|
|
struct gui_synclist *this_list)
|
|
{
|
|
if (action == ACTION_STD_OK)
|
|
action = ACTION_EXIT_AFTER_THIS_MENUITEM;
|
|
return action;
|
|
(void)this_item;
|
|
(void)this_list;
|
|
}
|
|
MAKE_MENU(handle_radio_preset_menu, ID2P(LANG_PRESET),
|
|
radio_preset_callback, Icon_NOICON, &radio_edit_preset_item,
|
|
&radio_delete_preset_item);
|
|
/* present a list of preset stations */
|
|
static const char* presets_get_name(int selected_item, void *data,
|
|
char *buffer, size_t buffer_len)
|
|
{
|
|
(void)data;
|
|
struct fmstation *p = &presets[selected_item];
|
|
if(p->name[0])
|
|
return p->name;
|
|
int freq = p->frequency / FREQ_DISP_DIVISOR;
|
|
int frac = freq % 100;
|
|
freq /= 100;
|
|
snprintf(buffer, buffer_len,
|
|
str(LANG_FM_DEFAULT_PRESET_NAME), freq, frac);
|
|
return buffer;
|
|
}
|
|
|
|
static int presets_speak_name(int selected_item, void * data)
|
|
{
|
|
(void)data;
|
|
preset_talk(selected_item, true, false);
|
|
return 0;
|
|
}
|
|
|
|
int handle_radio_presets(void)
|
|
{
|
|
struct gui_synclist lists;
|
|
int result = 0;
|
|
int action = ACTION_NONE;
|
|
|
|
if(presets_loaded == false)
|
|
return result;
|
|
|
|
gui_synclist_init(&lists, presets_get_name, NULL, false, 1, NULL);
|
|
gui_synclist_set_title(&lists, str(LANG_PRESET), NOICON);
|
|
if(global_settings.talk_file)
|
|
gui_synclist_set_voice_callback(&lists, presets_speak_name);
|
|
gui_synclist_set_nb_items(&lists, num_presets);
|
|
gui_synclist_select_item(&lists, curr_preset<0 ? 0 : curr_preset);
|
|
gui_synclist_speak_item(&lists);
|
|
|
|
while (result == 0)
|
|
{
|
|
gui_synclist_draw(&lists);
|
|
list_do_action(CONTEXT_STD, TIMEOUT_BLOCK, &lists, &action);
|
|
switch (action)
|
|
{
|
|
case ACTION_STD_MENU:
|
|
if (handle_radio_add_preset())
|
|
{
|
|
gui_synclist_set_nb_items(&lists, num_presets);
|
|
gui_synclist_select_item(&lists, num_presets - 1);
|
|
}
|
|
break;
|
|
case ACTION_STD_CANCEL:
|
|
result = 1;
|
|
break;
|
|
case ACTION_STD_OK:
|
|
curr_preset = gui_synclist_get_sel_pos(&lists);
|
|
radio_set_current_frequency(presets[curr_preset].frequency);
|
|
next_station(0);
|
|
result = 1;
|
|
break;
|
|
case ACTION_STD_CONTEXT:
|
|
selected_preset = gui_synclist_get_sel_pos(&lists);
|
|
do_menu(&handle_radio_preset_menu, NULL, NULL, false);
|
|
gui_synclist_set_nb_items(&lists, num_presets);
|
|
gui_synclist_select_item(&lists, selected_preset);
|
|
gui_synclist_speak_item(&lists);
|
|
break;
|
|
default:
|
|
if(default_event_handler(action) == SYS_USB_CONNECTED)
|
|
result = 2;
|
|
}
|
|
}
|
|
return result - 1;
|
|
}
|
|
|
|
|
|
int presets_scan(void *viewports)
|
|
{
|
|
bool do_scan = true;
|
|
int curr_freq = radio_get_current_frequency();
|
|
struct viewport *vp = (struct viewport *)viewports;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].set_viewport(vp?&vp[i]:NULL);
|
|
if(num_presets > 0) /* Do that to avoid 2 questions. */
|
|
do_scan = yesno_pop(ID2P(LANG_FM_CLEAR_PRESETS));
|
|
|
|
if(do_scan)
|
|
{
|
|
const struct fm_region_data * const fmr =
|
|
&fm_region_data[global_settings.fm_region];
|
|
|
|
curr_freq = fmr->freq_min;
|
|
num_presets = 0;
|
|
memset(presets, 0, sizeof(presets));
|
|
|
|
tuner_set(RADIO_MUTE, 1);
|
|
|
|
while(curr_freq <= fmr->freq_max)
|
|
{
|
|
int freq, frac;
|
|
if(num_presets >= MAX_PRESETS || action_userabort(TIMEOUT_NOBLOCK))
|
|
break;
|
|
|
|
freq = curr_freq / FREQ_DISP_DIVISOR;
|
|
frac = freq % 100;
|
|
freq /= 100;
|
|
|
|
splashf(0, ID2P(LANG_FM_SCANNING), freq, frac);
|
|
|
|
if(tuner_set(RADIO_SCAN_FREQUENCY, curr_freq))
|
|
{
|
|
/* add preset */
|
|
presets[num_presets].name[0] = '\0';
|
|
presets[num_presets].frequency = curr_freq;
|
|
num_presets++;
|
|
}
|
|
|
|
curr_freq += fmr->freq_step;
|
|
}
|
|
radio_set_current_frequency(curr_freq);
|
|
|
|
if (get_radio_status() == FMRADIO_PLAYING)
|
|
tuner_set(RADIO_MUTE, 0);
|
|
|
|
presets_changed = true;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].clear_viewport();
|
|
screens[i].update_viewport();
|
|
}
|
|
|
|
if(num_presets > 0)
|
|
{
|
|
radio_set_current_frequency(presets[0].frequency);
|
|
radio_set_mode(RADIO_PRESET_MODE);
|
|
presets_loaded = true;
|
|
next_station(0);
|
|
}
|
|
else
|
|
{
|
|
/* Wrap it to beginning or we'll be past end of band */
|
|
presets_loaded = false;
|
|
next_station(1);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void presets_save(void)
|
|
{
|
|
if(filepreset[0] == '\0')
|
|
preset_list_save();
|
|
else
|
|
radio_save_presets();
|
|
}
|
|
|
|
#if 0 /* disabled in draw_progressbar() */
|
|
static inline void draw_vertical_line_mark(struct screen * screen,
|
|
int x, int y, int h)
|
|
{
|
|
screen->set_drawmode(DRMODE_COMPLEMENT);
|
|
screen->vline(x, y, y+h-1);
|
|
}
|
|
|
|
/* draw the preset markers for a track of length "tracklen",
|
|
between (x,y) and (x+w,y) */
|
|
void presets_draw_markers(struct screen *screen,
|
|
int x, int y, int w, int h)
|
|
{
|
|
int i,xi;
|
|
const struct fm_region_data *region_data =
|
|
&(fm_region_data[global_settings.fm_region]);
|
|
int len = region_data->freq_max - region_data->freq_min;
|
|
for (i=0; i < radio_preset_count(); i++)
|
|
{
|
|
int freq = radio_get_preset(i)->frequency;
|
|
int diff = freq - region_data->freq_min;
|
|
xi = x + (w * diff)/len;
|
|
draw_vertical_line_mark(screen, xi, y, h);
|
|
}
|
|
}
|
|
#endif
|