Index: apps/gui/skin_engine/skin_parser.c =================================================================== --- apps/gui/skin_engine/skin_parser.c (revision 24198) +++ apps/gui/skin_engine/skin_parser.c (working copy) @@ -160,6 +160,8 @@ #ifdef HAVE_LCD_BITMAP static int parse_viewport_display(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); +static int parse_playlistview(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); static int parse_viewport(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); static int parse_statusbar_enable(const char *wps_bufptr, @@ -373,6 +375,7 @@ { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC, parse_viewport_display }, + { WPS_VIEWPORT_CUSTOMLIST, "Vp", WPS_REFRESH_STATIC, parse_playlistview }, { WPS_NO_TOKEN, "V", 0, parse_viewport }, #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) @@ -714,6 +717,97 @@ return 1; } +bool parse_playlistview_text(struct playlistviewer *viewer, + enum info_line_type line, char* text) +{ + int cur_string = 0; + const struct wps_tag *tag; + int taglen; + if (*text != '|') + return false; + text++; + viewer->lines[line].count = 0; + while (*text != '|') + { + if (*text == '%') /* it is a token of some type */ + { + text++; + switch(*text) + { + case '%': + case '<': + case '|': + case '>': + case ';': + case '#': + /* escaped characters */ + viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_CHARACTER; + viewer->lines[line].strings[cur_string][0] = *text; + viewer->lines[line].strings[cur_string++][0] = '\0'; + break; + default: + for (tag = all_tags; + strncmp(text, tag->name, strlen(tag->name)) != 0; + tag++) ; + if (tag->type == WPS_TOKEN_UNKNOWN) + { + int i = 0; + /* just copy the string */ + viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_STRING; + while (i<(MAX_PLAYLISTLINE_STRLEN-1) && text[i] != '|' && text[i] != '%') + { + viewer->lines[line].strings[cur_string][i] = text[i]; + i++; + } + viewer->lines[line].strings[cur_string][i] = '\0'; + cur_string++; + taglen = i; + } + else + { + taglen = strlen(tag->name); + viewer->lines[line].tokens[viewer->lines[line].count++] = tag->type; + } + text += taglen; + } + } + else + { + /* regular string */ + int i = 0; + /* just copy the string */ + viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_STRING; + while (i<(MAX_PLAYLISTLINE_STRLEN-1) && text[i] != '|' && text[i] != '%') + { + viewer->lines[line].strings[cur_string][i] = text[i]; + i++; + } + viewer->lines[line].strings[cur_string][i] = '\0'; + cur_string++; + text += i; + } + } + return true; +} + + +static int parse_playlistview(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data) +{ + /* %Vp|||info line text|no info text| */ + struct playlistviewer *viewer = skin_buffer_alloc(sizeof(struct playlistviewer)); + char *ptr = strchr(wps_bufptr, '|'); + if (!viewer || !ptr) + return WPS_ERROR_INVALID_PARAM; + viewer->vp = &curr_vp->vp; + viewer->show_icons = true; + viewer->start_offset = 0; + token->value.data = (void*)viewer; + parse_playlistview_text(viewer, TRACK_HAS_INFO, "|%pp.%ia - %it|"); + + return skip_end_of_line(wps_bufptr); +} + static int parse_viewport(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data) Index: apps/gui/skin_engine/skin_display.c =================================================================== --- apps/gui/skin_engine/skin_display.c (revision 24198) +++ apps/gui/skin_engine/skin_display.c (working copy) @@ -35,6 +35,8 @@ #include "statusbar.h" #include "scrollbar.h" #include "screen_access.h" +#include "playlist.h" +#include "playback.h" #ifdef HAVE_LCD_BITMAP #include "peakmeter.h" @@ -163,7 +165,76 @@ cue_draw_markers(display, state->id3->cuesheet, length, pb->x, pb->x + pb->width, y+1, pb->height-2); } +bool audio_peek_track(struct mp3entry* id3, int offset); +static void draw_playlist_viewer_list(struct gui_wps *gwps, + struct playlistviewer *viewer) +{ + int lines = viewport_get_nb_lines(viewer->vp); + int line_height = font_get(viewer->vp->font)->height; + int cur_playlist_pos = playlist_get_display_index(); + int start_item = MAX(0, cur_playlist_pos + viewer->start_offset); + int i; + + struct mp3entry *pid3, id3; + char buf[MAX_PATH], timebuf[16]; + + + gwps->display->set_viewport(viewer->vp); + for(i=start_item; (i-start_item)cur_playlist_pos) && audio_peek_track(&id3, i-cur_playlist_pos)) + { + pid3 = &id3; + } + else + pid3 = NULL; + + if (pid3) + { + int token = 0, cur_string = 0; + buf[0] = '\0'; + while (token < viewer->lines[TRACK_HAS_INFO].count) + { + switch (viewer->lines[TRACK_HAS_INFO].tokens[token]) + { + case WPS_TOKEN_STRING: + case WPS_TOKEN_CHARACTER: + strcat(buf, viewer->lines[TRACK_HAS_INFO].strings[cur_string++]); + break; + case WPS_TOKEN_PLAYLIST_POSITION: + snprintf(timebuf, sizeof(timebuf), "%d", i); + strcat(buf, timebuf); + break; + case WPS_TOKEN_METADATA_ARTIST: + strcat(buf, pid3->artist ? pid3->artist : ""); + break; + case WPS_TOKEN_METADATA_TRACK_TITLE: + strcat(buf, pid3->title ? pid3->title : ""); + break; + default: + break; + } + token++; + } + } + else + { + snprintf(buf, sizeof(buf), "%d: %s", i, strrchr(playlist_peek(i-cur_playlist_pos), '/')); + } + + gwps->display->putsxy(0, (i-start_item)*line_height, buf ); + } +} + /* clears the area where the image was shown */ static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img) { @@ -595,6 +666,9 @@ } } break; + case WPS_VIEWPORT_CUSTOMLIST: + draw_playlist_viewer_list(gwps, data->tokens[i].value.data); + break; default: { /* get the value of the tag and copy it to the buffer */ Index: apps/gui/skin_engine/skin_tokens.h =================================================================== --- apps/gui/skin_engine/skin_tokens.h (revision 24198) +++ apps/gui/skin_engine/skin_tokens.h (working copy) @@ -54,6 +54,7 @@ /* Viewport display */ WPS_VIEWPORT_ENABLE, + WPS_VIEWPORT_CUSTOMLIST, /* Battery */ TOKEN_MARKER_BATTERY, Index: apps/gui/skin_engine/wps_internals.h =================================================================== --- apps/gui/skin_engine/wps_internals.h (revision 24198) +++ apps/gui/skin_engine/wps_internals.h (working copy) @@ -225,7 +225,28 @@ }; #endif +#define MAX_PLAYLISTLINE_TOKENS 16 +#define MAX_PLAYLISTLINE_STRINGS 8 +#define MAX_PLAYLISTLINE_STRLEN 8 +enum info_line_type { + TRACK_HAS_INFO = 0, + TRACK_HAS_NO_INFO +}; +struct playlistviewer { + struct viewport *vp; + bool show_icons; + int start_offset; + struct { + enum wps_token_type tokens[MAX_PLAYLISTLINE_TOKENS]; + char strings[MAX_PLAYLISTLINE_STRINGS][MAX_PLAYLISTLINE_STRLEN]; + int count; + } lines[2]; + enum wps_token_type noinfolinetokens[MAX_PLAYLISTLINE_TOKENS]; + int noinfoline_count; +}; + + #ifdef HAVE_ALBUMART struct skin_albumart { /* Album art support */ Index: apps/playback.c =================================================================== --- apps/playback.c (revision 24198) +++ apps/playback.c (working copy) @@ -629,6 +629,23 @@ return NULL; } +bool audio_peek_track(struct mp3entry* id3, int offset) +{ + int next_idx; + int new_offset = ci.new_track + wps_offset + offset; + + if (!audio_have_tracks()) + return false; + next_idx = (track_ridx + new_offset) & MAX_TRACK_MASK; + + if (tracks[next_idx].id3_hid >= 0) + { + bufread(tracks[next_idx].id3_hid, sizeof(struct mp3entry), id3); + return true; + } + return false; +} + #ifdef HAVE_ALBUMART int playback_current_aa_hid(int slot) { Index: apps/playback.h =================================================================== --- apps/playback.h (revision 24198) +++ apps/playback.h (working copy) @@ -64,6 +64,7 @@ void audio_set_crossfade(int enable); #endif + enum { AUDIO_WANT_PLAYBACK = 0,