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,1103 +94,6 @@ 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) -{ - 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; -} - -/* 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) -{ - 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; - - case 'y': /* year */ - if( id3->year_string ) - return id3->year_string; - - if (id3->year) { - snprintf(buf, buf_size, "%d", id3->year); - return buf; - } - return NULL; - - case 'g': /* genre */ - return id3->genre_string; - - case 'v': /* id3 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; - } - } - 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; - - case 'f': /* File Frequency */ - snprintf(buf, buf_size, "%ld", id3->frequency); - return buf; - - case 'p': /* File Path */ - return id3->path; - - case 'm': /* File Name - With Extension */ - return get_dir(buf, buf_size, id3->path, 0); - - 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; - } - - case 's': /* sleep timer */ - { - if (get_sleep_timer() == 0) - { - return NULL; - } - else - { - 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; - } - } -#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; -#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 */ - else - *intval = type + 2; - - if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE) - *intval += 2; - } - - 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; -} - -#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 - -/* 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); - } - } -#endif - break; - - case '|': - if(1 == level) { - if (enums) - (*enums)++; - last_alternative = fmt; - if(num) { - count--; - if(count == 0) - return fmt; - continue; - } - } - 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; - } - continue; - - default: - continue; - } - - switch (*fmt++) - { - case 0: - case '%': - case '|': - case '<': - case '>': - break; - - case '?': - while (*fmt && ('<' != *fmt)) - fmt++; - - if ('<' == *fmt) - fmt++; - - level++; - break; - - 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; -#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; - } - } - -#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++; - 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) { @@ -1514,798 +139,6 @@ void fade(bool fade_in) } } -/* 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; - } - 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); - } - } - 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)); -} -#endif - -bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset, - unsigned char refresh_mode) -{ - 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) - { - return false; - } -#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; - } -#endif - /* reset to first subline if refresh all flag is set */ - if (refresh_mode == WPS_REFRESH_ALL) - { - for (i=0; icurr_subline[i] = SUBLINE_RESET; - } - } - -#ifdef HAVE_LCD_CHARCELLS - for (i=0; i<8; i++) { - if (data->wps_progress_pat[i]==0) - data->wps_progress_pat[i]=display->get_locked_pattern(); - } -#endif - - if (!state->id3) - { - display->stop_scroll(); - return false; - } - - state->ff_rewind_count = ffwd_offset; - - for (i = 0; i < WPS_MAX_LINES; i++) - { - reset_subline = (data->curr_subline[i] == 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[i] - 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[i]; - for (search=0; searchcurr_subline[i]++; - - /* 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 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 - { - /* 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; - } - } - } - - } - - update_line = false; - - if ( !data->format_lines[i][data->curr_subline[i]] ) - break; - - if ((data->line_type[i][data->curr_subline[i]] & refresh_mode) || - (refresh_mode == WPS_REFRESH_ALL) || - new_subline_refresh) - { - 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; - - 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; - } - - if (flags & WPS_REFRESH_SCROLL) { - - /* scroll line */ - if ((refresh_mode & WPS_REFRESH_SCROLL) || - new_subline_refresh) { - - 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); -#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; - -#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 - } - -#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; -#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 ( n < 1000 ) - n = 5000; /* use 5s if backlight is always on or off */ - - if (((state->id3->elapsed < 1000) || - ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && - (state->paused == false)) - backlight_on(); - } -#endif -#ifdef HAVE_REMOTE_LCD - if (global_settings.remote_caption_backlight && state->id3) { - /* turn on remote backlight n seconds before track ends, and turn it - off n seconds into the new track. n == remote_backlight_timeout, - or 5s */ - int n = backlight_timeout_value[global_settings.remote_backlight_timeout] - * 1000; - - if ( n < 1000 ) - n = 5000; /* use 5s if backlight is always on or off */ - - if (((state->id3->elapsed < 1000) || - ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && - (state->paused == false)) - remote_backlight_on(); - } -#endif - return true; -} - -#ifdef HAVE_LCD_CHARCELLS -static bool draw_player_progress(struct gui_wps *gwps) -{ - char player_progressbar[7]; - char binline[36]; - int songpos = 0; - int i,j; - struct wps_state *state = gwps->state; - struct screen *display = gwps->display; - if (!state->id3) - return false; - - memset(binline, 1, sizeof binline); - memset(player_progressbar, 1, sizeof player_progressbar); - - if(state->id3->elapsed >= state->id3->length) - songpos = 0; - else - { - if(state->wps_time_countup == false) - songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) / - state->id3->length; - else - songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / - state->id3->length; - } - for (i=0; i < songpos; i++) - binline[i] = 0; - - for (i=0; i<=6; i++) { - for (j=0;j<5;j++) { - player_progressbar[i] <<= 1; - player_progressbar[i] += binline[i*5+j]; - } - } - display->define_pattern(gwps->data->wps_progress_pat[0], player_progressbar); - return true; -} - -static char map_fullbar_char(char ascii_val) -{ - if (ascii_val >= '0' && ascii_val <= '9') { - return(ascii_val - '0'); - } - else if (ascii_val == ':') { - return(10); - } - else - return(11); /* anything besides a number or ':' is mapped to */ -} - -static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size) -{ - int i,j,lcd_char_pos; - - char player_progressbar[7]; - char binline[36]; - static const char numbers[12][4][3]={ - {{1,1,1},{1,0,1},{1,0,1},{1,1,1}},/*0*/ - {{0,1,0},{1,1,0},{0,1,0},{0,1,0}},/*1*/ - {{1,1,1},{0,0,1},{0,1,0},{1,1,1}},/*2*/ - {{1,1,1},{0,0,1},{0,1,1},{1,1,1}},/*3*/ - {{1,0,0},{1,1,0},{1,1,1},{0,1,0}},/*4*/ - {{1,1,1},{1,1,0},{0,0,1},{1,1,0}},/*5*/ - {{1,1,1},{1,0,0},{1,1,1},{1,1,1}},/*6*/ - {{1,1,1},{0,0,1},{0,1,0},{1,0,0}},/*7*/ - {{1,1,1},{1,1,1},{1,0,1},{1,1,1}},/*8*/ - {{1,1,1},{1,1,1},{0,0,1},{1,1,1}},/*9*/ - {{0,0,0},{0,1,0},{0,0,0},{0,1,0}},/*:*/ - {{0,0,0},{0,0,0},{0,0,0},{0,0,0}} /**/ - }; - - int songpos = 0; - int digits[6]; - int time; - char timestr[7]; - - struct wps_state *state = gwps->state; - struct screen *display = gwps->display; - struct wps_data *data = gwps->data; - - for (i=0; i < buf_size; i++) - buf[i] = ' '; - - if(state->id3->elapsed >= state->id3->length) - songpos = 55; - else { - if(state->wps_time_countup == false) - songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / - state->id3->length; - else - songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / - state->id3->length; - } - - time=(state->id3->elapsed + state->ff_rewind_count); - - memset(timestr, 0, sizeof(timestr)); - format_time(timestr, sizeof(timestr), time); - for(lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) { - digits[lcd_char_pos] = map_fullbar_char(timestr[lcd_char_pos]); - } - - /* build the progressbar-icons */ - for (lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) { - memset(binline, 0, sizeof binline); - memset(player_progressbar, 0, sizeof player_progressbar); - - /* make the character (progressbar & digit)*/ - for (i=0; i<7; i++) { - for (j=0;j<5;j++) { - /* make the progressbar */ - if (lcd_char_pos==(songpos/5)) { - /* partial */ - if ((j<(songpos%5))&&(i>4)) - binline[i*5+j] = 1; - else - binline[i*5+j] = 0; - } - else { - if (lcd_char_pos<(songpos/5)) { - /* full character */ - if (i>4) - binline[i*5+j] = 1; - } - } - /* insert the digit */ - if ((j<3)&&(i<4)) { - if (numbers[digits[lcd_char_pos]][i][j]==1) - binline[i*5+j] = 1; - } - } - } - - for (i=0; i<=6; i++) { - for (j=0;j<5;j++) { - player_progressbar[i] <<= 1; - player_progressbar[i] += binline[i*5+j]; - } - } - - display->define_pattern(data->wps_progress_pat[lcd_char_pos+1], - player_progressbar); - buf = utf8encode(data->wps_progress_pat[lcd_char_pos+1], buf); - } - - /* make rest of the progressbar if necessary */ - if (songpos/5>5) { - - /* set the characters positions that use the full 5 pixel wide bar */ - for (lcd_char_pos=6; lcd_char_pos < (songpos/5); lcd_char_pos++) - buf = utf8encode(0xe115, buf); /* 2/7 '_' */ - - /* build the partial bar character for the tail character position */ - memset(binline, 0, sizeof binline); - memset(player_progressbar, 0, sizeof player_progressbar); - - for (i=5; i<7; i++) { - for (j=0;j<5;j++) { - if (j<(songpos%5)) { - binline[i*5+j] = 1; - } - } - } - - for (i=0; i<7; i++) { - for (j=0;j<5;j++) { - player_progressbar[i] <<= 1; - player_progressbar[i] += binline[i*5+j]; - } - } - - display->define_pattern(data->wps_progress_pat[7],player_progressbar); - buf = utf8encode(data->wps_progress_pat[7], buf); - *buf = '\0'; - } -} -#endif - /* set volume */ void setvol(void) { @@ -2482,12 +315,10 @@ bool gui_wps_display(void) if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY)) { global_status.resume_index = -1; -#ifdef HAVE_LCD_CHARCELLS - gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER)); -#else +#ifdef HAVE_LCD_BITMAP gui_syncstatusbar_draw(&statusbars, true); - gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER)); #endif + gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER)); return true; } else @@ -2496,7 +327,7 @@ bool gui_wps_display(void) { gui_wps[i].display->clear_display(); if (!gui_wps[i].data->wps_loaded) { - if ( !gui_wps[i].data->format_buffer[0] ) { + if ( !gui_wps[i].data->num_tokens ) { /* set the default wps for the main-screen */ if(i == 0) { @@ -2643,3 +474,1476 @@ void display_keylock_text(bool locked) gui_syncsplash(HZ, s); } +#ifdef HAVE_LCD_BITMAP + +static void draw_progressbar(struct gui_wps *gwps, int line) +{ + struct wps_data *data = gwps->data; + struct screen *display = gwps->display; + struct wps_state *state = gwps->state; + int h = font_get(FONT_UI)->height; + + int sb_y; + if (data->progress_top < 0) + sb_y = line*h + display->getymargin() + + ((h > data->progress_height + 1) + ? (h - data->progress_height) / 2 : 1); + else + sb_y = data->progress_top; + + if (!data->progress_end) + data->progress_end=display->width; + + if (gwps->data->progressbar.have_bitmap_pb) + gui_bitmap_scrollbar_draw(display, data->progressbar.bm, + data->progress_start, sb_y, + data->progress_end-data->progress_start, + data->progressbar.bm.height, + state->id3->length ? state->id3->length : 1, 0, + state->id3->length ? state->id3->elapsed + + state->ff_rewind_count : 0, + HORIZONTAL); + else + gui_scrollbar_draw(display, data->progress_start, sb_y, + data->progress_end-data->progress_start, + data->progress_height, + state->id3->length ? state->id3->length : 1, 0, + state->id3->length ? state->id3->elapsed + + state->ff_rewind_count : 0, + HORIZONTAL); + +#ifdef AB_REPEAT_ENABLE + if ( ab_repeat_mode_enabled() ) + ab_draw_markers(display, state->id3->length, + data->progress_start, data->progress_end, sb_y, + data->progress_height); +#endif + + if ( cuesheet_is_enabled() && state->id3->cuesheet_type ) + cue_draw_markers(display, state->id3->length, + data->progress_start, data->progress_end, + sb_y+1, data->progress_height-2); +} + +/* clears the area where the image was shown */ +static void clear_image_pos(struct gui_wps *gwps, int n) +{ + if(!gwps) + return; + struct wps_data *data = gwps->data; + gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + gwps->display->fillrect(data->img[n].x, data->img[n].y, + data->img[n].bm.width, data->img[n].bm.height); + gwps->display->set_drawmode(DRMODE_SOLID); +} + +static void wps_draw_image(struct gui_wps *gwps, int n) +{ + struct screen *display = gwps->display; + struct wps_data *data = gwps->data; + if(data->img[n].always_display) + display->set_drawmode(DRMODE_FG); + else + display->set_drawmode(DRMODE_SOLID); + +#if LCD_DEPTH > 1 + if(data->img[n].bm.format == FORMAT_MONO) { +#endif + display->mono_bitmap(data->img[n].bm.data, data->img[n].x, + data->img[n].y, data->img[n].bm.width, + data->img[n].bm.height); +#if LCD_DEPTH > 1 + } else { + display->transparent_bitmap((fb_data *)data->img[n].bm.data, + data->img[n].x, + data->img[n].y, data->img[n].bm.width, + data->img[n].bm.height); + } +#endif +} + +static void wps_display_images(struct gui_wps *gwps) +{ + if(!gwps || !gwps->data || !gwps->display) + return; + + int n; + struct wps_data *data = gwps->data; + struct screen *display = gwps->display; + + for (n = 0; n < MAX_IMAGES; n++) + { + if (data->img[n].loaded && + (data->img[n].display || data->img[n].always_display)) + { + wps_draw_image(gwps, n); + } + } + display->set_drawmode(DRMODE_SOLID); +} + +#else /* HAVE_LCD_CHARCELL */ + +static bool draw_player_progress(struct gui_wps *gwps) +{ + char player_progressbar[7]; + char binline[36]; + int songpos = 0; + int i,j; + struct wps_state *state = gwps->state; + struct screen *display = gwps->display; + if (!state->id3) + return false; + + memset(binline, 1, sizeof binline); + memset(player_progressbar, 1, sizeof player_progressbar); + + if(state->id3->elapsed >= state->id3->length) + songpos = 0; + else + { + if(state->wps_time_countup == false) + songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) / + state->id3->length; + else + songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / + state->id3->length; + } + for (i=0; i < songpos; i++) + binline[i] = 0; + + for (i=0; i<=6; i++) { + for (j=0;j<5;j++) { + player_progressbar[i] <<= 1; + player_progressbar[i] += binline[i*5+j]; + } + } + display->define_pattern(gwps->data->wps_progress_pat[0], player_progressbar); + return true; +} + +static char map_fullbar_char(char ascii_val) +{ + if (ascii_val >= '0' && ascii_val <= '9') { + return(ascii_val - '0'); + } + else if (ascii_val == ':') { + return(10); + } + else + return(11); /* anything besides a number or ':' is mapped to */ +} + +static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size) +{ + int i,j,lcd_char_pos; + + char player_progressbar[7]; + char binline[36]; + static const char numbers[12][4][3]={ + {{1,1,1},{1,0,1},{1,0,1},{1,1,1}},/*0*/ + {{0,1,0},{1,1,0},{0,1,0},{0,1,0}},/*1*/ + {{1,1,1},{0,0,1},{0,1,0},{1,1,1}},/*2*/ + {{1,1,1},{0,0,1},{0,1,1},{1,1,1}},/*3*/ + {{1,0,0},{1,1,0},{1,1,1},{0,1,0}},/*4*/ + {{1,1,1},{1,1,0},{0,0,1},{1,1,0}},/*5*/ + {{1,1,1},{1,0,0},{1,1,1},{1,1,1}},/*6*/ + {{1,1,1},{0,0,1},{0,1,0},{1,0,0}},/*7*/ + {{1,1,1},{1,1,1},{1,0,1},{1,1,1}},/*8*/ + {{1,1,1},{1,1,1},{0,0,1},{1,1,1}},/*9*/ + {{0,0,0},{0,1,0},{0,0,0},{0,1,0}},/*:*/ + {{0,0,0},{0,0,0},{0,0,0},{0,0,0}} /**/ + }; + + int songpos = 0; + int digits[6]; + int time; + char timestr[7]; + + struct wps_state *state = gwps->state; + struct screen *display = gwps->display; + struct wps_data *data = gwps->data; + + for (i=0; i < buf_size; i++) + buf[i] = ' '; + + if(state->id3->elapsed >= state->id3->length) + songpos = 55; + else { + if(state->wps_time_countup == false) + songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / + state->id3->length; + else + songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / + state->id3->length; + } + + time=(state->id3->elapsed + state->ff_rewind_count); + + memset(timestr, 0, sizeof(timestr)); + format_time(timestr, sizeof(timestr), time); + for(lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) { + digits[lcd_char_pos] = map_fullbar_char(timestr[lcd_char_pos]); + } + + /* build the progressbar-icons */ + for (lcd_char_pos=0; lcd_char_pos<6; lcd_char_pos++) { + memset(binline, 0, sizeof binline); + memset(player_progressbar, 0, sizeof player_progressbar); + + /* make the character (progressbar & digit)*/ + for (i=0; i<7; i++) { + for (j=0;j<5;j++) { + /* make the progressbar */ + if (lcd_char_pos==(songpos/5)) { + /* partial */ + if ((j<(songpos%5))&&(i>4)) + binline[i*5+j] = 1; + else + binline[i*5+j] = 0; + } + else { + if (lcd_char_pos<(songpos/5)) { + /* full character */ + if (i>4) + binline[i*5+j] = 1; + } + } + /* insert the digit */ + if ((j<3)&&(i<4)) { + if (numbers[digits[lcd_char_pos]][i][j]==1) + binline[i*5+j] = 1; + } + } + } + + for (i=0; i<=6; i++) { + for (j=0;j<5;j++) { + player_progressbar[i] <<= 1; + player_progressbar[i] += binline[i*5+j]; + } + } + + display->define_pattern(data->wps_progress_pat[lcd_char_pos+1], + player_progressbar); + buf = utf8encode(data->wps_progress_pat[lcd_char_pos+1], buf); + } + + /* make rest of the progressbar if necessary */ + if (songpos/5>5) { + + /* set the characters positions that use the full 5 pixel wide bar */ + for (lcd_char_pos=6; lcd_char_pos < (songpos/5); lcd_char_pos++) + buf = utf8encode(0xe115, buf); /* 2/7 '_' */ + + /* build the partial bar character for the tail character position */ + memset(binline, 0, sizeof binline); + memset(player_progressbar, 0, sizeof player_progressbar); + + for (i=5; i<7; i++) { + for (j=0;j<5;j++) { + if (j<(songpos%5)) { + binline[i*5+j] = 1; + } + } + } + + for (i=0; i<7; i++) { + for (j=0;j<5;j++) { + player_progressbar[i] <<= 1; + player_progressbar[i] += binline[i*5+j]; + } + } + + display->define_pattern(data->wps_progress_pat[7],player_progressbar); + buf = utf8encode(data->wps_progress_pat[7], buf); + *buf = '\0'; + } +} + +#endif /* HAVE_LCD_CHARCELL */ + +/* Extract a part from a path. + * + * buf - buffer extract part to. + * buf_size - size of buffer. + * path - path to extract from. + * level - what to extract. 0 is file name, 1 is parent of file, 2 is + * parent of parent, etc. + * + * Returns buf if the desired level was found, NULL otherwise. + */ +static char* get_dir(char* buf, int buf_size, const char* path, int level) +{ + const char* sep; + const char* last_sep; + int len; + + sep = path + strlen(path); + last_sep = sep; + + while (sep > path) + { + if ('/' == *(--sep)) + { + if (!level) + break; + + level--; + last_sep = sep - 1; + } + } + + if (level || (last_sep <= sep)) + return NULL; + + len = MIN(last_sep - sep, buf_size - 1); + strncpy(buf, sep + 1, len); + buf[len] = 0; + return buf; +} + +/* Return the tag found at index i and write its value in buf. + The return value is buf if the tag had a value, or NULL if not. + + intval is used with enums: when this function is called, it should contain + the number of options in the enum. When this function returns, it will + contain the enum case we are actually in. + When not treating an enum, intval should be NULL. +*/ +static char *get_tag(struct gui_wps *gwps, + int i, + char *buf, + int buf_size, + int *intval) +{ + if (!gwps) + return NULL; + + struct wps_data *data = gwps->data; + struct wps_state *state = gwps->state; + + if (!data || !state) + return NULL; + + struct mp3entry *id3; + + if (data->tokens[i].next) + id3 = state->nid3; + else + id3 = state->id3; + + if (!id3) + return NULL; + + int limit = 1; + if (intval) + limit = *intval; + +#if CONFIG_RTC + static struct tm* tm; +#endif + + switch (data->tokens[i].type) + { + case WPS_TOKEN_CHARACTER: + return &(data->tokens[i].value.c); + + case WPS_TOKEN_STRING: + return data->strings[data->tokens[i].value.i]; + + case WPS_TOKEN_TRACK_TIME_ELAPSED: + format_time(buf, buf_size, + id3->elapsed + state->ff_rewind_count); + return buf; + + case WPS_TOKEN_TRACK_TIME_REMAINING: + format_time(buf, buf_size, + id3->length - id3->elapsed - + state->ff_rewind_count); + return buf; + + case WPS_TOKEN_TRACK_LENGTH: + format_time(buf, buf_size, id3->length); + return buf; + + case WPS_TOKEN_PLAYLIST_ENTRIES: + snprintf(buf, buf_size, "%d", playlist_amount()); + return buf; + + case WPS_TOKEN_PLAYLIST_NAME: + return playlist_name(NULL, buf, buf_size); + + case WPS_TOKEN_PLAYLIST_POSITION: + snprintf(buf, buf_size, "%d", + playlist_get_display_index()); + return buf; + + case WPS_TOKEN_PLAYLIST_SHUFFLE: + if ( global_settings.playlist_shuffle ) + return "s"; + else + return NULL; + break; + + case WPS_TOKEN_VOLUME: + snprintf(buf, buf_size, "%d", global_settings.volume); + if (intval) + { + *intval = limit * (global_settings.volume + - sound_min(SOUND_VOLUME)) + / (sound_max(SOUND_VOLUME) + - sound_min(SOUND_VOLUME)) + 1; + } + return buf; + + case WPS_TOKEN_METADATA_ARTIST: + return id3->artist; + + case WPS_TOKEN_METADATA_COMPOSER: + return id3->composer; + + case WPS_TOKEN_METADATA_ALBUM: + return id3->album; + + case WPS_TOKEN_METADATA_ALBUM_ARTIST: + return id3->albumartist; + + case WPS_TOKEN_METADATA_GENRE: + return id3->genre_string; + + case WPS_TOKEN_METADATA_TRACK_NUMBER: + if (id3->track_string) + return id3->track_string; + + if (id3->tracknum) { + snprintf(buf, buf_size, "%d", id3->tracknum); + return buf; + } + return NULL; + + case WPS_TOKEN_METADATA_TRACK_TITLE: + return id3->title; + + case WPS_TOKEN_METADATA_VERSION: + switch (id3->id3version) + { + case ID3_VER_1_0: + return "1"; + + case ID3_VER_1_1: + return "1.1"; + + case ID3_VER_2_2: + return "2.2"; + + case ID3_VER_2_3: + return "2.3"; + + case ID3_VER_2_4: + return "2.4"; + + default: + return NULL; + } + + case WPS_TOKEN_METADATA_YEAR: + if( id3->year_string ) + return id3->year_string; + + if (id3->year) { + snprintf(buf, buf_size, "%d", id3->year); + return buf; + } + return NULL; + + case WPS_TOKEN_METADATA_COMMENT: + return id3->comment; + + case WPS_TOKEN_FILE_BITRATE: + if(id3->bitrate) + snprintf(buf, buf_size, "%d", id3->bitrate); + else + snprintf(buf, buf_size, "?"); + return buf; + + case WPS_TOKEN_FILE_CODEC: + if (intval) + { + if(id3->codectype == AFMT_UNKNOWN) + *intval = AFMT_NUM_CODECS; + else + *intval = id3->codectype; + } + return id3_get_codec(id3); + + case WPS_TOKEN_FILE_FREQUENCY: + snprintf(buf, buf_size, "%ld", id3->frequency); + return buf; + + case WPS_TOKEN_FILE_NAME: + if (get_dir(buf, buf_size, id3->path, 0)) { + /* Remove extension */ + char* sep = strrchr(buf, '.'); + if (NULL != sep) { + *sep = 0; + } + return buf; + } + else { + return NULL; + } + + case WPS_TOKEN_FILE_NAME_WITH_EXTENSION: + return get_dir(buf, buf_size, id3->path, 0); + + case WPS_TOKEN_FILE_PATH: + return id3->path; + + case WPS_TOKEN_FILE_SIZE: + snprintf(buf, buf_size, "%ld", id3->filesize / 1024); + return buf; + + case WPS_TOKEN_FILE_VBR: + return id3->vbr ? "(avg)" : NULL; + + case WPS_TOKEN_FILE_DIRECTORY: + return get_dir(buf, buf_size, id3->path, data->tokens[i].value.i); + + case WPS_TOKEN_BATTERY_PERCENT: + { + int l = battery_level(); + + if (intval) + { + limit = MAX(limit, 2); + if (l > -1) { + /* First enum is used for "unknown level". */ + *intval = (limit - 1) * l / 100 + 2; + } else { + *intval = 1; + } + } + + if (l > -1) { + snprintf(buf, buf_size, "%d", l); + return buf; + } else { + return "?"; + } + } + + case WPS_TOKEN_BATTERY_VOLTS: + { + unsigned int v = battery_voltage(); + snprintf(buf, buf_size, "%d.%02d", v/100, v%100); + return buf; + } + + case WPS_TOKEN_BATTERY_TIME: + { + int t = battery_time(); + if (t >= 0) + snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60); + else + strncpy(buf, "?h ?m", buf_size); + return buf; + } + +#if CONFIG_CHARGING + case WPS_TOKEN_BATTERY_CHARGER_CONNECTED: + { + if(charger_input_state==CHARGER) + return "p"; + else + return NULL; + } +#endif +#if CONFIG_CHARGING >= CHARGING_MONITOR + case WPS_TOKEN_BATTERY_CHARGING: + { + if (charge_state == CHARGING || charge_state == TOPOFF) { + return "c"; + } else { + return NULL; + } + } +#endif + + case WPS_TOKEN_PLAYBACK_STATUS: + { + int status = audio_status(); + int mode = 1; + if (status == AUDIO_STATUS_PLAY && \ + !(status & AUDIO_STATUS_PAUSE)) + mode = 2; + if (audio_status() & AUDIO_STATUS_PAUSE && \ + (! status_get_ffmode())) + mode = 3; + if (status_get_ffmode() == STATUS_FASTFORWARD) + mode = 4; + if (status_get_ffmode() == STATUS_FASTBACKWARD) + mode = 5; + + if (intval) { + *intval = mode; + } + + snprintf(buf, buf_size, "%d", mode); + return buf; + } + + case WPS_TOKEN_REPEAT_MODE: + if (intval) + *intval = global_settings.repeat_mode + 1; + snprintf(buf, buf_size, "%d", *intval); + return buf; + +#if CONFIG_RTC + case WPS_TOKEN_RTC: + tm = get_time(); + return NULL; + + case WPS_TOKEN_RTC_DAY_OF_MONTH: + /* d: day of month (01..31) */ + if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL; + snprintf(buf, buf_size, "%02d", tm->tm_mday); + return buf; + + case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED: + /* e: day of month, blank padded ( 1..31) */ + if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL; + snprintf(buf, buf_size, "%2d", tm->tm_mday); + return buf; + + case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED: + /* H: hour (00..23) */ + if (tm->tm_hour > 23) return NULL; + snprintf(buf, buf_size, "%02d", tm->tm_hour); + return buf; + + case WPS_TOKEN_RTC_HOUR_24: + /* k: hour ( 0..23) */ + if (tm->tm_hour > 23) return NULL; + snprintf(buf, buf_size, "%2d", tm->tm_hour); + return buf; + + case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED: + /* I: hour (01..12) */ + if (tm->tm_hour > 23) return NULL; + snprintf(buf, buf_size, "%02d", + (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12); + return buf; + + case WPS_TOKEN_RTC_HOUR_12: + /* l: hour ( 1..12) */ + if (tm->tm_hour > 23) return NULL; + snprintf(buf, buf_size, "%2d", + (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12); + return buf; + + case WPS_TOKEN_RTC_MONTH: + /* m: month (01..12) */ + if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL; + snprintf(buf, buf_size, "%02d", tm->tm_mon + 1); + return buf; + + case WPS_TOKEN_RTC_MINUTE: + /* M: minute (00..59) */ + if (tm->tm_min > 59 || tm->tm_min < 0) return NULL; + snprintf(buf, buf_size, "%02d", tm->tm_min); + return buf; + + case WPS_TOKEN_RTC_SECOND: + /* S: second (00..59) */ + if (tm->tm_sec > 59 || tm->tm_sec < 0) return NULL; + snprintf(buf, buf_size, "%02d", tm->tm_sec); + return buf; + + case WPS_TOKEN_RTC_YEAR_2_DIGITS: + /* y: last two digits of year (00..99) */ + snprintf(buf, buf_size, "%02d", tm->tm_year % 100); + return buf; + + case WPS_TOKEN_RTC_YEAR_4_DIGITS: + /* Y: year (1970...) */ + if (tm->tm_year > 199 || tm->tm_year < 100) return NULL; + snprintf(buf, buf_size, "%04d", tm->tm_year + 1900); + return buf; + + case WPS_TOKEN_RTC_AM_PM_UPPER: + /* p: upper case AM or PM indicator */ + snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM"); + return buf; + + case WPS_TOKEN_RTC_AM_PM_LOWER: + /* P: lower case am or pm indicator */ + snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm"); + return buf; + + case WPS_TOKEN_RTC_WEEKDAY_NAME: + /* a: abbreviated weekday name (Sun..Sat) */ + if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL; + snprintf(buf, buf_size, "%s",str(dayname[tm->tm_wday])); + return buf; + + case WPS_TOKEN_RTC_MONTH_NAME: + /* b: abbreviated month name (Jan..Dec) */ + if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL; + snprintf(buf, buf_size, "%s",str(monthname[tm->tm_mon])); + return buf; + + case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON: + /* u: day of week (1..7); 1 is Monday */ + if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL; + snprintf(buf, buf_size, "%1d", tm->tm_wday + 1); + return buf; + + case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN: + /* w: day of week (0..6); 0 is Sunday */ + if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL; + snprintf(buf, buf_size, "%1d", tm->tm_wday); + return buf; +#endif + +#ifdef HAVE_LCD_CHARCELLS + case WPS_TOKEN_PROGRESSBAR: + { + char *end = utf8encode(data->wps_progress_pat[0], buf); + *end = '\0'; + return buf; + } + + case WPS_TOKEN_PLAYER_PROGRESSBAR: + if(is_new_player()) + { + /* we need 11 characters (full line) for + progress-bar */ + snprintf(buf, buf_size, " "); + } + else + { + /* Tell the user if we have an OldPlayer */ + snprintf(buf, buf_size, " "); + } + 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; + + struct wps_data *data = gwps->data; + + int ret; + int num_options = data->tokens[cond_index].value.i; + char result[128], *value; + int cond_start = cond_index; + + /* find the index of the conditional start token */ + while (data->tokens[cond_start].type != WPS_TOKEN_CONDITIONAL_START + && cond_start < data->num_tokens) + cond_start++; + + if (num_options > 2) /* enum */ + { + int intval = num_options; + /* get_tag needs to know the number of options in the enum */ + get_tag(gwps, cond_index + 1, result, sizeof(result), &intval); + /* intval is now the number of the enum option we want to read, + starting from 1 */ + if (intval > num_options || intval < 1) + intval = num_options; + + int next = cond_start; + int i; + for (i = 1; i < intval; i++) + { + next = data->tokens[next].value.i; + } + ret = next; + } + else /* %?xx or %? */ + { + value = get_tag(gwps, cond_index + 1, result, sizeof(result), NULL); + ret = value ? cond_start : data->tokens[cond_start].value.i; + } + +#ifdef HAVE_LCD_BITMAP + /* clear all pictures in the conditional */ + int i; + for (i=0; i < MAX_IMAGES; i++) + { + if (data->img[i].cond_index == cond_index) + clear_image_pos(gwps, i); + } +#endif + + return ret; +} + +/* Read a (sub)line to the given alignment format buffer. + linebuf is the buffer where the data is actually stored. + align is the alignment format that'll be used to display the text. + The return value indicates whether the line needs to be updated. +*/ +static bool get_line(struct gui_wps *gwps, + int line, int subline, + struct align_pos *align, + char *linebuf, + int linebuf_size) +{ + struct wps_data *data = gwps->data; + + char temp_buf[128]; + char *buf = linebuf; /* will always point to the writing position */ + char *linebuf_end = linebuf + linebuf_size - 1; + bool update = false; + + /* alignment-related variables */ + int cur_align; + char* cur_align_start; + cur_align_start = buf; + cur_align = WPS_ALIGN_LEFT; + align->left = 0; + align->center = 0; + align->right = 0; + + /* start at the beginning of the current (sub)line */ + int i = data->format_lines[line][subline]; + + while (data->tokens[i].type != WPS_TOKEN_EOL + && data->tokens[i].type != WPS_TOKEN_SUBLINE_SEPARATOR + && i < data->num_tokens) + { + switch(data->tokens[i].type) + { + case WPS_TOKEN_CONDITIONAL: + /* place ourselves in the right conditional case */ + i = evaluate_conditional(gwps, i); + break; + + case WPS_TOKEN_CONDITIONAL_OPTION: + /* we've finished in the curent conditional case, + skip to the end of the conditional structure */ + i = find_conditional_end(data, i); + break; + +#ifdef HAVE_LCD_BITMAP + case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY: + { + struct gui_img *img = data->img; + int n = data->tokens[i].value.i; + if (n >= 0 && n < MAX_IMAGES && img[n].loaded) + img[n].display = true; + break; + } +#endif + + case WPS_TOKEN_ALIGN_LEFT: + case WPS_TOKEN_ALIGN_CENTER: + case WPS_TOKEN_ALIGN_RIGHT: + /* remember where the current aligned text started */ + switch (cur_align) + { + case WPS_ALIGN_LEFT: + align->left = cur_align_start; + break; + + case WPS_ALIGN_CENTER: + align->center = cur_align_start; + break; + + case WPS_ALIGN_RIGHT: + align->right = cur_align_start; + break; + } + /* start a new alignment */ + switch (data->tokens[i].type) + { + case WPS_TOKEN_ALIGN_LEFT: + cur_align = WPS_ALIGN_LEFT; + break; + case WPS_TOKEN_ALIGN_CENTER: + cur_align = WPS_ALIGN_CENTER; + break; + case WPS_TOKEN_ALIGN_RIGHT: + cur_align = WPS_ALIGN_RIGHT; + break; + default: + break; + } + *buf++ = 0; + cur_align_start = buf; + break; + + default: + { + /* get the value of the tag and copy it to the buffer */ + char *value = get_tag(gwps, i, temp_buf, + sizeof(temp_buf), NULL); + if (value) + { + update = true; + while (*value && (buf < linebuf_end)) + *buf++ = *value++; + } + break; + } + } + i++; + } + + /* close the current alignment */ + switch (cur_align) + { + case WPS_ALIGN_LEFT: + align->left = cur_align_start; + break; + + case WPS_ALIGN_CENTER: + align->center = cur_align_start; + break; + + case WPS_ALIGN_RIGHT: + align->right = cur_align_start; + break; + } + + return update; +} + +/* Calculate which subline should be displayed for each line */ +static bool get_curr_subline(struct wps_data *data, int line) +{ + int search, search_start; + bool reset_subline; + bool new_subline_refresh; + bool only_one_subline; + + reset_subline = (data->curr_subline[line] == SUBLINE_RESET); + new_subline_refresh = false; + only_one_subline = false; + + /* if time to advance to next sub-line */ + if (TIME_AFTER(current_tick, data->subline_expire_time[line] - 1) || + reset_subline) + { + /* search all sublines until the next subline with time > 0 + is found or we get back to the subline we started with */ + if (reset_subline) + search_start = 0; + else + search_start = data->curr_subline[line]; + + for (search = 0; search < WPS_MAX_SUBLINES; search++) + { + data->curr_subline[line]++; + + /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */ + if ((!data->format_lines[line][data->curr_subline[line]]) || + (data->curr_subline[line] == WPS_MAX_SUBLINES)) + { + if (data->curr_subline[line] == 1) + only_one_subline = true; + data->curr_subline[line] = 0; + } + + /* if back where we started after search or + only one subline is defined on the line */ + if (((search > 0) && (data->curr_subline[line] == search_start)) || + only_one_subline) + { + /* no other subline with a time > 0 exists */ + data->subline_expire_time[line] = (reset_subline? + current_tick : data->subline_expire_time[line]) + 100 * HZ; + break; + } + else + { + /* only use this subline if subline time > 0 */ + if (data->time_mult[line][data->curr_subline[line]] > 0) + { + new_subline_refresh = true; + data->subline_expire_time[line] = (reset_subline ? + current_tick : data->subline_expire_time[line]) + + BASE_SUBLINE_TIME * data->time_mult[line][data->curr_subline[line]]; + break; + } + } + } + } + + return new_subline_refresh; +} + +/* Display a line appropriately according to its alignment format. + format_align contains the text, separated between left, center and right. + line is the index of the line on the screen. + scroll indicates whether the line is a scrolling one or not. +*/ +static void write_line(struct screen *display, + struct align_pos *format_align, + int line, + bool scroll) +{ + + int left_width, left_xpos; + int center_width, center_xpos; + int right_width, right_xpos; + int ypos; + int space_width; + int string_height; + + /* calculate different string sizes and positions */ + display->getstringsize((unsigned char *)" ", &space_width, &string_height); + if (format_align->left != 0) { + display->getstringsize((unsigned char *)format_align->left, + &left_width, &string_height); + } + else { + left_width = 0; + } + left_xpos = 0; + + if (format_align->center != 0) { + display->getstringsize((unsigned char *)format_align->center, + ¢er_width, &string_height); + } + else { + center_width = 0; + } + center_xpos=(display->width - center_width) / 2; + + if (format_align->right != 0) { + display->getstringsize((unsigned char *)format_align->right, + &right_width, &string_height); + } + else { + right_width = 0; + } + right_xpos = (display->width - right_width); + + /* Checks for overlapping strings. + If needed the overlapping strings will be merged, separated by a + space */ + + /* CASE 1: left and centered string overlap */ + /* there is a left string, need to merge left and center */ + if ((left_width != 0 && center_width != 0) && + (left_xpos + left_width + space_width > center_xpos)) { + /* replace the former separator '\0' of left and + center string with a space */ + *(--format_align->center) = ' '; + /* calculate the new width and position of the merged string */ + left_width = left_width + space_width + center_width; + left_xpos = 0; + /* there is no centered string anymore */ + center_width = 0; + } + /* there is no left string, move center to left */ + if ((left_width == 0 && center_width != 0) && + (left_xpos + left_width > center_xpos)) { + /* move the center string to the left string */ + format_align->left = format_align->center; + /* calculate the new width and position of the string */ + left_width = center_width; + left_xpos = 0; + /* there is no centered string anymore */ + center_width = 0; + } + + /* CASE 2: centered and right string overlap */ + /* there is a right string, need to merge center and right */ + if ((center_width != 0 && right_width != 0) && + (center_xpos + center_width + space_width > right_xpos)) { + /* replace the former separator '\0' of center and + right string with a space */ + *(--format_align->right) = ' '; + /* move the center string to the right after merge */ + format_align->right = format_align->center; + /* calculate the new width and position of the merged string */ + right_width = center_width + space_width + right_width; + right_xpos = (display->width - right_width); + /* there is no centered string anymore */ + center_width = 0; + } + /* there is no right string, move center to right */ + if ((center_width != 0 && right_width == 0) && + (center_xpos + center_width > right_xpos)) { + /* move the center string to the right string */ + format_align->right = format_align->center; + /* calculate the new width and position of the string */ + right_width = center_width; + right_xpos = (display->width - right_width); + /* there is no centered string anymore */ + center_width = 0; + } + + /* CASE 3: left and right overlap + There is no center string anymore, either there never + was one or it has been merged in case 1 or 2 */ + /* there is a left string, need to merge left and right */ + if ((left_width != 0 && center_width == 0 && right_width != 0) && + (left_xpos + left_width + space_width > right_xpos)) { + /* replace the former separator '\0' of left and + right string with a space */ + *(--format_align->right) = ' '; + /* calculate the new width and position of the string */ + left_width = left_width + space_width + right_width; + left_xpos = 0; + /* there is no right string anymore */ + right_width = 0; + } + /* there is no left string, move right to left */ + if ((left_width == 0 && center_width == 0 && right_width != 0) && + (left_xpos + left_width > right_xpos)) { + /* move the right string to the left string */ + format_align->left = format_align->right; + /* calculate the new width and position of the string */ + left_width = right_width; + left_xpos = 0; + /* there is no right string anymore */ + right_width = 0; + } + + ypos = (line * string_height) + display->getymargin(); + + + if (scroll && left_width > display->width) + { + display->puts_scroll(0, line, + (unsigned char *)format_align->left); + } + else + { +#ifdef HAVE_LCD_BITMAP + /* clear the line first */ + display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + display->fillrect(0, ypos, display->width, string_height); + display->set_drawmode(DRMODE_SOLID); +#endif + + /* Nasty hack: we output an empty scrolling string, + which will reset the scroller for that line */ + display->puts_scroll(0, line, (unsigned char *)""); + + /* print aligned strings */ + if (left_width != 0) + { + display->putsxy(left_xpos, ypos, + (unsigned char *)format_align->left); + } + if (center_width != 0) + { + display->putsxy(center_xpos, ypos, + (unsigned char *)format_align->center); + } + if (right_width != 0) + { + display->putsxy(right_xpos, ypos, + (unsigned char *)format_align->right); + } + } +} + +/* Refresh the WPS according to refresh_mode. */ +bool gui_wps_refresh(struct gui_wps *gwps, + int ffwd_offset, + unsigned char refresh_mode) +{ + struct wps_data *data = gwps->data; + struct screen *display = gwps->display; + struct wps_state *state = gwps->state; + + if(!gwps || !data || !state || !display) + return false; + + int line, i; + unsigned char flags; + char linebuf[MAX_PATH]; + + struct align_pos align; + align.left = NULL; + align.center = NULL; + align.right = NULL; + + bool update_line, new_subline_refresh; + +#ifdef HAVE_LCD_BITMAP + gui_wps_statusbar_draw(gwps, true); + + /* to find out wether the peak meter is enabled we + assume it wasn't until we find a line that contains + the peak meter. We can't use peak_meter_enabled itself + because that would mean to turn off the meter thread + temporarily. (That shouldn't matter unless yield + or sleep is called but who knows...) + */ + bool enable_pm = false; + + /* Set images to not to be displayed */ + for (i = 0; i < MAX_IMAGES; i++) + { + data->img[i].display = false; + } +#endif + + /* reset to first subline if refresh all flag is set */ + if (refresh_mode == WPS_REFRESH_ALL) + { + for (i = 0; i < data->num_lines; i++) + { + data->curr_subline[i] = SUBLINE_RESET; + } + } + +#ifdef HAVE_LCD_CHARCELLS + for (i = 0; i < 8; i++) + { + if (data->wps_progress_pat[i] == 0) + data->wps_progress_pat[i] = display->get_locked_pattern(); + } +#endif + + if (!state->id3) + { + display->stop_scroll(); + return false; + } + + state->ff_rewind_count = ffwd_offset; + + for (line = 0; line < data->num_lines; line++) + { + memset(linebuf, 0, sizeof(linebuf)); + update_line = false; + + /* get current subline for the line */ + new_subline_refresh = get_curr_subline(data, line); + + flags = data->line_type[line][data->curr_subline[line]]; + + if (refresh_mode == WPS_REFRESH_ALL || flags & refresh_mode + || new_subline_refresh) + { + /* get_line tells us if we need to update the line */ + update_line = get_line(gwps, line, data->curr_subline[line], + &align, linebuf, sizeof(linebuf)); + } + +#ifdef HAVE_LCD_BITMAP + /* progressbar */ + if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) + { + /* the progressbar should be alone on its line */ + update_line = false; + draw_progressbar(gwps, line); + } + + /* peakmeter */ + if (flags & refresh_mode & WPS_REFRESH_PEAK_METER) + { + /* the peakmeter should be alone on its line */ + update_line = false; + + int h = font_get(FONT_UI)->height; + int peak_meter_y = display->getymargin() + line * h; + + /* The user might decide to have the peak meter in the last + line so that it is only displayed if no status bar is + visible. If so we neither want do draw nor enable the + peak meter. */ + if (peak_meter_y + h <= display->height) { + /* found a line with a peak meter -> remember that we must + enable it later */ + enable_pm = true; + peak_meter_screen(gwps->display, 0, peak_meter_y, + MIN(h, display->height - peak_meter_y)); + } + } + +#else /* HAVE_LCD_CHARCELL */ + + /* progressbar */ + if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) + { + if (data->full_line_progressbar) + draw_player_fullbar(gwps, linebuf, sizeof(linebuf)); + else + draw_player_progress(gwps); + } +#endif + + if (update_line) + { + /* calculate alignment and draw the strings */ + write_line(display, &align, line, flags & WPS_REFRESH_SCROLL); + } + } + +#ifdef HAVE_LCD_BITMAP + data->peak_meter_enabled = enable_pm; + wps_display_images(gwps); +#endif + + display->update(); + +#if CONFIG_BACKLIGHT + if (global_settings.caption_backlight && state->id3) + { + /* turn on backlight n seconds before track ends, and turn it off n + seconds into the new track. n == backlight_timeout, or 5s */ + int n = backlight_timeout_value[global_settings.backlight_timeout] + * 1000; + + if ( n < 1000 ) + n = 5000; /* use 5s if backlight is always on or off */ + + if (((state->id3->elapsed < 1000) || + ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && + (state->paused == false)) + backlight_on(); + } +#endif +#ifdef HAVE_REMOTE_LCD + if (global_settings.remote_caption_backlight && state->id3) + { + /* turn on remote backlight n seconds before track ends, and turn it + off n seconds into the new track. n == remote_backlight_timeout, + or 5s */ + int n = backlight_timeout_value[global_settings.remote_backlight_timeout] + * 1000; + + if ( n < 1000 ) + n = 5000; /* use 5s if backlight is always on or off */ + + if (((state->id3->elapsed < 1000) || + ((state->id3->length - state->id3->elapsed) < (unsigned)n)) && + (state->paused == false)) + remote_backlight_on(); + } +#endif + + return true; +} 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" *: ""