830a3a4720
* Fix the pitch tag and allow it to be used on all targets except the Archos Player. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13020 a1c6a512-1295-4272-9138-f99709370657
1953 lines
60 KiB
C
1953 lines
60 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2007 Nicolas Pennequin
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "gwps-common.h"
|
|
#include "font.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "system.h"
|
|
#include "settings.h"
|
|
#include "rbunicode.h"
|
|
#include "rtc.h"
|
|
#include "audio.h"
|
|
#include "status.h"
|
|
#include "power.h"
|
|
#include "powermgmt.h"
|
|
#include "sound.h"
|
|
#include "debug.h"
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
#include "hwcompat.h"
|
|
#endif
|
|
#include "abrepeat.h"
|
|
#include "mp3_playback.h"
|
|
#include "backlight.h"
|
|
#include "lang.h"
|
|
#include "misc.h"
|
|
#include "splash.h"
|
|
#include "scrollbar.h"
|
|
#include "led.h"
|
|
#include "lcd.h"
|
|
#ifdef HAVE_LCD_BITMAP
|
|
#include "peakmeter.h"
|
|
/* Image stuff */
|
|
#include "bmp.h"
|
|
#include "atoi.h"
|
|
#endif
|
|
#if LCD_DEPTH > 1
|
|
#include "backdrop.h"
|
|
#endif
|
|
#include "dsp.h"
|
|
#include "action.h"
|
|
#include "cuesheet.h"
|
|
|
|
#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
|
|
/* 3% of 30min file == 54s step size */
|
|
#define MIN_FF_REWIND_STEP 500
|
|
|
|
#if 0
|
|
/* Skip leading UTF-8 BOM, if present. */
|
|
static char* skip_utf8_bom(char* buf)
|
|
{
|
|
unsigned char* s = (unsigned char*) buf;
|
|
|
|
if(s[0] == 0xef && s[1] == 0xbb && s[2] == 0xbf)
|
|
{
|
|
buf += 3;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
/* draws the statusbar on the given wps-screen */
|
|
#ifdef HAVE_LCD_BITMAP
|
|
void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
|
|
{
|
|
bool draw = global_settings.statusbar;
|
|
|
|
if(wps->data->wps_sb_tag
|
|
&& wps->data->show_sb_on_wps)
|
|
draw = true;
|
|
else if(wps->data->wps_sb_tag)
|
|
draw = false;
|
|
if(draw)
|
|
gui_statusbar_draw(wps->statusbar, force);
|
|
}
|
|
#else
|
|
#define gui_wps_statusbar_draw(wps, force) \
|
|
gui_statusbar_draw((wps)->statusbar, (force))
|
|
#endif
|
|
|
|
/* fades the volume */
|
|
void fade(bool fade_in)
|
|
{
|
|
int fp_global_vol = global_settings.volume << 8;
|
|
int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
|
|
int fp_step = (fp_global_vol - fp_min_vol) / 30;
|
|
|
|
if (fade_in) {
|
|
/* fade in */
|
|
int fp_volume = fp_min_vol;
|
|
|
|
/* zero out the sound */
|
|
sound_set_volume(fp_min_vol >> 8);
|
|
|
|
sleep(HZ/10); /* let audio thread run */
|
|
audio_resume();
|
|
|
|
while (fp_volume < fp_global_vol - fp_step) {
|
|
fp_volume += fp_step;
|
|
sound_set_volume(fp_volume >> 8);
|
|
sleep(1);
|
|
}
|
|
sound_set_volume(global_settings.volume);
|
|
}
|
|
else {
|
|
/* fade out */
|
|
int fp_volume = fp_global_vol;
|
|
|
|
while (fp_volume > fp_min_vol + fp_step) {
|
|
fp_volume -= fp_step;
|
|
sound_set_volume(fp_volume >> 8);
|
|
sleep(1);
|
|
}
|
|
audio_pause();
|
|
#ifndef SIMULATOR
|
|
/* let audio thread run and wait for the mas to run out of data */
|
|
while (!mp3_pause_done())
|
|
#endif
|
|
sleep(HZ/10);
|
|
|
|
/* reset volume to what it was before the fade */
|
|
sound_set_volume(global_settings.volume);
|
|
}
|
|
}
|
|
|
|
/* set volume */
|
|
void setvol(void)
|
|
{
|
|
if (global_settings.volume < sound_min(SOUND_VOLUME))
|
|
global_settings.volume = sound_min(SOUND_VOLUME);
|
|
if (global_settings.volume > sound_max(SOUND_VOLUME))
|
|
global_settings.volume = sound_max(SOUND_VOLUME);
|
|
sound_set_volume(global_settings.volume);
|
|
settings_save();
|
|
}
|
|
/* return true if screen restore is needed
|
|
return false otherwise
|
|
*/
|
|
bool update_onvol_change(struct gui_wps * gwps)
|
|
{
|
|
gui_wps_statusbar_draw(gwps, false);
|
|
gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
|
|
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
gui_splash(gwps->display, 0, "Vol: %3d dB",
|
|
sound_val2phys(SOUND_VOLUME, global_settings.volume));
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool ffwd_rew(int button)
|
|
{
|
|
static const int ff_rew_steps[] = {
|
|
1000, 2000, 3000, 4000,
|
|
5000, 6000, 8000, 10000,
|
|
15000, 20000, 25000, 30000,
|
|
45000, 60000
|
|
};
|
|
|
|
unsigned int step = 0; /* current ff/rewind step */
|
|
unsigned int max_step = 0; /* maximum ff/rewind step */
|
|
int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
|
|
int direction = -1; /* forward=1 or backward=-1 */
|
|
long accel_tick = 0; /* next time at which to bump the step size */
|
|
bool exit = false;
|
|
bool usb = false;
|
|
int i = 0;
|
|
|
|
if (button == ACTION_NONE)
|
|
{
|
|
status_set_ffmode(0);
|
|
return usb;
|
|
}
|
|
while (!exit)
|
|
{
|
|
switch ( button )
|
|
{
|
|
case ACTION_WPS_SEEKFWD:
|
|
direction = 1;
|
|
case ACTION_WPS_SEEKBACK:
|
|
if (wps_state.ff_rewind)
|
|
{
|
|
if (direction == 1)
|
|
{
|
|
/* fast forwarding, calc max step relative to end */
|
|
max_step = (wps_state.id3->length -
|
|
(wps_state.id3->elapsed +
|
|
ff_rewind_count)) *
|
|
FF_REWIND_MAX_PERCENT / 100;
|
|
}
|
|
else
|
|
{
|
|
/* rewinding, calc max step relative to start */
|
|
max_step = (wps_state.id3->elapsed + ff_rewind_count) *
|
|
FF_REWIND_MAX_PERCENT / 100;
|
|
}
|
|
|
|
max_step = MAX(max_step, MIN_FF_REWIND_STEP);
|
|
|
|
if (step > max_step)
|
|
step = max_step;
|
|
|
|
ff_rewind_count += step * direction;
|
|
|
|
if (global_settings.ff_rewind_accel != 0 &&
|
|
current_tick >= accel_tick)
|
|
{
|
|
step *= 2;
|
|
accel_tick = current_tick +
|
|
global_settings.ff_rewind_accel*HZ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (audio_status() & AUDIO_STATUS_PLAY) &&
|
|
wps_state.id3 && wps_state.id3->length )
|
|
{
|
|
if (!wps_state.paused)
|
|
#if (CONFIG_CODEC == SWCODEC)
|
|
audio_pre_ff_rewind();
|
|
#else
|
|
audio_pause();
|
|
#endif
|
|
#if CONFIG_KEYPAD == PLAYER_PAD
|
|
FOR_NB_SCREENS(i)
|
|
gui_wps[i].display->stop_scroll();
|
|
#endif
|
|
if (direction > 0)
|
|
status_set_ffmode(STATUS_FASTFORWARD);
|
|
else
|
|
status_set_ffmode(STATUS_FASTBACKWARD);
|
|
|
|
wps_state.ff_rewind = true;
|
|
|
|
step = ff_rew_steps[global_settings.ff_rewind_min_step];
|
|
|
|
accel_tick = current_tick +
|
|
global_settings.ff_rewind_accel*HZ;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (direction > 0) {
|
|
if ((wps_state.id3->elapsed + ff_rewind_count) >
|
|
wps_state.id3->length)
|
|
ff_rewind_count = wps_state.id3->length -
|
|
wps_state.id3->elapsed;
|
|
}
|
|
else {
|
|
if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
|
|
ff_rewind_count = -wps_state.id3->elapsed;
|
|
}
|
|
|
|
FOR_NB_SCREENS(i)
|
|
gui_wps_refresh(&gui_wps[i],
|
|
(wps_state.wps_time_countup == false)?
|
|
ff_rewind_count:-ff_rewind_count,
|
|
WPS_REFRESH_PLAYER_PROGRESS |
|
|
WPS_REFRESH_DYNAMIC);
|
|
|
|
break;
|
|
|
|
case ACTION_WPS_STOPSEEK:
|
|
wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
|
|
audio_ff_rewind(wps_state.id3->elapsed);
|
|
ff_rewind_count = 0;
|
|
wps_state.ff_rewind = false;
|
|
status_set_ffmode(0);
|
|
#if (CONFIG_CODEC != SWCODEC)
|
|
if (!wps_state.paused)
|
|
audio_resume();
|
|
#endif
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
gui_wps_display();
|
|
#endif
|
|
exit = true;
|
|
break;
|
|
|
|
default:
|
|
if(default_event_handler(button) == SYS_USB_CONNECTED) {
|
|
status_set_ffmode(0);
|
|
usb = true;
|
|
exit = true;
|
|
}
|
|
break;
|
|
}
|
|
if (!exit)
|
|
button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK);
|
|
}
|
|
action_signalscreenchange();
|
|
return usb;
|
|
}
|
|
|
|
bool gui_wps_display(void)
|
|
{
|
|
int i;
|
|
if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
|
|
{
|
|
global_status.resume_index = -1;
|
|
#ifdef HAVE_LCD_BITMAP
|
|
gui_syncstatusbar_draw(&statusbars, true);
|
|
#endif
|
|
gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
gui_wps[i].display->clear_display();
|
|
if (!gui_wps[i].data->wps_loaded) {
|
|
if ( !gui_wps[i].data->num_tokens ) {
|
|
/* set the default wps for the main-screen */
|
|
if(i == 0)
|
|
{
|
|
#ifdef HAVE_LCD_BITMAP
|
|
#if LCD_DEPTH > 1
|
|
unload_wps_backdrop();
|
|
#endif
|
|
wps_data_load(gui_wps[i].data,
|
|
"%s%?it<%?in<%in. |>%it|%fn>\n"
|
|
"%s%?ia<%ia|%?d2<%d2|(root)>>\n"
|
|
"%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
|
|
"\n"
|
|
"%al%pc/%pt%ar[%pp:%pe]\n"
|
|
"%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
|
|
"%pb\n"
|
|
"%pm\n", false);
|
|
#else
|
|
wps_data_load(gui_wps[i].data,
|
|
"%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
|
|
"%pc%?ps<*|/>%pt\n", false);
|
|
#endif
|
|
}
|
|
#if NB_SCREENS == 2
|
|
/* set the default wps for the remote-screen */
|
|
else if(i == 1)
|
|
{
|
|
wps_data_load(gui_wps[i].data,
|
|
"%s%?ia<%ia|%?d2<%d2|(root)>>\n"
|
|
"%s%?it<%?in<%in. |>%it|%fn>\n"
|
|
"%al%pc/%pt%ar[%pp:%pe]\n"
|
|
"%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
|
|
"%pb", false);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
yield();
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool update(struct gui_wps *gwps)
|
|
{
|
|
bool track_changed = audio_has_changed_track();
|
|
bool retcode = false;
|
|
|
|
gwps->state->nid3 = audio_next_track();
|
|
if (track_changed)
|
|
{
|
|
gwps->display->stop_scroll();
|
|
gwps->state->id3 = audio_current_track();
|
|
|
|
if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
|
|
&& strcmp(gwps->state->id3->path, curr_cue->audio_filename))
|
|
{
|
|
/* the current cuesheet isn't the right one any more */
|
|
|
|
if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
|
|
/* We have the new cuesheet in memory (temp_cue),
|
|
let's make it the current one ! */
|
|
memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
|
|
}
|
|
else {
|
|
/* We need to parse the new cuesheet */
|
|
|
|
char cuepath[MAX_PATH];
|
|
strncpy(cuepath, gwps->state->id3->path, MAX_PATH);
|
|
char *dot = strrchr(cuepath, '.');
|
|
strcpy(dot, ".cue");
|
|
|
|
if (parse_cuesheet(cuepath, curr_cue))
|
|
{
|
|
gwps->state->id3->cuesheet_type = 1;
|
|
strcpy(curr_cue->audio_filename, gwps->state->id3->path);
|
|
}
|
|
}
|
|
|
|
cue_spoof_id3(curr_cue, gwps->state->id3);
|
|
}
|
|
|
|
if (gui_wps_display())
|
|
retcode = true;
|
|
else{
|
|
gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
|
|
}
|
|
|
|
if (gwps->state->id3)
|
|
memcpy(gwps->state->current_track_path, gwps->state->id3->path,
|
|
sizeof(gwps->state->current_track_path));
|
|
}
|
|
|
|
if (gwps->state->id3)
|
|
{
|
|
if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
|
|
&& (gwps->state->id3->elapsed < curr_cue->curr_track->offset
|
|
|| (curr_cue->curr_track_idx < curr_cue->track_count - 1
|
|
&& gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
|
|
{
|
|
/* We've changed tracks within the cuesheet :
|
|
we need to update the ID3 info and refresh the WPS */
|
|
|
|
cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
|
|
cue_spoof_id3(curr_cue, gwps->state->id3);
|
|
|
|
gwps->display->stop_scroll();
|
|
if (gui_wps_display())
|
|
retcode = true;
|
|
else
|
|
gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
|
|
}
|
|
else
|
|
gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
|
|
}
|
|
|
|
gui_wps_statusbar_draw(gwps, false);
|
|
|
|
return retcode;
|
|
}
|
|
|
|
|
|
void display_keylock_text(bool locked)
|
|
{
|
|
char* s;
|
|
int i;
|
|
FOR_NB_SCREENS(i)
|
|
gui_wps[i].display->stop_scroll();
|
|
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
if(locked)
|
|
s = str(LANG_KEYLOCK_ON_PLAYER);
|
|
else
|
|
s = str(LANG_KEYLOCK_OFF_PLAYER);
|
|
#else
|
|
if(locked)
|
|
s = str(LANG_KEYLOCK_ON_RECORDER);
|
|
else
|
|
s = str(LANG_KEYLOCK_OFF_RECORDER);
|
|
#endif
|
|
gui_syncsplash(HZ, s);
|
|
}
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
|
|
static void draw_progressbar(struct gui_wps *gwps, int line)
|
|
{
|
|
struct wps_data *data = gwps->data;
|
|
struct screen *display = gwps->display;
|
|
struct wps_state *state = gwps->state;
|
|
int h = font_get(FONT_UI)->height;
|
|
|
|
int sb_y;
|
|
if (data->progress_top < 0)
|
|
sb_y = line*h + display->getymargin() +
|
|
((h > data->progress_height + 1)
|
|
? (h - data->progress_height) / 2 : 1);
|
|
else
|
|
sb_y = data->progress_top;
|
|
|
|
if (!data->progress_end)
|
|
data->progress_end=display->width;
|
|
|
|
if (gwps->data->progressbar.have_bitmap_pb)
|
|
gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
|
|
data->progress_start, sb_y,
|
|
data->progress_end-data->progress_start,
|
|
data->progressbar.bm.height,
|
|
state->id3->length ? state->id3->length : 1, 0,
|
|
state->id3->length ? state->id3->elapsed
|
|
+ state->ff_rewind_count : 0,
|
|
HORIZONTAL);
|
|
else
|
|
gui_scrollbar_draw(display, data->progress_start, sb_y,
|
|
data->progress_end-data->progress_start,
|
|
data->progress_height,
|
|
state->id3->length ? state->id3->length : 1, 0,
|
|
state->id3->length ? state->id3->elapsed
|
|
+ state->ff_rewind_count : 0,
|
|
HORIZONTAL);
|
|
|
|
#ifdef AB_REPEAT_ENABLE
|
|
if ( ab_repeat_mode_enabled() )
|
|
ab_draw_markers(display, state->id3->length,
|
|
data->progress_start, data->progress_end, sb_y,
|
|
data->progress_height);
|
|
#endif
|
|
|
|
if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
|
|
cue_draw_markers(display, state->id3->length,
|
|
data->progress_start, data->progress_end,
|
|
sb_y+1, data->progress_height-2);
|
|
}
|
|
|
|
/* clears the area where the image was shown */
|
|
static void clear_image_pos(struct gui_wps *gwps, int n)
|
|
{
|
|
if(!gwps)
|
|
return;
|
|
struct wps_data *data = gwps->data;
|
|
gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
gwps->display->fillrect(data->img[n].x, data->img[n].y,
|
|
data->img[n].bm.width, data->img[n].bm.height);
|
|
gwps->display->set_drawmode(DRMODE_SOLID);
|
|
}
|
|
|
|
static void wps_draw_image(struct gui_wps *gwps, int n)
|
|
{
|
|
struct screen *display = gwps->display;
|
|
struct wps_data *data = gwps->data;
|
|
if(data->img[n].always_display)
|
|
display->set_drawmode(DRMODE_FG);
|
|
else
|
|
display->set_drawmode(DRMODE_SOLID);
|
|
|
|
#if LCD_DEPTH > 1
|
|
if(data->img[n].bm.format == FORMAT_MONO) {
|
|
#endif
|
|
display->mono_bitmap(data->img[n].bm.data, data->img[n].x,
|
|
data->img[n].y, data->img[n].bm.width,
|
|
data->img[n].bm.height);
|
|
#if LCD_DEPTH > 1
|
|
} else {
|
|
display->transparent_bitmap((fb_data *)data->img[n].bm.data,
|
|
data->img[n].x,
|
|
data->img[n].y, data->img[n].bm.width,
|
|
data->img[n].bm.height);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void wps_display_images(struct gui_wps *gwps)
|
|
{
|
|
if(!gwps || !gwps->data || !gwps->display)
|
|
return;
|
|
|
|
int n;
|
|
struct wps_data *data = gwps->data;
|
|
struct screen *display = gwps->display;
|
|
|
|
for (n = 0; n < MAX_IMAGES; n++)
|
|
{
|
|
if (data->img[n].loaded &&
|
|
(data->img[n].display || data->img[n].always_display))
|
|
{
|
|
wps_draw_image(gwps, n);
|
|
}
|
|
}
|
|
display->set_drawmode(DRMODE_SOLID);
|
|
}
|
|
|
|
#else /* HAVE_LCD_CHARCELL */
|
|
|
|
static bool draw_player_progress(struct gui_wps *gwps)
|
|
{
|
|
char player_progressbar[7];
|
|
char binline[36];
|
|
int songpos = 0;
|
|
int i,j;
|
|
struct wps_state *state = gwps->state;
|
|
struct screen *display = gwps->display;
|
|
if (!state->id3)
|
|
return false;
|
|
|
|
memset(binline, 1, sizeof binline);
|
|
memset(player_progressbar, 1, sizeof player_progressbar);
|
|
|
|
if(state->id3->elapsed >= state->id3->length)
|
|
songpos = 0;
|
|
else
|
|
{
|
|
if(state->wps_time_countup == false)
|
|
songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) /
|
|
state->id3->length;
|
|
else
|
|
songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) /
|
|
state->id3->length;
|
|
}
|
|
for (i=0; i < songpos; i++)
|
|
binline[i] = 0;
|
|
|
|
for (i=0; i<=6; i++) {
|
|
for (j=0;j<5;j++) {
|
|
player_progressbar[i] <<= 1;
|
|
player_progressbar[i] += binline[i*5+j];
|
|
}
|
|
}
|
|
display->define_pattern(gwps->data->wps_progress_pat[0], player_progressbar);
|
|
return true;
|
|
}
|
|
|
|
static char map_fullbar_char(char ascii_val)
|
|
{
|
|
if (ascii_val >= '0' && ascii_val <= '9') {
|
|
return(ascii_val - '0');
|
|
}
|
|
else if (ascii_val == ':') {
|
|
return(10);
|
|
}
|
|
else
|
|
return(11); /* anything besides a number or ':' is mapped to <blank> */
|
|
}
|
|
|
|
static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
|
|
{
|
|
int i,j,lcd_char_pos;
|
|
|
|
char player_progressbar[7];
|
|
char binline[36];
|
|
static const char numbers[12][4][3]={
|
|
{{1,1,1},{1,0,1},{1,0,1},{1,1,1}},/*0*/
|
|
{{0,1,0},{1,1,0},{0,1,0},{0,1,0}},/*1*/
|
|
{{1,1,1},{0,0,1},{0,1,0},{1,1,1}},/*2*/
|
|
{{1,1,1},{0,0,1},{0,1,1},{1,1,1}},/*3*/
|
|
{{1,0,0},{1,1,0},{1,1,1},{0,1,0}},/*4*/
|
|
{{1,1,1},{1,1,0},{0,0,1},{1,1,0}},/*5*/
|
|
{{1,1,1},{1,0,0},{1,1,1},{1,1,1}},/*6*/
|
|
{{1,1,1},{0,0,1},{0,1,0},{1,0,0}},/*7*/
|
|
{{1,1,1},{1,1,1},{1,0,1},{1,1,1}},/*8*/
|
|
{{1,1,1},{1,1,1},{0,0,1},{1,1,1}},/*9*/
|
|
{{0,0,0},{0,1,0},{0,0,0},{0,1,0}},/*:*/
|
|
{{0,0,0},{0,0,0},{0,0,0},{0,0,0}} /*<blank>*/
|
|
};
|
|
|
|
int songpos = 0;
|
|
int digits[6];
|
|
int time;
|
|
char timestr[7];
|
|
|
|
struct wps_state *state = gwps->state;
|
|
struct screen *display = gwps->display;
|
|
struct wps_data *data = gwps->data;
|
|
|
|
for (i=0; i < buf_size; i++)
|
|
buf[i] = ' ';
|
|
|
|
if(state->id3->elapsed >= state->id3->length)
|
|
songpos = 55;
|
|
else {
|
|
if(state->wps_time_countup == false)
|
|
songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) /
|
|
state->id3->length;
|
|
else
|
|
songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) /
|
|
state->id3->length;
|
|
}
|
|
|
|
time=(state->id3->elapsed + state->ff_rewind_count);
|
|
|
|
memset(timestr, 0, sizeof(timestr));
|
|
format_time(timestr, sizeof(timestr), time);
|
|
for(lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) {
|
|
digits[lcd_char_pos] = map_fullbar_char(timestr[lcd_char_pos]);
|
|
}
|
|
|
|
/* build the progressbar-icons */
|
|
for (lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) {
|
|
memset(binline, 0, sizeof binline);
|
|
memset(player_progressbar, 0, sizeof player_progressbar);
|
|
|
|
/* make the character (progressbar & digit)*/
|
|
for (i=0; i<7; i++) {
|
|
for (j=0;j<5;j++) {
|
|
/* make the progressbar */
|
|
if (lcd_char_pos==(songpos/5)) {
|
|
/* partial */
|
|
if ((j<(songpos%5))&&(i>4))
|
|
binline[i*5+j] = 1;
|
|
else
|
|
binline[i*5+j] = 0;
|
|
}
|
|
else {
|
|
if (lcd_char_pos<(songpos/5)) {
|
|
/* full character */
|
|
if (i>4)
|
|
binline[i*5+j] = 1;
|
|
}
|
|
}
|
|
/* insert the digit */
|
|
if ((j<3)&&(i<4)) {
|
|
if (numbers[digits[lcd_char_pos]][i][j]==1)
|
|
binline[i*5+j] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<=6; i++) {
|
|
for (j=0;j<5;j++) {
|
|
player_progressbar[i] <<= 1;
|
|
player_progressbar[i] += binline[i*5+j];
|
|
}
|
|
}
|
|
|
|
display->define_pattern(data->wps_progress_pat[lcd_char_pos+1],
|
|
player_progressbar);
|
|
buf = utf8encode(data->wps_progress_pat[lcd_char_pos+1], buf);
|
|
}
|
|
|
|
/* make rest of the progressbar if necessary */
|
|
if (songpos/5>5) {
|
|
|
|
/* set the characters positions that use the full 5 pixel wide bar */
|
|
for (lcd_char_pos=6; lcd_char_pos < (songpos/5); lcd_char_pos++)
|
|
buf = utf8encode(0xe115, buf); /* 2/7 '_' */
|
|
|
|
/* build the partial bar character for the tail character position */
|
|
memset(binline, 0, sizeof binline);
|
|
memset(player_progressbar, 0, sizeof player_progressbar);
|
|
|
|
for (i=5; i<7; i++) {
|
|
for (j=0;j<5;j++) {
|
|
if (j<(songpos%5)) {
|
|
binline[i*5+j] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<7; i++) {
|
|
for (j=0;j<5;j++) {
|
|
player_progressbar[i] <<= 1;
|
|
player_progressbar[i] += binline[i*5+j];
|
|
}
|
|
}
|
|
|
|
display->define_pattern(data->wps_progress_pat[7],player_progressbar);
|
|
buf = utf8encode(data->wps_progress_pat[7], buf);
|
|
*buf = '\0';
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_LCD_CHARCELL */
|
|
|
|
/* Extract a part from a path.
|
|
*
|
|
* buf - buffer extract part to.
|
|
* buf_size - size of buffer.
|
|
* path - path to extract from.
|
|
* level - what to extract. 0 is file name, 1 is parent of file, 2 is
|
|
* parent of parent, etc.
|
|
*
|
|
* Returns buf if the desired level was found, NULL otherwise.
|
|
*/
|
|
static char* get_dir(char* buf, int buf_size, const char* path, int level)
|
|
{
|
|
const char* sep;
|
|
const char* last_sep;
|
|
int len;
|
|
|
|
sep = path + strlen(path);
|
|
last_sep = sep;
|
|
|
|
while (sep > path)
|
|
{
|
|
if ('/' == *(--sep))
|
|
{
|
|
if (!level)
|
|
break;
|
|
|
|
level--;
|
|
last_sep = sep - 1;
|
|
}
|
|
}
|
|
|
|
if (level || (last_sep <= sep))
|
|
return NULL;
|
|
|
|
len = MIN(last_sep - sep, buf_size - 1);
|
|
strncpy(buf, sep + 1, len);
|
|
buf[len] = 0;
|
|
return buf;
|
|
}
|
|
|
|
/* Return the tag found at index i and write its value in buf.
|
|
The return value is buf if the tag had a value, or NULL if not.
|
|
|
|
intval is used with enums: when this function is called, it should contain
|
|
the number of options in the enum. When this function returns, it will
|
|
contain the enum case we are actually in.
|
|
When not treating an enum, intval should be NULL.
|
|
*/
|
|
static char *get_tag(struct gui_wps *gwps,
|
|
int i,
|
|
char *buf,
|
|
int buf_size,
|
|
int *intval)
|
|
{
|
|
if (!gwps)
|
|
return NULL;
|
|
|
|
struct wps_data *data = gwps->data;
|
|
struct wps_state *state = gwps->state;
|
|
|
|
if (!data || !state)
|
|
return NULL;
|
|
|
|
struct mp3entry *id3;
|
|
|
|
if (data->tokens[i].next)
|
|
id3 = state->nid3;
|
|
else
|
|
id3 = state->id3;
|
|
|
|
if (!id3)
|
|
return NULL;
|
|
|
|
int limit = 1;
|
|
if (intval)
|
|
limit = *intval;
|
|
|
|
#if CONFIG_RTC
|
|
static struct tm* tm;
|
|
#endif
|
|
|
|
switch (data->tokens[i].type)
|
|
{
|
|
case WPS_TOKEN_CHARACTER:
|
|
return &(data->tokens[i].value.c);
|
|
|
|
case WPS_TOKEN_STRING:
|
|
return data->strings[data->tokens[i].value.i];
|
|
|
|
case WPS_TOKEN_TRACK_TIME_ELAPSED:
|
|
format_time(buf, buf_size,
|
|
id3->elapsed + state->ff_rewind_count);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_TRACK_TIME_REMAINING:
|
|
format_time(buf, buf_size,
|
|
id3->length - id3->elapsed -
|
|
state->ff_rewind_count);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_TRACK_LENGTH:
|
|
format_time(buf, buf_size, id3->length);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_PLAYLIST_ENTRIES:
|
|
snprintf(buf, buf_size, "%d", playlist_amount());
|
|
return buf;
|
|
|
|
case WPS_TOKEN_PLAYLIST_NAME:
|
|
return playlist_name(NULL, buf, buf_size);
|
|
|
|
case WPS_TOKEN_PLAYLIST_POSITION:
|
|
snprintf(buf, buf_size, "%d",
|
|
playlist_get_display_index());
|
|
return buf;
|
|
|
|
case WPS_TOKEN_PLAYLIST_SHUFFLE:
|
|
if ( global_settings.playlist_shuffle )
|
|
return "s";
|
|
else
|
|
return NULL;
|
|
break;
|
|
|
|
case WPS_TOKEN_VOLUME:
|
|
snprintf(buf, buf_size, "%d", global_settings.volume);
|
|
if (intval)
|
|
{
|
|
*intval = limit * (global_settings.volume
|
|
- sound_min(SOUND_VOLUME))
|
|
/ (sound_max(SOUND_VOLUME)
|
|
- sound_min(SOUND_VOLUME)) + 1;
|
|
}
|
|
return buf;
|
|
|
|
case WPS_TOKEN_METADATA_ARTIST:
|
|
return id3->artist;
|
|
|
|
case WPS_TOKEN_METADATA_COMPOSER:
|
|
return id3->composer;
|
|
|
|
case WPS_TOKEN_METADATA_ALBUM:
|
|
return id3->album;
|
|
|
|
case WPS_TOKEN_METADATA_ALBUM_ARTIST:
|
|
return id3->albumartist;
|
|
|
|
case WPS_TOKEN_METADATA_GENRE:
|
|
return id3->genre_string;
|
|
|
|
case WPS_TOKEN_METADATA_TRACK_NUMBER:
|
|
if (id3->track_string)
|
|
return id3->track_string;
|
|
|
|
if (id3->tracknum) {
|
|
snprintf(buf, buf_size, "%d", id3->tracknum);
|
|
return buf;
|
|
}
|
|
return NULL;
|
|
|
|
case WPS_TOKEN_METADATA_TRACK_TITLE:
|
|
return id3->title;
|
|
|
|
case WPS_TOKEN_METADATA_VERSION:
|
|
switch (id3->id3version)
|
|
{
|
|
case ID3_VER_1_0:
|
|
return "1";
|
|
|
|
case ID3_VER_1_1:
|
|
return "1.1";
|
|
|
|
case ID3_VER_2_2:
|
|
return "2.2";
|
|
|
|
case ID3_VER_2_3:
|
|
return "2.3";
|
|
|
|
case ID3_VER_2_4:
|
|
return "2.4";
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
case WPS_TOKEN_METADATA_YEAR:
|
|
if( id3->year_string )
|
|
return id3->year_string;
|
|
|
|
if (id3->year) {
|
|
snprintf(buf, buf_size, "%d", id3->year);
|
|
return buf;
|
|
}
|
|
return NULL;
|
|
|
|
case WPS_TOKEN_METADATA_COMMENT:
|
|
return id3->comment;
|
|
|
|
case WPS_TOKEN_FILE_BITRATE:
|
|
if(id3->bitrate)
|
|
snprintf(buf, buf_size, "%d", id3->bitrate);
|
|
else
|
|
snprintf(buf, buf_size, "?");
|
|
return buf;
|
|
|
|
case WPS_TOKEN_FILE_CODEC:
|
|
if (intval)
|
|
{
|
|
if(id3->codectype == AFMT_UNKNOWN)
|
|
*intval = AFMT_NUM_CODECS;
|
|
else
|
|
*intval = id3->codectype;
|
|
}
|
|
return id3_get_codec(id3);
|
|
|
|
case WPS_TOKEN_FILE_FREQUENCY:
|
|
snprintf(buf, buf_size, "%ld", id3->frequency);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_FILE_NAME:
|
|
if (get_dir(buf, buf_size, id3->path, 0)) {
|
|
/* Remove extension */
|
|
char* sep = strrchr(buf, '.');
|
|
if (NULL != sep) {
|
|
*sep = 0;
|
|
}
|
|
return buf;
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
|
|
case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
|
|
return get_dir(buf, buf_size, id3->path, 0);
|
|
|
|
case WPS_TOKEN_FILE_PATH:
|
|
return id3->path;
|
|
|
|
case WPS_TOKEN_FILE_SIZE:
|
|
snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_FILE_VBR:
|
|
return id3->vbr ? "(avg)" : NULL;
|
|
|
|
case WPS_TOKEN_FILE_DIRECTORY:
|
|
return get_dir(buf, buf_size, id3->path, data->tokens[i].value.i);
|
|
|
|
case WPS_TOKEN_BATTERY_PERCENT:
|
|
{
|
|
int l = battery_level();
|
|
|
|
if (intval)
|
|
{
|
|
limit = MAX(limit, 2);
|
|
if (l > -1) {
|
|
/* First enum is used for "unknown level". */
|
|
*intval = (limit - 1) * l / 100 + 2;
|
|
} else {
|
|
*intval = 1;
|
|
}
|
|
}
|
|
|
|
if (l > -1) {
|
|
snprintf(buf, buf_size, "%d", l);
|
|
return buf;
|
|
} else {
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
case WPS_TOKEN_BATTERY_VOLTS:
|
|
{
|
|
unsigned int v = battery_voltage();
|
|
snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
|
|
return buf;
|
|
}
|
|
|
|
case WPS_TOKEN_BATTERY_TIME:
|
|
{
|
|
int t = battery_time();
|
|
if (t >= 0)
|
|
snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
|
|
else
|
|
strncpy(buf, "?h ?m", buf_size);
|
|
return buf;
|
|
}
|
|
|
|
#if CONFIG_CHARGING
|
|
case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
|
|
{
|
|
if(charger_input_state==CHARGER)
|
|
return "p";
|
|
else
|
|
return NULL;
|
|
}
|
|
#endif
|
|
#if CONFIG_CHARGING >= CHARGING_MONITOR
|
|
case WPS_TOKEN_BATTERY_CHARGING:
|
|
{
|
|
if (charge_state == CHARGING || charge_state == TOPOFF) {
|
|
return "c";
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
case WPS_TOKEN_PLAYBACK_STATUS:
|
|
{
|
|
int status = audio_status();
|
|
int mode = 1;
|
|
if (status == AUDIO_STATUS_PLAY && \
|
|
!(status & AUDIO_STATUS_PAUSE))
|
|
mode = 2;
|
|
if (audio_status() & AUDIO_STATUS_PAUSE && \
|
|
(! status_get_ffmode()))
|
|
mode = 3;
|
|
if (status_get_ffmode() == STATUS_FASTFORWARD)
|
|
mode = 4;
|
|
if (status_get_ffmode() == STATUS_FASTBACKWARD)
|
|
mode = 5;
|
|
|
|
if (intval) {
|
|
*intval = mode;
|
|
}
|
|
|
|
snprintf(buf, buf_size, "%d", mode);
|
|
return buf;
|
|
}
|
|
|
|
case WPS_TOKEN_REPEAT_MODE:
|
|
if (intval)
|
|
*intval = global_settings.repeat_mode + 1;
|
|
snprintf(buf, buf_size, "%d", *intval);
|
|
return buf;
|
|
|
|
#if CONFIG_RTC
|
|
case WPS_TOKEN_RTC:
|
|
tm = get_time();
|
|
return NULL;
|
|
|
|
case WPS_TOKEN_RTC_DAY_OF_MONTH:
|
|
/* d: day of month (01..31) */
|
|
if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
|
|
snprintf(buf, buf_size, "%02d", tm->tm_mday);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
|
|
/* e: day of month, blank padded ( 1..31) */
|
|
if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
|
|
snprintf(buf, buf_size, "%2d", tm->tm_mday);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
|
|
/* H: hour (00..23) */
|
|
if (tm->tm_hour > 23) return NULL;
|
|
snprintf(buf, buf_size, "%02d", tm->tm_hour);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_HOUR_24:
|
|
/* k: hour ( 0..23) */
|
|
if (tm->tm_hour > 23) return NULL;
|
|
snprintf(buf, buf_size, "%2d", tm->tm_hour);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
|
|
/* I: hour (01..12) */
|
|
if (tm->tm_hour > 23) return NULL;
|
|
snprintf(buf, buf_size, "%02d",
|
|
(tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_HOUR_12:
|
|
/* l: hour ( 1..12) */
|
|
if (tm->tm_hour > 23) return NULL;
|
|
snprintf(buf, buf_size, "%2d",
|
|
(tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_MONTH:
|
|
/* m: month (01..12) */
|
|
if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
|
|
snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_MINUTE:
|
|
/* M: minute (00..59) */
|
|
if (tm->tm_min > 59 || tm->tm_min < 0) return NULL;
|
|
snprintf(buf, buf_size, "%02d", tm->tm_min);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_SECOND:
|
|
/* S: second (00..59) */
|
|
if (tm->tm_sec > 59 || tm->tm_sec < 0) return NULL;
|
|
snprintf(buf, buf_size, "%02d", tm->tm_sec);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_YEAR_2_DIGITS:
|
|
/* y: last two digits of year (00..99) */
|
|
snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_YEAR_4_DIGITS:
|
|
/* Y: year (1970...) */
|
|
if (tm->tm_year > 199 || tm->tm_year < 100) return NULL;
|
|
snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_AM_PM_UPPER:
|
|
/* p: upper case AM or PM indicator */
|
|
snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_AM_PM_LOWER:
|
|
/* P: lower case am or pm indicator */
|
|
snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_WEEKDAY_NAME:
|
|
/* a: abbreviated weekday name (Sun..Sat) */
|
|
if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
|
|
snprintf(buf, buf_size, "%s",str(dayname[tm->tm_wday]));
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_MONTH_NAME:
|
|
/* b: abbreviated month name (Jan..Dec) */
|
|
if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
|
|
snprintf(buf, buf_size, "%s",str(monthname[tm->tm_mon]));
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
|
|
/* u: day of week (1..7); 1 is Monday */
|
|
if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
|
|
snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
|
|
/* w: day of week (0..6); 0 is Sunday */
|
|
if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
|
|
snprintf(buf, buf_size, "%1d", tm->tm_wday);
|
|
return buf;
|
|
#endif
|
|
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
case WPS_TOKEN_PROGRESSBAR:
|
|
{
|
|
char *end = utf8encode(data->wps_progress_pat[0], buf);
|
|
*end = '\0';
|
|
return buf;
|
|
}
|
|
|
|
case WPS_TOKEN_PLAYER_PROGRESSBAR:
|
|
if(is_new_player())
|
|
{
|
|
/* we need 11 characters (full line) for
|
|
progress-bar */
|
|
snprintf(buf, buf_size, " ");
|
|
}
|
|
else
|
|
{
|
|
/* Tell the user if we have an OldPlayer */
|
|
snprintf(buf, buf_size, " <Old LCD> ");
|
|
}
|
|
return buf;
|
|
#endif
|
|
|
|
case WPS_TOKEN_DATABASE_PLAYCOUNT:
|
|
if (intval) {
|
|
*intval = id3->playcount + 1;
|
|
}
|
|
snprintf(buf, buf_size, "%ld", id3->playcount);
|
|
return buf;
|
|
|
|
case WPS_TOKEN_DATABASE_RATING:
|
|
if (intval) {
|
|
*intval = id3->rating + 1;
|
|
}
|
|
snprintf(buf, buf_size, "%d", id3->rating);
|
|
return buf;
|
|
|
|
#if (CONFIG_CODEC == SWCODEC)
|
|
case WPS_TOKEN_REPLAYGAIN:
|
|
{
|
|
int val;
|
|
|
|
if (global_settings.replaygain == 0)
|
|
val = 1; /* off */
|
|
else
|
|
{
|
|
int type =
|
|
get_replaygain_mode(id3->track_gain_string != NULL,
|
|
id3->album_gain_string != NULL);
|
|
if (type < 0)
|
|
val = 6; /* no tag */
|
|
else
|
|
val = type + 2;
|
|
|
|
if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
|
|
val += 2;
|
|
}
|
|
|
|
if (intval)
|
|
*intval = val;
|
|
|
|
switch (val)
|
|
{
|
|
case 1:
|
|
case 6:
|
|
return "+0.00 dB";
|
|
break;
|
|
case 2:
|
|
case 4:
|
|
strncpy(buf, id3->track_gain_string, buf_size);
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
strncpy(buf, id3->album_gain_string, buf_size);
|
|
break;
|
|
}
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
#if (CONFIG_CODEC != MAS3507D)
|
|
case WPS_TOKEN_SOUND_PITCH:
|
|
{
|
|
int val = sound_get_pitch();
|
|
snprintf(buf, buf_size, "%d.%d",
|
|
val / 10, val % 10);
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAS_BUTTON_HOLD
|
|
case WPS_TOKEN_MAIN_HOLD:
|
|
if (button_hold())
|
|
return "h";
|
|
else
|
|
return NULL;
|
|
#endif
|
|
#ifdef HAS_REMOTE_BUTTON_HOLD
|
|
case WPS_TOKEN_REMOTE_HOLD:
|
|
if (remote_button_hold())
|
|
return "r";
|
|
else
|
|
return NULL;
|
|
#endif
|
|
|
|
#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
|
|
case WPS_TOKEN_VLED_HDD:
|
|
if(led_read(HZ/2))
|
|
return "h";
|
|
else
|
|
return NULL;
|
|
#endif
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Return the index to the end token for the conditional token at index.
|
|
The conditional token can be either a start token or a separator
|
|
(i.e. option) token.
|
|
*/
|
|
static int find_conditional_end(struct wps_data *data, int index)
|
|
{
|
|
int type = data->tokens[index].type;
|
|
|
|
if (type != WPS_TOKEN_CONDITIONAL_START
|
|
&& type != WPS_TOKEN_CONDITIONAL_OPTION)
|
|
{
|
|
/* this function should only be used with "index" pointing to a
|
|
WPS_TOKEN_CONDITIONAL_START or a WPS_TOKEN_CONDITIONAL_OPTION */
|
|
return index + 1;
|
|
}
|
|
|
|
int ret = index;
|
|
do
|
|
ret = data->tokens[ret].value.i;
|
|
while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END);
|
|
|
|
/* ret now is the index to the end token for the conditional. */
|
|
return ret;
|
|
}
|
|
|
|
/* Return the index of the appropriate case for the conditional
|
|
that starts at cond_index.
|
|
*/
|
|
static int evaluate_conditional(struct gui_wps *gwps, int cond_index)
|
|
{
|
|
if (!gwps)
|
|
return 0;
|
|
|
|
struct wps_data *data = gwps->data;
|
|
|
|
int ret;
|
|
int num_options = data->tokens[cond_index].value.i;
|
|
char result[128], *value;
|
|
int cond_start = cond_index;
|
|
|
|
/* find the index of the conditional start token */
|
|
while (data->tokens[cond_start].type != WPS_TOKEN_CONDITIONAL_START
|
|
&& cond_start < data->num_tokens)
|
|
cond_start++;
|
|
|
|
if (num_options > 2) /* enum */
|
|
{
|
|
int intval = num_options;
|
|
/* get_tag needs to know the number of options in the enum */
|
|
get_tag(gwps, cond_index + 1, result, sizeof(result), &intval);
|
|
/* intval is now the number of the enum option we want to read,
|
|
starting from 1 */
|
|
if (intval > num_options || intval < 1)
|
|
intval = num_options;
|
|
|
|
int next = cond_start;
|
|
int i;
|
|
for (i = 1; i < intval; i++)
|
|
{
|
|
next = data->tokens[next].value.i;
|
|
}
|
|
ret = next;
|
|
}
|
|
else /* %?xx<true|false> or %?<true> */
|
|
{
|
|
value = get_tag(gwps, cond_index + 1, result, sizeof(result), NULL);
|
|
ret = value ? cond_start : data->tokens[cond_start].value.i;
|
|
}
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
/* clear all pictures in the conditional */
|
|
int i;
|
|
for (i=0; i < MAX_IMAGES; i++)
|
|
{
|
|
if (data->img[i].cond_index == cond_index)
|
|
clear_image_pos(gwps, i);
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Read a (sub)line to the given alignment format buffer.
|
|
linebuf is the buffer where the data is actually stored.
|
|
align is the alignment format that'll be used to display the text.
|
|
The return value indicates whether the line needs to be updated.
|
|
*/
|
|
static bool get_line(struct gui_wps *gwps,
|
|
int line, int subline,
|
|
struct align_pos *align,
|
|
char *linebuf,
|
|
int linebuf_size)
|
|
{
|
|
struct wps_data *data = gwps->data;
|
|
|
|
char temp_buf[128];
|
|
char *buf = linebuf; /* will always point to the writing position */
|
|
char *linebuf_end = linebuf + linebuf_size - 1;
|
|
bool update = false;
|
|
|
|
/* alignment-related variables */
|
|
int cur_align;
|
|
char* cur_align_start;
|
|
cur_align_start = buf;
|
|
cur_align = WPS_ALIGN_LEFT;
|
|
align->left = 0;
|
|
align->center = 0;
|
|
align->right = 0;
|
|
|
|
/* start at the beginning of the current (sub)line */
|
|
int i = data->format_lines[line][subline];
|
|
|
|
while (data->tokens[i].type != WPS_TOKEN_EOL
|
|
&& data->tokens[i].type != WPS_TOKEN_SUBLINE_SEPARATOR
|
|
&& i < data->num_tokens)
|
|
{
|
|
switch(data->tokens[i].type)
|
|
{
|
|
case WPS_TOKEN_CONDITIONAL:
|
|
/* place ourselves in the right conditional case */
|
|
i = evaluate_conditional(gwps, i);
|
|
break;
|
|
|
|
case WPS_TOKEN_CONDITIONAL_OPTION:
|
|
/* we've finished in the curent conditional case,
|
|
skip to the end of the conditional structure */
|
|
i = find_conditional_end(data, i);
|
|
break;
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
|
|
{
|
|
struct gui_img *img = data->img;
|
|
int n = data->tokens[i].value.i;
|
|
if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
|
|
img[n].display = true;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case WPS_TOKEN_ALIGN_LEFT:
|
|
case WPS_TOKEN_ALIGN_CENTER:
|
|
case WPS_TOKEN_ALIGN_RIGHT:
|
|
/* remember where the current aligned text started */
|
|
switch (cur_align)
|
|
{
|
|
case WPS_ALIGN_LEFT:
|
|
align->left = cur_align_start;
|
|
break;
|
|
|
|
case WPS_ALIGN_CENTER:
|
|
align->center = cur_align_start;
|
|
break;
|
|
|
|
case WPS_ALIGN_RIGHT:
|
|
align->right = cur_align_start;
|
|
break;
|
|
}
|
|
/* start a new alignment */
|
|
switch (data->tokens[i].type)
|
|
{
|
|
case WPS_TOKEN_ALIGN_LEFT:
|
|
cur_align = WPS_ALIGN_LEFT;
|
|
break;
|
|
case WPS_TOKEN_ALIGN_CENTER:
|
|
cur_align = WPS_ALIGN_CENTER;
|
|
break;
|
|
case WPS_TOKEN_ALIGN_RIGHT:
|
|
cur_align = WPS_ALIGN_RIGHT;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
*buf++ = 0;
|
|
cur_align_start = buf;
|
|
break;
|
|
|
|
default:
|
|
{
|
|
/* get the value of the tag and copy it to the buffer */
|
|
char *value = get_tag(gwps, i, temp_buf,
|
|
sizeof(temp_buf), NULL);
|
|
if (value)
|
|
{
|
|
update = true;
|
|
while (*value && (buf < linebuf_end))
|
|
*buf++ = *value++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* close the current alignment */
|
|
switch (cur_align)
|
|
{
|
|
case WPS_ALIGN_LEFT:
|
|
align->left = cur_align_start;
|
|
break;
|
|
|
|
case WPS_ALIGN_CENTER:
|
|
align->center = cur_align_start;
|
|
break;
|
|
|
|
case WPS_ALIGN_RIGHT:
|
|
align->right = cur_align_start;
|
|
break;
|
|
}
|
|
|
|
return update;
|
|
}
|
|
|
|
/* Calculate which subline should be displayed for each line */
|
|
static bool get_curr_subline(struct wps_data *data, int line)
|
|
{
|
|
int search, search_start;
|
|
bool reset_subline;
|
|
bool new_subline_refresh;
|
|
bool only_one_subline;
|
|
|
|
reset_subline = (data->curr_subline[line] == SUBLINE_RESET);
|
|
new_subline_refresh = false;
|
|
only_one_subline = false;
|
|
|
|
/* if time to advance to next sub-line */
|
|
if (TIME_AFTER(current_tick, data->subline_expire_time[line] - 1) ||
|
|
reset_subline)
|
|
{
|
|
/* search all sublines until the next subline with time > 0
|
|
is found or we get back to the subline we started with */
|
|
if (reset_subline)
|
|
search_start = 0;
|
|
else
|
|
search_start = data->curr_subline[line];
|
|
|
|
for (search = 0; search < WPS_MAX_SUBLINES; search++)
|
|
{
|
|
data->curr_subline[line]++;
|
|
|
|
/* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
|
|
if ((!data->format_lines[line][data->curr_subline[line]]) ||
|
|
(data->curr_subline[line] == WPS_MAX_SUBLINES))
|
|
{
|
|
if (data->curr_subline[line] == 1)
|
|
only_one_subline = true;
|
|
data->curr_subline[line] = 0;
|
|
}
|
|
|
|
/* if back where we started after search or
|
|
only one subline is defined on the line */
|
|
if (((search > 0) && (data->curr_subline[line] == search_start)) ||
|
|
only_one_subline)
|
|
{
|
|
/* no other subline with a time > 0 exists */
|
|
data->subline_expire_time[line] = (reset_subline?
|
|
current_tick : data->subline_expire_time[line]) + 100 * HZ;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* only use this subline if subline time > 0 */
|
|
if (data->time_mult[line][data->curr_subline[line]] > 0)
|
|
{
|
|
new_subline_refresh = true;
|
|
data->subline_expire_time[line] = (reset_subline ?
|
|
current_tick : data->subline_expire_time[line]) +
|
|
BASE_SUBLINE_TIME * data->time_mult[line][data->curr_subline[line]];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return new_subline_refresh;
|
|
}
|
|
|
|
/* Display a line appropriately according to its alignment format.
|
|
format_align contains the text, separated between left, center and right.
|
|
line is the index of the line on the screen.
|
|
scroll indicates whether the line is a scrolling one or not.
|
|
*/
|
|
static void write_line(struct screen *display,
|
|
struct align_pos *format_align,
|
|
int line,
|
|
bool scroll)
|
|
{
|
|
|
|
int left_width, left_xpos;
|
|
int center_width, center_xpos;
|
|
int right_width, right_xpos;
|
|
int ypos;
|
|
int space_width;
|
|
int string_height;
|
|
|
|
/* calculate different string sizes and positions */
|
|
display->getstringsize((unsigned char *)" ", &space_width, &string_height);
|
|
if (format_align->left != 0) {
|
|
display->getstringsize((unsigned char *)format_align->left,
|
|
&left_width, &string_height);
|
|
}
|
|
else {
|
|
left_width = 0;
|
|
}
|
|
left_xpos = 0;
|
|
|
|
if (format_align->center != 0) {
|
|
display->getstringsize((unsigned char *)format_align->center,
|
|
¢er_width, &string_height);
|
|
}
|
|
else {
|
|
center_width = 0;
|
|
}
|
|
center_xpos=(display->width - center_width) / 2;
|
|
|
|
if (format_align->right != 0) {
|
|
display->getstringsize((unsigned char *)format_align->right,
|
|
&right_width, &string_height);
|
|
}
|
|
else {
|
|
right_width = 0;
|
|
}
|
|
right_xpos = (display->width - right_width);
|
|
|
|
/* Checks for overlapping strings.
|
|
If needed the overlapping strings will be merged, separated by a
|
|
space */
|
|
|
|
/* CASE 1: left and centered string overlap */
|
|
/* there is a left string, need to merge left and center */
|
|
if ((left_width != 0 && center_width != 0) &&
|
|
(left_xpos + left_width + space_width > center_xpos)) {
|
|
/* replace the former separator '\0' of left and
|
|
center string with a space */
|
|
*(--format_align->center) = ' ';
|
|
/* calculate the new width and position of the merged string */
|
|
left_width = left_width + space_width + center_width;
|
|
left_xpos = 0;
|
|
/* there is no centered string anymore */
|
|
center_width = 0;
|
|
}
|
|
/* there is no left string, move center to left */
|
|
if ((left_width == 0 && center_width != 0) &&
|
|
(left_xpos + left_width > center_xpos)) {
|
|
/* move the center string to the left string */
|
|
format_align->left = format_align->center;
|
|
/* calculate the new width and position of the string */
|
|
left_width = center_width;
|
|
left_xpos = 0;
|
|
/* there is no centered string anymore */
|
|
center_width = 0;
|
|
}
|
|
|
|
/* CASE 2: centered and right string overlap */
|
|
/* there is a right string, need to merge center and right */
|
|
if ((center_width != 0 && right_width != 0) &&
|
|
(center_xpos + center_width + space_width > right_xpos)) {
|
|
/* replace the former separator '\0' of center and
|
|
right string with a space */
|
|
*(--format_align->right) = ' ';
|
|
/* move the center string to the right after merge */
|
|
format_align->right = format_align->center;
|
|
/* calculate the new width and position of the merged string */
|
|
right_width = center_width + space_width + right_width;
|
|
right_xpos = (display->width - right_width);
|
|
/* there is no centered string anymore */
|
|
center_width = 0;
|
|
}
|
|
/* there is no right string, move center to right */
|
|
if ((center_width != 0 && right_width == 0) &&
|
|
(center_xpos + center_width > right_xpos)) {
|
|
/* move the center string to the right string */
|
|
format_align->right = format_align->center;
|
|
/* calculate the new width and position of the string */
|
|
right_width = center_width;
|
|
right_xpos = (display->width - right_width);
|
|
/* there is no centered string anymore */
|
|
center_width = 0;
|
|
}
|
|
|
|
/* CASE 3: left and right overlap
|
|
There is no center string anymore, either there never
|
|
was one or it has been merged in case 1 or 2 */
|
|
/* there is a left string, need to merge left and right */
|
|
if ((left_width != 0 && center_width == 0 && right_width != 0) &&
|
|
(left_xpos + left_width + space_width > right_xpos)) {
|
|
/* replace the former separator '\0' of left and
|
|
right string with a space */
|
|
*(--format_align->right) = ' ';
|
|
/* calculate the new width and position of the string */
|
|
left_width = left_width + space_width + right_width;
|
|
left_xpos = 0;
|
|
/* there is no right string anymore */
|
|
right_width = 0;
|
|
}
|
|
/* there is no left string, move right to left */
|
|
if ((left_width == 0 && center_width == 0 && right_width != 0) &&
|
|
(left_xpos + left_width > right_xpos)) {
|
|
/* move the right string to the left string */
|
|
format_align->left = format_align->right;
|
|
/* calculate the new width and position of the string */
|
|
left_width = right_width;
|
|
left_xpos = 0;
|
|
/* there is no right string anymore */
|
|
right_width = 0;
|
|
}
|
|
|
|
ypos = (line * string_height) + display->getymargin();
|
|
|
|
|
|
if (scroll && left_width > display->width)
|
|
{
|
|
display->puts_scroll(0, line,
|
|
(unsigned char *)format_align->left);
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_LCD_BITMAP
|
|
/* clear the line first */
|
|
display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
display->fillrect(0, ypos, display->width, string_height);
|
|
display->set_drawmode(DRMODE_SOLID);
|
|
#endif
|
|
|
|
/* Nasty hack: we output an empty scrolling string,
|
|
which will reset the scroller for that line */
|
|
display->puts_scroll(0, line, (unsigned char *)"");
|
|
|
|
/* print aligned strings */
|
|
if (left_width != 0)
|
|
{
|
|
display->putsxy(left_xpos, ypos,
|
|
(unsigned char *)format_align->left);
|
|
}
|
|
if (center_width != 0)
|
|
{
|
|
display->putsxy(center_xpos, ypos,
|
|
(unsigned char *)format_align->center);
|
|
}
|
|
if (right_width != 0)
|
|
{
|
|
display->putsxy(right_xpos, ypos,
|
|
(unsigned char *)format_align->right);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Refresh the WPS according to refresh_mode. */
|
|
bool gui_wps_refresh(struct gui_wps *gwps,
|
|
int ffwd_offset,
|
|
unsigned char refresh_mode)
|
|
{
|
|
struct wps_data *data = gwps->data;
|
|
struct screen *display = gwps->display;
|
|
struct wps_state *state = gwps->state;
|
|
|
|
if(!gwps || !data || !state || !display)
|
|
return false;
|
|
|
|
int line, i;
|
|
unsigned char flags;
|
|
char linebuf[MAX_PATH];
|
|
|
|
struct align_pos align;
|
|
align.left = NULL;
|
|
align.center = NULL;
|
|
align.right = NULL;
|
|
|
|
bool update_line, new_subline_refresh;
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
gui_wps_statusbar_draw(gwps, true);
|
|
|
|
/* to find out wether the peak meter is enabled we
|
|
assume it wasn't until we find a line that contains
|
|
the peak meter. We can't use peak_meter_enabled itself
|
|
because that would mean to turn off the meter thread
|
|
temporarily. (That shouldn't matter unless yield
|
|
or sleep is called but who knows...)
|
|
*/
|
|
bool enable_pm = false;
|
|
|
|
/* Set images to not to be displayed */
|
|
for (i = 0; i < MAX_IMAGES; i++)
|
|
{
|
|
data->img[i].display = false;
|
|
}
|
|
#endif
|
|
|
|
/* reset to first subline if refresh all flag is set */
|
|
if (refresh_mode == WPS_REFRESH_ALL)
|
|
{
|
|
for (i = 0; i < data->num_lines; i++)
|
|
{
|
|
data->curr_subline[i] = SUBLINE_RESET;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LCD_CHARCELLS
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
if (data->wps_progress_pat[i] == 0)
|
|
data->wps_progress_pat[i] = display->get_locked_pattern();
|
|
}
|
|
#endif
|
|
|
|
if (!state->id3)
|
|
{
|
|
display->stop_scroll();
|
|
return false;
|
|
}
|
|
|
|
state->ff_rewind_count = ffwd_offset;
|
|
|
|
for (line = 0; line < data->num_lines; line++)
|
|
{
|
|
memset(linebuf, 0, sizeof(linebuf));
|
|
update_line = false;
|
|
|
|
/* get current subline for the line */
|
|
new_subline_refresh = get_curr_subline(data, line);
|
|
|
|
flags = data->line_type[line][data->curr_subline[line]];
|
|
|
|
if (refresh_mode == WPS_REFRESH_ALL || flags & refresh_mode
|
|
|| new_subline_refresh)
|
|
{
|
|
/* get_line tells us if we need to update the line */
|
|
update_line = get_line(gwps, line, data->curr_subline[line],
|
|
&align, linebuf, sizeof(linebuf));
|
|
}
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
/* progressbar */
|
|
if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
|
|
{
|
|
/* the progressbar should be alone on its line */
|
|
update_line = false;
|
|
draw_progressbar(gwps, line);
|
|
}
|
|
|
|
/* peakmeter */
|
|
if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
|
|
{
|
|
/* the peakmeter should be alone on its line */
|
|
update_line = false;
|
|
|
|
int h = font_get(FONT_UI)->height;
|
|
int peak_meter_y = display->getymargin() + line * h;
|
|
|
|
/* The user might decide to have the peak meter in the last
|
|
line so that it is only displayed if no status bar is
|
|
visible. If so we neither want do draw nor enable the
|
|
peak meter. */
|
|
if (peak_meter_y + h <= display->height) {
|
|
/* found a line with a peak meter -> remember that we must
|
|
enable it later */
|
|
enable_pm = true;
|
|
peak_meter_screen(gwps->display, 0, peak_meter_y,
|
|
MIN(h, display->height - peak_meter_y));
|
|
}
|
|
}
|
|
|
|
#else /* HAVE_LCD_CHARCELL */
|
|
|
|
/* progressbar */
|
|
if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
|
|
{
|
|
if (data->full_line_progressbar)
|
|
draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
|
|
else
|
|
draw_player_progress(gwps);
|
|
}
|
|
#endif
|
|
|
|
if (update_line)
|
|
{
|
|
/* calculate alignment and draw the strings */
|
|
write_line(display, &align, line, flags & WPS_REFRESH_SCROLL);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
data->peak_meter_enabled = enable_pm;
|
|
wps_display_images(gwps);
|
|
#endif
|
|
|
|
display->update();
|
|
|
|
#if CONFIG_BACKLIGHT
|
|
if (global_settings.caption_backlight && state->id3)
|
|
{
|
|
/* turn on backlight n seconds before track ends, and turn it off n
|
|
seconds into the new track. n == backlight_timeout, or 5s */
|
|
int n = backlight_timeout_value[global_settings.backlight_timeout]
|
|
* 1000;
|
|
|
|
if ( n < 1000 )
|
|
n = 5000; /* use 5s if backlight is always on or off */
|
|
|
|
if (((state->id3->elapsed < 1000) ||
|
|
((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
|
|
(state->paused == false))
|
|
backlight_on();
|
|
}
|
|
#endif
|
|
#ifdef HAVE_REMOTE_LCD
|
|
if (global_settings.remote_caption_backlight && state->id3)
|
|
{
|
|
/* turn on remote backlight n seconds before track ends, and turn it
|
|
off n seconds into the new track. n == remote_backlight_timeout,
|
|
or 5s */
|
|
int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
|
|
* 1000;
|
|
|
|
if ( n < 1000 )
|
|
n = 5000; /* use 5s if backlight is always on or off */
|
|
|
|
if (((state->id3->elapsed < 1000) ||
|
|
((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
|
|
(state->paused == false))
|
|
remote_backlight_on();
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|