From ab90d58801e95d01dc286a8919c7d5823d81ec41 Mon Sep 17 00:00:00 2001 From: Nicolas Pennequin Date: Wed, 4 Apr 2007 14:41:40 +0000 Subject: Introducing the WPS tokenizer ! When a WPS file is loaded, it is parsed to an array of tokens, which allows more efficient displaying. More info on the tracker entry : FS #6862. The parsing code is completely independant and is all in wps_parser.c. The displaying part stays in gwps-common.c. Debugging code is provided (with the right ifdefs) and is disabled by default. Overall, the code should be easier to read and maintain. Adding new WPS tags is made quite trivial. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13019 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 2 + apps/gui/gwps-common.c | 3776 ++++++++++++++++++++---------------------------- apps/gui/gwps-common.h | 12 +- apps/gui/gwps.c | 141 -- apps/gui/gwps.h | 206 ++- apps/gui/wps_debug.c | 407 ++++++ apps/gui/wps_parser.c | 957 ++++++++++++ apps/lang/english.lang | 8 +- 8 files changed, 3108 insertions(+), 2401 deletions(-) create mode 100644 apps/gui/wps_debug.c create mode 100644 apps/gui/wps_parser.c diff --git a/apps/SOURCES b/apps/SOURCES index 4a4d83d0e6..7e6f84b03d 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -58,6 +58,8 @@ gui/splash.c gui/statusbar.c gui/textarea.c gui/yesno.c +gui/wps_debug.c +gui/wps_parser.c #ifdef HAVE_LCD_CHARCELLS player/icons.c diff --git a/apps/gui/gwps-common.c b/apps/gui/gwps-common.c index bce213ab4d..2bd2e4c617 100644 --- a/apps/gui/gwps-common.c +++ b/apps/gui/gwps-common.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2002 Björn Stenberg + * 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. @@ -56,16 +56,11 @@ #include "action.h" #include "cuesheet.h" -#ifdef HAVE_LCD_CHARCELLS -static bool draw_player_progress(struct gui_wps *gwps); -static void draw_player_fullbar(struct gui_wps *gwps, - char* buf, int buf_size); -#endif - #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) { @@ -78,287 +73,14 @@ static char* skip_utf8_bom(char* buf) return buf; } - -/* - * returns the image_id between - * a..z and A..Z - */ -#ifdef HAVE_LCD_BITMAP -static int get_image_id(int c) -{ - if(c >= 'a' && c <= 'z') - return c - 'a'; - else if(c >= 'A' && c <= 'Z') - return c - 'A' + 26; - else - return -1; -} -#endif - -/* - * parse the given buffer for following static tags: - * %x - load image for always display - * %X - load backdrop image - * %xl - preload image - * %we - enable statusbar on wps regardless of the global setting - * %wd - disable statusbar on wps regardless of the global setting - * and also for: - * # - a comment line - * - * it returns true if one of these tags is found and handled - * false otherwise - */ -bool wps_data_preload_tags(struct wps_data *data, char *buf, - const char *bmpdir, size_t bmpdirlen) -{ - if(!data || !buf) return false; - - char c; -#ifndef HAVE_LCD_BITMAP - /* no bitmap-lcd == no bitmap loading */ - (void)bmpdir; - (void)bmpdirlen; -#endif - buf = skip_utf8_bom(buf); - - if(*buf == '#') - return true; - if('%' != *buf) - return false; - buf++; - - c = *buf; - switch (c) - { -#ifdef HAVE_LCD_BITMAP - case 'w': - /* - * if tag found then return because these two tags must be on - * must be on their own line - */ - if(*(buf+1) == 'd' || *(buf+1) == 'e') - { - data->wps_sb_tag = true; - if( *(buf+1) == 'e' ) - data->show_sb_on_wps = true; - return true; - } - break; - -#if LCD_DEPTH > 1 - case 'X': - /* Backdrop image - must be the same size as the LCD */ - { - char *ptr = buf+2; - char *pos = NULL; - char imgname[MAX_PATH]; - - /* format: %X|filename.bmp| */ - - /* get filename */ - pos = strchr(ptr, '|'); - if ((pos - ptr) < - (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2) - { - memcpy(imgname, bmpdir, bmpdirlen); - imgname[bmpdirlen] = '/'; - memcpy(&imgname[bmpdirlen+1], - ptr, pos - ptr); - imgname[bmpdirlen+1+pos-ptr] = 0; - } - else - { - /* filename too long */ - imgname[0] = 0; - } - - /* load the image */ - return load_wps_backdrop(imgname); - } - - break; -#endif - - case 'P': - /* progress bar image */ - { - int ret = 0; - char *ptr = buf+2; - char *pos = NULL; - char imgname[MAX_PATH]; - - /* format: %P|filename.bmp| */ - { - /* get filename */ - pos = strchr(ptr, '|'); - if ((pos - ptr) < - (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2) - { - memcpy(imgname, bmpdir, bmpdirlen); - imgname[bmpdirlen] = '/'; - memcpy(&imgname[bmpdirlen+1], - ptr, pos - ptr); - imgname[bmpdirlen+1+pos-ptr] = 0; - } - else - /* filename too long */ - imgname[0] = 0; - - ptr = pos+1; - - /* load the image */ - data->progressbar.bm.data=data->img_buf_ptr; - ret = read_bmp_file(imgname, &data->progressbar.bm, - data->img_buf_free, - FORMAT_ANY|FORMAT_TRANSPARENT); - - if (ret > 0) - { -#if LCD_DEPTH == 16 - if (ret % 2) ret++; - /* Always consume an even number of bytes */ -#endif - - data->img_buf_ptr += ret; - data->img_buf_free -= ret; - - if (data->progressbar.bm.width <= LCD_WIDTH) { - data->progressbar.have_bitmap_pb=true; - return true; - } else - return false; - } - - } - } - - break; - - case 'x': - /* Preload images so the %xd# tag can display it */ - { - int ret = 0; - int n; - char *ptr = buf+1; - char *pos = NULL; - char imgname[MAX_PATH]; - char qual = *ptr; - - if (qual == 'l' || qual == '|') /* format: - %x|n|filename.bmp|x|y| - or - %xl|n|filename.bmp|x|y| - */ - { - ptr = strchr(ptr, '|') + 1; - pos = strchr(ptr, '|'); - if (pos) - { - /* get the image ID */ - n = get_image_id(*ptr); - - if(n < 0 || n >= MAX_IMAGES) - { - /* Skip the rest of the line */ - while(*buf != '\n') - buf++; - return false; - } - ptr = pos+1; - - /* check the image number and load state */ - if (data->img[n].loaded) - { - /* Skip the rest of the line */ - while(*buf != '\n') - buf++; - return false; - } - else - { - /* get filename */ - pos = strchr(ptr, '|'); - - if (pos == NULL) - return false; - - if ((pos - ptr) < - (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2) - { - memcpy(imgname, bmpdir, bmpdirlen); - imgname[bmpdirlen] = '/'; - memcpy(&imgname[bmpdirlen+1], - ptr, pos - ptr); - imgname[bmpdirlen+1+pos-ptr] = 0; - } - else - /* filename too long */ - imgname[0] = 0; - - ptr = pos+1; - - /* get x-position */ - pos = strchr(ptr, '|'); - if (pos) - data->img[n].x = atoi(ptr); - else - { - /* weird syntax, bail out */ - buf++; - return false; - } - - /* get y-position */ - ptr = pos+1; - pos = strchr(ptr, '|'); - if (pos) - data->img[n].y = atoi(ptr); - else - { - /* weird syntax, bail out */ - buf++; - return false; - } - - /* load the image */ - data->img[n].bm.data = data->img_buf_ptr; - ret = read_bmp_file(imgname, &data->img[n].bm, - data->img_buf_free, - FORMAT_ANY|FORMAT_TRANSPARENT); - - if (ret > 0) - { -#if LCD_DEPTH == 16 - if (ret % 2) ret++; - /* Always consume an even number of bytes */ -#endif - - data->img_buf_ptr += ret; - data->img_buf_free -= ret; - data->img[n].loaded = true; - if(qual == '|') - data->img[n].always_display = true; - } - return true; - } - } - } - } - - break; #endif - } - /* no of these tags found */ - return false; -} - /* draws the statusbar on the given wps-screen */ #ifdef HAVE_LCD_BITMAP -static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force) +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; @@ -372,1762 +94,496 @@ static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force) gui_statusbar_draw((wps)->statusbar, (force)) #endif -/* 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) +/* fades the volume */ +void fade(bool fade_in) { - const char* sep; - const char* last_sep; - int len; + 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; - sep = path + strlen(path); - last_sep = sep; + if (fade_in) { + /* fade in */ + int fp_volume = fp_min_vol; - while (sep > path) - { - if ('/' == *(--sep)) - { - if (!level) - { - break; - } + /* zero out the sound */ + sound_set_volume(fp_min_vol >> 8); - level--; - last_sep = sep - 1; + 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; - if (level || (last_sep <= sep)) - { - return NULL; - } + 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); - len = MIN(last_sep - sep, buf_size - 1); - strncpy(buf, sep + 1, len); - buf[len] = 0; - return buf; + /* reset volume to what it was before the fade */ + sound_set_volume(global_settings.volume); + } } -/* Get the tag specified by the two characters at fmt. - * - * cid3 - ID3 data to get tag values from. - * nid3 - next-song ID3 data to get tag values from. - * tag - string (of two characters) specifying the tag to get. - * buf - buffer to certain tags, such as track number, play time or - * directory name. - * buf_size - size of buffer. - * flags - returns the type of the line. See constants i wps-display.h - * - * Returns the tag. NULL indicates the tag wasn't available. - */ -static char* get_tag(struct wps_data* wps_data, - struct mp3entry* cid3, - struct mp3entry* nid3, - const char* tag, - char* buf, - int buf_size, - unsigned char* tag_len, - unsigned short* subline_time_mult, - unsigned char* flags, - int *intval) +/* set volume */ +void setvol(void) { - struct mp3entry *id3 = cid3; /* default to current song */ - int limit = *intval; -#ifndef HAVE_LCD_CHARCELLS - (void)wps_data; -#endif - if ((0 == tag[0]) || (0 == tag[1])) - { - *tag_len = 0; - return NULL; - } - - *tag_len = 2; - - *intval = 0; - - switch (tag[0]) - { - case 'I': /* ID3 Information */ - id3 = nid3; /* display next-song data */ - *flags |= WPS_REFRESH_DYNAMIC; - if(!id3) - return NULL; /* no such info (yet) */ - /* fall-through */ - case 'i': /* ID3 Information */ - *flags |= WPS_REFRESH_STATIC; - switch (tag[1]) - { - case 't': /* ID3 Title */ - return id3->title; - - case 'a': /* ID3 Artist */ - return id3->artist; - - case 'n': /* ID3 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 'd': /* ID3 Album/Disc */ - return id3->album; - - case 'c': /* ID3 Composer */ - return id3->composer; - - case 'C': /* ID3 Comment */ - return id3->comment; - - case 'A': /* ID3 Albumartist */ - return id3->albumartist; + 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); - case 'y': /* year */ - if( id3->year_string ) - return id3->year_string; +#ifdef HAVE_LCD_CHARCELLS + gui_splash(gwps->display, 0, "Vol: %3d dB", + sound_val2phys(SOUND_VOLUME, global_settings.volume)); + return true; +#endif + return false; +} - if (id3->year) { - snprintf(buf, buf_size, "%d", id3->year); - return buf; - } - return NULL; +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 + }; - case 'g': /* genre */ - return id3->genre_string; + 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; - case 'v': /* id3 version */ - switch (id3->id3version) + 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) { - 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; + /* 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; } - } - break; - - case 'F': /* File Information */ - id3 = nid3; - *flags |= WPS_REFRESH_DYNAMIC; - if(!id3) - return NULL; /* no such info (yet) */ - /* fall-through */ - case 'f': /* File Information */ - *flags |= WPS_REFRESH_STATIC; - switch(tag[1]) - { - case 'v': /* VBR file? */ - return id3->vbr ? "(avg)" : NULL; - - case 'b': /* File Bitrate */ - if(id3->bitrate) - snprintf(buf, buf_size, "%d", id3->bitrate); else - snprintf(buf, buf_size, "?"); - return buf; + { + /* rewinding, calc max step relative to start */ + max_step = (wps_state.id3->elapsed + ff_rewind_count) * + FF_REWIND_MAX_PERCENT / 100; + } - case 'f': /* File Frequency */ - snprintf(buf, buf_size, "%ld", id3->frequency); - return buf; + max_step = MAX(max_step, MIN_FF_REWIND_STEP); - case 'p': /* File Path */ - return id3->path; + if (step > max_step) + step = max_step; - case 'm': /* File Name - With Extension */ - return get_dir(buf, buf_size, id3->path, 0); + ff_rewind_count += step * direction; - case 'n': /* 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 's': /* File Size (in kilobytes) */ - snprintf(buf, buf_size, "%ld", id3->filesize / 1024); - return buf; - - case 'c': /* File Codec */ - if(id3->codectype == AFMT_UNKNOWN) - *intval = AFMT_NUM_CODECS; - else - *intval = id3->codectype; - return id3_get_codec(id3); - } - break; - - case 'p': /* Playlist/Song Information */ - switch(tag[1]) - { - case 'b': /* progress bar */ - *flags |= WPS_REFRESH_PLAYER_PROGRESS; -#ifdef HAVE_LCD_CHARCELLS - char *end = utf8encode(wps_data->wps_progress_pat[0], buf); - *end = '\0'; - wps_data->full_line_progressbar=0; - return buf; -#else - /* default values : */ - wps_data->progress_top = -1; - wps_data->progress_height = 6; - wps_data->progress_start = 0; - wps_data->progress_end = 0; - - char *prev=strchr(tag, '|'); - if (prev) { - char *p=strchr(prev+1, '|'); - if (p) { - wps_data->progress_height=atoi(++prev); - prev=strchr(prev, '|'); - p=strchr(++p, '|'); - if (p) { - wps_data->progress_start=atoi(++prev); - prev=strchr(prev, '|'); - p=strchr(++p, '|'); - if (p) { - wps_data->progress_end=atoi(++prev); - prev=strchr(prev, '|'); - p=strchr(++p, '|'); - if(p) - wps_data->progress_top = atoi(++prev); - } - - if (wps_data->progress_height<3) - wps_data->progress_height=3; - if (wps_data->progress_endprogress_start+3) - wps_data->progress_end=0; - } - } - } - return "\x01"; -#endif - case 'f': /* full-line progress bar */ -#ifdef HAVE_LCD_CHARCELLS - if(is_new_player()) { - *flags |= WPS_REFRESH_PLAYER_PROGRESS; - *flags |= WPS_REFRESH_DYNAMIC; - wps_data->full_line_progressbar=1; - /* 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, " "); - } - return buf; -#endif - case 'p': /* Playlist Position */ - *flags |= WPS_REFRESH_STATIC; - snprintf(buf, buf_size, "%d", - playlist_get_display_index()); - return buf; - - case 'n': /* Playlist Name (without path) */ - *flags |= WPS_REFRESH_STATIC; - return playlist_name(NULL, buf, buf_size); - - case 'e': /* Playlist Total Entries */ - *flags |= WPS_REFRESH_STATIC; - snprintf(buf, buf_size, "%d", playlist_amount()); - return buf; - - case 'c': /* Current Time in Song */ - *flags |= WPS_REFRESH_DYNAMIC; - format_time(buf, buf_size, - id3->elapsed + wps_state.ff_rewind_count); - return buf; - - case 'r': /* Remaining Time in Song */ - *flags |= WPS_REFRESH_DYNAMIC; - format_time(buf, buf_size, - id3->length - id3->elapsed - - wps_state.ff_rewind_count); - return buf; - - case 't': /* Total Time */ - *flags |= WPS_REFRESH_STATIC; - format_time(buf, buf_size, id3->length); - return buf; - -#ifdef HAVE_LCD_BITMAP - case 'm': /* Peak Meter */ - *flags |= WPS_REFRESH_PEAK_METER; - return "\x01"; -#endif - case 's': /* shuffle */ - *flags |= WPS_REFRESH_DYNAMIC; - if ( global_settings.playlist_shuffle ) - return "s"; - else - return NULL; - break; - - case 'v': /* volume */ - *flags |= WPS_REFRESH_DYNAMIC; - snprintf(buf, buf_size, "%d", global_settings.volume); - *intval = limit * (global_settings.volume - - sound_min(SOUND_VOLUME)) - / (sound_max(SOUND_VOLUME) - - sound_min(SOUND_VOLUME)) + 1; - return buf; - - } - break; - -#if (CONFIG_CODEC == SWCODEC) - case 'S': /* DSP/Equalizer/Sound settings */ - switch (tag[1]) - { - case 'p': /* pitch */ - *intval = sound_get_pitch(); - snprintf(buf, buf_size, "%d.%d", - *intval / 10, *intval % 10); - return buf; - } - break; -#endif - - case 'm': - switch (tag[1]) - { - case 'm': /* playback repeat mode */ - *flags |= WPS_REFRESH_DYNAMIC; - *intval = global_settings.repeat_mode + 1; - snprintf(buf, buf_size, "%d", *intval); - return buf; - - /* playback status */ - case 'p': /* play */ - *flags |= WPS_REFRESH_DYNAMIC; - int status = audio_status(); - *intval = 1; - if (status == AUDIO_STATUS_PLAY && \ - !(status & AUDIO_STATUS_PAUSE)) - *intval = 2; - if (audio_status() & AUDIO_STATUS_PAUSE && \ - (! status_get_ffmode())) - *intval = 3; - if (status_get_ffmode() == STATUS_FASTFORWARD) - *intval = 4; - if (status_get_ffmode() == STATUS_FASTBACKWARD) - *intval = 5; - snprintf(buf, buf_size, "%d", *intval); - return buf; - -#ifdef HAS_BUTTON_HOLD - case 'h': /* hold */ - *flags |= WPS_REFRESH_DYNAMIC; - if (button_hold()) - return "h"; - else - return NULL; -#endif -#ifdef HAS_REMOTE_BUTTON_HOLD - case 'r': /* remote hold */ - *flags |= WPS_REFRESH_DYNAMIC; - if (remote_button_hold()) - return "r"; - else - return NULL; -#endif - } - break; - - case 'b': /* battery info */ - *flags |= WPS_REFRESH_DYNAMIC; - switch (tag[1]) - { - case 'l': /* battery level */ - { - int l = battery_level(); - limit = MAX(limit, 2); - if (l > -1) - { - snprintf(buf, buf_size, "%d", l); - /* First enum is used for "unknown level". */ - *intval = (limit - 1) * l / 100 + 1 + 1; - } - else - { - *intval = 1; - return "?"; - } - return buf; - } - - case 'v': /* battery voltage */ - { - unsigned int v = battery_voltage(); - snprintf(buf, buf_size, "%d.%02d", v/100, v%100); - return buf; - } - - case 't': /* estimated 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 (global_settings.ff_rewind_accel != 0 && + current_tick >= accel_tick) + { + step *= 2; + accel_tick = current_tick + + global_settings.ff_rewind_accel*HZ; + } } - - case 's': /* sleep timer */ + else { - if (get_sleep_timer() == 0) - { - return NULL; - } - else + if ( (audio_status() & AUDIO_STATUS_PLAY) && + wps_state.id3 && wps_state.id3->length ) { - format_time(buf, buf_size, \ - get_sleep_timer() * 1000); - return buf; - } - } - -#if CONFIG_CHARGING - case 'p': /* External power plugged in? */ - { - if(charger_input_state==CHARGER) - return "p"; - else - return NULL; - } -#endif -#if CONFIG_CHARGING >= CHARGING_MONITOR - case 'c': /* Charging */ - { - if (charge_state == CHARGING || charge_state == TOPOFF) { - return "c"; - } else { - return NULL; - } - } + if (!wps_state.paused) +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); +#else + audio_pause(); #endif - } - break; - -#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD) - case 'l': /* VIRTUAL_LED */ - { - switch(tag[1]) - { - case 'h': /* Only one we have so far HDD LED */ - *flags |= WPS_REFRESH_DYNAMIC; - if(led_read(HZ/2)) - return "h"; - else - return NULL; - } - } - break; +#if CONFIG_KEYPAD == PLAYER_PAD + FOR_NB_SCREENS(i) + gui_wps[i].display->stop_scroll(); #endif - - case 'D': /* Directory path information */ - id3 = nid3; /* next song please! */ - *flags |= WPS_REFRESH_DYNAMIC; - if(!id3) - return NULL; /* no such info (yet) */ - /* fall-through */ - case 'd': /* Directory path information */ - { - int level = tag[1] - '0'; - *flags |= WPS_REFRESH_STATIC; - /* d1 through d9 */ - if ((0 < level) && (9 > level)) - { - return get_dir(buf, buf_size, id3->path, level); - } - } - break; - - case 't': /* set sub line time multiplier */ - { - int d = 1; - int time_mult = 0; - bool have_point = false; - bool have_tenth = false; - - while (((tag[d] >= '0') && - (tag[d] <= '9')) || - (tag[d] == '.')) - { - if (tag[d] != '.') - { - time_mult = time_mult * 10; - time_mult = time_mult + tag[d] - '0'; - if (have_point) - { - have_tenth = true; - d++; - break; - } - } - else - { - have_point = true; - } - d++; - } - - if (have_tenth == false) - time_mult *= 10; - - *subline_time_mult = time_mult; - *tag_len = d; - - buf[0] = 0; - return buf; - } - break; - case 'r': /* Runtime database Information and Replaygain */ - switch(tag[1]) - { - case 'p': /* Playcount */ - *flags |= WPS_REFRESH_DYNAMIC; - *intval = cid3->playcount+1; - snprintf(buf, buf_size, "%ld", cid3->playcount); - return buf; - case 'r': /* Rating */ - *flags |= WPS_REFRESH_DYNAMIC; - *intval = cid3->rating+1; - snprintf(buf, buf_size, "%d", cid3->rating); - return buf; -#if CONFIG_CODEC == SWCODEC - case 'g': /* ReplayGain */ - *flags |= WPS_REFRESH_STATIC; - if (global_settings.replaygain == 0) - *intval = 1; /* off */ - else - { - int type = get_replaygain_mode( - id3->track_gain_string != NULL, - id3->album_gain_string != NULL); - - if (type < 0) - *intval = 6; /* no tag */ + if (direction > 0) + status_set_ffmode(STATUS_FASTFORWARD); else - *intval = type + 2; - - if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE) - *intval += 2; - } + status_set_ffmode(STATUS_FASTBACKWARD); - switch (*intval) - { - 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 - } - break; -#if CONFIG_RTC - case 'c': /* Real Time Clock display */ - *flags |= WPS_REFRESH_DYNAMIC; - { - int value; - char *format = 0; - char *bufptr = buf; - struct tm* tm = get_time(); - int i; - for (i=1;/*break*/;i++) { - switch(tag[i]) - { - case 'a': /* abbreviated weekday name (Sun..Sat) */ - value = tm->tm_wday; - if (value > 6 || value < 0) continue; - value = snprintf( - bufptr,buf_size,"%s",str(dayname[value])); - bufptr += value; - buf_size -= value; - continue; - case 'b': /* abbreviated month name (Jan..Dec) */ - value = tm->tm_mon; - if (value > 11 || value < 0) continue; - value = snprintf( - bufptr,buf_size,"%s",str(monthname[value])); - bufptr += value; - buf_size -= value; - continue; - case 'd': /* day of month (01..31) */ - value = tm->tm_mday; - if (value > 31 || value < 1) continue; - format = "%02d"; - break; - case 'e': /* day of month, blank padded ( 1..31) */ - value = tm->tm_mday; - if (value > 31 || value < 1) continue; - format = "%2d"; - break; - case 'H': /* hour (00..23) */ - value = tm->tm_hour; - if (value > 23) continue; - format = "%02d"; - break; - case 'k': /* hour ( 0..23) */ - value = tm->tm_hour; - if (value > 23) continue; - format = "%2d"; - break; - case 'I': /* hour (01..12) */ - value = tm->tm_hour; - if (value > 23) continue; - value %= 12; - if (value == 0) value = 12; - format = "%02d"; - break; - case 'l': /* hour ( 1..12) */ - value = tm->tm_hour; - if (value > 23 || value < 0) continue; - value %= 12; - if (value == 0) value = 12; - format = "%2d"; - break; - case 'm': /* month (01..12) */ - value = tm->tm_mon; - if (value > 11 || value < 0) continue; - value++; - format = "%02d"; - break; - case 'M': /* minute (00..59) */ - value = tm->tm_min; - if (value > 59 || value < 0) continue; - format = "%02d"; - break; - case 'S': /* second (00..59) */ - value = tm->tm_sec; - if (value > 59 || value < 0) continue; - format = "%02d"; - break; - case 'y': /* last two digits of year (00..99) */ - value = tm->tm_year; - value %= 100; - format = "%02d"; - break; - case 'Y': /* year (1970...) */ - value = tm->tm_year; - if (value > 199 || value < 100) continue; - value += 1900; - format = "%04d"; - break; - case 'p': /* upper case AM or PM indicator */ - if (tm->tm_hour/12 == 0) format = "AM"; - else format = "PM"; - snprintf(bufptr,buf_size,"%s",format); - bufptr += 2; - buf_size -= 2; - continue; - case 'P': /* lower case am or pm indicator */ - if (tm->tm_hour/12 == 0) format = "am"; - else format = "pm"; - snprintf(bufptr,buf_size,"%s",format); - bufptr += 2; - buf_size -= 2; - continue; - case 'u': /* day of week (1..7); 1 is Monday */ - value = tm->tm_wday; - if (value < 0 || value > 6) continue; - value++; - format = "%1d"; - break; - case 'w': /* day of week (0..6); 0 is Sunday */ - value = tm->tm_wday; - if (value < 0 || value > 6) continue; - format = "%1d"; - break; - default: - if (tag[i] == 'c') { - i++; - value = -1; - break; - } else if (tag[i] == '\n') { - value = -1; - break; - } - snprintf(bufptr,buf_size,"%c",tag[i]); - bufptr++; - buf_size--; - continue; - } /* switch */ - if (value < 0) break; - - value = snprintf(bufptr, buf_size, format, value); - bufptr += value; - buf_size -= value; - } /* while */ - *tag_len = i; - return buf; - } -#endif /* CONFIG_RTC */ -#if CONFIG_CODEC == SWCODEC - case 'x': - *flags |= WPS_REFRESH_DYNAMIC; - switch(tag[1]) - { - case 'd': /* crossfeed */ - if(global_settings.crossfeed) - return "d"; - else - return NULL; - case 'f': /* crossfade */ - *intval = global_settings.crossfade+1; - snprintf(buf, buf_size, "%d", global_settings.crossfade); - return buf; - } - break; -#endif - } - return NULL; -} + wps_state.ff_rewind = true; -#ifdef HAVE_LCD_BITMAP -/* 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); -} -#endif + step = ff_rew_steps[global_settings.ff_rewind_min_step]; -/* Skip to the end of the current %? conditional. - * - * fmt - string to skip it. Should point to somewhere after the leading - * "<" char (and before or at the last ">"). - * num - number of |'s to skip, or 0 to skip to the end (the ">"). - * enums - If not NULL, set to the number of |'s found in the current - * conditional (sub-conditionals are ignored). num should be 0 - * to find all |'s. - * - * Returns the new position in fmt. - */ -static const char* skip_conditional(struct gui_wps *gwps, const char* fmt, - int num, int *enums) -{ - int level = 1; - int count = num; - const char *last_alternative = NULL; -#ifdef HAVE_LCD_BITMAP - struct wps_data *data = NULL; - int last_x=-1, last_y=-1, last_w=-1, last_h=-1; - if(gwps) - data = gwps->data; - if (enums) - *enums = 0; -#else - (void)gwps; -#endif - while (*fmt) - { - switch (*fmt++) - { - case '%': -#ifdef HAVE_LCD_BITMAP - if(data && *(fmt) == 'x' && *(fmt+1) == 'd' ) - { - fmt +=2; - int n = *fmt; - if(n >= 'a' && n <= 'z') - n -= 'a'; - if(n >= 'A' && n <= 'Z') - n = n - 'A' + 26; - if(last_x != data->img[n].x || last_y != data->img[n].y - || last_w != data->img[n].bm.width - || last_h != data->img[n].bm.height) - { - last_x = data->img[n].x; - last_y = data->img[n].y; - last_w = data->img[n].bm.width; - last_h = data->img[n].bm.height; - clear_image_pos(gwps,n); + accel_tick = current_tick + + global_settings.ff_rewind_accel*HZ; } + else + break; } -#endif - break; - case '|': - if(1 == level) { - if (enums) - (*enums)++; - last_alternative = fmt; - if(num) { - count--; - if(count == 0) - return fmt; - continue; - } + 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; } - continue; - - case '>': - if (0 == --level) - { - /* We're just skipping to the end */ - if(num == 0) - return fmt; - - /* If we are parsing an enum, we'll return the selected - item. If there weren't enough items in the enum, we'll - return the last one found. */ - if(count && last_alternative) - { - return last_alternative; - } - return fmt - 1; + else { + if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0) + ff_rewind_count = -wps_state.id3->elapsed; } - continue; - - default: - continue; - } - switch (*fmt++) - { - case 0: - case '%': - case '|': - case '<': - case '>': - break; - - case '?': - while (*fmt && ('<' != *fmt)) - fmt++; - - if ('<' == *fmt) - fmt++; - - level++; - break; + 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); - default: break; - } - } - - return fmt; -} -/* Generate the display based on id3 information and format string. - * - * buf - char buffer to write the display to. - * buf_size - the size of buffer. - * id3 - the ID3 data to format with. - * nid3 - the ID3 data of the next song (might by NULL) - * fmt - format description. - * flags - returns the type of the line. See constants i wps-display.h - */ -static void format_display(struct gui_wps *gwps, char* buf, - int buf_size, - struct mp3entry* id3, - struct mp3entry* nid3, /* next song's id3 */ - const char* fmt, - struct align_pos* align, - unsigned short* subline_time_mult, - unsigned char* flags) -{ - char temp_buf[128]; - char* buf_start = buf; - char* buf_end = buf + buf_size - 1; /* Leave room for end null */ - char* value = NULL; - int level = 0; - unsigned char tag_length; - int intval; - int cur_align; - char* cur_align_start; -#ifdef HAVE_LCD_BITMAP - struct gui_img *img = gwps->data->img; - int n; + 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 - - cur_align_start = buf; - cur_align = WPS_ALIGN_LEFT; - *subline_time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER; - - align->left = 0; - align->center = 0; - align->right = 0; - - while (fmt && *fmt && buf < buf_end) - { - switch (*fmt) - { - case '%': - ++fmt; - break; - - case '|': - case '>': - if (level > 0) - { - fmt = skip_conditional(NULL, fmt, 0, NULL); - level--; - continue; - } - /* Else fall through */ - - default: - *buf++ = *fmt++; - continue; - } - - switch (*fmt) - { - case 0: - *buf++ = '%'; - break; - case 'a': - ++fmt; - /* 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 (*fmt) - { - case 'l': - cur_align = WPS_ALIGN_LEFT; - break; - case 'c': - cur_align = WPS_ALIGN_CENTER; - break; - case 'r': - cur_align = WPS_ALIGN_RIGHT; - break; - } - *buf++=0; - cur_align_start = buf; - ++fmt; - break; - case 's': - *flags |= WPS_REFRESH_SCROLL; - ++fmt; - break; - - case 'x': /* image support */ -#ifdef HAVE_LCD_BITMAP - if ('d' == *(fmt+1) ) - { - fmt+=2; - - /* get the image ID */ - n = *fmt; - if(n >= 'a' && n <= 'z') - n -= 'a'; - if(n >= 'A' && n <= 'Z') - n = n - 'A' + 26; - if (n >= 0 && n < MAX_IMAGES && img[n].loaded) { - img[n].display = true; - } - } - +#ifdef HAVE_LCD_CHARCELLS + gui_wps_display(); #endif - fmt++; - break; - - - case '%': - case '|': - case '<': - case '>': - case ';': - *buf++ = *fmt++; - break; - - case '?': - fmt++; - /* Get number of "|" chars in the current conditional; - * used by get_tag when calculating levels. - */ - skip_conditional(gwps, fmt, 0, &intval); - value = get_tag(gwps->data, id3, nid3, fmt, temp_buf, - sizeof(temp_buf),&tag_length, - subline_time_mult, flags, &intval); - - while (*fmt && ('<' != *fmt)) - fmt++; - - if ('<' == *fmt) - fmt++; - - /* No value, so skip to else part, using a sufficiently high - value to "hit" the last part of the conditional */ - if ((!value) || (!strlen(value))) - fmt = skip_conditional(NULL, fmt, 1000, NULL); - else - if(intval > 1) /* enum */ - fmt = skip_conditional(NULL, fmt, intval - 1, NULL); - - level++; + exit = true; break; default: - intval = 1; - value = get_tag(gwps->data, id3, nid3, fmt, temp_buf, - sizeof(temp_buf), &tag_length, - subline_time_mult, flags,&intval); - fmt += tag_length; - - if (value) - { - while (*value && (buf < buf_end)) - *buf++ = *value++; - } - } - } - - /* 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; - } - - *buf = 0; - - /* if resulting line is an empty line, set the subline time to 0 */ - if (buf - buf_start == 0) - *subline_time_mult = 0; - - /* If no flags have been set, the line didn't contain any format codes. - We still want to refresh it. */ - if(*flags == 0) - *flags = WPS_REFRESH_STATIC; -} - -/* 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 format string to use for WPS, splitting it into lines */ -void gui_wps_format(struct wps_data *data) -{ - char* buf = data->format_buffer; - char* start_of_line = data->format_buffer; - int line = 0; - int subline; - char c; - if(!data) - return; - - for (line=0; lineformat_lines[line][subline] = 0; - data->time_mult[line][subline] = 0; - } - data->subline_expire_time[line] = 0; - data->curr_subline[line] = SUBLINE_RESET; - } - - line = 0; - subline = 0; - buf = skip_utf8_bom(buf); - data->format_lines[line][subline] = buf; - - while ((*buf) && (line < WPS_MAX_LINES)) - { - c = *buf; - - switch (c) - { - /* - * skip % sequences so "%;" doesn't start a new subline - * don't skip %x lines (pre-load bitmaps) - */ - case '%': - buf++; - break; - - case '\r': /* CR */ - *buf = 0; - break; - - case '\n': /* LF */ - *buf = 0; - - if (*start_of_line != '#') /* A comment? */ - line++; - - if (line < WPS_MAX_LINES) - { - /* the next line starts on the next byte */ - subline = 0; - data->format_lines[line][subline] = buf+1; - start_of_line = data->format_lines[line][subline]; - } - break; - - case ';': /* start a new subline */ - *buf = 0; - subline++; - if (subline < WPS_MAX_SUBLINES) - { - data->format_lines[line][subline] = buf+1; - } - else /* exceeded max sublines, skip rest of line */ - { - while (*(++buf)) - { - if ((*buf == '\r') || (*buf == '\n')) - { - break; - } - } - buf--; - subline = 0; + if(default_event_handler(button) == SYS_USB_CONNECTED) { + status_set_ffmode(0); + usb = true; + exit = true; } break; } - buf++; - } -} - -#ifdef HAVE_LCD_BITMAP -/* Display images */ -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, bool always) -{ - 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) { - if( (!always && data->img[n].display) - || (always && data->img[n].always_display) ) - wps_draw_image(gwps, n); - } + if (!exit) + button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK); } - display->set_drawmode(DRMODE_SOLID); -} -#endif - -#if 0 /* currently unused */ -void gui_wps_reset(struct gui_wps *gui_wps) -{ - if(!gui_wps || !gui_wps->data) - return; - gui_wps->data->wps_loaded = false; - memset(&gui_wps->data->format_buffer, 0, - sizeof(gui_wps->data->format_buffer)); + action_signalscreenchange(); + return usb; } -#endif -bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset, - unsigned char refresh_mode) +bool gui_wps_display(void) { - char buf[MAX_PATH]; - unsigned char flags; int i; - bool update_line; - bool only_one_subline; - bool new_subline_refresh; - bool reset_subline; - int search; - int search_start; - struct align_pos format_align; - struct wps_data *data = gwps->data; - struct wps_state *state = gwps->state; - struct screen *display = gwps->display; - if(!gwps || !data || !state || !display) + if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY)) { - return false; - } + global_status.resume_index = -1; #ifdef HAVE_LCD_BITMAP - int h = font_get(FONT_UI)->height; - int offset = 0; - gui_wps_statusbar_draw(gwps, true); - if(data->wps_sb_tag && data->show_sb_on_wps) - offset = STATUSBAR_HEIGHT; - else if ( global_settings.statusbar && !data->wps_sb_tag) - offset = STATUSBAR_HEIGHT; - - /* 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; - } + gui_syncstatusbar_draw(&statusbars, true); #endif - /* reset to first subline if refresh all flag is set */ - if (refresh_mode == WPS_REFRESH_ALL) + gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER)); + return true; + } + else { - for (i=0; icurr_subline[i] = SUBLINE_RESET; + 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 %?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 %?iv<(id3v%iv)|(no id3)>\n" + "%pb", false); + } +#endif + } + } } } - -#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) + yield(); + FOR_NB_SCREENS(i) { - display->stop_scroll(); - return false; + gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL); } + return false; +} - state->ff_rewind_count = ffwd_offset; +bool update(struct gui_wps *gwps) +{ + bool track_changed = audio_has_changed_track(); + bool retcode = false; - for (i = 0; i < WPS_MAX_LINES; i++) + gwps->state->nid3 = audio_next_track(); + if (track_changed) { - reset_subline = (data->curr_subline[i] == SUBLINE_RESET); - new_subline_refresh = false; - only_one_subline = false; + gwps->display->stop_scroll(); + gwps->state->id3 = audio_current_track(); - /* if time to advance to next sub-line */ - if (TIME_AFTER(current_tick, data->subline_expire_time[i] - 1) || - reset_subline) + if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type + && strcmp(gwps->state->id3->path, curr_cue->audio_filename)) { - /* 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[i]; - for (search=0; searchcurr_subline[i]++; + /* the current cuesheet isn't the right one any more */ - /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */ - if ((!data->format_lines[i][data->curr_subline[i]]) || - (data->curr_subline[i] == WPS_MAX_SUBLINES)) - { - if (data->curr_subline[i] == 1) - only_one_subline = true; - data->curr_subline[i] = 0; - } + 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 */ - /* if back where we started after search or - only one subline is defined on the line */ - if (((search > 0) && (data->curr_subline[i] == search_start)) || - only_one_subline) - { - /* no other subline with a time > 0 exists */ - data->subline_expire_time[i] = (reset_subline? - current_tick : data->subline_expire_time[i]) + 100 * HZ; - break; - } - else + 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)) { - /* get initial time multiplier and - line type flags for this subline */ - format_display(gwps, buf, sizeof(buf), - state->id3, state->nid3, - data->format_lines[i][data->curr_subline[i]], - &format_align, - &data->time_mult[i][data->curr_subline[i]], - &data->line_type[i][data->curr_subline[i]]); - - /* only use this subline if subline time > 0 */ - if (data->time_mult[i][data->curr_subline[i]] > 0) - { - new_subline_refresh = true; - data->subline_expire_time[i] = (reset_subline? - current_tick : data->subline_expire_time[i]) + - BASE_SUBLINE_TIME * data->time_mult[i][data->curr_subline[i]]; - break; - } + gwps->state->id3->cuesheet_type = 1; + strcpy(curr_cue->audio_filename, gwps->state->id3->path); } } + cue_spoof_id3(curr_cue, gwps->state->id3); } - update_line = false; + if (gui_wps_display()) + retcode = true; + else{ + gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); + } - if ( !data->format_lines[i][data->curr_subline[i]] ) - break; + if (gwps->state->id3) + memcpy(gwps->state->current_track_path, gwps->state->id3->path, + sizeof(gwps->state->current_track_path)); + } - if ((data->line_type[i][data->curr_subline[i]] & refresh_mode) || - (refresh_mode == WPS_REFRESH_ALL) || - new_subline_refresh) + 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))) { - flags = 0; - int left_width, left_xpos; - int center_width, center_xpos; - int right_width, right_xpos; - int space_width; - int string_height; - int ypos; - - format_display(gwps, buf, sizeof(buf), - state->id3, state->nid3, - data->format_lines[i][data->curr_subline[i]], - &format_align, - &data->time_mult[i][data->curr_subline[i]], - &flags); - data->line_type[i][data->curr_subline[i]] = flags; - -#ifdef HAVE_LCD_BITMAP - /* progress */ - if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) - { - int sb_y; - if (data->progress_top == -1) - sb_y = i*h + offset + ((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); - } - - update_line = true; - } - if (flags & refresh_mode & WPS_REFRESH_PEAK_METER) { - /* peak meter */ - int peak_meter_y; - - update_line = true; - peak_meter_y = i * h + offset; - - /* 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 - /* progress */ - if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) { - if (data->full_line_progressbar) - draw_player_fullbar(gwps, buf, sizeof(buf)); - else - draw_player_progress(gwps); - } -#endif - /* 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; + /* We've changed tracks within the cuesheet : + we need to update the ID3 info and refresh the WPS */ - 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; + cue_find_current_track(curr_cue, gwps->state->id3->elapsed); + cue_spoof_id3(curr_cue, gwps->state->id3); - 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; - } + 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); + } - /* 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; - } + gui_wps_statusbar_draw(gwps, false); - /* 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; - } + return retcode; +} - if (flags & WPS_REFRESH_SCROLL) { - /* scroll line */ - if ((refresh_mode & WPS_REFRESH_SCROLL) || - new_subline_refresh) { +void display_keylock_text(bool locked) +{ + char* s; + int i; + FOR_NB_SCREENS(i) + gui_wps[i].display->stop_scroll(); - ypos = (i*string_height)+display->getymargin(); - update_line = true; - - if (left_width>display->width) { - display->puts_scroll(0, i, - (unsigned char *)format_align.left); - } else { - /* clear the line first */ -#ifdef HAVE_LCD_BITMAP - display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); - display->fillrect(0, ypos, display->width, string_height); - display->set_drawmode(DRMODE_SOLID); +#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 - /* Nasty hack: we output an empty scrolling string, - which will reset the scroller for that line */ - display->puts_scroll(0, i, (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); - } - } - } - } - else if (flags & (WPS_REFRESH_DYNAMIC | WPS_REFRESH_STATIC)) - { - /* dynamic / static line */ - if ((refresh_mode & (WPS_REFRESH_DYNAMIC|WPS_REFRESH_STATIC)) || - new_subline_refresh) - { - ypos = (i*string_height)+display->getymargin(); - update_line = true; + gui_syncsplash(HZ, s); +} #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, i, (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); - } - } - } - } -#ifdef HAVE_LCD_BITMAP - if (update_line) { - wps_display_images(gwps,false); - } -#endif - } +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; -#ifdef HAVE_LCD_BITMAP - /* Display all images */ - wps_display_images(gwps,true); - display->update(); - /* Now we know wether the peak meter is used. - So we can enable / disable the peak meter thread */ - data->peak_meter_enabled = enable_pm; + 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 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 ( 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); +} - if ( n < 1000 ) - n = 5000; /* use 5s if backlight is always on or off */ +/* 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); +} - if (((state->id3->elapsed < 1000) || - ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && - (state->paused == false)) - backlight_on(); +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 -#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 */ +static void wps_display_images(struct gui_wps *gwps) +{ + if(!gwps || !gwps->data || !gwps->display) + return; - if (((state->id3->elapsed < 1000) || - ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && - (state->paused == false)) - remote_backlight_on(); + 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); + } } -#endif - return true; + display->set_drawmode(DRMODE_SOLID); } -#ifdef HAVE_LCD_CHARCELLS +#else /* HAVE_LCD_CHARCELL */ + static bool draw_player_progress(struct gui_wps *gwps) { char player_progressbar[7]; @@ -2150,7 +606,7 @@ static bool draw_player_progress(struct gui_wps *gwps) songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) / state->id3->length; else - songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / + songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / state->id3->length; } for (i=0; i < songpos; i++) @@ -2215,10 +671,10 @@ static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size) songpos = 55; else { if(state->wps_time_countup == false) - songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / + songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / state->id3->length; else - songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / + songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / state->id3->length; } @@ -2273,373 +729,1221 @@ static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size) buf = utf8encode(data->wps_progress_pat[lcd_char_pos+1], buf); } - /* make rest of the progressbar if necessary */ - if (songpos/5>5) { + /* 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, " "); + } + 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; + } + + case WPS_TOKEN_SOUND_PITCH: + snprintf(buf, buf_size, "%d.%d", + *intval / 10, *intval % 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; - /* 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 '_' */ + struct wps_data *data = gwps->data; - /* build the partial bar character for the tail character position */ - memset(binline, 0, sizeof binline); - memset(player_progressbar, 0, sizeof player_progressbar); + int ret; + int num_options = data->tokens[cond_index].value.i; + char result[128], *value; + int cond_start = cond_index; - for (i=5; i<7; i++) { - for (j=0;j<5;j++) { - if (j<(songpos%5)) { - binline[i*5+j] = 1; - } - } - } + /* 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++; - for (i=0; i<7; i++) { - for (j=0;j<5;j++) { - player_progressbar[i] <<= 1; - player_progressbar[i] += binline[i*5+j]; - } + 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 or %? */ + { + value = get_tag(gwps, cond_index + 1, result, sizeof(result), NULL); + ret = value ? cond_start : data->tokens[cond_start].value.i; + } - display->define_pattern(data->wps_progress_pat[7],player_progressbar); - buf = utf8encode(data->wps_progress_pat[7], buf); - *buf = '\0'; +#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 -/* 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 ret; } -/* return true if screen restore is needed - return false otherwise + +/* 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. */ -bool update_onvol_change(struct gui_wps * gwps) +static bool get_line(struct gui_wps *gwps, + int line, int subline, + struct align_pos *align, + char *linebuf, + int linebuf_size) { - gui_wps_statusbar_draw(gwps, false); - gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC); + struct wps_data *data = gwps->data; -#ifdef HAVE_LCD_CHARCELLS - gui_splash(gwps->display, 0, "Vol: %3d dB", - sound_val2phys(SOUND_VOLUME, global_settings.volume)); - return true; -#endif - return false; -} + char temp_buf[128]; + char *buf = linebuf; /* will always point to the writing position */ + char *linebuf_end = linebuf + linebuf_size - 1; + bool update = 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 - }; + /* 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; - 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; + /* start at the beginning of the current (sub)line */ + int i = data->format_lines[line][subline]; - if (button == ACTION_NONE) - { - status_set_ffmode(0); - return usb; - } - while (!exit) + while (data->tokens[i].type != WPS_TOKEN_EOL + && data->tokens[i].type != WPS_TOKEN_SUBLINE_SEPARATOR + && i < data->num_tokens) { - switch ( button ) + switch(data->tokens[i].type) { - 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; + case WPS_TOKEN_CONDITIONAL: + /* place ourselves in the right conditional case */ + i = evaluate_conditional(gwps, i); + break; - ff_rewind_count += step * direction; + 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; - 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(); +#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 - if (direction > 0) - status_set_ffmode(STATUS_FASTFORWARD); - else - status_set_ffmode(STATUS_FASTBACKWARD); - wps_state.ff_rewind = true; + 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; - step = ff_rew_steps[global_settings.ff_rewind_min_step]; + case WPS_ALIGN_CENTER: + align->center = cur_align_start; + break; - accel_tick = current_tick + - global_settings.ff_rewind_accel*HZ; - } - else + case WPS_ALIGN_RIGHT: + align->right = cur_align_start; 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; + /* 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; } - else { - if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0) - ff_rewind_count = -wps_state.id3->elapsed; + *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++; + } - 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); + /* close the current alignment */ + switch (cur_align) + { + case WPS_ALIGN_LEFT: + align->left = cur_align_start; + break; - break; + case WPS_ALIGN_CENTER: + align->center = cur_align_start; + 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; + case WPS_ALIGN_RIGHT: + align->right = cur_align_start; + break; + } - default: - if(default_event_handler(button) == SYS_USB_CONNECTED) { - status_set_ffmode(0); - usb = true; - exit = true; + 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; } - break; + } } - if (!exit) - button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK); } - action_signalscreenchange(); - return usb; + + return new_subline_refresh; } -bool gui_wps_display(void) +/* 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 i; - if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY)) + + 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) { - global_status.resume_index = -1; -#ifdef HAVE_LCD_CHARCELLS - gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER)); -#else - gui_syncstatusbar_draw(&statusbars, true); - gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER)); -#endif - return true; + display->puts_scroll(0, line, + (unsigned char *)format_align->left); } else { - FOR_NB_SCREENS(i) - { - gui_wps[i].display->clear_display(); - if (!gui_wps[i].data->wps_loaded) { - if ( !gui_wps[i].data->format_buffer[0] ) { - /* 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 %?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 %?iv<(id3v%iv)|(no id3)>\n" - "%pb", false); - } + /* 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); } } - yield(); - FOR_NB_SCREENS(i) - { - gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL); - } - return false; } -bool update(struct gui_wps *gwps) +/* Refresh the WPS according to refresh_mode. */ +bool gui_wps_refresh(struct gui_wps *gwps, + int ffwd_offset, + unsigned char refresh_mode) { - bool track_changed = audio_has_changed_track(); - bool retcode = false; + struct wps_data *data = gwps->data; + struct screen *display = gwps->display; + struct wps_state *state = gwps->state; - gwps->state->nid3 = audio_next_track(); - if (track_changed) - { - gwps->display->stop_scroll(); - gwps->state->id3 = audio_current_track(); + if(!gwps || !data || !state || !display) + return false; - 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 */ + int line, i; + unsigned char flags; + char linebuf[MAX_PATH]; - 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 */ + struct align_pos align; + align.left = NULL; + align.center = NULL; + align.right = NULL; - char cuepath[MAX_PATH]; - strncpy(cuepath, gwps->state->id3->path, MAX_PATH); - char *dot = strrchr(cuepath, '.'); - strcpy(dot, ".cue"); + bool update_line, new_subline_refresh; - if (parse_cuesheet(cuepath, curr_cue)) - { - gwps->state->id3->cuesheet_type = 1; - strcpy(curr_cue->audio_filename, gwps->state->id3->path); - } - } +#ifdef HAVE_LCD_BITMAP + gui_wps_statusbar_draw(gwps, true); - cue_spoof_id3(curr_cue, gwps->state->id3); - } + /* 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; - if (gui_wps_display()) - retcode = true; - else{ - gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); + /* 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; } + } - if (gwps->state->id3) - memcpy(gwps->state->current_track_path, gwps->state->id3->path, - sizeof(gwps->state->current_track_path)); +#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 (gwps->state->id3) + if (!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))) + 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) { - /* We've changed tracks within the cuesheet : - we need to update the ID3 info and refresh the WPS */ + /* 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)); + } - cue_find_current_track(curr_cue, gwps->state->id3->elapsed); - cue_spoof_id3(curr_cue, gwps->state->id3); +#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); + } - gwps->display->stop_scroll(); - if (gui_wps_display()) - retcode = true; + /* 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 - gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); + draw_player_progress(gwps); + } +#endif + + if (update_line) + { + /* calculate alignment and draw the strings */ + write_line(display, &align, line, flags & WPS_REFRESH_SCROLL); } - else - gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC); } - gui_wps_statusbar_draw(gwps, false); +#ifdef HAVE_LCD_BITMAP + data->peak_meter_enabled = enable_pm; + wps_display_images(gwps); +#endif - return retcode; -} + 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; -void display_keylock_text(bool locked) -{ - char* s; - int i; - FOR_NB_SCREENS(i) - gui_wps[i].display->stop_scroll(); + if ( n < 1000 ) + n = 5000; /* use 5s if backlight is always on or off */ -#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); + if (((state->id3->elapsed < 1000) || + ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && + (state->paused == false)) + backlight_on(); + } #endif - gui_syncsplash(HZ, s); -} +#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; +} diff --git a/apps/gui/gwps-common.h b/apps/gui/gwps-common.h index 77bec83951..1203113be1 100644 --- a/apps/gui/gwps-common.h +++ b/apps/gui/gwps-common.h @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2002 Björn Stenberg + * 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. @@ -24,16 +24,14 @@ #include "gwps.h" void fade(bool fade_in); -void gui_wps_format(struct wps_data *data); -bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset, - unsigned char refresh_mode); bool gui_wps_display(void); void setvol(void); bool update_onvol_change(struct gui_wps * gwps); bool update(struct gui_wps *gwps); bool ffwd_rew(int button); -bool wps_data_preload_tags(struct wps_data *data, char *buf, - const char *bmpdir, size_t bmpdirlen); void display_keylock_text(bool locked); -#endif +bool gui_wps_refresh(struct gui_wps *gwps, + int ffwd_offset, + unsigned char refresh_mode); +#endif diff --git a/apps/gui/gwps.c b/apps/gui/gwps.c index d70863d895..b43ff9d96c 100644 --- a/apps/gui/gwps.c +++ b/apps/gui/gwps.c @@ -61,8 +61,6 @@ #include "ata_idle_notify.h" #include "root_menu.h" -#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps" -#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps" /* currently only on wps_state is needed */ struct wps_state wps_state; struct gui_wps gui_wps[NB_SCREENS]; @@ -81,7 +79,6 @@ static void gui_wps_set_disp(struct gui_wps *gui_wps, struct screen *display); /* connects a wps with a statusbar*/ static void gui_wps_set_statusbar(struct gui_wps *gui_wps, struct gui_statusbar *statusbar); - #ifdef HAVE_LCD_BITMAP static void gui_wps_set_margin(struct gui_wps *gwps) { @@ -671,144 +668,6 @@ long gui_wps_show(void) /* needs checking if needed end*/ -/* wps_data*/ -#ifdef HAVE_LCD_BITMAP -/* Clear the WPS image cache */ -static void wps_clear(struct wps_data *data ) -{ - int i; - /* set images to unloaded and not displayed */ - for (i = 0; i < MAX_IMAGES; i++) { - data->img[i].loaded = false; - data->img[i].display = false; - data->img[i].always_display = false; - } - data->wps_sb_tag = false; - data->show_sb_on_wps = false; - data->progressbar.have_bitmap_pb=false; -} -#else -#define wps_clear(a) -#endif - -/* initial setup of wps_data */ -void wps_data_init(struct wps_data *wps_data) -{ -#ifdef HAVE_LCD_BITMAP - wps_clear(wps_data); -#else /* HAVE_LCD_CHARCELLS */ - { - int i; - for(i = 0; i < 8; i++) - wps_data->wps_progress_pat[i] = 0; - wps_data->full_line_progressbar = 0; - } -#endif - wps_data->format_buffer[0] = '\0'; - wps_data->wps_loaded = false; - wps_data->peak_meter_enabled = false; -} - -static void wps_reset(struct wps_data *data) -{ - data->wps_loaded = false; - memset(&data->format_buffer, 0, sizeof data->format_buffer); - wps_data_init(data); -} - -/* to setup up the wps-data from a format-buffer (isfile = false) - from a (wps-)file (isfile = true)*/ -bool wps_data_load(struct wps_data *wps_data, - const char *buf, - bool isfile) -{ - int fd; - - if(!wps_data || !buf) - return false; - - if(!isfile) - { - wps_clear(wps_data); - strncpy(wps_data->format_buffer, buf, sizeof(wps_data->format_buffer)); - wps_data->format_buffer[sizeof(wps_data->format_buffer) - 1] = 0; - gui_wps_format(wps_data); - return true; - } - else - { - /* - * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this - * wants to be a virtual file. Feel free to modify dirbrowse() - * if you're feeling brave. - */ - if (! strcmp(buf, WPS_DEFAULTCFG) ) - { - wps_reset(wps_data); - global_settings.wps_file[0] = 0; - return false; - } - -#ifdef HAVE_REMOTE_LCD - if (! strcmp(buf, RWPS_DEFAULTCFG) ) - { - wps_reset(wps_data); - global_settings.rwps_file[0] = 0; - return false; - } -#endif - - size_t bmpdirlen; - char *bmpdir = strrchr(buf, '.'); - bmpdirlen = bmpdir - buf; - - fd = open(buf, O_RDONLY); - - if (fd >= 0) - { - unsigned int start = 0; - - wps_reset(wps_data); -#ifdef HAVE_LCD_BITMAP - wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */ - - wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */ -#endif - while( ( read_line(fd, &wps_data->format_buffer[start], - sizeof(wps_data->format_buffer)-start) ) > 0 ) - { - if(!wps_data_preload_tags(wps_data, - &wps_data->format_buffer[start], - buf, bmpdirlen)) - { - start += strlen(&wps_data->format_buffer[start]); - - if (start < sizeof(wps_data->format_buffer) - 1) - { - wps_data->format_buffer[start++] = '\n'; - wps_data->format_buffer[start] = 0; - } - } - } - - if (start > 0) - { - gui_wps_format(wps_data); - } - - close(fd); - - wps_data->wps_loaded = true; - - return start > 0; - } - } - - return false; -} - -/* wps_data end */ - /* wps_state */ static void wps_state_init(void) diff --git a/apps/gui/gwps.h b/apps/gui/gwps.h index 123bb8fa7c..c18fc0c4e3 100644 --- a/apps/gui/gwps.h +++ b/apps/gui/gwps.h @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2002 Jerome Kuptz + * 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. @@ -49,6 +49,9 @@ struct gui_img{ bool loaded; /* load state */ bool display; /* is to be displayed */ bool always_display; /* not using the preload/display mechanism */ + + /* the index of the conditional the image is in */ + unsigned short cond_index; }; struct prog_img{ /*progressbar image*/ @@ -64,23 +67,189 @@ struct align_pos { }; #ifdef HAVE_LCD_BITMAP + #define MAX_IMAGES (26*2) /* a-z and A-Z */ #define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \ + (2*LCD_HEIGHT*LCD_WIDTH/8)) #define WPS_MAX_LINES (LCD_HEIGHT/5+1) -#define FORMAT_BUFFER_SIZE 3072 +#define WPS_MAX_TOKENS 1024 +#define WPS_MAX_STRINGS 128 +#define STRING_BUFFER_SIZE 512 +#define WPS_MAX_COND_LEVEL 10 + #else + #define WPS_MAX_LINES 2 -#define FORMAT_BUFFER_SIZE 400 +#define WPS_MAX_TOKENS 64 +#define WPS_MAX_STRINGS 32 +#define STRING_BUFFER_SIZE 64 +#define WPS_MAX_COND_LEVEL 5 + #endif + #define WPS_MAX_SUBLINES 12 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* (10ths of sec) */ #define BASE_SUBLINE_TIME 10 /* base time that multiplier is applied to (1/HZ sec, or 100ths of sec) */ #define SUBLINE_RESET -1 +enum wps_token_type { + WPS_NO_TOKEN, /* for WPS tags we don't want to save as tokens */ + WPS_TOKEN_UNKNOWN, + + /* Markers */ + WPS_TOKEN_CHARACTER, + WPS_TOKEN_STRING, + WPS_TOKEN_EOL, + + /* Alignment */ + WPS_TOKEN_ALIGN_LEFT, + WPS_TOKEN_ALIGN_CENTER, + WPS_TOKEN_ALIGN_RIGHT, + + /* Scrolling */ + WPS_TOKEN_SCROLL, + + /* Alternating sublines */ + WPS_TOKEN_SUBLINE_SEPARATOR, + WPS_TOKEN_SUBLINE_TIMEOUT, + + /* Battery */ + WPS_TOKEN_BATTERY_PERCENT, + WPS_TOKEN_BATTERY_VOLTS, + WPS_TOKEN_BATTERY_TIME, + WPS_TOKEN_BATTERY_CHARGER_CONNECTED, + WPS_TOKEN_BATTERY_CHARGING, + WPS_TOKEN_BATTERY_SLEEPTIME, + +#if (CONFIG_CODEC == SWCODEC) + /* Sound */ + WPS_TOKEN_SOUND_PITCH, + WPS_TOKEN_REPLAYGAIN, +#endif + +#if CONFIG_RTC + /* Time */ + WPS_TOKEN_RTC, + WPS_TOKEN_RTC_DAY_OF_MONTH, + WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED, + WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, + WPS_TOKEN_RTC_HOUR_24, + WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, + WPS_TOKEN_RTC_HOUR_12, + WPS_TOKEN_RTC_MONTH, + WPS_TOKEN_RTC_MINUTE, + WPS_TOKEN_RTC_SECOND, + WPS_TOKEN_RTC_YEAR_2_DIGITS, + WPS_TOKEN_RTC_YEAR_4_DIGITS, + WPS_TOKEN_RTC_AM_PM_UPPER, + WPS_TOKEN_RTC_AM_PM_LOWER, + WPS_TOKEN_RTC_WEEKDAY_NAME, + WPS_TOKEN_RTC_MONTH_NAME, + WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, + WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, +#endif + + /* Conditional */ + WPS_TOKEN_CONDITIONAL, + WPS_TOKEN_CONDITIONAL_START, + WPS_TOKEN_CONDITIONAL_OPTION, + WPS_TOKEN_CONDITIONAL_END, + + /* Database */ + WPS_TOKEN_DATABASE_PLAYCOUNT, + WPS_TOKEN_DATABASE_RATING, + + /* File */ + WPS_TOKEN_FILE_BITRATE, + WPS_TOKEN_FILE_CODEC, + WPS_TOKEN_FILE_FREQUENCY, + WPS_TOKEN_FILE_NAME, + WPS_TOKEN_FILE_NAME_WITH_EXTENSION, + WPS_TOKEN_FILE_PATH, + WPS_TOKEN_FILE_SIZE, + WPS_TOKEN_FILE_VBR, + WPS_TOKEN_FILE_DIRECTORY, + +#ifdef HAVE_LCD_BITMAP + /* Image */ + WPS_TOKEN_IMAGE_BACKDROP, + WPS_TOKEN_IMAGE_PROGRESS_BAR, + WPS_TOKEN_IMAGE_PRELOAD, + WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, + WPS_TOKEN_IMAGE_DISPLAY, +#endif + + /* Metadata */ + WPS_TOKEN_METADATA_ARTIST, + WPS_TOKEN_METADATA_COMPOSER, + WPS_TOKEN_METADATA_ALBUM_ARTIST, + WPS_TOKEN_METADATA_ALBUM, + WPS_TOKEN_METADATA_GENRE, + WPS_TOKEN_METADATA_TRACK_NUMBER, + WPS_TOKEN_METADATA_TRACK_TITLE, + WPS_TOKEN_METADATA_VERSION, + WPS_TOKEN_METADATA_YEAR, + WPS_TOKEN_METADATA_COMMENT, + + /* Mode */ + WPS_TOKEN_REPEAT_MODE, + WPS_TOKEN_PLAYBACK_STATUS, + +#ifdef HAS_BUTTON_HOLD + WPS_TOKEN_MAIN_HOLD, +#endif +#ifdef HAS_REMOTE_BUTTON_HOLD + WPS_TOKEN_REMOTE_HOLD, +#endif + + /* Progressbar */ + WPS_TOKEN_PROGRESSBAR, + WPS_TOKEN_PLAYER_PROGRESSBAR, + +#ifdef HAVE_LCD_BITMAP + /* Peakmeter */ + WPS_TOKEN_PEAKMETER, +#endif + + /* Volume level */ + WPS_TOKEN_VOLUME, + + /* Current track */ + WPS_TOKEN_TRACK_TIME_ELAPSED, + WPS_TOKEN_TRACK_TIME_REMAINING, + WPS_TOKEN_TRACK_LENGTH, + + /* Playlist */ + WPS_TOKEN_PLAYLIST_ENTRIES, + WPS_TOKEN_PLAYLIST_NAME, + WPS_TOKEN_PLAYLIST_POSITION, + WPS_TOKEN_PLAYLIST_SHUFFLE, + +#ifdef HAVE_LCD_BITMAP + /* Statusbar */ + WPS_TOKEN_STATUSBAR_ENABLED, + WPS_TOKEN_STATUSBAR_DISABLED, +#endif + +#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD) + /* Virtual LED */ + WPS_TOKEN_VLED_HDD +#endif +}; + +struct wps_token { + enum wps_token_type type; + bool next; + union { + char c; + unsigned short i; + } value; +}; + + /* wps_data - this struct old all necessary data which describes the + this struct holds all necessary data which describes the viewable content of a wps */ struct wps_data { @@ -92,23 +261,32 @@ struct wps_data int img_buf_free; bool wps_sb_tag; bool show_sb_on_wps; -#endif -#ifdef HAVE_LCD_CHARCELLS + + short progress_top; + short progress_height; + short progress_start; + short progress_end; + bool peak_meter_enabled; +#else /*HAVE_LCD_CHARCELLS */ unsigned short wps_progress_pat[8]; bool full_line_progressbar; #endif - char format_buffer[FORMAT_BUFFER_SIZE]; - char* format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES]; + unsigned short format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES]; + unsigned char num_lines; unsigned char line_type[WPS_MAX_LINES][WPS_MAX_SUBLINES]; unsigned short time_mult[WPS_MAX_LINES][WPS_MAX_SUBLINES]; long subline_expire_time[WPS_MAX_LINES]; - int curr_subline[WPS_MAX_LINES]; - int progress_top; - int progress_height; - int progress_start; - int progress_end; + short curr_subline[WPS_MAX_LINES]; + unsigned char num_sublines[WPS_MAX_LINES]; + + struct wps_token tokens[WPS_MAX_TOKENS]; + unsigned short num_tokens; + + char string_buffer[STRING_BUFFER_SIZE]; + char *strings[WPS_MAX_STRINGS]; + unsigned char num_strings; + bool wps_loaded; - bool peak_meter_enabled; }; /* initial setup of wps_data */ diff --git a/apps/gui/wps_debug.c b/apps/gui/wps_debug.c new file mode 100644 index 0000000000..4532151d71 --- /dev/null +++ b/apps/gui/wps_debug.c @@ -0,0 +1,407 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr + * + * 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. + * + ****************************************************************************/ + +#ifdef DEBUG + +#include +#include +#include "gwps.h" +#include "debug.h" + +void dump_wps_tokens(struct wps_data *data) +{ + int i, j; + int indent = 0; + char buf[64]; + bool next; + + /* Dump parsed WPS */ + for(i = 0; i < data->num_tokens && i < WPS_MAX_TOKENS; i++) { + + next = data->tokens[i].next; + + switch(data->tokens[i].type) { + case WPS_TOKEN_UNKNOWN: + snprintf(buf, sizeof(buf), "Unknown token"); + break; + case WPS_TOKEN_CHARACTER: + snprintf(buf, sizeof(buf), "Character '%c'", + data->tokens[i].value.c); + break; + + case WPS_TOKEN_STRING: + snprintf(buf, sizeof(buf), "String '%s'", + data->strings[data->tokens[i].value.i]); + break; + + case WPS_TOKEN_EOL: + snprintf(buf, sizeof(buf), "%s", "EOL"); + break; + +#ifdef HAVE_LCD_BITMAP + case WPS_TOKEN_ALIGN_LEFT: + snprintf(buf, sizeof(buf), "%s", "align left"); + break; + + case WPS_TOKEN_ALIGN_CENTER: + snprintf(buf, sizeof(buf), "%s", "align center"); + break; + + case WPS_TOKEN_ALIGN_RIGHT: + snprintf(buf, sizeof(buf), "%s", "align right"); + break; +#endif + + case WPS_TOKEN_CONDITIONAL: + snprintf(buf, sizeof(buf), "%s, %d options", "conditional", + data->tokens[i].value.i); + break; + + case WPS_TOKEN_CONDITIONAL_START: + snprintf(buf, sizeof(buf), "%s, next cond: %d", + "conditional start", data->tokens[i].value.i); + indent++; + break; + + case WPS_TOKEN_CONDITIONAL_OPTION: + snprintf(buf, sizeof(buf), "%s, next cond: %d", + "conditional option", data->tokens[i].value.i); + break; + + case WPS_TOKEN_CONDITIONAL_END: + snprintf(buf, sizeof(buf), "%s", "conditional end"); + indent--; + break; + +#ifdef HAVE_LCD_BITMAP + case WPS_TOKEN_IMAGE_PRELOAD: + snprintf(buf, sizeof(buf), "%s", "preload image"); + break; + + case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY: + snprintf(buf, sizeof(buf), "%s %d", "display preloaded image", + data->tokens[i].value.i); + break; + + case WPS_TOKEN_IMAGE_DISPLAY: + snprintf(buf, sizeof(buf), "%s", "display image"); + break; +#endif + +#ifdef HAS_BUTTON_HOLD + case WPS_TOKEN_MAIN_HOLD: + snprintf(buf, sizeof(buf), "%s", "mode hold"); + break; +#endif + +#ifdef HAS_REMOTE_BUTTON_HOLD + case WPS_TOKEN_REMOTE_HOLD: + snprintf(buf, sizeof(buf), "%s", "mode remote hold"); + break; +#endif + + case WPS_TOKEN_REPEAT_MODE: + snprintf(buf, sizeof(buf), "%s", "mode repeat"); + break; + + case WPS_TOKEN_PLAYBACK_STATUS: + snprintf(buf, sizeof(buf), "%s", "mode playback"); + break; + +#if CONFIG_RTC + case WPS_TOKEN_RTC_DAY_OF_MONTH: + case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED: + case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED: + case WPS_TOKEN_RTC_HOUR_24: + case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED: + case WPS_TOKEN_RTC_HOUR_12: + case WPS_TOKEN_RTC_MONTH: + case WPS_TOKEN_RTC_MINUTE: + case WPS_TOKEN_RTC_SECOND: + case WPS_TOKEN_RTC_YEAR_2_DIGITS: + case WPS_TOKEN_RTC_YEAR_4_DIGITS: + case WPS_TOKEN_RTC_AM_PM_UPPER: + case WPS_TOKEN_RTC_AM_PM_LOWER: + case WPS_TOKEN_RTC_WEEKDAY_NAME: + case WPS_TOKEN_RTC_MONTH_NAME: + case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON: + case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN: + case WPS_TOKEN_RTC: + snprintf(buf, sizeof(buf), "%s %c", "real-time clock tag:", + data->tokens[i].value.c); + break; +#endif + +#ifdef HAVE_LCD_BITMAP + case WPS_TOKEN_IMAGE_BACKDROP: + snprintf(buf, sizeof(buf), "%s", "backdrop image"); + break; + + case WPS_TOKEN_IMAGE_PROGRESS_BAR: + snprintf(buf, sizeof(buf), "%s", "progressbar bitmap"); + break; + + + case WPS_TOKEN_STATUSBAR_ENABLED: + snprintf(buf, sizeof(buf), "%s", "statusbar enable"); + break; + + case WPS_TOKEN_STATUSBAR_DISABLED: + snprintf(buf, sizeof(buf), "%s", "statusbar disable"); + break; + + case WPS_TOKEN_PEAKMETER: + snprintf(buf, sizeof(buf), "%s", "peakmeter"); + break; +#endif + + case WPS_TOKEN_PROGRESSBAR: + snprintf(buf, sizeof(buf), "%s", "progressbar"); + break; + +#ifdef HAVE_LCD_CHARCELLS + case WPS_TOKEN_PLAYER_PROGRESSBAR: + snprintf(buf, sizeof(buf), "%s", "full line progressbar"); + break; +#endif + + case WPS_TOKEN_TRACK_TIME_ELAPSED: + snprintf(buf, sizeof(buf), "%s", "time elapsed in track"); + break; + + case WPS_TOKEN_PLAYLIST_ENTRIES: + snprintf(buf, sizeof(buf), "%s", "number of entries in playlist"); + break; + + case WPS_TOKEN_PLAYLIST_NAME: + snprintf(buf, sizeof(buf), "%s", "playlist name"); + break; + + case WPS_TOKEN_PLAYLIST_POSITION: + snprintf(buf, sizeof(buf), "%s", "position in playlist"); + break; + + case WPS_TOKEN_TRACK_TIME_REMAINING: + snprintf(buf, sizeof(buf), "%s", "time remaining in track"); + break; + + case WPS_TOKEN_PLAYLIST_SHUFFLE: + snprintf(buf, sizeof(buf), "%s", "playlist shuffle mode"); + break; + + case WPS_TOKEN_TRACK_LENGTH: + snprintf(buf, sizeof(buf), "%s", "track length"); + break; + + case WPS_TOKEN_VOLUME: + snprintf(buf, sizeof(buf), "%s", "volume"); + break; + + case WPS_TOKEN_METADATA_ARTIST: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track artist"); + break; + + case WPS_TOKEN_METADATA_COMPOSER: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track composer"); + break; + + case WPS_TOKEN_METADATA_ALBUM: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track album"); + break; + + case WPS_TOKEN_METADATA_GENRE: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track genre"); + break; + + case WPS_TOKEN_METADATA_TRACK_NUMBER: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track number"); + break; + + case WPS_TOKEN_METADATA_TRACK_TITLE: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track title"); + break; + + case WPS_TOKEN_METADATA_VERSION: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track ID3 version"); + break; + + case WPS_TOKEN_METADATA_YEAR: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "track year"); + break; + + case WPS_TOKEN_BATTERY_PERCENT: + snprintf(buf, sizeof(buf), "%s", "battery percentage"); + break; + + case WPS_TOKEN_BATTERY_VOLTS: + snprintf(buf, sizeof(buf), "%s", "battery voltage"); + break; + + case WPS_TOKEN_BATTERY_TIME: + snprintf(buf, sizeof(buf), "%s", "battery time left"); + break; + + case WPS_TOKEN_BATTERY_CHARGER_CONNECTED: + snprintf(buf, sizeof(buf), "%s", "battery charger connected"); + break; + + case WPS_TOKEN_BATTERY_CHARGING: + snprintf(buf, sizeof(buf), "%s", "battery charging"); + break; + + case WPS_TOKEN_FILE_BITRATE: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file bitrate"); + break; + + case WPS_TOKEN_FILE_CODEC: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file codec"); + break; + + case WPS_TOKEN_FILE_FREQUENCY: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file audio frequency"); + break; + + case WPS_TOKEN_FILE_NAME: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file name"); + break; + + case WPS_TOKEN_FILE_NAME_WITH_EXTENSION: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file name with extension"); + break; + + case WPS_TOKEN_FILE_PATH: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file path"); + break; + + case WPS_TOKEN_FILE_SIZE: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file size"); + break; + + case WPS_TOKEN_FILE_VBR: + snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "", + "file is vbr"); + break; + + case WPS_TOKEN_FILE_DIRECTORY: + snprintf(buf, sizeof(buf), "%s%s: %d", next ? "next " : "", + "file directory, level", + data->tokens[i].value.i); + break; + + case WPS_TOKEN_SCROLL: + snprintf(buf, sizeof(buf), "%s", "scrolling line"); + break; + + case WPS_TOKEN_SUBLINE_TIMEOUT: + snprintf(buf, sizeof(buf), "%s: %d", "subline timeout value", + data->tokens[i].value.i); + break; + + case WPS_TOKEN_SUBLINE_SEPARATOR: + snprintf(buf, sizeof(buf), "%s", "subline separator"); + break; + + default: + snprintf(buf, sizeof(buf), "%s (code: %d)", "FIXME", + data->tokens[i].type); + break; + } + + for(j = 0; j < indent; j++) { + DEBUGF("\t"); + } + + DEBUGF("[%03d] = %s\n", i, buf); + } + DEBUGF("\n"); +} + +void print_line_info(struct wps_data *data) +{ + int line, subline; + + DEBUGF("line/subline start indexes :\n"); + for (line = 0; line < data->num_lines; line++) + { + DEBUGF("%2d. ", line); + for (subline = 0; subline < data->num_sublines[line]; subline++) + { + DEBUGF("%3d ", data->format_lines[line][subline]); + } + DEBUGF("\n"); + } + + DEBUGF("\n"); + + DEBUGF("subline time multipliers :\n"); + for (line = 0; line < data->num_lines; line++) + { + DEBUGF("%2d. ", line); + for (subline = 0; subline < data->num_sublines[line]; subline++) + { + DEBUGF("%3d ", data->time_mult[line][subline]); + } + DEBUGF("\n"); + } + +} + +void print_wps_strings(struct wps_data *data) +{ + DEBUGF("strings :\n"); + int i, len = 0; + for (i=0; i < data->num_strings; i++) + { + len += strlen(data->strings[i]); + DEBUGF("%2d: '%s'\n", i, data->strings[i]); + } + DEBUGF("total length : %d\n", len); + DEBUGF("\n"); +} + +#ifdef HAVE_LCD_BITMAP +void print_img_cond_indexes(struct wps_data *data) +{ + DEBUGF("image conditional indexes :\n"); + int i; + for (i=0; i < MAX_IMAGES; i++) + { + if (data->img[i].cond_index) + DEBUGF("%2d: %d\n", i, data->img[i].cond_index); + } + DEBUGF("\n"); +} +#endif /*HAVE_LCD_BITMAP */ + +#endif /* DEBUG */ diff --git a/apps/gui/wps_parser.c b/apps/gui/wps_parser.c new file mode 100644 index 0000000000..ef9d446444 --- /dev/null +++ b/apps/gui/wps_parser.c @@ -0,0 +1,957 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr + * + * 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 +#include +#include +#include +#include +#include "atoi.h" +#include "gwps.h" +#include "settings.h" +#include "debug.h" +#include "plugin.h" + +#ifdef HAVE_LCD_BITMAP +#include "bmp.h" +#if LCD_DEPTH > 1 +#include "backdrop.h" +#endif +#endif + +#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps" +#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps" + +/* level of current conditional. + -1 means we're not in a conditional. */ +int level = -1; + +/* index of the last WPS_TOKEN_CONDITIONAL_OPTION + or WPS_TOKEN_CONDITIONAL_START in current level */ +int lastcond[WPS_MAX_COND_LEVEL]; + +/* index of the WPS_TOKEN_CONDITIONAL in current level */ +int condindex[WPS_MAX_COND_LEVEL]; + +/* number of condtional options in current level */ +int numoptions[WPS_MAX_COND_LEVEL]; + +#ifdef HAVE_LCD_BITMAP +/* pointers to the bitmap filenames in the WPS source */ +const char *bmp_names[MAX_IMAGES]; +const char *pb_bmp_name; +#if LCD_DEPTH > 1 +const char *backdrop_bmp_name; +#endif +#endif + +#ifdef DEBUG +/* debugging functions */ +extern void dump_wps_tokens(struct wps_data *data); +extern void print_line_info(struct wps_data *data); +extern void print_img_cond_indexes(struct wps_data *data); +extern void print_wps_strings(struct wps_data *data); +#endif + +typedef int (*wps_tag_parse_func)(const char *wps_token, struct wps_data *wps_data); + +struct wps_tag { + const char name[3]; + enum wps_token_type type; + unsigned char refresh_type; + wps_tag_parse_func parse_func; +}; + +/* prototypes of all special parse functions : */ + +static int parse_subline_timeout(const char *wps_token, struct wps_data *wps_data); +static int parse_progressbar(const char *wps_token, struct wps_data *wps_data); +static int parse_dir_level(const char *wps_token, struct wps_data *wps_data); +#ifdef HAVE_LCD_BITMAP +static int parse_image_special(const char *wps_token, struct wps_data *wps_data); +static int parse_statusbar(const char *wps_token, struct wps_data *wps_data); +static int parse_image_display(const char *wps_token, struct wps_data *wps_data); +static int parse_image_load(const char *wps_token, struct wps_data *wps_data); +#endif /*HAVE_LCD_BITMAP */ +#if CONFIG_RTC +static int parse_rtc_format(const char *wps_token, struct wps_data *wps_data); + +/* RTC tokens array */ +static const struct wps_tag rtc_tags[] = { + { "d", WPS_TOKEN_RTC_DAY_OF_MONTH, 0, NULL }, + { "e", WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED, 0, NULL }, + { "H", WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, 0, NULL }, + { "k", WPS_TOKEN_RTC_HOUR_24, 0, NULL }, + { "I", WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, 0, NULL }, + { "l", WPS_TOKEN_RTC_HOUR_12, 0, NULL }, + { "m", WPS_TOKEN_RTC_MONTH, 0, NULL }, + { "M", WPS_TOKEN_RTC_MINUTE, 0, NULL }, + { "S", WPS_TOKEN_RTC_SECOND, 0, NULL }, + { "y", WPS_TOKEN_RTC_YEAR_2_DIGITS, 0, NULL }, + { "Y", WPS_TOKEN_RTC_YEAR_4_DIGITS, 0, NULL }, + { "p", WPS_TOKEN_RTC_AM_PM_UPPER, 0, NULL }, + { "P", WPS_TOKEN_RTC_AM_PM_LOWER, 0, NULL }, + { "a", WPS_TOKEN_RTC_WEEKDAY_NAME, 0, NULL }, + { "b", WPS_TOKEN_RTC_MONTH_NAME, 0, NULL }, + { "u", WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, 0, NULL }, + { "w", WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, 0, NULL }, + { "\0",WPS_TOKEN_CHARACTER, 0, NULL } + /* the array MUST end with a "\0" token */ +}; +#endif + +/* array of available tags - those with more characters have to go first + (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */ +static const struct wps_tag all_tags[] = { + + { "ac", WPS_TOKEN_ALIGN_CENTER, 0, NULL }, + { "al", WPS_TOKEN_ALIGN_LEFT, 0, NULL }, + { "ar", WPS_TOKEN_ALIGN_RIGHT, 0, NULL }, + + { "bl", WPS_TOKEN_BATTERY_PERCENT, WPS_REFRESH_DYNAMIC, NULL }, + { "bv", WPS_TOKEN_BATTERY_VOLTS, WPS_REFRESH_DYNAMIC, NULL }, + { "bt", WPS_TOKEN_BATTERY_TIME, WPS_REFRESH_DYNAMIC, NULL }, + { "bs", WPS_TOKEN_BATTERY_SLEEPTIME, WPS_REFRESH_DYNAMIC, NULL }, +#if CONFIG_CHARGING >= CHARGING_MONITOR + { "bc", WPS_TOKEN_BATTERY_CHARGING, WPS_REFRESH_DYNAMIC, NULL }, +#endif +#if CONFIG_CHARGING + { "bp", WPS_TOKEN_BATTERY_CHARGER_CONNECTED,WPS_REFRESH_DYNAMIC, NULL }, +#endif + +#if CONFIG_RTC + { "c", WPS_TOKEN_RTC, WPS_REFRESH_DYNAMIC, parse_rtc_format }, +#endif + + /* current file */ + { "fb", WPS_TOKEN_FILE_BITRATE, WPS_REFRESH_STATIC, NULL }, + { "fc", WPS_TOKEN_FILE_CODEC, WPS_REFRESH_STATIC, NULL }, + { "ff", WPS_TOKEN_FILE_FREQUENCY, WPS_REFRESH_STATIC, NULL }, + { "fm", WPS_TOKEN_FILE_NAME_WITH_EXTENSION, WPS_REFRESH_STATIC, NULL }, + { "fn", WPS_TOKEN_FILE_NAME, WPS_REFRESH_STATIC, NULL }, + { "fp", WPS_TOKEN_FILE_PATH, WPS_REFRESH_STATIC, NULL }, + { "fs", WPS_TOKEN_FILE_SIZE, WPS_REFRESH_STATIC, NULL }, + { "fv", WPS_TOKEN_FILE_VBR, WPS_REFRESH_STATIC, NULL }, + { "d", WPS_TOKEN_FILE_DIRECTORY, WPS_REFRESH_STATIC, parse_dir_level }, + + /* next file */ + { "Fb", WPS_TOKEN_FILE_BITRATE, WPS_REFRESH_DYNAMIC, NULL }, + { "Fc", WPS_TOKEN_FILE_CODEC, WPS_REFRESH_DYNAMIC, NULL }, + { "Ff", WPS_TOKEN_FILE_FREQUENCY, WPS_REFRESH_DYNAMIC, NULL }, + { "Fm", WPS_TOKEN_FILE_NAME_WITH_EXTENSION, WPS_REFRESH_DYNAMIC, NULL }, + { "Fn", WPS_TOKEN_FILE_NAME, WPS_REFRESH_DYNAMIC, NULL }, + { "Fp", WPS_TOKEN_FILE_PATH, WPS_REFRESH_DYNAMIC, NULL }, + { "Fs", WPS_TOKEN_FILE_SIZE, WPS_REFRESH_DYNAMIC, NULL }, + { "Fv", WPS_TOKEN_FILE_VBR, WPS_REFRESH_DYNAMIC, NULL }, + { "D", WPS_TOKEN_FILE_DIRECTORY, WPS_REFRESH_DYNAMIC,parse_dir_level }, + + /* current metadata */ + { "ia", WPS_TOKEN_METADATA_ARTIST, WPS_REFRESH_STATIC, NULL }, + { "ic", WPS_TOKEN_METADATA_COMPOSER, WPS_REFRESH_STATIC, NULL }, + { "id", WPS_TOKEN_METADATA_ALBUM, WPS_REFRESH_STATIC, NULL }, + { "iA", WPS_TOKEN_METADATA_ALBUM_ARTIST, WPS_REFRESH_STATIC, NULL }, + { "ig", WPS_TOKEN_METADATA_GENRE, WPS_REFRESH_STATIC, NULL }, + { "in", WPS_TOKEN_METADATA_TRACK_NUMBER, WPS_REFRESH_STATIC, NULL }, + { "it", WPS_TOKEN_METADATA_TRACK_TITLE, WPS_REFRESH_STATIC, NULL }, + { "iv", WPS_TOKEN_METADATA_VERSION, WPS_REFRESH_STATIC, NULL }, + { "iy", WPS_TOKEN_METADATA_YEAR, WPS_REFRESH_STATIC, NULL }, + { "iC", WPS_TOKEN_METADATA_COMMENT, WPS_REFRESH_DYNAMIC, NULL }, + + /* next metadata */ + { "Ia", WPS_TOKEN_METADATA_ARTIST, WPS_REFRESH_DYNAMIC, NULL }, + { "Ic", WPS_TOKEN_METADATA_COMPOSER, WPS_REFRESH_DYNAMIC, NULL }, + { "Id", WPS_TOKEN_METADATA_ALBUM, WPS_REFRESH_DYNAMIC, NULL }, + { "IA", WPS_TOKEN_METADATA_ALBUM_ARTIST, WPS_REFRESH_STATIC, NULL }, + { "Ig", WPS_TOKEN_METADATA_GENRE, WPS_REFRESH_DYNAMIC, NULL }, + { "In", WPS_TOKEN_METADATA_TRACK_NUMBER, WPS_REFRESH_DYNAMIC, NULL }, + { "It", WPS_TOKEN_METADATA_TRACK_TITLE, WPS_REFRESH_DYNAMIC, NULL }, + { "Iv", WPS_TOKEN_METADATA_VERSION, WPS_REFRESH_DYNAMIC, NULL }, + { "Iy", WPS_TOKEN_METADATA_YEAR, WPS_REFRESH_DYNAMIC, NULL }, + { "IC", WPS_TOKEN_METADATA_COMMENT, WPS_REFRESH_DYNAMIC, NULL }, + +#if (CONFIG_CODEC == SWCODEC) + { "Sp", WPS_TOKEN_SOUND_PITCH, WPS_REFRESH_DYNAMIC, NULL }, +#endif + +#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD) + { "lh", WPS_TOKEN_VLED_HDD, WPS_REFRESH_DYNAMIC, NULL }, +#endif + +#ifdef HAS_BUTTON_HOLD + { "mh", WPS_TOKEN_MAIN_HOLD, WPS_REFRESH_DYNAMIC, NULL }, +#endif +#ifdef HAS_REMOTE_BUTTON_HOLD + { "mr", WPS_TOKEN_REMOTE_HOLD, WPS_REFRESH_DYNAMIC, NULL }, +#endif + + { "mm", WPS_TOKEN_REPEAT_MODE, WPS_REFRESH_DYNAMIC, NULL }, + { "mp", WPS_TOKEN_PLAYBACK_STATUS, WPS_REFRESH_DYNAMIC, NULL }, + +#ifdef HAVE_LCD_BITMAP + { "pm", WPS_TOKEN_PEAKMETER, + WPS_REFRESH_PEAK_METER, NULL }, +#else + { "pf", WPS_TOKEN_PLAYER_PROGRESSBAR, + WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, + parse_progressbar }, +#endif + { "pb", WPS_TOKEN_PROGRESSBAR, + WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar }, + + { "pv", WPS_TOKEN_VOLUME, WPS_REFRESH_DYNAMIC, NULL }, + + { "pc", WPS_TOKEN_TRACK_TIME_ELAPSED, WPS_REFRESH_DYNAMIC, NULL }, + { "pr", WPS_TOKEN_TRACK_TIME_REMAINING, WPS_REFRESH_DYNAMIC, NULL }, + { "pt", WPS_TOKEN_TRACK_LENGTH, WPS_REFRESH_STATIC, NULL }, + + { "pp", WPS_TOKEN_PLAYLIST_POSITION, WPS_REFRESH_STATIC, NULL }, + { "pe", WPS_TOKEN_PLAYLIST_ENTRIES, WPS_REFRESH_STATIC, NULL }, + { "pn", WPS_TOKEN_PLAYLIST_NAME, WPS_REFRESH_STATIC, NULL }, + { "ps", WPS_TOKEN_PLAYLIST_SHUFFLE, WPS_REFRESH_DYNAMIC, NULL }, + + { "rp", WPS_TOKEN_DATABASE_PLAYCOUNT, WPS_REFRESH_DYNAMIC, NULL }, + { "rr", WPS_TOKEN_DATABASE_RATING, WPS_REFRESH_DYNAMIC, NULL }, +#if CONFIG_CODEC == SWCODEC + { "rg", WPS_TOKEN_REPLAYGAIN, WPS_REFRESH_STATIC, NULL }, +#endif + + { "s", WPS_TOKEN_SCROLL, WPS_REFRESH_SCROLL, NULL }, + { "t", WPS_TOKEN_SUBLINE_TIMEOUT, 0, parse_subline_timeout }, + +#ifdef HAVE_LCD_BITMAP + { "we", WPS_TOKEN_STATUSBAR_ENABLED, 0, parse_statusbar }, + { "wd", WPS_TOKEN_STATUSBAR_DISABLED, 0, parse_statusbar }, + + { "xl", WPS_NO_TOKEN, 0, parse_image_load }, + + { "xd", WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, + WPS_REFRESH_STATIC, parse_image_display }, + + { "x", WPS_TOKEN_IMAGE_DISPLAY, 0, parse_image_load }, + { "P", WPS_TOKEN_IMAGE_PROGRESS_BAR, 0, parse_image_special }, +#if LCD_DEPTH > 1 + { "X", WPS_TOKEN_IMAGE_BACKDROP, 0, parse_image_special }, +#endif +#endif + + { "\0", WPS_TOKEN_UNKNOWN, 0, NULL } + /* the array MUST end with a "\0" token */ +}; + + +static int skip_end_of_line(const char *wps_token) +{ + int skip = 0; + while(*(wps_token + skip) != '\n') + skip++; + return ++skip; +} + +#if CONFIG_RTC +static int parse_rtc_format(const char *wps_token, struct wps_data *wps_data) +{ + int skip = 0, i; + + /* RTC tag format ends with a c or a newline */ + while (wps_token && *wps_token != 'c' && *wps_token != '\n') + { + /* find what format char we have */ + i = 0; + while (*(rtc_tags[i].name) && *wps_token != *(rtc_tags[i].name)) + i++; + + wps_data->num_tokens++; + wps_data->tokens[wps_data->num_tokens].type = rtc_tags[i].type; + wps_data->tokens[wps_data->num_tokens].value.c = *wps_token; + skip ++; + wps_token++; + } + + /* eat the unwanted c at the end of the format */ + if (*wps_token == 'c') + skip++; + + return skip; +} +#endif + +#ifdef HAVE_LCD_BITMAP + +static int parse_statusbar(const char *wps_token, struct wps_data *wps_data) +{ + wps_data->wps_sb_tag = true; + + if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_STATUSBAR_ENABLED) + wps_data->show_sb_on_wps = true; + else + wps_data->show_sb_on_wps = false; + + /* Skip the rest of the line */ + return skip_end_of_line(wps_token); +} + +static bool load_bitmap(struct wps_data *wps_data, + char* filename, + struct bitmap *bm) +{ + int ret = read_bmp_file(filename, bm, + wps_data->img_buf_free, + FORMAT_ANY|FORMAT_TRANSPARENT); + + if (ret > 0) + { +#if LCD_DEPTH == 16 + if (ret % 2) ret++; + /* Always consume an even number of bytes */ +#endif + wps_data->img_buf_ptr += ret; + wps_data->img_buf_free -= ret; + + return true; + } + else + return false; +} + +static int get_image_id(int c) +{ + if(c >= 'a' && c <= 'z') + return c - 'a'; + else if(c >= 'A' && c <= 'Z') + return c - 'A' + 26; + else + return -1; +} + +static char *get_image_filename(const char *start, const char* bmpdir, + char *buf, int buf_size) +{ + const char *end = strchr(start, '|'); + + if ( !end || (end - start) >= (buf_size - ROCKBOX_DIR_LEN - 2) ) + { + buf = "\0"; + return NULL; + } + + int bmpdirlen = strlen(bmpdir); + + strcpy(buf, bmpdir); + buf[bmpdirlen] = '/'; + memcpy( &buf[bmpdirlen + 1], start, end - start); + buf[bmpdirlen + 1 + end - start] = 0; + + return buf; +} + +static int parse_image_display(const char *wps_token, struct wps_data *wps_data) +{ + int n = get_image_id(*wps_token); + wps_data->tokens[wps_data->num_tokens].value.i = n; + + /* if the image is in a conditional, remember it */ + if (level >= 0) + wps_data->img[n].cond_index = condindex[level]; + + return 1; +} + +static int parse_image_load(const char *wps_token, struct wps_data *wps_data) +{ + int n; + const char *ptr = wps_token; + char *pos = NULL; + + /* format: %x|n|filename.bmp|x|y| + or %xl|n|filename.bmp|x|y| */ + + ptr = strchr(ptr, '|') + 1; + pos = strchr(ptr, '|'); + if (pos) + { + /* get the image ID */ + n = get_image_id(*ptr); + + /* check the image number and load state */ + if(n < 0 || n >= MAX_IMAGES || wps_data->img[n].loaded) + { + /* Skip the rest of the line */ + return skip_end_of_line(wps_token); + } + + ptr = pos + 1; + + /* get image name */ + bmp_names[n] = ptr; + + pos = strchr(ptr, '|'); + ptr = pos + 1; + + /* get x-position */ + pos = strchr(ptr, '|'); + if (pos) + wps_data->img[n].x = atoi(ptr); + else + { + /* weird syntax, bail out */ + return skip_end_of_line(wps_token); + } + + /* get y-position */ + ptr = pos + 1; + pos = strchr(ptr, '|'); + if (pos) + wps_data->img[n].y = atoi(ptr); + else + { + /* weird syntax, bail out */ + return skip_end_of_line(wps_token); + } + + if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_DISPLAY) + wps_data->img[n].always_display = true; + } + + /* Skip the rest of the line */ + return skip_end_of_line(wps_token); +} + +static int parse_image_special(const char *wps_token, struct wps_data *wps_data) +{ + if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_PROGRESS_BAR) + { + /* format: %P|filename.bmp| */ + pb_bmp_name = wps_token + 1; + } +#if LCD_DEPTH > 1 + else if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_BACKDROP) + { + /* format: %X|filename.bmp| */ + backdrop_bmp_name = wps_token + 1; + } +#endif + + (void)wps_data; /* to avoid a warning */ + + /* Skip the rest of the line */ + return skip_end_of_line(wps_token); +} + +#endif /* HAVE_LCD_BITMAP */ + +static int parse_dir_level(const char *wps_token, struct wps_data *wps_data) +{ + char val[] = { *wps_token, '\0' }; + wps_data->tokens[wps_data->num_tokens].value.i = atoi(val); + return 1; +} + +static int parse_subline_timeout(const char *wps_token, struct wps_data *wps_data) +{ + int skip = 0; + int val = 0; + bool have_point = false; + bool have_tenth = false; + + while ( isdigit(*wps_token) || *wps_token == '.' ) + { + if (*wps_token != '.') + { + val *= 10; + val += *wps_token - '0'; + if (have_point) + { + have_tenth = true; + wps_token++; + skip++; + break; + } + } + else + have_point = true; + + wps_token++; + skip++; + } + + if (have_tenth == false) + val *= 10; + + if (val > 0) + { + int line = wps_data->num_lines; + int subline = wps_data->num_sublines[line]; + wps_data->time_mult[line][subline] = val; + } + + wps_data->tokens[wps_data->num_tokens].value.i = val; + return skip; +} + +static int parse_progressbar(const char *wps_token, struct wps_data *wps_data) +{ +#ifdef HAVE_LCD_BITMAP + + short *vals[] = { + &wps_data->progress_height, + &wps_data->progress_start, + &wps_data->progress_end, + &wps_data->progress_top }; + + /* default values : */ + wps_data->progress_height = 6; + wps_data->progress_start = 0; + wps_data->progress_end = 0; + wps_data->progress_top = -1; + + int i = 0; + char *newline = strchr(wps_token, '\n'); + char *prev = strchr(wps_token, '|'); + if (prev && prev < newline) { + char *next = strchr(prev+1, '|'); + while (i < 4 && next && next < newline) + { + *(vals[i++]) = atoi(++prev); + prev = strchr(prev, '|'); + next = strchr(++next, '|'); + } + + if (wps_data->progress_height < 3) + wps_data->progress_height = 3; + if (wps_data->progress_end < wps_data->progress_start + 3) + wps_data->progress_end = 0; + } + + return newline - wps_token; + +#else + + if (*(wps_token-1) == 'f') + wps_data->full_line_progressbar = true; + else + wps_data->full_line_progressbar = false; + + return 0; + +#endif +} + +/* Parse a generic token from the given string. Return the length read */ +static int parse_token(const char *wps_token, struct wps_data *wps_data) +{ + int skip = 0, taglen = 0; + int i = 0; + int line = wps_data->num_lines; + int subline = wps_data->num_sublines[line]; + + switch(*wps_token) + { + + case '%': + case '<': + case '|': + case '>': + case ';': + /* escaped characters */ + wps_data->tokens[wps_data->num_tokens].type = WPS_TOKEN_CHARACTER; + wps_data->tokens[wps_data->num_tokens].value.c = *wps_token; + wps_data->num_tokens++; + skip++; + break; + + case '?': + /* conditional tag */ + wps_data->tokens[wps_data->num_tokens].type = WPS_TOKEN_CONDITIONAL; + level++; + condindex[level] = wps_data->num_tokens; + numoptions[level] = 1; + wps_data->num_tokens++; + wps_token++; + skip++; + /* no "break" because a '?' is followed by a regular tag */ + + default: + /* find what tag we have */ + while (all_tags[i].name && + strncmp(wps_token, all_tags[i].name, strlen(all_tags[i].name))) + i++; + + taglen = strlen(all_tags[i].name); + skip += taglen; + wps_data->tokens[wps_data->num_tokens].type = all_tags[i].type; + + /* if the tag has a special parsing function, we call it */ + if (all_tags[i].parse_func) + skip += all_tags[i].parse_func(wps_token + taglen, wps_data); + + /* Some tags we don't want to save as tokens */ + if (all_tags[i].type == WPS_NO_TOKEN) + break; + + /* tags that start with 'F', 'I' or 'D' are for the next file */ + if ( *(all_tags[i].name) == 'I' || *(all_tags[i].name) == 'F' + || *(all_tags[i].name) == 'D') + wps_data->tokens[wps_data->num_tokens].next = true; + + wps_data->line_type[line][subline] |= all_tags[i].refresh_type; + wps_data->num_tokens++; + break; + } + + return skip; +} + +static bool wps_parse(struct wps_data *data, const char *wps_buffer) +{ + if (!data || !wps_buffer || !*wps_buffer) + return false; + + int subline; + data->num_tokens = 0; + char *current_string = data->string_buffer; + + while(wps_buffer && *wps_buffer && data->num_tokens < WPS_MAX_TOKENS + && data->num_lines < WPS_MAX_LINES) + { + switch(*wps_buffer++) + { + + /* Regular tag */ + case '%': + wps_buffer += parse_token(wps_buffer, data); + break; + + /* Alternating sublines separator */ + case ';': + if (data->num_sublines[data->num_lines]+1 < WPS_MAX_SUBLINES) + { + data->tokens[data->num_tokens++].type = WPS_TOKEN_SUBLINE_SEPARATOR; + subline = ++(data->num_sublines[data->num_lines]); + data->format_lines[data->num_lines][subline] = data->num_tokens; + } + else + wps_buffer += skip_end_of_line(wps_buffer); + + break; + + /* Conditional list start */ + case '<': + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START; + lastcond[level] = data->num_tokens++; + break; + + /* Conditional list end */ + case '>': + if (level < 0) /* not in a conditional, ignore the char */ + break; + +condlistend: /* close a conditional. sometimes we want to close them even when + we don't have a closing token, e.g. at the end of a line. */ + + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END; + if (lastcond[level]) + data->tokens[lastcond[level]].value.i = data->num_tokens; + + lastcond[level] = 0; + data->num_tokens++; + data->tokens[condindex[level]].value.i = numoptions[level]; + level--; + break; + + /* Conditional list option */ + case '|': + if (level < 0) /* not in a conditional, ignore the char */ + break; + + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION; + if (lastcond[level]) + data->tokens[lastcond[level]].value.i = data->num_tokens; + + lastcond[level] = data->num_tokens; + numoptions[level]++; + data->num_tokens++; + break; + + /* Comment */ + case '#': + wps_buffer += skip_end_of_line(wps_buffer); + break; + + /* End of this line */ + case '\n': + if (level >= 0) + { + /* We have unclosed conditionals, so we + close them before adding the EOL token */ + wps_buffer--; + goto condlistend; + break; + } + data->tokens[data->num_tokens++].type = WPS_TOKEN_EOL; + (data->num_sublines[data->num_lines])++; + data->num_lines++; + + if (data->num_lines < WPS_MAX_LINES) + { + data->format_lines[data->num_lines][0] = data->num_tokens; + } + + break; + + /* String */ + default: + if (data->num_strings < WPS_MAX_STRINGS) + { + data->tokens[data->num_tokens].type = WPS_TOKEN_STRING; + data->strings[data->num_strings] = current_string; + data->tokens[data->num_tokens].value.i = data->num_strings++; + data->num_tokens++; + + /* Copy the first byte */ + *current_string++ = *(wps_buffer - 1); + + /* continue until we hit something that ends the string */ + while(wps_buffer && + *wps_buffer != '%' && //*wps_buffer != '#' && + *wps_buffer != '<' && *wps_buffer != '>' && + *wps_buffer != '|' && *wps_buffer != '\n') + { + *current_string++ = *wps_buffer++; + } + + /* null terminate the string */ + *current_string++ = '\0'; + } + + break; + } + } + +#ifdef DEBUG + /* debugging code */ + if (false) + { + dump_wps_tokens(data); + print_line_info(data); + print_wps_strings(data); +#ifdef HAVE_LCD_BITMAP + print_img_cond_indexes(data); +#endif + } +#endif + + return true; +} + +#ifdef HAVE_LCD_BITMAP +/* Clear the WPS image cache */ +static void wps_images_clear(struct wps_data *data) +{ + int i; + /* set images to unloaded and not displayed */ + for (i = 0; i < MAX_IMAGES; i++) + { + data->img[i].loaded = false; + data->img[i].display = false; + data->img[i].always_display = false; + } + data->progressbar.have_bitmap_pb = false; +} +#endif + +/* initial setup of wps_data */ +void wps_data_init(struct wps_data *wps_data) +{ +#ifdef HAVE_LCD_BITMAP + wps_images_clear(wps_data); + wps_data->wps_sb_tag = false; + wps_data->show_sb_on_wps = false; + wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */ + wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */ + wps_data->peak_meter_enabled = false; +#else /* HAVE_LCD_CHARCELLS */ + int i; + for(i = 0; i < 8; i++) + { + wps_data->wps_progress_pat[i] = 0; + } + wps_data->full_line_progressbar = false; +#endif + wps_data->wps_loaded = false; +} + +static void wps_reset(struct wps_data *data) +{ + memset(data, 0, sizeof(*data)); + data->wps_loaded = false; + wps_data_init(data); +} + +#ifdef HAVE_LCD_BITMAP + + +static void clear_bmp_names(void) +{ + int n; + for (n = 0; n < MAX_IMAGES; n++) + { + bmp_names[n] = NULL; + } + pb_bmp_name = NULL; +#if LCD_DEPTH > 1 + backdrop_bmp_name = NULL; +#endif +} + +static void load_wps_bitmaps(struct wps_data *wps_data, char *bmpdir) +{ + char img_path[MAX_PATH]; + + int n; + for (n = 0; n < MAX_IMAGES; n++) + { + if (bmp_names[n]) + { + get_image_filename(bmp_names[n], bmpdir, + img_path, sizeof(img_path)); + + /* load the image */ + wps_data->img[n].bm.data = wps_data->img_buf_ptr; + if (load_bitmap(wps_data, img_path, &wps_data->img[n].bm)) + { + wps_data->img[n].loaded = true; + } + } + } + + if (pb_bmp_name) + { + get_image_filename(pb_bmp_name, bmpdir, img_path, sizeof(img_path)); + + /* load the image */ + wps_data->progressbar.bm.data = wps_data->img_buf_ptr; + if (load_bitmap(wps_data, img_path, &wps_data->progressbar.bm) + && wps_data->progressbar.bm.width <= LCD_WIDTH) + { + wps_data->progressbar.have_bitmap_pb = true; + } + } + +#if LCD_DEPTH > 1 + if (backdrop_bmp_name) + { + get_image_filename(backdrop_bmp_name, bmpdir, + img_path, sizeof(img_path)); + load_wps_backdrop(img_path); + } +#endif +} + +#endif /* HAVE_LCD_BITMAP */ + +/* to setup up the wps-data from a format-buffer (isfile = false) + from a (wps-)file (isfile = true)*/ +bool wps_data_load(struct wps_data *wps_data, + const char *buf, + bool isfile) +{ + if (!wps_data || !buf) + return false; + + wps_reset(wps_data); + + if (!isfile) + { + return wps_parse(wps_data, buf); + } + else + { + /* + * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this + * wants to be a virtual file. Feel free to modify dirbrowse() + * if you're feeling brave. + */ + if (! strcmp(buf, WPS_DEFAULTCFG) ) + { + global_settings.wps_file[0] = 0; + return false; + } + +#ifdef HAVE_REMOTE_LCD + if (! strcmp(buf, RWPS_DEFAULTCFG) ) + { + global_settings.rwps_file[0] = 0; + return false; + } +#endif + + int fd = open(buf, O_RDONLY); + + if (fd < 0) + return false; + + /* get buffer space from the plugin buffer */ + unsigned int buffersize = 0; + char *wps_buffer = (char *)plugin_get_buffer(&buffersize); + + if (!wps_buffer) + return false; + + /* copy the file's content to the buffer for parsing */ + unsigned int start = 0; + while(read_line(fd, wps_buffer + start, buffersize - start) > 0) + { + start += strlen(wps_buffer + start); + if (start < buffersize - 1) + { + wps_buffer[start++] = '\n'; + wps_buffer[start] = 0; + } + } + + close(fd); + + if (start <= 0) + return false; + +#ifdef HAVE_LCD_BITMAP + clear_bmp_names(); +#endif + + /* parse the WPS source */ + if (!wps_parse(wps_data, wps_buffer)) + return false; + + wps_data->wps_loaded = true; + +#ifdef HAVE_LCD_BITMAP + /* get the bitmap dir */ + char bmpdir[MAX_PATH]; + size_t bmpdirlen; + char *dot = strrchr(buf, '.'); + bmpdirlen = dot - buf; + strncpy(bmpdir, buf, dot - buf); + bmpdir[bmpdirlen] = 0; + + /* load the bitmaps that were found by the parsing */ + load_wps_bitmaps(wps_data, bmpdir); +#endif + return true; + } +} diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 5cc37485bc..336beec32e 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -7286,13 +7286,13 @@ id: LANG_END_PLAYLIST_PLAYER - desc: when playlist has finished + desc: DEPRECATED user: - *: "End of List" + *: "" - *: "End of List" + *: deprecated *: "" @@ -7304,9 +7304,11 @@ user: *: "End of Song List" + player: "End of List" *: "End of Song List" + player: "End of List" *: "" -- cgit v1.2.3