Index: apps/wps-display.c =================================================================== RCS file: /cvsroot/rockbox/apps/wps-display.c,v retrieving revision 1.8 diff -u -r1.8 wps-display.c --- apps/wps-display.c 31 Aug 2002 11:54:19 -0000 1.8 +++ apps/wps-display.c 31 Aug 2002 14:40:06 -0000 @@ -16,6 +16,12 @@ * KIND, either express or implied. * ****************************************************************************/ + +/* ID3 formatting based on code from the MAD Winamp plugin (in_mad.dll), + * Copyright (C) 2000-2001 Robert Leslie. + * See http://www.mars.org/home/rob/proj/mpeg/ for more information. + */ + #include #include #include @@ -38,6 +44,9 @@ #include "ajf.h" #endif +/* Should go to stdlib.h */ +#define min(x,y) ((x)<(y)?(x):(y)) + #define WPS_CONFIG "/wps.config" #ifdef HAVE_LCD_BITMAP @@ -54,309 +63,465 @@ #define PLAY_DISPLAY_CUSTOM_WPS 5 #endif -#define LINE_LEN 64 +#define MAX_LINES 10 +#define FORMAT_BUFFER_SIZE 300 + +struct format_flags +{ + bool dynamic; + bool scroll; +#ifdef HAVE_LCD_CHARCELLS + bool player_progress; +#endif +}; +static char format_buffer[FORMAT_BUFFER_SIZE]; +static char* format_lines[MAX_LINES]; +static bool dynamic_lines[MAX_LINES]; static int ff_rewind_count; -static char custom_wps[5][LINE_LEN]; -static char display[5][LINE_LEN]; -static int scroll_line; -static int scroll_line_custom; bool wps_time_countup = true; -static bool load_custom_wps(void) +/* Set format string to use for WPS, splitting it into lines */ +static void wps_format(char* fmt) { - int fd; - int l = 0; - int numread = 1; -#ifdef SIMULATOR - char *cchr; -#else - char cchr[0]; -#endif + char* buf = format_buffer; + int line = 0; + + strncpy(format_buffer, fmt, sizeof(format_buffer)); + format_buffer[sizeof(format_buffer) - 1] = 0; + format_lines[line] = buf; + + while (*buf) + { + switch (*buf++) + { + case '\r': + *(buf - 1) = 0; + break; + + case '\n': /* LF */ + *(buf - 1) = 0; + line++; + + if (line < MAX_LINES) + { + format_lines[line] = buf; + } + + break; + } + } - for (l=0;l<=5;l++) - custom_wps[l][0] = 0; + for (; line < MAX_LINES; line++) + { + format_lines[line] = NULL; + } +} - l = 0; +static bool load_custom_wps(void) +{ + char buffer[FORMAT_BUFFER_SIZE]; + int fd; fd = open(WPS_CONFIG, O_RDONLY); - if (-1 == fd) + + if (-1 != fd) { + int numread = read(fd, buffer, sizeof(buffer) - 1); + + if (numread > 0) + { + buffer[numread] = 0; + wps_format(buffer); + } + close(fd); - return false; + return numread > 0; } + + return false; +} - while(l<=5) - { - numread = read(fd, cchr, 1); - if (numread==0) - break; +/* Format time into buf. + * + * buf - buffer to format to. + * buf_size - size of buffer. + * time - time to format, in milliseconds. + */ +static void format_time(char* buf, int buf_size, int time) +{ + snprintf(buf, buf_size, "%d:%02d", time / 60000, time % 60000 / 1000); +} - switch (cchr[0]) - { - case '\n': /* LF */ - l++; - break; +/* Extract a part from a path. + * + * buf - buffer extract part to. + * buf_size - size of buffer. + * time - 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, char* path, int level) +{ + char* sep; + char* last_sep; + int len; - case '\r': /* CR ... Ignore it */ - break; + sep = path + strlen(path); + last_sep = sep; - default: - snprintf(custom_wps[l], LINE_LEN, - "%s%c", custom_wps[l], cchr[0]); + while (sep > path) + { + if ('/' == *(--sep)) + { + if (!level) + { break; + } + + level--; + last_sep = sep - 1; } } - close(fd); - scroll_line_custom = 0; - for (l=0;l<=5;l++) + if (level || (last_sep <= sep)) { - if (custom_wps[l][0] == '%' && custom_wps[l][1] == 's') - scroll_line_custom = l; + return NULL; } - return true; + + len = min(last_sep - sep, buf_size - 1); + strncpy(buf, sep + 1, len); + buf[len] = 0; + return buf; } -static bool display_custom_wps( struct mp3entry* id3, - int x_val, - int y_val, - bool do_scroll, - char *wps_string) +/* Get the tag specified by the two characters at fmt. + * + * id3 - 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 - flags in this struct will be set depending on the tag: + * dynamic - if the tag data changes over time (like play time); + * player_progress - set if the tag is %pb. + * + * Returns the tag. NULL indicates the tag wasn't available. + */ +static char* get_tag(struct mp3entry* id3, char* tag, char* buf, int buf_size, + struct format_flags* flags) { - char bigbuf[LINE_LEN*2]; - char buf[LINE_LEN]; - int i; - int con_flag = 0; /* (0)Not inside of if/else - (1)Inside of If - (2)Inside of Else */ - char con_if[LINE_LEN]; - char con_else[LINE_LEN]; - char cchr1; - char cchr2; - char cchr3; - unsigned int seek; - - char* szLast; - - szLast = strrchr(id3->path, '/'); - if (szLast) - /* point to the first letter in the file name */ - szLast++; - - bigbuf[0] = 0; - - seek = -1; - while(1) - { - seek++; - cchr1 = wps_string[seek]; - buf[0] = 0; - if (cchr1 == '%') - { - seek++; - cchr2 = wps_string[seek]; - switch(cchr2) - { - case 'i': /* ID3 Information */ - seek++; - cchr3 = wps_string[seek]; - switch(cchr3) - { - case 't': /* ID3 Title */ - strncpy(buf, - id3->title ? id3->title : "", - LINE_LEN); - break; - case 'a': /* ID3 Artist */ - strncpy(buf, - id3->artist ? id3->artist : "", - LINE_LEN); - break; - case 'n': /* ID3 Track Number */ - snprintf(buf, LINE_LEN, "%d", - id3->tracknum); - break; - case 'd': /* ID3 Album/Disc */ - strncpy(buf, id3->album ? id3->album : "", LINE_LEN); - break; - } - break; - case 'f': /* File Information */ - seek++; - cchr3 = wps_string[seek]; - switch(cchr3) - { - case 'c': /* Conditional Filename \ ID3 Artist-Title */ - if (id3->artist && id3->title) - snprintf(buf, LINE_LEN, "%s - %s", - id3->artist?id3->artist:"", - id3->title?id3->title:""); - else - strncpy(buf, - szLast ? szLast : id3->path, - LINE_LEN ); - break; - - case 'd': /* Conditional Filename \ ID3 Title-Artist */ - if (id3->artist && id3->title) - snprintf(buf, LINE_LEN, "%s - %s", - id3->title?id3->title:"", - id3->artist?id3->artist:""); - else - strncpy(buf, szLast ? szLast : id3->path, - LINE_LEN); - break; - - case 'b': /* File Bitrate */ - snprintf(buf, LINE_LEN, "%d", id3->bitrate); - break; - - case 'f': /* File Frequency */ - snprintf(buf, LINE_LEN, "%d", id3->frequency); - break; - - case 'p': /* File Path */ - strncpy(buf, id3->path, LINE_LEN ); - break; - - case 'n': /* File Name */ - strncpy(buf, szLast ? szLast : id3->path, - LINE_LEN ); - break; - - case 's': /* File Size (In Kilobytes) */ - snprintf(buf, LINE_LEN, "%d", - id3->filesize / 1024); - break; - } - break; - - case 'p': /* Playlist/Song Information */ - seek++; - cchr3 = wps_string[seek]; + if ((0 == tag[0]) || (0 == tag[1])) + { + return NULL; + } + + switch (tag[0]) + { + case 'i': /* ID3 Information */ + switch (tag[1]) + { + case 't': /* ID3 Title */ + return id3->title; - switch(cchr3) - { -#if defined(HAVE_LCD_CHARCELLS) && !defined(SIMULATOR) - case 'b': /* Progress Bar (PLAYER ONLY)*/ - draw_player_progress(id3, ff_rewind_count); - snprintf(buf, LINE_LEN, "\x01"); - break; -#endif - case 'p': /* Playlist Position */ - snprintf(buf, LINE_LEN, "%d", id3->index + 1); - break; - - case 'e': /* Playlist Total Entries */ - snprintf(buf, LINE_LEN, "%d", playlist.amount); - break; - - case 'c': /* Current Time in Song */ - i = id3->elapsed + ff_rewind_count; - snprintf(buf, LINE_LEN, "%d:%02d", - i / 60000, - i % 60000 / 1000); - wps_time_countup = true; - break; - - case 'r': /* Remaining Time in Song */ - i = id3->length - id3->elapsed + ff_rewind_count; - snprintf(buf, LINE_LEN, "%d:%02d", - i / 60000, - i % 60000 / 1000 ); - wps_time_countup = false; - break; - - case 't': /* Total Time */ - snprintf(buf, LINE_LEN, "%d:%02d", - id3->length / 60000, - id3->length % 60000 / 1000); - break; - } - break; - - case '%': /* Displays % */ - buf[0] = '%'; - buf[1] = 0; - break; - - case '?': /* Conditional Display of ID3/File */ - switch(con_flag) - { - case 0: - con_if[0] = 0; - con_else[0] = 0; - con_flag = 1; - break; - default: - if (id3->artist && id3->title) - strncpy(buf, con_if, LINE_LEN); - else - strncpy(buf, con_else, LINE_LEN); - con_flag = 0; - break; - } - break; - - case ':': /* Seperator for Conditional ID3/File Display */ - con_flag = 2; - break; - } - - switch(con_flag) - { - case 0: - snprintf(bigbuf, sizeof bigbuf, "%s%s", bigbuf, buf); - break; - - case 1: - snprintf(con_if, sizeof con_if, "%s%s", con_if, buf); - break; - - case 2: - snprintf(con_else, sizeof con_else, "%s%s", con_else, buf); - break; - } - } - else - { - switch(con_flag) - { - case 0: - snprintf(bigbuf, sizeof bigbuf, "%s%c", bigbuf, cchr1); - break; - - case 1: - snprintf(con_if, sizeof con_if, "%s%c", con_if, cchr1); - break; - - case 2: - snprintf(con_else, sizeof con_else, "%s%c", - con_else, cchr1); - break; + case 'a': /* ID3 Artist */ + return id3->artist; + + case 'n': /* ID3 Track Number */ + if (id3->tracknum) + { + snprintf(buf, buf_size, "%d", id3->tracknum); + return buf; + } + else + { + return NULL; } + + case 'd': /* ID3 Album/Disc */ + return id3->album; } + break; - if (seek >= strlen(wps_string)) + case 'f': /* File Information */ + switch(tag[1]) { - if (do_scroll) + case 'v': /* VBR file? */ + return id3->vbr ? "(avg)" : NULL; + + case 'b': /* File Bitrate */ + snprintf(buf, buf_size, "%d", id3->bitrate); + return buf; + + case 'f': /* File Frequency */ + snprintf(buf, buf_size, "%d", 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)) { - lcd_stop_scroll(); - lcd_puts_scroll(x_val, y_val, bigbuf); + /* Remove extension */ + char* sep = strrchr(buf, '.'); + + if (NULL != sep) + { + *sep = 0; + } + + return buf; } else - lcd_puts(x_val, y_val, bigbuf); + { + return NULL; + } + + case 's': /* File Size (in kilobytes) */ + snprintf(buf, buf_size, "%d", id3->filesize / 1024); + return buf; + } + break; - return true; + case 'p': /* Playlist/Song Information */ + switch(tag[1]) + { +#if defined(HAVE_LCD_CHARCELLS) && !defined(SIMULATOR) + case 'b': /* Player progress bar */ + flags->player_progress = true; + flags->dynamic = true; + return "\x01"; +#endif + + case 'p': /* Playlist Position */ + snprintf(buf, buf_size, "%d", id3->index + 1); + return buf; + + case 'e': /* Playlist Total Entries */ + snprintf(buf, buf_size, "%d", playlist.amount); + return buf; + + case 'c': /* Current Time in Song */ + flags->dynamic = true; + format_time(buf, buf_size, id3->elapsed + ff_rewind_count); + return buf; + + case 'r': /* Remaining Time in Song */ + flags->dynamic = true; + format_time(buf, buf_size, id3->length - id3->elapsed + ff_rewind_count); + return buf; + + case 't': /* Total Time */ + format_time(buf, buf_size, id3->length); + return buf; + } + break; + + case 'd': /* Directory path information */ + switch(tag[1]) + { + case '1': /* Parent folder */ + return get_dir(buf, buf_size, id3->path, 1); + + case '2': /* Parent of parent */ + return get_dir(buf, buf_size, id3->path, 2); + + case '3': /* Parent of parent of parent */ + return get_dir(buf, buf_size, id3->path, 3); } + break; } - return true; + + return NULL; } -bool wps_refresh(struct mp3entry* id3, int ffwd_offset, bool refresh_scroll) +/* 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 ">"). + * to_else - if true, skip to the else part (after the "|", if any), else skip + * to the end (the ">"). + * + * Returns the new position in fmt. + */ +static char* skip_conditional(char* fmt, bool to_else) { - int l; + int level = 1; + + while (*fmt) + { + switch (*fmt++) + { + case '%': + break; + + case '|': + if (to_else && (1 == level)) + { + return fmt; + } + + continue; + + case '>': + if (0 == --level) + { + if (to_else) + { + fmt--; + } + + return fmt; + } + 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. + * fmt - format description. + * flags - flags in this struct will be set depending on the tag: + * dynamic - if the tag data changes over time (like play time); + * player_progress - set if the tag is %pb. + * scroll - if line scrolling is requested. + */ +static void format_display(char* buf, int buf_size, struct mp3entry* id3, + char* fmt, struct format_flags* flags) +{ + char temp_buf[128]; + char* buf_end = buf + buf_size - 1; /* Leave room for end null */ + char* value = NULL; + int level = 0; + + while (*fmt && buf < buf_end) + { + switch (*fmt) + { + case '%': + ++fmt; + break; + + case '|': + case '>': + if (level > 0) + { + fmt = skip_conditional(fmt, false); + level--; + continue; + } + /* Else fall through */ + + default: + *buf++ = *fmt++; + continue; + } + + switch (*fmt) + { + case 0: + *buf++ = '%'; + break; + + case 's': + flags->scroll = true; + ++fmt; + break; + + case '%': + case '|': + case '<': + case '>': + *buf++ = *fmt++; + break; + + case '?': + fmt++; + value = get_tag(id3, fmt, temp_buf, sizeof(temp_buf), flags); + + while (*fmt && ('<' != *fmt)) + { + fmt++; + } + + if ('<' == *fmt) + { + fmt++; + } + + if (NULL == value) + { + /* No value, so skip to else part */ + fmt = skip_conditional(fmt, true); + } + + level++; + break; + + default: + value = get_tag(id3, fmt, temp_buf, sizeof(temp_buf), flags); + fmt += 2; + + if (value) + { + while (*value && (buf < buf_end)) + { + *buf++ = *value++; + } + } + } + } + + *buf = 0; +} + +bool wps_refresh(struct mp3entry* id3, int ffwd_offset, bool refresh_all) +{ + char buf[MAX_PATH]; + struct format_flags flags; + bool scroll_active = false; + int i; #ifdef HAVE_LCD_BITMAP int bmp_time_line; #endif @@ -371,25 +536,38 @@ ff_rewind_count = ffwd_offset; #ifdef HAVE_LCD_CHARCELL - for (l = 0; l <= 1; l++) + for (i = 0; i <= 1; i++) #else - for (l = 0; l <= 5; l++) + /* TODO: Calculate number of lines that fits */ + for (i = 0; i <= 5; i++) #endif { - if (global_settings.wps_display == PLAY_DISPLAY_CUSTOM_WPS) - { - scroll_line = scroll_line_custom; - if (scroll_line != l) - display_custom_wps(id3, 0, l, false, custom_wps[l]); - if (scroll_line == l && refresh_scroll) - display_custom_wps(id3, 0, l, true, custom_wps[l]); - } - else + if (dynamic_lines[i] || refresh_all) { - if (scroll_line != l) - display_custom_wps(id3, 0, l, false, display[l]); - if (scroll_line == l && refresh_scroll) - display_custom_wps(id3, 0, l, true, display[l]); + flags.dynamic = false; + flags.scroll = false; +#ifdef HAVE_LCD_CHARCELLS + flags.player_progress = false; +#endif + format_display(buf, sizeof(buf), id3, format_lines[i], &flags); + dynamic_lines[i] = flags.dynamic; + +#ifdef HAVE_LCD_CHARCELLS + if (flags.player_progress) + { + draw_player_progress(id3, ff_rewind_count); + } +#endif + + if (!scroll_active && flags.scroll && !flags.dynamic) + { + scroll_active = true; + lcd_puts_scroll(0, i, buf); + } + else + { + lcd_puts(0, i, buf); + } } } #ifdef HAVE_LCD_BITMAP @@ -397,8 +575,9 @@ bmp_time_line = 5; else bmp_time_line = 6; - snprintf(display[bmp_time_line], sizeof display[bmp_time_line], - "%s","Time: %pc/%pt"); + + format_display(buf, sizeof(buf), id3, "Time: %pc/%pt", &flags); + lcd_puts(0, bmp_time_line, buf); slidebar(0, LCD_HEIGHT-6, LCD_WIDTH, 6, id3->elapsed*100/id3->length, Grow_Right); lcd_update(); @@ -418,6 +597,7 @@ #endif lcd_clear_display(); + if (!id3 && !mpeg_is_playing()) { #ifdef HAVE_LCD_CHARCELLS @@ -431,122 +611,73 @@ else { static int last_wps = -1; - if ((last_wps != global_settings.wps_display - && global_settings.wps_display == PLAY_DISPLAY_CUSTOM_WPS)) + + if (last_wps != global_settings.wps_display) { - load_custom_wps(); last_wps = global_settings.wps_display; + + if (global_settings.wps_display == PLAY_DISPLAY_CUSTOM_WPS) + { + load_custom_wps(); + } } switch ( global_settings.wps_display ) { case PLAY_DISPLAY_TRACK_TITLE: { - char ch = '/'; - char* end; - char* szTok; - char* szDelimit; - char* szPeriod; - char szArtist[26]; - char szBuff[257]; - int tmpcnt = 0; - - szBuff[sizeof(szBuff)-1] = 0; - strncpy(szBuff, id3->path, sizeof szBuff); - - szTok = strtok_r(szBuff, "/", &end); - szTok = strtok_r(NULL, "/", &end); - - /* Assume path format of: Genre/Artist/Album/Mp3_file */ - strncpy(szArtist, szTok, sizeof szArtist); - szArtist[sizeof(szArtist)-1] = 0; - szDelimit = strrchr(id3->path, ch); - lcd_puts(0, 0, szArtist ? szArtist : ""); - - /* removes the .mp3 from the end of the display buffer */ - szPeriod = strrchr(szDelimit, '.'); - if (szPeriod != NULL) - *szPeriod = 0; - - strncpy(display[0], ++szDelimit, sizeof display[0]); + /* Assume path format of: .../Artist/Album/Mp3_file */ + wps_format("%s%?d2<%d2|%> - %fn\n" #ifdef HAVE_LCD_CHARCELLS - snprintf(display[1], sizeof display[1], "%s", "%pc/%pt"); + "%pc/%pt\n" #endif - for (tmpcnt=2;tmpcnt<=5;tmpcnt++) - display[tmpcnt][0] = 0; - scroll_line = 0; + "\n\n\n\n\n\n" + ); break; } case PLAY_DISPLAY_FILENAME_SCROLL: { - snprintf(display[0], sizeof display[0], "%s", "%pp/%pe: %fn"); + wps_format("%s%pp/%pe: %fn\n" #ifdef HAVE_LCD_CHARCELLS - snprintf(display[1], sizeof display[1], "%s", "%pc/%pt"); + "%pc/%pt\n" #endif - scroll_line = 0; + ); break; } case PLAY_DISPLAY_2LINEID3: { #ifdef HAVE_LCD_BITMAP - int l = 0; - - strncpy( display[l++], "%fn", LINE_LEN ); - strncpy( display[l++], "%it", LINE_LEN ); - strncpy( display[l++], "%id", LINE_LEN ); - strncpy( display[l++], "%ia", LINE_LEN ); - - if (!global_settings.statusbar && font_height <= 8) - { - if (id3->vbr) - strncpy(display[l++], "%fb kbit (avg)", LINE_LEN); - else - strncpy(display[l++], "%fb kbit", LINE_LEN); - - strncpy(display[l++], "%ff Hz", LINE_LEN); - } - else - { - if (id3->vbr) - strncpy(display[l++], "%fb kbit(a) %ffHz", LINE_LEN); - else - strncpy(display[l++], "%fb kbit %ffHz", LINE_LEN); - } - scroll_line = 0; + wps_format("%s%fn\n%it\n%id\n%ia\n%fb kbit %fa\n%ff Hz"); #else - strncpy(display[0], "%ia", LINE_LEN); - strncpy(display[1], "%it", LINE_LEN); - scroll_line = 1; + wps_format("%ia\n%s%it\n"); #endif break; } #ifdef HAVE_LCD_CHARCELLS case PLAY_DISPLAY_1LINEID3: { - strncpy(display[0], "%pp/%pe: %fc", LINE_LEN); - strncpy(display[1], "%pc/%pt", LINE_LEN); - scroll_line = 0; + wps_format("%s%pp/%pe: %?ia<%ia - >%?it<%it|%fm>\n" + "%pc/%pt\n"); break; } case PLAY_DISPLAY_1LINEID3_PLUS: { - strncpy(display[0], "%pp/%pe: %fc", LINE_LEN); - strncpy(display[1], "%pr%pb%fbkps", LINE_LEN); - scroll_line = 0; + wps_format("%s%pp/%pe: %?ia<%ia - >%?it<%it|%fm>\n" + "%pr%pb%fbkps\n"); break; } #endif case PLAY_DISPLAY_CUSTOM_WPS: { - if (custom_wps[0] == 0) + if (format_buffer[0] == 0) { - strncpy(display[0], "Couldn't Load Custom WPS", LINE_LEN); - strncpy(display[1], "%pc/%pt", LINE_LEN); + wps_format("%sCouldn't Load Custom WPS\n%pc/%pt\n"); } break; } } } - wps_refresh(id3,0,false); + + wps_refresh(id3, 0, true); status_draw(); lcd_update(); }