summaryrefslogtreecommitdiffstats
path: root/apps/gui/wps_engine
diff options
context:
space:
mode:
Diffstat (limited to 'apps/gui/wps_engine')
-rw-r--r--apps/gui/wps_engine/gwps.h546
-rw-r--r--apps/gui/wps_engine/wps_debug.c641
-rw-r--r--apps/gui/wps_engine/wps_display.c1099
-rw-r--r--apps/gui/wps_engine/wps_engine.h49
-rw-r--r--apps/gui/wps_engine/wps_internals.h556
-rw-r--r--apps/gui/wps_engine/wps_parser.c1843
-rw-r--r--apps/gui/wps_engine/wps_tokens.c807
7 files changed, 5541 insertions, 0 deletions
diff --git a/apps/gui/wps_engine/gwps.h b/apps/gui/wps_engine/gwps.h
new file mode 100644
index 0000000000..2acde0dc39
--- /dev/null
+++ b/apps/gui/wps_engine/gwps.h
@@ -0,0 +1,546 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Nicolas Pennequin
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _WPS_H
+#define _WPS_H
+
+#include "screen_access.h"
+#include "statusbar.h"
+#include "metadata.h"
+
+/* constants used in line_type and as refresh_mode for wps_refresh */
+#define WPS_REFRESH_STATIC (1u<<0) /* line doesn't change over time */
+#define WPS_REFRESH_DYNAMIC (1u<<1) /* line may change (e.g. time flag) */
+#define WPS_REFRESH_SCROLL (1u<<2) /* line scrolls */
+#define WPS_REFRESH_PLAYER_PROGRESS (1u<<3) /* line contains a progress bar */
+#define WPS_REFRESH_PEAK_METER (1u<<4) /* line contains a peak meter */
+#define WPS_REFRESH_STATUSBAR (1u<<5) /* refresh statusbar */
+#define WPS_REFRESH_ALL (0xffffffffu) /* to refresh all line types */
+
+/* to refresh only those lines that change over time */
+#define WPS_REFRESH_NON_STATIC (WPS_REFRESH_DYNAMIC| \
+ WPS_REFRESH_PLAYER_PROGRESS| \
+ WPS_REFRESH_PEAK_METER)
+/* alignments */
+#define WPS_ALIGN_RIGHT 32
+#define WPS_ALIGN_CENTER 64
+#define WPS_ALIGN_LEFT 128
+
+#ifdef HAVE_ALBUMART
+
+/* albumart definitions */
+#define WPS_ALBUMART_NONE 0 /* WPS does not contain AA tag */
+#define WPS_ALBUMART_CHECK 1 /* WPS contains AA conditional tag */
+#define WPS_ALBUMART_LOAD 2 /* WPS contains AA tag */
+
+#define WPS_ALBUMART_ALIGN_RIGHT 1 /* x align: right */
+#define WPS_ALBUMART_ALIGN_CENTER 2 /* x/y align: center */
+#define WPS_ALBUMART_ALIGN_LEFT 4 /* x align: left */
+#define WPS_ALBUMART_ALIGN_TOP 1 /* y align: top */
+#define WPS_ALBUMART_ALIGN_BOTTOM 4 /* y align: bottom */
+
+#endif /* HAVE_ALBUMART */
+
+/* wps_data*/
+
+#ifdef HAVE_LCD_BITMAP
+struct gui_img {
+ struct bitmap bm;
+ struct viewport* vp; /* The viewport to display this image in */
+ short int x; /* x-pos */
+ short int y; /* y-pos */
+ short int num_subimages; /* number of sub-images */
+ short int subimage_height; /* height of each sub-image */
+ short int display; /* -1 for no display, 0..n to display a subimage */
+ bool loaded; /* load state */
+ bool always_display; /* not using the preload/display mechanism */
+};
+
+struct progressbar {
+ /* regular pb */
+ short x;
+ /* >=0: explicitly set in the tag -> y-coord within the viewport
+ <0 : not set in the tag -> negated 1-based line number within
+ the viewport. y-coord will be computed based on the font height */
+ short y;
+ short width;
+ short height;
+ /*progressbar image*/
+ struct bitmap bm;
+ bool have_bitmap_pb;
+};
+#endif
+
+
+
+struct align_pos {
+ char* left;
+ char* center;
+ char* right;
+};
+
+#ifdef HAVE_LCD_BITMAP
+
+#define MAX_IMAGES (26*2) /* a-z and A-Z */
+#define MAX_PROGRESSBARS 3
+
+/* The image buffer is big enough to store one full-screen native bitmap,
+ plus two full-screen mono bitmaps. */
+
+#define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \
+ + (2*LCD_HEIGHT*LCD_WIDTH/8))
+
+#define WPS_MAX_VIEWPORTS 24
+#define WPS_MAX_LINES ((LCD_HEIGHT/5+1) * 2)
+#define WPS_MAX_SUBLINES (WPS_MAX_LINES*3)
+#define WPS_MAX_TOKENS 1024
+#define WPS_MAX_STRINGS 128
+#define STRING_BUFFER_SIZE 1024
+#define WPS_MAX_COND_LEVEL 10
+
+#else
+
+#define WPS_MAX_VIEWPORTS 2
+#define WPS_MAX_LINES 2
+#define WPS_MAX_SUBLINES 12
+#define WPS_MAX_TOKENS 64
+#define WPS_MAX_STRINGS 32
+#define STRING_BUFFER_SIZE 64
+#define WPS_MAX_COND_LEVEL 5
+
+#endif
+
+#define SUBLINE_RESET -1
+
+enum wps_parse_error {
+ PARSE_OK,
+ PARSE_FAIL_UNCLOSED_COND,
+ PARSE_FAIL_INVALID_CHAR,
+ PARSE_FAIL_COND_SYNTAX_ERROR,
+ PARSE_FAIL_COND_INVALID_PARAM,
+ PARSE_FAIL_LIMITS_EXCEEDED,
+};
+
+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,
+
+ /* Alignment */
+ WPS_TOKEN_ALIGN_LEFT,
+ WPS_TOKEN_ALIGN_CENTER,
+ WPS_TOKEN_ALIGN_RIGHT,
+
+ /* Sublines */
+ 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,
+
+ /* Sound */
+#if (CONFIG_CODEC != MAS3507D)
+ WPS_TOKEN_SOUND_PITCH,
+#endif
+#if (CONFIG_CODEC == SWCODEC)
+ WPS_TOKEN_REPLAYGAIN,
+ WPS_TOKEN_CROSSFADE,
+#endif
+
+ /* Time */
+
+ WPS_TOKEN_RTC_PRESENT,
+
+ /* The begin/end values allow us to know if a token is an RTC one.
+ New RTC tokens should be added between the markers. */
+
+ WPS_TOKENS_RTC_BEGIN, /* just the start marker, not an actual token */
+
+ WPS_TOKEN_RTC_DAY_OF_MONTH,
+ WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,
+ WPS_TOKEN_RTC_12HOUR_CFG,
+ 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,
+
+ WPS_TOKENS_RTC_END, /* just the end marker, not an actual token */
+
+ /* Conditional */
+ WPS_TOKEN_CONDITIONAL,
+ WPS_TOKEN_CONDITIONAL_START,
+ WPS_TOKEN_CONDITIONAL_OPTION,
+ WPS_TOKEN_CONDITIONAL_END,
+
+ /* Database */
+#ifdef HAVE_TAGCACHE
+ WPS_TOKEN_DATABASE_PLAYCOUNT,
+ WPS_TOKEN_DATABASE_RATING,
+ WPS_TOKEN_DATABASE_AUTOSCORE,
+#endif
+
+ /* File */
+ WPS_TOKEN_FILE_BITRATE,
+ WPS_TOKEN_FILE_CODEC,
+ WPS_TOKEN_FILE_FREQUENCY,
+ WPS_TOKEN_FILE_FREQUENCY_KHZ,
+ 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
+
+#ifdef HAVE_ALBUMART
+ /* Albumart */
+ WPS_TOKEN_ALBUMART_DISPLAY,
+ WPS_TOKEN_ALBUMART_FOUND,
+#endif
+
+ /* Metadata */
+ WPS_TOKEN_METADATA_ARTIST,
+ WPS_TOKEN_METADATA_COMPOSER,
+ WPS_TOKEN_METADATA_ALBUM_ARTIST,
+ WPS_TOKEN_METADATA_GROUPING,
+ WPS_TOKEN_METADATA_ALBUM,
+ WPS_TOKEN_METADATA_GENRE,
+ WPS_TOKEN_METADATA_DISC_NUMBER,
+ 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,
+
+ WPS_TOKEN_MAIN_HOLD,
+
+#ifdef HAS_REMOTE_BUTTON_HOLD
+ WPS_TOKEN_REMOTE_HOLD,
+#endif
+
+ /* Progressbar */
+ WPS_TOKEN_PROGRESSBAR,
+#ifdef HAVE_LCD_CHARCELLS
+ WPS_TOKEN_PLAYER_PROGRESSBAR,
+#endif
+
+#ifdef HAVE_LCD_BITMAP
+ /* Peakmeter */
+ WPS_TOKEN_PEAKMETER,
+#endif
+
+ /* Volume level */
+ WPS_TOKEN_VOLUME,
+
+ /* Current track */
+ WPS_TOKEN_TRACK_ELAPSED_PERCENT,
+ 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,
+
+#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
+ /* Virtual LED */
+ WPS_TOKEN_VLED_HDD,
+#endif
+
+ /* Viewport display */
+ WPS_VIEWPORT_ENABLE,
+
+ /* buttons */
+ WPS_TOKEN_BUTTON_VOLUME,
+ WPS_TOKEN_LASTTOUCH,
+
+ /* Setting option */
+ WPS_TOKEN_SETTING,
+};
+
+struct wps_token {
+ unsigned char type; /* enough to store the token type */
+
+ /* Whether the tag (e.g. track name or the album) refers the
+ current or the next song (false=current, true=next) */
+ bool next;
+
+ union {
+ char c;
+ unsigned short i;
+ } value;
+};
+
+/* Description of a subline on the WPS */
+struct wps_subline {
+
+ /* Index of the first token for this subline in the token array.
+ Tokens of this subline end where tokens for the next subline
+ begin. */
+ unsigned short first_token_idx;
+
+ /* Bit or'ed WPS_REFRESH_xxx */
+ unsigned char line_type;
+
+ /* How long the subline should be displayed, in 10ths of sec */
+ unsigned char time_mult;
+};
+
+/* Description of a line on the WPS. A line is a set of sublines.
+ A subline is displayed for a certain amount of time. After that,
+ the next subline of the line is displayed. And so on. */
+struct wps_line {
+
+ /* Number of sublines in this line */
+ signed char num_sublines;
+
+ /* Number (0-based) of the subline within this line currently being displayed */
+ signed char curr_subline;
+
+ /* Index of the first subline of this line in the subline array.
+ Sublines for this line end where sublines for the next line begin. */
+ unsigned short first_subline_idx;
+
+ /* When the next subline of this line should be displayed
+ (absolute time value in ticks) */
+ long subline_expire_time;
+};
+
+#define VP_DRAW_HIDEABLE 0x1
+#define VP_DRAW_HIDDEN 0x2
+#define VP_DRAW_WASHIDDEN 0x4
+struct wps_viewport {
+ struct viewport vp; /* The LCD viewport struct */
+ struct progressbar *pb;
+ /* Indexes of the first and last lines belonging to this viewport in the
+ lines[] array */
+ int first_line, last_line;
+ char hidden_flags;
+ char label;
+};
+
+#ifdef HAVE_TOUCHSCREEN
+struct touchregion {
+ struct wps_viewport* wvp;/* The viewport this region is in */
+ short int x; /* x-pos */
+ short int y; /* y-pos */
+ short int width; /* width */
+ short int height; /* height */
+ bool repeat; /* requires the area be held for the action */
+ int action; /* action this button will return */
+};
+#define MAX_TOUCHREGIONS 15
+#endif
+/* wps_data
+ this struct holds all necessary data which describes the
+ viewable content of a wps */
+struct wps_data
+{
+#ifdef HAVE_LCD_BITMAP
+ struct gui_img img[MAX_IMAGES];
+ unsigned char img_buf[IMG_BUFSIZE];
+ unsigned char* img_buf_ptr;
+ int img_buf_free;
+ bool wps_sb_tag;
+ bool show_sb_on_wps;
+
+ struct progressbar progressbar[MAX_PROGRESSBARS];
+ short progressbar_count;
+
+ bool peak_meter_enabled;
+
+#ifdef HAVE_ALBUMART
+ /* Album art support */
+ unsigned char wps_uses_albumart; /* WPS_ALBUMART_NONE, _CHECK, _LOAD */
+ short albumart_x;
+ short albumart_y;
+ unsigned char albumart_xalign; /* WPS_ALBUMART_ALIGN_LEFT, _CENTER, _RIGHT */
+ unsigned char albumart_yalign; /* WPS_ALBUMART_ALIGN_TOP, _CENTER, _BOTTOM */
+ short albumart_max_width;
+ short albumart_max_height;
+
+ int albumart_cond_index;
+#endif
+
+#else /*HAVE_LCD_CHARCELLS */
+ unsigned short wps_progress_pat[8];
+ bool full_line_progressbar;
+#endif
+
+#ifdef HAVE_TOUCHSCREEN
+ struct touchregion touchregion[MAX_TOUCHREGIONS];
+ short touchregion_count;
+#endif
+
+#ifdef HAVE_REMOTE_LCD
+ bool remote_wps;
+#endif
+
+ /* Number of lines in the WPS. During WPS parsing, this is
+ the index of the line being parsed. */
+ int num_lines;
+
+ /* Number of viewports in the WPS */
+ int num_viewports;
+ struct wps_viewport viewports[WPS_MAX_VIEWPORTS];
+
+ struct wps_line lines[WPS_MAX_LINES];
+
+ /* Total number of sublines in the WPS. During WPS parsing, this is
+ the index of the subline where the parsed tokens are added to. */
+ int num_sublines;
+ struct wps_subline sublines[WPS_MAX_SUBLINES];
+
+ /* Total number of tokens in the WPS. During WPS parsing, this is
+ the index of the token being parsed. */
+ int num_tokens;
+ struct wps_token tokens[WPS_MAX_TOKENS];
+
+ char string_buffer[STRING_BUFFER_SIZE];
+ char *strings[WPS_MAX_STRINGS];
+ int num_strings;
+
+ bool wps_loaded;
+
+ /* tick the volume button was last pressed */
+ unsigned int button_time_volume;
+};
+
+/* initial setup of wps_data */
+void wps_data_init(struct wps_data *wps_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,
+ struct screen *display,
+ const char *buf,
+ bool isfile);
+
+/* Redraw statusbars if necessary */
+void gwps_draw_statusbars(void);
+
+/* Returns the index of the subline in the subline array
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_subline_index(struct wps_data *wps_data, int line, int subline);
+
+/* Returns the index of the first subline's token in the token array
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_first_token_index(struct wps_data *data, int line, int subline);
+
+/* Returns the index of the last subline's token in the token array.
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_last_token_index(struct wps_data *data, int line, int subline);
+
+/* wps_data end */
+
+/* wps_state
+ holds the data which belongs to the current played track,
+ the track which will be played afterwards, current path to the track
+ and some status infos */
+struct wps_state
+{
+ bool ff_rewind;
+ bool paused;
+ int ff_rewind_count;
+ bool wps_time_countup;
+ struct mp3entry* id3;
+ struct mp3entry* nid3;
+ bool do_full_update;
+};
+
+
+/* change the ff/rew-status
+ if ff_rew = true then we are in skipping mode
+ else we are in normal mode */
+/* void wps_state_update_ff_rew(bool ff_rew); Currently unused */
+
+/* change the tag-information of the current played track
+ and the following track */
+/* void wps_state_update_id3_nid3(struct mp3entry *id3, struct mp3entry *nid3); Currently unused */
+/* wps_state end*/
+
+/* gui_wps
+ defines a wps with its data, state,
+ and the screen on which the wps-content should be drawn */
+struct gui_wps
+{
+ struct screen *display;
+ struct wps_data *data;
+ struct wps_state *state;
+};
+
+/* gui_wps end */
+
+long gui_wps_show(void);
+
+/* currently only on wps_state is needed */
+extern struct wps_state wps_state;
+extern struct gui_wps gui_wps[NB_SCREENS];
+
+void gui_sync_wps_init(void);
+
+#ifdef HAVE_TOUCHSCREEN
+int wps_get_touchaction(struct wps_data *data);
+#endif
+
+#ifdef HAVE_ALBUMART
+/* gives back if WPS contains an albumart tag */
+bool gui_sync_wps_uses_albumart(void);
+#endif
+
+#endif
diff --git a/apps/gui/wps_engine/wps_debug.c b/apps/gui/wps_engine/wps_debug.c
new file mode 100644
index 0000000000..a89f61af9d
--- /dev/null
+++ b/apps/gui/wps_engine/wps_debug.c
@@ -0,0 +1,641 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#if defined(DEBUG) || defined(SIMULATOR)
+
+#include <stdio.h>
+#include <string.h>
+#include "wps_internals.h"
+#ifdef __PCTOOL__
+#ifdef WPSEDITOR
+#include "proxy.h"
+#else
+#define DEBUGF printf
+#endif
+#else
+#include "debug.h"
+#endif
+
+#if defined(SIMULATOR) || defined(__PCTOOL__)
+extern bool debug_wps;
+extern int wps_verbose_level;
+#endif
+
+static char *next_str(bool next) {
+ return next ? "next " : "";
+}
+
+static char *get_token_desc(struct wps_token *token, struct wps_data *data,
+ char *buf, int bufsize)
+{
+ bool next = token->next;
+
+ switch(token->type)
+ {
+ case WPS_NO_TOKEN:
+ snprintf(buf, bufsize, "No token");
+ break;
+
+ case WPS_TOKEN_UNKNOWN:
+ snprintf(buf, bufsize, "Unknown token");
+ break;
+
+ case WPS_TOKEN_CHARACTER:
+ snprintf(buf, bufsize, "Character '%c'",
+ token->value.c);
+ break;
+
+ case WPS_TOKEN_STRING:
+ snprintf(buf, bufsize, "String '%s'",
+ data->strings[token->value.i]);
+ break;
+
+#ifdef HAVE_LCD_BITMAP
+ case WPS_TOKEN_ALIGN_LEFT:
+ snprintf(buf, bufsize, "align left");
+ break;
+
+ case WPS_TOKEN_ALIGN_CENTER:
+ snprintf(buf, bufsize, "align center");
+ break;
+
+ case WPS_TOKEN_ALIGN_RIGHT:
+ snprintf(buf, bufsize, "align right");
+ break;
+#endif
+
+ case WPS_TOKEN_SUBLINE_TIMEOUT:
+ snprintf(buf, bufsize, "subline timeout value: %d",
+ token->value.i);
+ break;
+
+ case WPS_TOKEN_CONDITIONAL:
+ snprintf(buf, bufsize, "conditional, %d options",
+ token->value.i);
+ break;
+
+ case WPS_TOKEN_CONDITIONAL_START:
+ snprintf(buf, bufsize, "conditional start, next cond: %d",
+ token->value.i);
+ break;
+
+ case WPS_TOKEN_CONDITIONAL_OPTION:
+ snprintf(buf, bufsize, "conditional option, next cond: %d",
+ token->value.i);
+ break;
+
+ case WPS_TOKEN_CONDITIONAL_END:
+ snprintf(buf, bufsize, "conditional end");
+ break;
+
+#ifdef HAVE_LCD_BITMAP
+ case WPS_TOKEN_IMAGE_PRELOAD:
+ snprintf(buf, bufsize, "preload image");
+ break;
+
+ case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
+ snprintf(buf, bufsize, "display preloaded image %d",
+ token->value.i);
+ break;
+
+ case WPS_TOKEN_IMAGE_DISPLAY:
+ snprintf(buf, bufsize, "display image");
+ break;
+#endif
+
+#ifdef HAS_BUTTON_HOLD
+ case WPS_TOKEN_MAIN_HOLD:
+ snprintf(buf, bufsize, "mode hold");
+ break;
+#endif
+
+#ifdef HAS_REMOTE_BUTTON_HOLD
+ case WPS_TOKEN_REMOTE_HOLD:
+ snprintf(buf, bufsize, "mode remote hold");
+ break;
+#endif
+
+ case WPS_TOKEN_REPEAT_MODE:
+ snprintf(buf, bufsize, "mode repeat");
+ break;
+
+ case WPS_TOKEN_PLAYBACK_STATUS:
+ snprintf(buf, bufsize, "mode playback");
+ break;
+
+ case WPS_TOKEN_RTC_DAY_OF_MONTH:
+ snprintf(buf, bufsize, "rtc: day of month (01..31)");
+ break;
+ case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
+ snprintf(buf, bufsize,
+ "rtc: day of month, blank padded ( 1..31)");
+ break;
+ case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
+ snprintf(buf, bufsize, "rtc: hour (00..23)");
+ break;
+ case WPS_TOKEN_RTC_HOUR_24:
+ snprintf(buf, bufsize, "rtc: hour ( 0..23)");
+ break;
+ case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
+ snprintf(buf, bufsize, "rtc: hour (01..12)");
+ break;
+ case WPS_TOKEN_RTC_HOUR_12:
+ snprintf(buf, bufsize, "rtc: hour ( 1..12)");
+ break;
+ case WPS_TOKEN_RTC_MONTH:
+ snprintf(buf, bufsize, "rtc: month (01..12)");
+ break;
+ case WPS_TOKEN_RTC_MINUTE:
+ snprintf(buf, bufsize, "rtc: minute (00..59)");
+ break;
+ case WPS_TOKEN_RTC_SECOND:
+ snprintf(buf, bufsize, "rtc: second (00..59)");
+ break;
+ case WPS_TOKEN_RTC_YEAR_2_DIGITS:
+ snprintf(buf, bufsize,
+ "rtc: last two digits of year (00..99)");
+ break;
+ case WPS_TOKEN_RTC_YEAR_4_DIGITS:
+ snprintf(buf, bufsize, "rtc: year (1970...)");
+ break;
+ case WPS_TOKEN_RTC_AM_PM_UPPER:
+ snprintf(buf, bufsize,
+ "rtc: upper case AM or PM indicator");
+ break;
+ case WPS_TOKEN_RTC_AM_PM_LOWER:
+ snprintf(buf, bufsize,
+ "rtc: lower case am or pm indicator");
+ break;
+ case WPS_TOKEN_RTC_WEEKDAY_NAME:
+ snprintf(buf, bufsize,
+ "rtc: abbreviated weekday name (Sun..Sat)");
+ break;
+ case WPS_TOKEN_RTC_MONTH_NAME:
+ snprintf(buf, bufsize,
+ "rtc: abbreviated month name (Jan..Dec)");
+ break;
+ case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
+ snprintf(buf, bufsize,
+ "rtc: day of week (1..7); 1 is Monday");
+ break;
+ case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
+ snprintf(buf, bufsize,
+ "rtc: day of week (0..6); 0 is Sunday");
+ break;
+
+#if (CONFIG_CODEC == SWCODEC)
+ case WPS_TOKEN_CROSSFADE:
+ snprintf(buf, bufsize, "crossfade");
+ break;
+
+ case WPS_TOKEN_REPLAYGAIN:
+ snprintf(buf, bufsize, "replaygain");
+ break;
+#endif
+
+#ifdef HAVE_ALBUMART
+ case WPS_TOKEN_ALBUMART_DISPLAY:
+ snprintf(buf, bufsize, "album art display");
+ break;
+
+ case WPS_TOKEN_ALBUMART_FOUND:
+ snprintf(buf, bufsize, "%strack album art conditional",
+ next_str(next));
+ break;
+#endif
+
+#ifdef HAVE_LCD_BITMAP
+ case WPS_TOKEN_IMAGE_BACKDROP:
+ snprintf(buf, bufsize, "backdrop image");
+ break;
+
+ case WPS_TOKEN_IMAGE_PROGRESS_BAR:
+ snprintf(buf, bufsize, "progressbar bitmap");
+ break;
+
+ case WPS_TOKEN_PEAKMETER:
+ snprintf(buf, bufsize, "peakmeter");
+ break;
+#endif
+
+ case WPS_TOKEN_PROGRESSBAR:
+ snprintf(buf, bufsize, "progressbar");
+ break;
+
+#ifdef HAVE_LCD_CHARCELLS
+ case WPS_TOKEN_PLAYER_PROGRESSBAR:
+ snprintf(buf, bufsize, "full line progressbar");
+ break;
+#endif
+
+ case WPS_TOKEN_TRACK_TIME_ELAPSED:
+ snprintf(buf, bufsize, "time elapsed in track");
+ break;
+
+ case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
+ snprintf(buf, bufsize, "played percentage of track");
+ break;
+
+ case WPS_TOKEN_PLAYLIST_ENTRIES:
+ snprintf(buf, bufsize, "number of entries in playlist");
+ break;
+
+ case WPS_TOKEN_PLAYLIST_NAME:
+ snprintf(buf, bufsize, "playlist name");
+ break;
+
+ case WPS_TOKEN_PLAYLIST_POSITION:
+ snprintf(buf, bufsize, "position in playlist");
+ break;
+
+ case WPS_TOKEN_TRACK_TIME_REMAINING:
+ snprintf(buf, bufsize, "time remaining in track");
+ break;
+
+ case WPS_TOKEN_PLAYLIST_SHUFFLE:
+ snprintf(buf, bufsize, "playlist shuffle mode");
+ break;
+
+ case WPS_TOKEN_TRACK_LENGTH:
+ snprintf(buf, bufsize, "track length");
+ break;
+
+ case WPS_TOKEN_VOLUME:
+ snprintf(buf, bufsize, "volume");
+ break;
+
+ case WPS_TOKEN_METADATA_ARTIST:
+ snprintf(buf, bufsize, "%strack artist",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_COMPOSER:
+ snprintf(buf, bufsize, "%strack composer",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_ALBUM:
+ snprintf(buf, bufsize, "%strack album",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_GROUPING:
+ snprintf(buf, bufsize, "%strack grouping",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_GENRE:
+ snprintf(buf, bufsize, "%strack genre",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_DISC_NUMBER:
+ snprintf(buf, bufsize, "%strack disc", next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_TRACK_NUMBER:
+ snprintf(buf, bufsize, "%strack number",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_TRACK_TITLE:
+ snprintf(buf, bufsize, "%strack title",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_VERSION:
+ snprintf(buf, bufsize, "%strack ID3 version",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_ALBUM_ARTIST:
+ snprintf(buf, bufsize, "%strack album artist",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_COMMENT:
+ snprintf(buf, bufsize, "%strack comment",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_METADATA_YEAR:
+ snprintf(buf, bufsize, "%strack year", next_str(next));
+ break;
+
+#ifdef HAVE_TAGCACHE
+ case WPS_TOKEN_DATABASE_PLAYCOUNT:
+ snprintf(buf, bufsize, "track playcount (database)");
+ break;
+
+ case WPS_TOKEN_DATABASE_RATING:
+ snprintf(buf, bufsize, "track rating (database)");
+ break;
+
+ case WPS_TOKEN_DATABASE_AUTOSCORE:
+ snprintf(buf, bufsize, "track autoscore (database)");
+ break;
+#endif
+
+ case WPS_TOKEN_BATTERY_PERCENT:
+ snprintf(buf, bufsize, "battery percentage");
+ break;
+
+ case WPS_TOKEN_BATTERY_VOLTS:
+ snprintf(buf, bufsize, "battery voltage");
+ break;
+
+ case WPS_TOKEN_BATTERY_TIME:
+ snprintf(buf, bufsize, "battery time left");
+ break;
+
+ case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
+ snprintf(buf, bufsize, "battery charger connected");
+ break;
+
+ case WPS_TOKEN_BATTERY_CHARGING:
+ snprintf(buf, bufsize, "battery charging");
+ break;
+
+ case WPS_TOKEN_BATTERY_SLEEPTIME:
+ snprintf(buf, bufsize, "sleep timer");
+ break;
+
+ case WPS_TOKEN_FILE_BITRATE:
+ snprintf(buf, bufsize, "%sfile bitrate", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_CODEC:
+ snprintf(buf, bufsize, "%sfile codec", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_FREQUENCY:
+ snprintf(buf, bufsize, "%sfile audio frequency in Hz",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_FREQUENCY_KHZ:
+ snprintf(buf, bufsize, "%sfile audio frequency in KHz",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_NAME:
+ snprintf(buf, bufsize, "%sfile name", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
+ snprintf(buf, bufsize, "%sfile name with extension",
+ next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_PATH:
+ snprintf(buf, bufsize, "%sfile path", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_SIZE:
+ snprintf(buf, bufsize, "%sfile size", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_VBR:
+ snprintf(buf, bufsize, "%sfile is vbr", next_str(next));
+ break;
+
+ case WPS_TOKEN_FILE_DIRECTORY:
+ snprintf(buf, bufsize, "%sfile directory, level: %d",
+ next_str(next), token->value.i);
+ break;
+
+#if (CONFIG_CODEC != MAS3507D)
+ case WPS_TOKEN_SOUND_PITCH:
+ snprintf(buf, bufsize, "pitch value");
+ break;
+#endif
+ case WPS_VIEWPORT_ENABLE:
+ snprintf(buf, bufsize, "enable VP:%d",
+ token->value.i);
+ break;
+ case WPS_TOKEN_BUTTON_VOLUME:
+ snprintf(buf, bufsize, "Volume button timeout:%d",
+ token->value.i);
+ break;
+ default:
+ snprintf(buf, bufsize, "FIXME (code: %d)",
+ token->type);
+ break;
+ }
+
+ return buf;
+}
+
+#if defined(SIMULATOR) || defined(__PCTOOL__)
+static void dump_wps_tokens(struct wps_data *data)
+{
+ struct wps_token *token;
+ int i, j;
+ int indent = 0;
+ char buf[64];
+ int num_string_tokens = 0;
+
+ /* Dump parsed WPS */
+ for (i = 0, token = data->tokens; i < data->num_tokens; i++, token++)
+ {
+ get_token_desc(token, data, buf, sizeof(buf));
+
+ switch(token->type)
+ {
+ case WPS_TOKEN_STRING:
+ num_string_tokens++;
+ break;
+
+ case WPS_TOKEN_CONDITIONAL_START:
+ indent++;
+ break;
+
+ case WPS_TOKEN_CONDITIONAL_END:
+ indent--;
+ break;
+
+ default:
+ break;
+ }
+
+ if (wps_verbose_level > 2)
+ {
+ for(j = 0; j < indent; j++) {
+ DEBUGF("\t");
+ }
+
+ DEBUGF("[%3d] = (%2d) %s\n", i, token->type, buf);
+ }
+ }
+
+ if (wps_verbose_level > 0)
+ {
+ DEBUGF("\n");
+ DEBUGF("Number of string tokens: %d\n", num_string_tokens);
+ DEBUGF("\n");
+ }
+}
+
+static void print_line_info(struct wps_data *data)
+{
+ int i, j, v;
+ struct wps_line *line;
+ struct wps_subline *subline;
+
+ if (wps_verbose_level > 0)
+ {
+ DEBUGF("Number of viewports : %d\n", data->num_viewports);
+ for (v = 0; v < data->num_viewports; v++)
+ {
+ DEBUGF("vp %d: First line: %d\n", v, data->viewports[v].first_line);
+ DEBUGF("vp %d: Last line: %d\n", v, data->viewports[v].last_line);
+ }
+ DEBUGF("Number of sublines : %d\n", data->num_sublines);
+ DEBUGF("Number of tokens : %d\n", data->num_tokens);
+ DEBUGF("\n");
+ }
+
+ if (wps_verbose_level > 1)
+ {
+ for (v = 0; v < data->num_viewports; v++)
+ {
+ DEBUGF("Viewport %d - +%d+%d (%dx%d)\n",v,data->viewports[v].vp.x,
+ data->viewports[v].vp.y,
+ data->viewports[v].vp.width,
+ data->viewports[v].vp.height);
+ for (i = data->viewports[v].first_line, line = &data->lines[data->viewports[v].first_line]; i <= data->viewports[v].last_line; i++,line++)
+ {
+ DEBUGF("Line %2d (num_sublines=%d, first_subline=%d)\n",
+ i, line->num_sublines, line->first_subline_idx);
+
+ for (j = 0, subline = data->sublines + line->first_subline_idx;
+ j < line->num_sublines; j++, subline++)
+ {
+ DEBUGF(" Subline %d: first_token=%3d, last_token=%3d",
+ j, subline->first_token_idx,
+ wps_last_token_index(data, i, j));
+
+ if (subline->line_type & WPS_REFRESH_SCROLL)
+ DEBUGF(", scrolled");
+ else if (subline->line_type & WPS_REFRESH_PLAYER_PROGRESS)
+ DEBUGF(", progressbar");
+ else if (subline->line_type & WPS_REFRESH_PEAK_METER)
+ DEBUGF(", peakmeter");
+
+ DEBUGF("\n");
+ }
+ }
+ }
+
+ DEBUGF("\n");
+ }
+}
+
+static void print_wps_strings(struct wps_data *data)
+{
+ int i, len, total_len = 0, buf_used = 0;
+
+ if (wps_verbose_level > 1) DEBUGF("Strings:\n");
+ for (i = 0; i < data->num_strings; i++)
+ {
+ len = strlen(data->strings[i]);
+ total_len += len;
+ buf_used += len + 1;
+ if (wps_verbose_level > 1)
+ DEBUGF("%2d: (%2d) '%s'\n", i, len, data->strings[i]);
+ }
+ if (wps_verbose_level > 1) DEBUGF("\n");
+
+ if (wps_verbose_level > 0)
+ {
+ DEBUGF("Number of unique strings: %d (max: %d)\n",
+ data->num_strings, WPS_MAX_STRINGS);
+ DEBUGF("Total string length: %d\n", total_len);
+ DEBUGF("String buffer used: %d out of %d bytes\n",
+ buf_used, STRING_BUFFER_SIZE);
+ DEBUGF("\n");
+ }
+}
+#endif
+
+void print_debug_info(struct wps_data *data, enum wps_parse_error fail, int line)
+{
+#if defined(SIMULATOR) || defined(__PCTOOL__)
+ if (debug_wps && wps_verbose_level)
+ {
+ dump_wps_tokens(data);
+ print_wps_strings(data);
+ print_line_info(data);
+ }
+#endif /* SIMULATOR */
+
+ if (data->num_tokens >= WPS_MAX_TOKENS - 1) {
+ DEBUGF("Warning: Max number of tokens was reached (%d)\n",
+ WPS_MAX_TOKENS - 1);
+ }
+
+ if (fail != PARSE_OK)
+ {
+ char buf[64];
+
+ DEBUGF("ERR: Failed parsing on line %d : ", line);
+ switch (fail)
+ {
+ case PARSE_OK:
+ break;
+
+ case PARSE_FAIL_UNCLOSED_COND:
+ DEBUGF("ERR: Unclosed conditional");
+ break;
+
+ case PARSE_FAIL_INVALID_CHAR:
+ DEBUGF("ERR: Unexpected conditional char after token %d: \"%s\"",
+ data->num_tokens-1,
+ get_token_desc(&data->tokens[data->num_tokens-1], data,
+ buf, sizeof(buf))
+ );
+ break;
+
+ case PARSE_FAIL_COND_SYNTAX_ERROR:
+ DEBUGF("ERR: Conditional syntax error after token %d: \"%s\"",
+ data->num_tokens-1,
+ get_token_desc(&data->tokens[data->num_tokens-1], data,
+ buf, sizeof(buf))
+ );
+ break;
+
+ case PARSE_FAIL_COND_INVALID_PARAM:
+ DEBUGF("ERR: Invalid parameter list for token %d: \"%s\"",
+ data->num_tokens,
+ get_token_desc(&data->tokens[data->num_tokens], data,
+ buf, sizeof(buf))
+ );
+ break;
+
+ case PARSE_FAIL_LIMITS_EXCEEDED:
+ DEBUGF("ERR: Limits exceeded");
+ break;
+ }
+ DEBUGF("\n");
+ }
+}
+
+#endif /* DEBUG || SIMULATOR */
diff --git a/apps/gui/wps_engine/wps_display.c b/apps/gui/wps_engine/wps_display.c
new file mode 100644
index 0000000000..c74e2cedfa
--- /dev/null
+++ b/apps/gui/wps_engine/wps_display.c
@@ -0,0 +1,1099 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002-2007 Björn Stenberg
+ * Copyright (C) 2007-2008 Nicolas Pennequin
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "font.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "system.h"
+#include "settings.h"
+#include "settings_list.h"
+#include "rbunicode.h"
+#include "rtc.h"
+#include "audio.h"
+#include "status.h"
+#include "power.h"
+#include "powermgmt.h"
+#include "sound.h"
+#include "debug.h"
+#ifdef HAVE_LCD_CHARCELLS
+#include "hwcompat.h"
+#endif
+#include "abrepeat.h"
+#include "mp3_playback.h"
+#include "lang.h"
+#include "misc.h"
+#include "splash.h"
+#include "scrollbar.h"
+#include "led.h"
+#include "lcd.h"
+#ifdef HAVE_LCD_BITMAP
+#include "peakmeter.h"
+/* Image stuff */
+#include "bmp.h"
+#include "albumart.h"
+#endif
+#include "dsp.h"
+#include "action.h"
+#include "cuesheet.h"
+#include "playlist.h"
+#if CONFIG_CODEC == SWCODEC
+#include "playback.h"
+#endif
+#include "backdrop.h"
+#include "viewport.h"
+
+
+#include "wps_internals.h"
+#include "wps_engine.h"
+
+bool gui_wps_display(struct gui_wps *gwps)
+{
+ struct screen *display = gwps->display;
+ struct wps_data *data = gwps->data;
+ int screen = display->screen_type;
+
+ /* Update the values in the first (default) viewport - in case the user
+ has modified the statusbar or colour settings */
+#if LCD_DEPTH > 1
+ if (display->depth > 1)
+ {
+ data->viewports[0].vp.fg_pattern = display->get_foreground();
+ data->viewports[0].vp.bg_pattern = display->get_background();
+ }
+#endif
+ display->clear_display();
+ if (!data->wps_loaded) {
+ if ( !data->num_tokens ) {
+ /* set the default wps for the main-screen */
+ if(screen == SCREEN_MAIN)
+ {
+#if LCD_DEPTH > 1
+ unload_wps_backdrop();
+#endif
+ wps_data_load(data,
+ display,
+#ifdef HAVE_LCD_BITMAP
+ "%s%?it<%?in<%in. |>%it|%fn>\n"
+ "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
+ "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
+ "\n"
+ "%al%pc/%pt%ar[%pp:%pe]\n"
+ "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
+ "%pb\n"
+ "%pm\n", false);
+#else
+ "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
+ "%pc%?ps<*|/>%pt\n", false);
+#endif
+ }
+#ifdef HAVE_REMOTE_LCD
+ /* set the default wps for the remote-screen */
+ else if(screen == SCREEN_REMOTE)
+ {
+#if LCD_REMOTE_DEPTH > 1
+ unload_remote_wps_backdrop();
+#endif
+ wps_data_load(data,
+ display,
+ "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
+ "%s%?it<%?in<%in. |>%it|%fn>\n"
+ "%al%pc/%pt%ar[%pp:%pe]\n"
+ "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
+ "%pb\n", false);
+ }
+#endif
+ }
+ }
+ else
+ {
+#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
+ if (screen == SCREEN_REMOTE)
+ show_remote_wps_backdrop();
+ else if (screen == SCREEN_MAIN)
+#endif
+#if LCD_DEPTH > 1
+ show_wps_backdrop();
+#endif
+ }
+ return gui_wps_redraw(gwps, 0, WPS_REFRESH_ALL);
+}
+
+bool gui_wps_update(struct gui_wps *gwps)
+{
+ struct mp3entry *id3 = gwps->state->id3;
+ bool retval;
+ bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
+ gwps->state->do_full_update = cuesheet_update || gwps->state->do_full_update;
+ retval = gui_wps_redraw(gwps, 0,
+ gwps->state->do_full_update ?
+ WPS_REFRESH_ALL : WPS_REFRESH_NON_STATIC);
+ return retval;
+}
+
+
+#ifdef HAVE_LCD_BITMAP
+
+static void draw_progressbar(struct gui_wps *gwps,
+ struct wps_viewport *wps_vp)
+ {
+ struct screen *display = gwps->display;
+ struct wps_state *state = gwps->state;
+ struct progressbar *pb = wps_vp->pb;
+ int y = pb->y;
+
+ if (y < 0)
+ {
+ int line_height = font_get(wps_vp->vp.font)->height;
+ /* center the pb in the line, but only if the line is higher than the pb */
+ int center = (line_height-pb->height)/2;
+ /* if Y was not set calculate by font height,Y is -line_number-1 */
+ y = (-y -1)*line_height + (0 > center ? 0 : center);
+ }
+
+ if (pb->have_bitmap_pb)
+ gui_bitmap_scrollbar_draw(display, pb->bm,
+ pb->x, y, pb->width, pb->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, pb->x, y, pb->width, pb->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() && state->id3->length != 0 )
+ ab_draw_markers(display, state->id3->length,
+ pb->x, pb->x + pb->width, y, pb->height);
+#endif
+
+ if (state->id3->cuesheet)
+ cue_draw_markers(display, state->id3->cuesheet, state->id3->length,
+ pb->x, pb->x + pb->width, y+1, pb->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].subimage_height);
+ gwps->display->set_drawmode(DRMODE_SOLID);
+}
+
+static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
+{
+ 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_part(data->img[n].bm.data,
+ 0, data->img[n].subimage_height * subimage,
+ data->img[n].bm.width, data->img[n].x,
+ data->img[n].y, data->img[n].bm.width,
+ data->img[n].subimage_height);
+#if LCD_DEPTH > 1
+ } else {
+ display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
+ 0, data->img[n].subimage_height * subimage,
+ data->img[n].bm.width, data->img[n].x,
+ data->img[n].y, data->img[n].bm.width,
+ data->img[n].subimage_height);
+ }
+#endif
+}
+
+static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
+{
+ 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 (data->img[n].display >= 0)
+ {
+ wps_draw_image(gwps, n, data->img[n].display);
+ } else if (data->img[n].always_display && data->img[n].vp == vp)
+ {
+ wps_draw_image(gwps, n, 0);
+ }
+ }
+ }
+ display->set_drawmode(DRMODE_SOLID);
+}
+
+#else /* HAVE_LCD_CHARCELL */
+
+static bool draw_player_progress(struct gui_wps *gwps)
+{
+ struct wps_state *state = gwps->state;
+ struct screen *display = gwps->display;
+ unsigned char progress_pattern[7];
+ int pos = 0;
+ int i;
+
+ if (!state->id3)
+ return false;
+
+ if (state->id3->length)
+ pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
+ / state->id3->length;
+
+ for (i = 0; i < 7; i++, pos -= 5)
+ {
+ if (pos <= 0)
+ progress_pattern[i] = 0x1fu;
+ else if (pos >= 5)
+ progress_pattern[i] = 0x00u;
+ else
+ progress_pattern[i] = 0x1fu >> pos;
+ }
+
+ display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
+ return true;
+}
+
+static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
+{
+ static const unsigned char numbers[10][4] = {
+ {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
+ {0x04, 0x0c, 0x04, 0x04}, /* 1 */
+ {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
+ {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
+ {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
+ {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
+ {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
+ {0x0e, 0x02, 0x04, 0x08}, /* 7 */
+ {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
+ {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
+ };
+
+ struct wps_state *state = gwps->state;
+ struct screen *display = gwps->display;
+ struct wps_data *data = gwps->data;
+ unsigned char progress_pattern[7];
+ char timestr[10];
+ int time;
+ int time_idx = 0;
+ int pos = 0;
+ int pat_idx = 1;
+ int digit, i, j;
+ bool softchar;
+
+ if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
+ return;
+
+ time = state->id3->elapsed + state->ff_rewind_count;
+ if (state->id3->length)
+ pos = 55 * time / state->id3->length;
+
+ memset(timestr, 0, sizeof(timestr));
+ format_time(timestr, sizeof(timestr)-2, time);
+ timestr[strlen(timestr)] = ':'; /* always safe */
+
+ for (i = 0; i < 11; i++, pos -= 5)
+ {
+ softchar = false;
+ memset(progress_pattern, 0, sizeof(progress_pattern));
+
+ if ((digit = timestr[time_idx]))
+ {
+ softchar = true;
+ digit -= '0';
+
+ if (timestr[time_idx + 1] == ':') /* ones, left aligned */
+ {
+ memcpy(progress_pattern, numbers[digit], 4);
+ time_idx += 2;
+ }
+ else /* tens, shifted right */
+ {
+ for (j = 0; j < 4; j++)
+ progress_pattern[j] = numbers[digit][j] >> 1;
+
+ if (time_idx > 0) /* not the first group, add colon in front */
+ {
+ progress_pattern[1] |= 0x10u;
+ progress_pattern[3] |= 0x10u;
+ }
+ time_idx++;
+ }
+
+ if (pos >= 5)
+ progress_pattern[5] = progress_pattern[6] = 0x1fu;
+ }
+
+ if (pos > 0 && pos < 5)
+ {
+ softchar = true;
+ progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
+ }
+
+ if (softchar && pat_idx < 8)
+ {
+ display->define_pattern(data->wps_progress_pat[pat_idx],
+ progress_pattern);
+ buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
+ pat_idx++;
+ }
+ else if (pos <= 0)
+ buf = utf8encode(' ', buf);
+ else
+ buf = utf8encode(0xe115, buf); /* 2/7 _ */
+ }
+ *buf = '\0';
+}
+
+#endif /* HAVE_LCD_CHARCELL */
+
+/* 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 ret = index;
+ while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
+ ret = data->tokens[ret].value.i;
+
+ /* ret now is the index to the end token for the conditional. */
+ return ret;
+}
+
+/* Evaluate the conditional that is at *token_index and return whether a skip
+ has ocurred. *token_index is updated with the new position.
+*/
+static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
+{
+ if (!gwps)
+ return false;
+
+ struct wps_data *data = gwps->data;
+
+ int i, cond_end;
+ int cond_index = *token_index;
+ char result[128];
+ const char *value;
+ unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
+ unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
+
+ /* treat ?xx<true> constructs as if they had 2 options. */
+ if (num_options < 2)
+ num_options = 2;
+
+ int intval = num_options;
+ /* get_token_value needs to know the number of options in the enum */
+ value = get_token_value(gwps, &data->tokens[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 is -1, we check if value is empty. */
+ if (intval == -1)
+ intval = (value && *value) ? 1 : num_options;
+ else if (intval > num_options || intval < 1)
+ intval = num_options;
+
+ data->tokens[cond_index].value.i = (intval << 8) + num_options;
+
+ /* skip to the appropriate enum case */
+ int next = cond_index + 2;
+ for (i = 1; i < intval; i++)
+ {
+ next = data->tokens[next].value.i;
+ }
+ *token_index = next;
+
+ if (prev_val == intval)
+ {
+ /* Same conditional case as previously. Return without clearing the
+ pictures */
+ return false;
+ }
+
+ cond_end = find_conditional_end(data, cond_index + 2);
+ for (i = cond_index + 3; i < cond_end; i++)
+ {
+#ifdef HAVE_LCD_BITMAP
+ /* clear all pictures in the conditional and nested ones */
+ if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
+ clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
+#endif
+#ifdef HAVE_ALBUMART
+ if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
+ draw_album_art(gwps, audio_current_aa_hid(), true);
+#endif
+ }
+
+ return true;
+}
+
+/* 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;
+ int i, last_token_idx;
+ bool update = false;
+
+ /* alignment-related variables */
+ int cur_align;
+ char* cur_align_start;
+ cur_align_start = buf;
+ cur_align = WPS_ALIGN_LEFT;
+ align->left = NULL;
+ align->center = NULL;
+ align->right = NULL;
+
+ /* Process all tokens of the desired subline */
+ last_token_idx = wps_last_token_index(data, line, subline);
+ for (i = wps_first_token_index(data, line, subline);
+ i <= last_token_idx; i++)
+ {
+ switch(data->tokens[i].type)
+ {
+ case WPS_TOKEN_CONDITIONAL:
+ /* place ourselves in the right conditional case */
+ update |= 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 & 0xFF;
+ int subimage = data->tokens[i].value.i >> 8;
+
+ if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
+ img[n].display = subimage;
+ 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;
+ case WPS_VIEWPORT_ENABLE:
+ {
+ char label = data->tokens[i].value.i;
+ int j;
+ char temp = VP_DRAW_HIDEABLE;
+ for(j=0;j<data->num_viewports;j++)
+ {
+ temp = VP_DRAW_HIDEABLE;
+ if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
+ (data->viewports[j].label == label))
+ {
+ if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
+ temp |= VP_DRAW_WASHIDDEN;
+ data->viewports[j].hidden_flags = temp;
+ }
+ }
+ }
+ break;
+ default:
+ {
+ /* get the value of the tag and copy it to the buffer */
+ const char *value = get_token_value(gwps, &data->tokens[i],
+ temp_buf, sizeof(temp_buf), NULL);
+ if (value)
+ {
+ update = true;
+ while (*value && (buf < linebuf_end))
+ *buf++ = *value++;
+ }
+ break;
+ }
+ }
+ }
+
+ /* 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;
+}
+
+static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
+{
+ struct wps_data *data = gwps->data;
+ int i;
+ int subline_idx = wps_subline_index(data, line, subline);
+ int last_token_idx = wps_last_token_index(data, line, subline);
+
+ data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
+
+ for (i = wps_first_token_index(data, line, subline);
+ i <= last_token_idx; i++)
+ {
+ switch(data->tokens[i].type)
+ {
+ case WPS_TOKEN_CONDITIONAL:
+ /* place ourselves in the right conditional case */
+ 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;
+
+ case WPS_TOKEN_SUBLINE_TIMEOUT:
+ data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/* Calculates which subline should be displayed for the specified line
+ Returns true iff the subline must be refreshed */
+static bool update_curr_subline(struct gui_wps *gwps, int line)
+{
+ struct wps_data *data = gwps->data;
+
+ int search, search_start, num_sublines;
+ bool reset_subline;
+ bool new_subline_refresh;
+ bool only_one_subline;
+
+ num_sublines = data->lines[line].num_sublines;
+ reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
+ new_subline_refresh = false;
+ only_one_subline = false;
+
+ /* if time to advance to next sub-line */
+ if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 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->lines[line].curr_subline;
+
+ for (search = 0; search < num_sublines; search++)
+ {
+ data->lines[line].curr_subline++;
+
+ /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
+ if (data->lines[line].curr_subline == num_sublines)
+ {
+ if (data->lines[line].curr_subline == 1)
+ only_one_subline = true;
+ data->lines[line].curr_subline = 0;
+ }
+
+ /* if back where we started after search or
+ only one subline is defined on the line */
+ if (((search > 0) &&
+ (data->lines[line].curr_subline == search_start)) ||
+ only_one_subline)
+ {
+ /* no other subline with a time > 0 exists */
+ data->lines[line].subline_expire_time = (reset_subline ?
+ current_tick :
+ data->lines[line].subline_expire_time) + 100 * HZ;
+ break;
+ }
+ else
+ {
+ /* get initial time multiplier for this subline */
+ get_subline_timeout(gwps, line, data->lines[line].curr_subline);
+
+ int subline_idx = wps_subline_index(data, line,
+ data->lines[line].curr_subline);
+
+ /* only use this subline if subline time > 0 */
+ if (data->sublines[subline_idx].time_mult > 0)
+ {
+ new_subline_refresh = true;
+ data->lines[line].subline_expire_time = (reset_subline ?
+ current_tick : data->lines[line].subline_expire_time) +
+ TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
+ 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 = 0, left_xpos;
+ int center_width = 0, center_xpos;
+ int right_width = 0, right_xpos;
+ int ypos;
+ int space_width;
+ int string_height;
+ int scroll_width;
+
+ /* 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);
+ }
+
+ if (format_align->right != 0) {
+ display->getstringsize((unsigned char *)format_align->right,
+ &right_width, &string_height);
+ }
+
+ if (format_align->center != 0) {
+ display->getstringsize((unsigned char *)format_align->center,
+ &center_width, &string_height);
+ }
+
+ left_xpos = 0;
+ right_xpos = (display->getwidth() - right_width);
+ center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
+
+ scroll_width = display->getwidth() - left_xpos;
+
+ /* 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;
+ /* 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;
+ /* 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->getwidth() - 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->getwidth() - 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;
+ /* 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_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;
+ /* there is no right string anymore */
+ right_width = 0;
+ }
+
+ ypos = (line * string_height);
+
+
+ if (scroll && ((left_width > scroll_width) ||
+ (center_width > scroll_width) ||
+ (right_width > scroll_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(left_xpos, ypos, display->getwidth(), 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);
+ }
+ }
+}
+
+bool gui_wps_redraw(struct gui_wps *gwps,
+ int ffwd_offset,
+ unsigned refresh_mode)
+{
+ struct wps_data *data = gwps->data;
+ struct screen *display = gwps->display;
+ struct wps_state *state = gwps->state;
+
+ if (!data || !state || !display)
+ return false;
+
+ struct mp3entry *id3 = state->id3;
+
+ if (!id3)
+ return false;
+
+ int v, line, i, subline_idx;
+ unsigned 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
+
+ /* 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;
+
+#endif
+
+ /* reset to first subline if refresh all flag is set */
+ if (refresh_mode == WPS_REFRESH_ALL)
+ {
+ display->set_viewport(&data->viewports[0].vp);
+ display->clear_viewport();
+
+ for (i = 0; i <= data->num_lines; i++)
+ {
+ data->lines[i].curr_subline = 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
+
+ state->ff_rewind_count = ffwd_offset;
+
+ /* disable any viewports which are conditionally displayed */
+ for (v = 0; v < data->num_viewports; v++)
+ {
+ if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
+ {
+ if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
+ data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
+ else
+ data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
+ }
+ }
+ for (v = 0; v < data->num_viewports; v++)
+ {
+ struct wps_viewport *wps_vp = &(data->viewports[v]);
+ unsigned vp_refresh_mode = refresh_mode;
+ display->set_viewport(&wps_vp->vp);
+
+#ifdef HAVE_LCD_BITMAP
+ /* Set images to not to be displayed */
+ for (i = 0; i < MAX_IMAGES; i++)
+ {
+ data->img[i].display = -1;
+ }
+#endif
+ /* dont redraw the viewport if its disabled */
+ if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
+ {
+ if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
+ display->scroll_stop(&wps_vp->vp);
+ wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
+ continue;
+ }
+ else if (((wps_vp->hidden_flags&
+ (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
+ == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
+ {
+ vp_refresh_mode = WPS_REFRESH_ALL;
+ wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
+ }
+ if (vp_refresh_mode == WPS_REFRESH_ALL)
+ {
+ display->clear_viewport();
+ }
+
+ for (line = wps_vp->first_line;
+ line <= wps_vp->last_line; line++)
+ {
+ memset(linebuf, 0, sizeof(linebuf));
+ update_line = false;
+
+ /* get current subline for the line */
+ new_subline_refresh = update_curr_subline(gwps, line);
+
+ subline_idx = wps_subline_index(data, line,
+ data->lines[line].curr_subline);
+ flags = data->sublines[subline_idx].line_type;
+
+ if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
+ || new_subline_refresh)
+ {
+ /* get_line tells us if we need to update the line */
+ update_line = get_line(gwps, line, data->lines[line].curr_subline,
+ &align, linebuf, sizeof(linebuf));
+ }
+#ifdef HAVE_LCD_BITMAP
+ /* peakmeter */
+ if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
+ {
+ /* the peakmeter should be alone on its line */
+ update_line = false;
+
+ int h = font_get(wps_vp->vp.font)->height;
+ int peak_meter_y = (line - wps_vp->first_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->getheight()) {
+ /* found a line with a peak meter -> remember that we must
+ enable it later */
+ enable_pm = true;
+ peak_meter_enabled = true;
+ peak_meter_screen(gwps->display, 0, peak_meter_y,
+ MIN(h, display->getheight() - peak_meter_y));
+ }
+ else
+ {
+ peak_meter_enabled = false;
+ }
+ }
+
+#else /* HAVE_LCD_CHARCELL */
+
+ /* progressbar */
+ if (flags & vp_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 &&
+ /* conditionals clear the line which means if the %Vd is put into the default
+ viewport there will be a blank line.
+ To get around this we dont allow any actual drawing to happen in the
+ deault vp if other vp's are defined */
+ ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
+ {
+ if (flags & WPS_REFRESH_SCROLL)
+ {
+ /* if the line is a scrolling one we don't want to update
+ too often, so that it has the time to scroll */
+ if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
+ write_line(display, &align, line - wps_vp->first_line, true);
+ }
+ else
+ write_line(display, &align, line - wps_vp->first_line, false);
+ }
+ }
+
+#ifdef HAVE_LCD_BITMAP
+ /* progressbar */
+ if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
+ {
+ if (wps_vp->pb)
+ {
+ draw_progressbar(gwps, wps_vp);
+ }
+ }
+ /* Now display any images in this viewport */
+ wps_display_images(gwps, &wps_vp->vp);
+#endif
+ }
+
+#ifdef HAVE_LCD_BITMAP
+ data->peak_meter_enabled = enable_pm;
+#endif
+
+ if (refresh_mode & WPS_REFRESH_STATUSBAR)
+ {
+ gwps_draw_statusbars();
+ }
+ /* Restore the default viewport */
+ display->set_viewport(NULL);
+
+ display->update();
+
+ return true;
+}
diff --git a/apps/gui/wps_engine/wps_engine.h b/apps/gui/wps_engine/wps_engine.h
new file mode 100644
index 0000000000..fe034f9138
--- /dev/null
+++ b/apps/gui/wps_engine/wps_engine.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id: gwps.h 22003 2009-07-22 22:10:25Z kugel $
+ *
+ * Copyright (C) 2007 Nicolas Pennequin
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+ /** Use this for stuff which external code needs to include **/
+
+#ifndef _WPS_ENGINE_H
+#define _WPS_ENGINE_H
+#include <stdbool.h>
+#include "wps_internals.h" /* TODO: remove this line.. shoudlnt be needed */
+
+
+#ifdef HAVE_TOUCHSCREEN
+int wps_get_touchaction(struct wps_data *data);
+#endif
+
+#ifdef HAVE_ALBUMART
+/* gives back if WPS contains an albumart tag */
+bool gui_sync_wps_uses_albumart(void);
+#endif
+
+/* setup and display a WPS for the first time */
+bool gui_wps_display(struct gui_wps *gwps);
+/* do a requested redraw */
+bool gui_wps_redraw(struct gui_wps *gwps,
+ int ffwd_offset,
+ unsigned refresh_mode);
+/* do a partial redraw, or full if required, also do any housekeeping
+ * which might be needed */
+bool gui_wps_update(struct gui_wps *gwps);
+
+#endif
diff --git a/apps/gui/wps_engine/wps_internals.h b/apps/gui/wps_engine/wps_internals.h
new file mode 100644
index 0000000000..581763fb9a
--- /dev/null
+++ b/apps/gui/wps_engine/wps_internals.h
@@ -0,0 +1,556 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Nicolas Pennequin
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+ /* This stuff is for the wps engine only.. anyone caught using this outside
+ * of apps/gui/wps_engine will be shot on site! */
+
+#ifndef _WPS_ENGINE_INTERNALS_
+#define _WPS_ENGINE_INTERNALS_
+/* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
+ (possibly with a decimal fraction) but stored as integer values.
+ E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
+*/
+#define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
+#define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
+
+
+
+
+/* TODO: sort this mess out */
+
+#include "screen_access.h"
+#include "statusbar.h"
+#include "metadata.h"
+
+/* constants used in line_type and as refresh_mode for wps_refresh */
+#define WPS_REFRESH_STATIC (1u<<0) /* line doesn't change over time */
+#define WPS_REFRESH_DYNAMIC (1u<<1) /* line may change (e.g. time flag) */
+#define WPS_REFRESH_SCROLL (1u<<2) /* line scrolls */
+#define WPS_REFRESH_PLAYER_PROGRESS (1u<<3) /* line contains a progress bar */
+#define WPS_REFRESH_PEAK_METER (1u<<4) /* line contains a peak meter */
+#define WPS_REFRESH_STATUSBAR (1u<<5) /* refresh statusbar */
+#define WPS_REFRESH_ALL (0xffffffffu) /* to refresh all line types */
+
+/* to refresh only those lines that change over time */
+#define WPS_REFRESH_NON_STATIC (WPS_REFRESH_DYNAMIC| \
+ WPS_REFRESH_PLAYER_PROGRESS| \
+ WPS_REFRESH_PEAK_METER)
+/* alignments */
+#define WPS_ALIGN_RIGHT 32
+#define WPS_ALIGN_CENTER 64
+#define WPS_ALIGN_LEFT 128
+
+#ifdef HAVE_ALBUMART
+
+/* albumart definitions */
+#define WPS_ALBUMART_NONE 0 /* WPS does not contain AA tag */
+#define WPS_ALBUMART_CHECK 1 /* WPS contains AA conditional tag */
+#define WPS_ALBUMART_LOAD 2 /* WPS contains AA tag */
+
+#define WPS_ALBUMART_ALIGN_RIGHT 1 /* x align: right */
+#define WPS_ALBUMART_ALIGN_CENTER 2 /* x/y align: center */
+#define WPS_ALBUMART_ALIGN_LEFT 4 /* x align: left */
+#define WPS_ALBUMART_ALIGN_TOP 1 /* y align: top */
+#define WPS_ALBUMART_ALIGN_BOTTOM 4 /* y align: bottom */
+
+#endif /* HAVE_ALBUMART */
+
+/* wps_data*/
+
+#ifdef HAVE_LCD_BITMAP
+struct gui_img {
+ struct bitmap bm;
+ struct viewport* vp; /* The viewport to display this image in */
+ short int x; /* x-pos */
+ short int y; /* y-pos */
+ short int num_subimages; /* number of sub-images */
+ short int subimage_height; /* height of each sub-image */
+ short int display; /* -1 for no display, 0..n to display a subimage */
+ bool loaded; /* load state */
+ bool always_display; /* not using the preload/display mechanism */
+};
+
+struct progressbar {
+ /* regular pb */
+ short x;
+ /* >=0: explicitly set in the tag -> y-coord within the viewport
+ <0 : not set in the tag -> negated 1-based line number within
+ the viewport. y-coord will be computed based on the font height */
+ short y;
+ short width;
+ short height;
+ /*progressbar image*/
+ struct bitmap bm;
+ bool have_bitmap_pb;
+};
+#endif
+
+
+
+struct align_pos {
+ char* left;
+ char* center;
+ char* right;
+};
+
+#ifdef HAVE_LCD_BITMAP
+
+#define MAX_IMAGES (26*2) /* a-z and A-Z */
+#define MAX_PROGRESSBARS 3
+
+/* The image buffer is big enough to store one full-screen native bitmap,
+ plus two full-screen mono bitmaps. */
+
+#define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \
+ + (2*LCD_HEIGHT*LCD_WIDTH/8))
+
+#define WPS_MAX_VIEWPORTS 24
+#define WPS_MAX_LINES ((LCD_HEIGHT/5+1) * 2)
+#define WPS_MAX_SUBLINES (WPS_MAX_LINES*3)
+#define WPS_MAX_TOKENS 1024
+#define WPS_MAX_STRINGS 128
+#define STRING_BUFFER_SIZE 1024
+#define WPS_MAX_COND_LEVEL 10
+
+#else
+
+#define WPS_MAX_VIEWPORTS 2
+#define WPS_MAX_LINES 2
+#define WPS_MAX_SUBLINES 12
+#define WPS_MAX_TOKENS 64
+#define WPS_MAX_STRINGS 32
+#define STRING_BUFFER_SIZE 64
+#define WPS_MAX_COND_LEVEL 5
+
+#endif
+
+#define SUBLINE_RESET -1
+
+enum wps_parse_error {
+ PARSE_OK,
+ PARSE_FAIL_UNCLOSED_COND,
+ PARSE_FAIL_INVALID_CHAR,
+ PARSE_FAIL_COND_SYNTAX_ERROR,
+ PARSE_FAIL_COND_INVALID_PARAM,
+ PARSE_FAIL_LIMITS_EXCEEDED,
+};
+
+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,
+
+ /* Alignment */
+ WPS_TOKEN_ALIGN_LEFT,
+ WPS_TOKEN_ALIGN_CENTER,
+ WPS_TOKEN_ALIGN_RIGHT,
+
+ /* Sublines */
+ 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,
+
+ /* Sound */
+#if (CONFIG_CODEC != MAS3507D)
+ WPS_TOKEN_SOUND_PITCH,
+#endif
+#if (CONFIG_CODEC == SWCODEC)
+ WPS_TOKEN_REPLAYGAIN,
+ WPS_TOKEN_CROSSFADE,
+#endif
+
+ /* Time */
+
+ WPS_TOKEN_RTC_PRESENT,
+
+ /* The begin/end values allow us to know if a token is an RTC one.
+ New RTC tokens should be added between the markers. */
+
+ WPS_TOKENS_RTC_BEGIN, /* just the start marker, not an actual token */
+
+ WPS_TOKEN_RTC_DAY_OF_MONTH,
+ WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,
+ WPS_TOKEN_RTC_12HOUR_CFG,
+ 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,
+
+ WPS_TOKENS_RTC_END, /* just the end marker, not an actual token */
+
+ /* Conditional */
+ WPS_TOKEN_CONDITIONAL,
+ WPS_TOKEN_CONDITIONAL_START,
+ WPS_TOKEN_CONDITIONAL_OPTION,
+ WPS_TOKEN_CONDITIONAL_END,
+
+ /* Database */
+#ifdef HAVE_TAGCACHE
+ WPS_TOKEN_DATABASE_PLAYCOUNT,
+ WPS_TOKEN_DATABASE_RATING,
+ WPS_TOKEN_DATABASE_AUTOSCORE,
+#endif
+
+ /* File */
+ WPS_TOKEN_FILE_BITRATE,
+ WPS_TOKEN_FILE_CODEC,
+ WPS_TOKEN_FILE_FREQUENCY,
+ WPS_TOKEN_FILE_FREQUENCY_KHZ,
+ 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
+
+#ifdef HAVE_ALBUMART
+ /* Albumart */
+ WPS_TOKEN_ALBUMART_DISPLAY,
+ WPS_TOKEN_ALBUMART_FOUND,
+#endif
+
+ /* Metadata */
+ WPS_TOKEN_METADATA_ARTIST,
+ WPS_TOKEN_METADATA_COMPOSER,
+ WPS_TOKEN_METADATA_ALBUM_ARTIST,
+ WPS_TOKEN_METADATA_GROUPING,
+ WPS_TOKEN_METADATA_ALBUM,
+ WPS_TOKEN_METADATA_GENRE,
+ WPS_TOKEN_METADATA_DISC_NUMBER,
+ 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,
+
+ WPS_TOKEN_MAIN_HOLD,
+
+#ifdef HAS_REMOTE_BUTTON_HOLD
+ WPS_TOKEN_REMOTE_HOLD,
+#endif
+
+ /* Progressbar */
+ WPS_TOKEN_PROGRESSBAR,
+#ifdef HAVE_LCD_CHARCELLS
+ WPS_TOKEN_PLAYER_PROGRESSBAR,
+#endif
+
+#ifdef HAVE_LCD_BITMAP
+ /* Peakmeter */
+ WPS_TOKEN_PEAKMETER,
+#endif
+
+ /* Volume level */
+ WPS_TOKEN_VOLUME,
+
+ /* Current track */
+ WPS_TOKEN_TRACK_ELAPSED_PERCENT,
+ 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,
+
+#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
+ /* Virtual LED */
+ WPS_TOKEN_VLED_HDD,
+#endif
+
+ /* Viewport display */
+ WPS_VIEWPORT_ENABLE,
+
+ /* buttons */
+ WPS_TOKEN_BUTTON_VOLUME,
+ WPS_TOKEN_LASTTOUCH,
+
+ /* Setting option */
+ WPS_TOKEN_SETTING,
+};
+
+struct wps_token {
+ unsigned char type; /* enough to store the token type */
+
+ /* Whether the tag (e.g. track name or the album) refers the
+ current or the next song (false=current, true=next) */
+ bool next;
+
+ union {
+ char c;
+ unsigned short i;
+ } value;
+};
+
+/* Description of a subline on the WPS */
+struct wps_subline {
+
+ /* Index of the first token for this subline in the token array.
+ Tokens of this subline end where tokens for the next subline
+ begin. */
+ unsigned short first_token_idx;
+
+ /* Bit or'ed WPS_REFRESH_xxx */
+ unsigned char line_type;
+
+ /* How long the subline should be displayed, in 10ths of sec */
+ unsigned char time_mult;
+};
+
+/* Description of a line on the WPS. A line is a set of sublines.
+ A subline is displayed for a certain amount of time. After that,
+ the next subline of the line is displayed. And so on. */
+struct wps_line {
+
+ /* Number of sublines in this line */
+ signed char num_sublines;
+
+ /* Number (0-based) of the subline within this line currently being displayed */
+ signed char curr_subline;
+
+ /* Index of the first subline of this line in the subline array.
+ Sublines for this line end where sublines for the next line begin. */
+ unsigned short first_subline_idx;
+
+ /* When the next subline of this line should be displayed
+ (absolute time value in ticks) */
+ long subline_expire_time;
+};
+
+#define VP_DRAW_HIDEABLE 0x1
+#define VP_DRAW_HIDDEN 0x2
+#define VP_DRAW_WASHIDDEN 0x4
+struct wps_viewport {
+ struct viewport vp; /* The LCD viewport struct */
+ struct progressbar *pb;
+ /* Indexes of the first and last lines belonging to this viewport in the
+ lines[] array */
+ int first_line, last_line;
+ char hidden_flags;
+ char label;
+};
+
+#ifdef HAVE_TOUCHSCREEN
+struct touchregion {
+ struct wps_viewport* wvp;/* The viewport this region is in */
+ short int x; /* x-pos */
+ short int y; /* y-pos */
+ short int width; /* width */
+ short int height; /* height */
+ bool repeat; /* requires the area be held for the action */
+ int action; /* action this button will return */
+};
+#define MAX_TOUCHREGIONS 15
+#endif
+/* wps_data
+ this struct holds all necessary data which describes the
+ viewable content of a wps */
+struct wps_data
+{
+#ifdef HAVE_LCD_BITMAP
+ struct gui_img img[MAX_IMAGES];
+ unsigned char img_buf[IMG_BUFSIZE];
+ unsigned char* img_buf_ptr;
+ int img_buf_free;
+ bool wps_sb_tag;
+ bool show_sb_on_wps;
+
+ struct progressbar progressbar[MAX_PROGRESSBARS];
+ short progressbar_count;
+
+ bool peak_meter_enabled;
+
+#ifdef HAVE_ALBUMART
+ /* Album art support */
+ unsigned char wps_uses_albumart; /* WPS_ALBUMART_NONE, _CHECK, _LOAD */
+ short albumart_x;
+ short albumart_y;
+ unsigned char albumart_xalign; /* WPS_ALBUMART_ALIGN_LEFT, _CENTER, _RIGHT */
+ unsigned char albumart_yalign; /* WPS_ALBUMART_ALIGN_TOP, _CENTER, _BOTTOM */
+ short albumart_max_width;
+ short albumart_max_height;
+
+ int albumart_cond_index;
+#endif
+
+#else /*HAVE_LCD_CHARCELLS */
+ unsigned short wps_progress_pat[8];
+ bool full_line_progressbar;
+#endif
+
+#ifdef HAVE_TOUCHSCREEN
+ struct touchregion touchregion[MAX_TOUCHREGIONS];
+ short touchregion_count;
+#endif
+
+#ifdef HAVE_REMOTE_LCD
+ bool remote_wps;
+#endif
+
+ /* Number of lines in the WPS. During WPS parsing, this is
+ the index of the line being parsed. */
+ int num_lines;
+
+ /* Number of viewports in the WPS */
+ int num_viewports;
+ struct wps_viewport viewports[WPS_MAX_VIEWPORTS];
+
+ struct wps_line lines[WPS_MAX_LINES];
+
+ /* Total number of sublines in the WPS. During WPS parsing, this is
+ the index of the subline where the parsed tokens are added to. */
+ int num_sublines;
+ struct wps_subline sublines[WPS_MAX_SUBLINES];
+
+ /* Total number of tokens in the WPS. During WPS parsing, this is
+ the index of the token being parsed. */
+ int num_tokens;
+ struct wps_token tokens[WPS_MAX_TOKENS];
+
+ char string_buffer[STRING_BUFFER_SIZE];
+ char *strings[WPS_MAX_STRINGS];
+ int num_strings;
+
+ bool wps_loaded;
+
+ /* tick the volume button was last pressed */
+ unsigned int button_time_volume;
+};
+
+/* initial setup of wps_data */
+void wps_data_init(struct wps_data *wps_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,
+ struct screen *display,
+ const char *buf,
+ bool isfile);
+
+/* Redraw statusbars if necessary */
+void gwps_draw_statusbars(void);
+
+/* Returns the index of the subline in the subline array
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_subline_index(struct wps_data *wps_data, int line, int subline);
+
+/* Returns the index of the first subline's token in the token array
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_first_token_index(struct wps_data *data, int line, int subline);
+
+/* Returns the index of the last subline's token in the token array.
+ line - 0-based line number
+ subline - 0-based subline number within the line
+ */
+int wps_last_token_index(struct wps_data *data, int line, int subline);
+
+/* wps_data end */
+
+/* wps_state
+ holds the data which belongs to the current played track,
+ the track which will be played afterwards, current path to the track
+ and some status infos */
+struct wps_state
+{
+ bool ff_rewind;
+ bool paused;
+ int ff_rewind_count;
+ bool wps_time_countup;
+ struct mp3entry* id3;
+ struct mp3entry* nid3;
+ bool do_full_update;
+};
+
+
+/* change the ff/rew-status
+ if ff_rew = true then we are in skipping mode
+ else we are in normal mode */
+/* void wps_state_update_ff_rew(bool ff_rew); Currently unused */
+
+/* change the tag-information of the current played track
+ and the following track */
+/* void wps_state_update_id3_nid3(struct mp3entry *id3, struct mp3entry *nid3); Currently unused */
+/* wps_state end*/
+
+/* gui_wps
+ defines a wps with its data, state,
+ and the screen on which the wps-content should be drawn */
+struct gui_wps
+{
+ struct screen *display;
+ struct wps_data *data;
+ struct wps_state *state;
+};
+
+/* gui_wps end */
+
+
+/* currently only on wps_state is needed */
+extern struct wps_state wps_state;
+extern struct gui_wps gui_wps[NB_SCREENS];
+
+/***** wps_tokens.c ******/
+
+const char *get_token_value(struct gui_wps *gwps,
+ struct wps_token *token,
+ char *buf, int buf_size,
+ int *intval);
+
+#endif
diff --git a/apps/gui/wps_engine/wps_parser.c b/apps/gui/wps_engine/wps_parser.c
new file mode 100644
index 0000000000..15acc1401d
--- /dev/null
+++ b/apps/gui/wps_engine/wps_parser.c
@@ -0,0 +1,1843 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "file.h"
+#include "misc.h"
+#include "plugin.h"
+
+#ifdef __PCTOOL__
+#ifdef WPSEDITOR
+#include "proxy.h"
+#include "sysfont.h"
+#else
+#include "checkwps.h"
+#include "audio.h"
+#define DEBUGF printf
+#endif /*WPSEDITOR*/
+#else
+#include "debug.h"
+#endif /*__PCTOOL__*/
+
+#include <ctype.h>
+#include <stdbool.h>
+#include "font.h"
+
+#include "wps_internals.h"
+#include "settings.h"
+#include "settings_list.h"
+
+#ifdef HAVE_LCD_BITMAP
+#include "bmp.h"
+#endif
+
+#include "backdrop.h"
+
+#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
+#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
+
+#define WPS_ERROR_INVALID_PARAM -1
+
+/* level of current conditional.
+ -1 means we're not in a conditional. */
+static int level = -1;
+
+/* index of the last WPS_TOKEN_CONDITIONAL_OPTION
+ or WPS_TOKEN_CONDITIONAL_START in current level */
+static int lastcond[WPS_MAX_COND_LEVEL];
+
+/* index of the WPS_TOKEN_CONDITIONAL in current level */
+static int condindex[WPS_MAX_COND_LEVEL];
+
+/* number of condtional options in current level */
+static int numoptions[WPS_MAX_COND_LEVEL];
+
+/* the current line in the file */
+static int line;
+
+#ifdef HAVE_LCD_BITMAP
+
+#if LCD_DEPTH > 1
+#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */
+#else
+#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */
+#endif
+
+#define PROGRESSBAR_BMP MAX_IMAGES
+#define BACKDROP_BMP (MAX_BITMAPS-1)
+
+/* pointers to the bitmap filenames in the WPS source */
+static const char *bmp_names[MAX_BITMAPS];
+
+#endif /* HAVE_LCD_BITMAP */
+
+#if defined(DEBUG) || defined(SIMULATOR)
+/* debugging function */
+extern void print_debug_info(struct wps_data *data, int fail, int line);
+#endif
+
+static void wps_reset(struct wps_data *data);
+
+/* Function for parsing of details for a token. At the moment the
+ function is called, the token type has already been set. The
+ function must fill in the details and possibly add more tokens
+ to the token array. It should return the number of chars that
+ has been consumed.
+
+ wps_bufptr points to the char following the tag (i.e. where
+ details begin).
+ token is the pointer to the 'main' token being parsed
+ */
+typedef int (*wps_tag_parse_func)(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+
+struct wps_tag {
+ enum wps_token_type type;
+ const char name[3];
+ unsigned char refresh_type;
+ const wps_tag_parse_func parse_func;
+};
+static int skip_end_of_line(const char *wps_bufptr);
+/* prototypes of all special parse functions : */
+static int parse_timeout(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_progressbar(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_dir_level(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_setting(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+
+#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_viewport(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_statusbar_enable(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_statusbar_disable(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_image_display(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_image_load(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+#endif /*HAVE_LCD_BITMAP */
+#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
+static int parse_image_special(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+#endif
+#ifdef HAVE_ALBUMART
+static int parse_albumart_load(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+static int parse_albumart_conditional(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+#endif /* HAVE_ALBUMART */
+#ifdef HAVE_TOUCHSCREEN
+static int parse_touchregion(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data);
+#else
+static int fulline_tag_not_supported(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data)
+{
+ (void)token; (void)wps_data;
+ return skip_end_of_line(wps_bufptr);
+}
+#define parse_touchregion fulline_tag_not_supported
+#endif
+#ifdef CONFIG_RTC
+#define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC
+#else
+#define WPS_RTC_REFRESH WPS_REFRESH_STATIC
+#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[] = {
+
+ { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL },
+ { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL },
+ { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL },
+
+ { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL },
+#if CONFIG_CHARGING >= CHARGING_MONITOR
+ { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+#if CONFIG_CHARGING
+ { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+
+ { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL },
+ { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL },
+
+ /* current file */
+ { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC,
+ parse_dir_level },
+
+ /* next file */
+ { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC,
+ parse_dir_level },
+
+ /* current metadata */
+ { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL },
+
+ /* next metadata */
+ { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL },
+
+#if (CONFIG_CODEC != MAS3507D)
+ { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+
+#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
+ { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+
+ { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL },
+
+#ifdef HAS_REMOTE_BUTTON_HOLD
+ { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL },
+#else
+ { WPS_TOKEN_UNKNOWN, "mr", 0, NULL },
+#endif
+
+ { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC,
+ parse_timeout },
+
+#ifdef HAVE_LCD_BITMAP
+ { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL },
+#else
+ { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf",
+ WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
+#endif
+ { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS,
+ parse_progressbar },
+
+ { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL },
+
+ { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL },
+
+ { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL },
+
+#ifdef HAVE_TAGCACHE
+ { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL },
+ { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+
+#if CONFIG_CODEC == SWCODEC
+ { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL },
+ { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL },
+#endif
+
+ { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL },
+ { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout },
+
+#ifdef HAVE_LCD_BITMAP
+ { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable },
+ { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable },
+
+ { WPS_NO_TOKEN, "xl", 0, parse_image_load },
+
+ { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC,
+ parse_image_display },
+
+ { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load },
+#ifdef HAVE_ALBUMART
+ { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load },
+ { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC,
+ parse_albumart_conditional },
+#endif
+
+ { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC,
+ parse_viewport_display },
+ { WPS_NO_TOKEN, "V", 0, parse_viewport },
+
+#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
+ { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special },
+#endif
+#endif
+
+ { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC, parse_setting },
+
+ { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout },
+ { WPS_NO_TOKEN, "T", 0, parse_touchregion },
+
+ { WPS_TOKEN_UNKNOWN, "", 0, NULL }
+ /* the array MUST end with an empty string (first char is \0) */
+};
+
+/* Returns the number of chars that should be skipped to jump
+ immediately after the first eol, i.e. to the start of the next line */
+static int skip_end_of_line(const char *wps_bufptr)
+{
+ line++;
+ int skip = 0;
+ while(*(wps_bufptr + skip) != '\n')
+ skip++;
+ return ++skip;
+}
+
+/* Starts a new subline in the current line during parsing */
+static void wps_start_new_subline(struct wps_data *data)
+{
+ data->num_sublines++;
+ data->sublines[data->num_sublines].first_token_idx = data->num_tokens;
+ data->lines[data->num_lines].num_sublines++;
+}
+
+#ifdef HAVE_LCD_BITMAP
+
+static int parse_statusbar_enable(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)token; /* Kill warnings */
+ wps_data->wps_sb_tag = true;
+ wps_data->show_sb_on_wps = true;
+ if (wps_data->viewports[0].vp.y == 0)
+ {
+ wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
+ wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
+ }
+ return skip_end_of_line(wps_bufptr);
+}
+
+static int parse_statusbar_disable(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)token; /* Kill warnings */
+ wps_data->wps_sb_tag = true;
+ wps_data->show_sb_on_wps = false;
+ if (wps_data->viewports[0].vp.y == STATUSBAR_HEIGHT)
+ {
+ wps_data->viewports[0].vp.y = 0;
+ wps_data->viewports[0].vp.height += STATUSBAR_HEIGHT;
+ }
+ return skip_end_of_line(wps_bufptr);
+}
+
+static bool load_bitmap(struct wps_data *wps_data,
+ char* filename,
+ struct bitmap *bm)
+{
+ int format;
+#ifdef HAVE_REMOTE_LCD
+ if (wps_data->remote_wps)
+ format = FORMAT_ANY|FORMAT_REMOTE;
+ else
+#endif
+ format = FORMAT_ANY|FORMAT_TRANSPARENT;
+
+ int ret = read_bmp_file(filename, bm,
+ wps_data->img_buf_free,
+ format,NULL);
+
+ 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 - (int)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_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)wps_data;
+ int n = get_image_id(wps_bufptr[0]);
+ int subimage;
+
+ if (n == -1)
+ {
+ /* invalid picture display tag */
+ return WPS_ERROR_INVALID_PARAM;
+ }
+
+ if ((subimage = get_image_id(wps_bufptr[1])) != -1)
+ {
+ /* Sanity check */
+ if (subimage >= wps_data->img[n].num_subimages)
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* Store sub-image number to display in high bits */
+ token->value.i = n | (subimage << 8);
+ return 2; /* We have consumed 2 bytes */
+ } else {
+ token->value.i = n;
+ return 1; /* We have consumed 1 byte */
+ }
+}
+
+static int parse_image_load(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ int n;
+ const char *ptr = wps_bufptr;
+ const char *pos;
+ const char* filename;
+ const char* id;
+ const char *newline;
+ int x,y;
+
+ /* format: %x|n|filename.bmp|x|y|
+ or %xl|n|filename.bmp|x|y|
+ or %xl|n|filename.bmp|x|y|num_subimages|
+ */
+
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+
+ ptr++;
+
+ if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y)))
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* Check there is a terminating | */
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* get the image ID */
+ n = get_image_id(*id);
+
+ /* check the image number and load state */
+ if(n < 0 || n >= MAX_IMAGES || wps_data->img[n].loaded)
+ {
+ /* Invalid image ID */
+ return WPS_ERROR_INVALID_PARAM;
+ }
+
+ /* save a pointer to the filename */
+ bmp_names[n] = filename;
+
+ wps_data->img[n].x = x;
+ wps_data->img[n].y = y;
+
+ /* save current viewport */
+ wps_data->img[n].vp = &wps_data->viewports[wps_data->num_viewports].vp;
+
+ if (token->type == WPS_TOKEN_IMAGE_DISPLAY)
+ {
+ wps_data->img[n].always_display = true;
+ }
+ else
+ {
+ /* Parse the (optional) number of sub-images */
+ ptr++;
+ newline = strchr(ptr, '\n');
+ pos = strchr(ptr, '|');
+ if (pos && pos < newline)
+ wps_data->img[n].num_subimages = atoi(ptr);
+
+ if (wps_data->img[n].num_subimages <= 0)
+ return WPS_ERROR_INVALID_PARAM;
+ }
+
+ /* Skip the rest of the line */
+ return skip_end_of_line(wps_bufptr);
+}
+
+static int parse_viewport_display(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)wps_data;
+ char letter = wps_bufptr[0];
+
+ if (letter < 'a' || letter > 'z')
+ {
+ /* invalid viewport tag */
+ return WPS_ERROR_INVALID_PARAM;
+ }
+ token->value.i = letter;
+ return 1;
+}
+
+static int parse_viewport(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)token; /* Kill warnings */
+ const char *ptr = wps_bufptr;
+ struct viewport* vp;
+ int depth;
+ uint32_t set = 0;
+ enum {
+ PL_X = 0,
+ PL_Y,
+ PL_WIDTH,
+ PL_HEIGHT,
+ PL_FONT,
+ PL_FG,
+ PL_BG,
+ };
+ int lcd_width = LCD_WIDTH, lcd_height = LCD_HEIGHT;
+#ifdef HAVE_REMOTE_LCD
+ if (wps_data->remote_wps)
+ {
+ lcd_width = LCD_REMOTE_WIDTH;
+ lcd_height = LCD_REMOTE_HEIGHT;
+ }
+#endif
+
+ if (wps_data->num_viewports >= WPS_MAX_VIEWPORTS)
+ return WPS_ERROR_INVALID_PARAM;
+
+ wps_data->num_viewports++;
+ /* check for the optional letter to signify its a hideable viewport */
+ /* %Vl|<label>|<rest of tags>| */
+ wps_data->viewports[wps_data->num_viewports].hidden_flags = 0;
+
+ if (*ptr == 'l')
+ {
+ if (*(ptr+1) == '|')
+ {
+ char label = *(ptr+2);
+ if (label >= 'a' && label <= 'z')
+ {
+ wps_data->viewports[wps_data->num_viewports].hidden_flags = VP_DRAW_HIDEABLE;
+ wps_data->viewports[wps_data->num_viewports].label = label;
+ }
+ else
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
+ ptr += 3;
+ }
+ }
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+
+ ptr++;
+ vp = &wps_data->viewports[wps_data->num_viewports].vp;
+ /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */
+
+ /* Set the defaults for fields not user-specified */
+ vp->drawmode = DRMODE_SOLID;
+
+ /* Work out the depth of this display */
+#ifdef HAVE_REMOTE_LCD
+ depth = (wps_data->remote_wps ? LCD_REMOTE_DEPTH : LCD_DEPTH);
+#else
+ depth = LCD_DEPTH;
+#endif
+
+#ifdef HAVE_LCD_COLOR
+ if (depth == 16)
+ {
+ if (!(ptr = parse_list("dddddcc", &set, '|', ptr, &vp->x, &vp->y, &vp->width,
+ &vp->height, &vp->font, &vp->fg_pattern,&vp->bg_pattern)))
+ return WPS_ERROR_INVALID_PARAM;
+ }
+ else
+#endif
+#if (LCD_DEPTH == 2) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 2)
+ if (depth == 2) {
+ /* Default to black on white */
+ vp->fg_pattern = 0;
+ vp->bg_pattern = 3;
+ if (!(ptr = parse_list("dddddgg", &set, '|', ptr, &vp->x, &vp->y, &vp->width,
+ &vp->height, &vp->font, &vp->fg_pattern, &vp->bg_pattern)))
+ return WPS_ERROR_INVALID_PARAM;
+ }
+ else
+#endif
+#if (LCD_DEPTH == 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 1)
+ if (depth == 1)
+ {
+ if (!(ptr = parse_list("ddddd", &set, '|', ptr, &vp->x, &vp->y,
+ &vp->width, &vp->height, &vp->font)))
+ return WPS_ERROR_INVALID_PARAM;
+ }
+ else
+#endif
+ {}
+
+ /* Check for trailing | */
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+
+ if (!LIST_VALUE_PARSED(set, PL_X) || !LIST_VALUE_PARSED(set, PL_Y))
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* fix defaults */
+ if (!LIST_VALUE_PARSED(set, PL_WIDTH))
+ vp->width = lcd_width - vp->x;
+ if (!LIST_VALUE_PARSED(set, PL_HEIGHT))
+ vp->height = lcd_height - vp->y;
+
+ /* Default to using the user font if the font was an invalid number */
+ if (!LIST_VALUE_PARSED(set, PL_FONT) ||
+ ((vp->font != FONT_SYSFIXED) && (vp->font != FONT_UI)))
+ vp->font = FONT_UI;
+
+ /* Validate the viewport dimensions - we know that the numbers are
+ non-negative integers */
+ if ((vp->x >= lcd_width) ||
+ ((vp->x + vp->width) > lcd_width) ||
+ (vp->y >= lcd_height) ||
+ ((vp->y + vp->height) > lcd_height))
+ {
+ return WPS_ERROR_INVALID_PARAM;
+ }
+
+#ifdef HAVE_LCD_COLOR
+ if (depth == 16)
+ {
+ if (!LIST_VALUE_PARSED(set, PL_FG))
+ vp->fg_pattern = global_settings.fg_color;
+ if (!LIST_VALUE_PARSED(set, PL_BG))
+ vp->bg_pattern = global_settings.bg_color;
+ }
+#endif
+
+ wps_data->viewports[wps_data->num_viewports-1].last_line = wps_data->num_lines - 1;
+
+ wps_data->viewports[wps_data->num_viewports].first_line = wps_data->num_lines;
+
+ if (wps_data->num_sublines < WPS_MAX_SUBLINES)
+ {
+ wps_data->lines[wps_data->num_lines].first_subline_idx =
+ wps_data->num_sublines;
+
+ wps_data->sublines[wps_data->num_sublines].first_token_idx =
+ wps_data->num_tokens;
+ }
+
+ /* Skip the rest of the line */
+ return skip_end_of_line(wps_bufptr);
+}
+
+#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
+static int parse_image_special(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)wps_data; /* kill warning */
+ (void)token;
+ const char *pos = NULL;
+ const char *newline;
+
+ pos = strchr(wps_bufptr + 1, '|');
+ newline = strchr(wps_bufptr, '\n');
+
+ if (pos > newline)
+ return WPS_ERROR_INVALID_PARAM;
+#if LCD_DEPTH > 1
+ if (token->type == WPS_TOKEN_IMAGE_BACKDROP)
+ {
+ /* format: %X|filename.bmp| */
+ bmp_names[BACKDROP_BMP] = wps_bufptr + 1;
+ }
+#endif
+
+ /* Skip the rest of the line */
+ return skip_end_of_line(wps_bufptr);
+}
+#endif
+
+#endif /* HAVE_LCD_BITMAP */
+
+static int parse_setting(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)wps_data;
+ const char *ptr = wps_bufptr;
+ const char *end;
+ int i;
+
+ /* Find the setting's cfg_name */
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+ ptr++;
+ end = strchr(ptr,'|');
+ if (!end)
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* Find the setting */
+ for (i=0; i<nb_settings; i++)
+ if (settings[i].cfg_name &&
+ !strncmp(settings[i].cfg_name,ptr,end-ptr) &&
+ /* prevent matches on cfg_name prefixes */
+ strlen(settings[i].cfg_name)==(size_t)(end-ptr))
+ break;
+ if (i == nb_settings)
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* Store the setting number */
+ token->value.i = i;
+
+ /* Skip the rest of the line */
+ return end-ptr+2;
+}
+
+
+static int parse_dir_level(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ char val[] = { *wps_bufptr, '\0' };
+ token->value.i = atoi(val);
+ (void)wps_data; /* Kill warnings */
+ return 1;
+}
+
+static int parse_timeout(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ int skip = 0;
+ int val = 0;
+ bool have_point = false;
+ bool have_tenth = false;
+
+ (void)wps_data; /* Kill the warning */
+
+ while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' )
+ {
+ if (*wps_bufptr != '.')
+ {
+ val *= 10;
+ val += *wps_bufptr - '0';
+ if (have_point)
+ {
+ have_tenth = true;
+ wps_bufptr++;
+ skip++;
+ break;
+ }
+ }
+ else
+ have_point = true;
+
+ wps_bufptr++;
+ skip++;
+ }
+
+ if (have_tenth == false)
+ val *= 10;
+
+ if (val == 0 && skip == 0)
+ {
+ /* decide what to do if no value was specified */
+ switch (token->type)
+ {
+ case WPS_TOKEN_SUBLINE_TIMEOUT:
+ return -1;
+ case WPS_TOKEN_BUTTON_VOLUME:
+ val = 10;
+ break;
+ }
+ }
+ token->value.i = val;
+
+ return skip;
+}
+
+static int parse_progressbar(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ (void)token; /* Kill warnings */
+ /* %pb or %pb|filename|x|y|width|height|
+ using - for any of the params uses "sane" values */
+#ifdef HAVE_LCD_BITMAP
+ enum {
+ PB_FILENAME = 0,
+ PB_X,
+ PB_Y,
+ PB_WIDTH,
+ PB_HEIGHT
+ };
+ const char *filename;
+ int x, y, height, width;
+ uint32_t set = 0;
+ const char *ptr = wps_bufptr;
+ struct progressbar *pb;
+ struct viewport *vp = &wps_data->viewports[wps_data->num_viewports].vp;
+#ifndef __PCTOOL__
+ int font_height = font_get(vp->font)->height;
+#else
+ int font_height = 8;
+#endif
+ int line_num = wps_data->num_lines -
+ wps_data->viewports[wps_data->num_viewports].first_line;
+
+ if (wps_data->progressbar_count >= MAX_PROGRESSBARS)
+ return WPS_ERROR_INVALID_PARAM;
+
+ pb = &wps_data->progressbar[wps_data->progressbar_count];
+ pb->have_bitmap_pb = false;
+
+ if (*wps_bufptr != '|') /* regular old style */
+ {
+ pb->x = 0;
+ pb->width = vp->width;
+ pb->height = SYSFONT_HEIGHT-2;
+ pb->y = -line_num - 1; /* Will be computed during the rendering */
+
+ wps_data->viewports[wps_data->num_viewports].pb = pb;
+ wps_data->progressbar_count++;
+ return 0;
+ }
+ ptr = wps_bufptr + 1;
+
+ if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename,
+ &x, &y, &width, &height)))
+ return WPS_ERROR_INVALID_PARAM;
+
+ if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */
+ bmp_names[PROGRESSBAR_BMP+wps_data->progressbar_count] = filename;
+
+ if (LIST_VALUE_PARSED(set, PB_X)) /* x */
+ pb->x = x;
+ else
+ pb->x = vp->x;
+
+ if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */
+ {
+ /* A zero width causes a divide-by-zero error later, so reject it */
+ if (width == 0)
+ return WPS_ERROR_INVALID_PARAM;
+
+ pb->width = width;
+ }
+ else
+ pb->width = vp->width - pb->x;
+
+ if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */
+ {
+ /* A zero height makes no sense - reject it */
+ if (height == 0)
+ return WPS_ERROR_INVALID_PARAM;
+
+ pb->height = height;
+ }
+ else
+ pb->height = font_height;
+
+ if (LIST_VALUE_PARSED(set, PB_Y)) /* y */
+ pb->y = y;
+ else
+ pb->y = -line_num - 1; /* Will be computed during the rendering */
+
+ wps_data->viewports[wps_data->num_viewports].pb = pb;
+ wps_data->progressbar_count++;
+
+ /* Skip the rest of the line */
+ return skip_end_of_line(wps_bufptr)-1;
+#else
+
+ if (*(wps_bufptr-1) == 'f')
+ wps_data->full_line_progressbar = true;
+ else
+ wps_data->full_line_progressbar = false;
+
+ return 0;
+
+#endif
+}
+
+#ifdef HAVE_ALBUMART
+static int parse_albumart_load(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ const char *_pos, *newline;
+ bool parsing;
+ (void)token; /* silence warning */
+
+ /* reset albumart info in wps */
+ wps_data->albumart_max_width = -1;
+ wps_data->albumart_max_height = -1;
+ wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
+ wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
+
+ /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */
+
+ newline = strchr(wps_bufptr, '\n');
+
+ /* initial validation and parsing of x and y components */
+ if (*wps_bufptr != '|')
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
+
+ _pos = wps_bufptr + 1;
+ if (!isdigit(*_pos))
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|@ */
+ wps_data->albumart_x = atoi(_pos);
+
+ _pos = strchr(_pos, '|');
+ if (!_pos || _pos > newline || !isdigit(*(++_pos)))
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|7\n or %Cl|7|@ */
+
+ wps_data->albumart_y = atoi(_pos);
+
+ _pos = strchr(_pos, '|');
+ if (!_pos || _pos > newline)
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after y coordinate
+ e.g. %Cl|7|59\n */
+
+ /* parsing width field */
+ parsing = true;
+ while (parsing)
+ {
+ /* apply each modifier in turn */
+ ++_pos;
+ switch (*_pos)
+ {
+ case 'l':
+ case 'L':
+ case '+':
+ wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_LEFT;
+ break;
+ case 'c':
+ case 'C':
+ wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER;
+ break;
+ case 'r':
+ case 'R':
+ case '-':
+ wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_RIGHT;
+ break;
+ case 'd':
+ case 'D':
+ case 'i':
+ case 'I':
+ case 's':
+ case 'S':
+ /* simply ignored */
+ break;
+ default:
+ parsing = false;
+ break;
+ }
+ }
+ /* extract max width data */
+ if (*_pos != '|')
+ {
+ if (!isdigit(*_pos)) /* malformed token: e.g. %Cl|7|59|# */
+ return WPS_ERROR_INVALID_PARAM;
+
+ wps_data->albumart_max_width = atoi(_pos);
+
+ _pos = strchr(_pos, '|');
+ if (!_pos || _pos > newline)
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after width field
+ e.g. %Cl|7|59|200\n */
+ }
+
+ /* parsing height field */
+ parsing = true;
+ while (parsing)
+ {
+ /* apply each modifier in turn */
+ ++_pos;
+ switch (*_pos)
+ {
+ case 't':
+ case 'T':
+ case '-':
+ wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_TOP;
+ break;
+ case 'c':
+ case 'C':
+ wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER;
+ break;
+ case 'b':
+ case 'B':
+ case '+':
+ wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_BOTTOM;
+ break;
+ case 'd':
+ case 'D':
+ case 'i':
+ case 'I':
+ case 's':
+ case 'S':
+ /* simply ignored */
+ break;
+ default:
+ parsing = false;
+ break;
+ }
+ }
+ /* extract max height data */
+ if (*_pos != '|')
+ {
+ if (!isdigit(*_pos))
+ return WPS_ERROR_INVALID_PARAM; /* malformed token e.g. %Cl|7|59|200|@ */
+
+ wps_data->albumart_max_height = atoi(_pos);
+
+ _pos = strchr(_pos, '|');
+ if (!_pos || _pos > newline)
+ return WPS_ERROR_INVALID_PARAM; /* malformed token: no closing |
+ e.g. %Cl|7|59|200|200\n */
+ }
+
+ /* if we got here, we parsed everything ok .. ! */
+ if (wps_data->albumart_max_width < 0)
+ wps_data->albumart_max_width = 0;
+ else if (wps_data->albumart_max_width > LCD_WIDTH)
+ wps_data->albumart_max_width = LCD_WIDTH;
+
+ if (wps_data->albumart_max_height < 0)
+ wps_data->albumart_max_height = 0;
+ else if (wps_data->albumart_max_height > LCD_HEIGHT)
+ wps_data->albumart_max_height = LCD_HEIGHT;
+
+ wps_data->wps_uses_albumart = WPS_ALBUMART_LOAD;
+
+ /* Skip the rest of the line */
+ return skip_end_of_line(wps_bufptr);
+}
+
+static int parse_albumart_conditional(const char *wps_bufptr,
+ struct wps_token *token,
+ struct wps_data *wps_data)
+{
+ struct wps_token *prevtoken = token;
+ --prevtoken;
+ if (wps_data->num_tokens >= 1 && prevtoken->type == WPS_TOKEN_CONDITIONAL)
+ {
+ /* This %C is part of a %?C construct.
+ It's either %?C<blah> or %?Cn<blah> */
+ token->type = WPS_TOKEN_ALBUMART_FOUND;
+ if (*wps_bufptr == 'n' && *(wps_bufptr + 1) == '<')
+ {
+ token->next = true;
+ return 1;
+ }
+ else if (*wps_bufptr == '<')
+ {
+ return 0;
+ }
+ else
+ {
+ token->type = WPS_NO_TOKEN;
+ return 0;
+ }
+ }
+ else
+ {
+ /* This %C tag is in a conditional construct. */
+ wps_data->albumart_cond_index = condindex[level];
+ return 0;
+ }
+};
+#endif /* HAVE_ALBUMART */
+
+#ifdef HAVE_TOUCHSCREEN
+
+struct touchaction {char* s; int action;};
+static struct touchaction touchactions[] = {
+ {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
+ {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT },
+ {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK },
+ {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE },
+ {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
+ {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT },
+ {"playlist", ACTION_WPS_VIEW_PLAYLIST },
+};
+static int parse_touchregion(const char *wps_bufptr,
+ struct wps_token *token, struct wps_data *wps_data)
+{
+ (void)token;
+ unsigned i, imax;
+ struct touchregion *region;
+ const char *ptr = wps_bufptr;
+ const char *action;
+ int x,y,w,h;
+
+ /* format: %T|x|y|width|height|action|
+ * if action starts with & the area must be held to happen
+ * action is one of:
+ * play - play/pause playback
+ * stop - stop playback, exit the wps
+ * prev - prev track
+ * next - next track
+ * ffwd - seek forward
+ * rwd - seek backwards
+ * menu - go back to the main menu
+ * browse - go back to the file/db browser
+ * shuffle - toggle shuffle mode
+ * repmode - cycle the repeat mode
+ * quickscreen - go into the quickscreen
+ * contextmenu - open the context menu
+ */
+
+
+ if ((wps_data->touchregion_count +1 >= MAX_TOUCHREGIONS) || (*ptr != '|'))
+ return WPS_ERROR_INVALID_PARAM;
+ ptr++;
+
+ if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action)))
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* Check there is a terminating | */
+ if (*ptr != '|')
+ return WPS_ERROR_INVALID_PARAM;
+
+ /* should probably do some bounds checking here with the viewport... but later */
+ region = &wps_data->touchregion[wps_data->touchregion_count];
+ region->action = ACTION_NONE;
+ region->x = x;
+ region->y = y;
+ region->width = w;
+ region->height = h;
+ region->wvp = &wps_data->viewports[wps_data->num_viewports];
+ i = 0;
+ if (*action == '&')
+ {
+ action++;
+ region->repeat = true;
+ }
+ else
+ region->repeat = false;
+
+ imax = ARRAYLEN(touchactions);
+ while ((region->action == ACTION_NONE) &&
+ (i < imax))
+ {
+ /* try to match with one of our touchregion screens */
+ int len = strlen(touchactions[i].s);
+ if (!strncmp(touchactions[i].s, action, len)
+ && *(action+len) == '|')
+ region->action = touchactions[i].action;
+ i++;
+ }
+ if (region->action == ACTION_NONE)
+ return WPS_ERROR_INVALID_PARAM;
+ wps_data->touchregion_count++;
+ return skip_end_of_line(wps_bufptr);
+}
+#endif
+
+/* Parse a generic token from the given string. Return the length read */
+static int parse_token(const char *wps_bufptr, struct wps_data *wps_data)
+{
+ int skip = 0, taglen = 0, ret;
+ struct wps_token *token = wps_data->tokens + wps_data->num_tokens;
+ const struct wps_tag *tag;
+
+ switch(*wps_bufptr)
+ {
+
+ case '%':
+ case '<':
+ case '|':
+ case '>':
+ case ';':
+ case '#':
+ /* escaped characters */
+ token->type = WPS_TOKEN_CHARACTER;
+ token->value.c = *wps_bufptr;
+ taglen = 1;
+ wps_data->num_tokens++;
+ break;
+
+ case '?':
+ /* conditional tag */
+ token->type = WPS_TOKEN_CONDITIONAL;
+ level++;
+ condindex[level] = wps_data->num_tokens;
+ numoptions[level] = 1;
+ wps_data->num_tokens++;
+ ret = parse_token(wps_bufptr + 1, wps_data);
+ if (ret < 0) return ret;
+ taglen = 1 + ret;
+ break;
+
+ default:
+ /* find what tag we have */
+ for (tag = all_tags;
+ strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0;
+ tag++) ;
+
+ taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2;
+ token->type = tag->type;
+ wps_data->sublines[wps_data->num_sublines].line_type |=
+ tag->refresh_type;
+
+ /* if the tag has a special parsing function, we call it */
+ if (tag->parse_func)
+ {
+ ret = tag->parse_func(wps_bufptr + taglen, token, wps_data);
+ if (ret < 0) return ret;
+ skip += ret;
+ }
+
+ /* Some tags we don't want to save as tokens */
+ if (tag->type == WPS_NO_TOKEN)
+ break;
+
+ /* tags that start with 'F', 'I' or 'D' are for the next file */
+ if ( *(tag->name) == 'I' || *(tag->name) == 'F' ||
+ *(tag->name) == 'D')
+ token->next = true;
+
+ wps_data->num_tokens++;
+ break;
+ }
+
+ skip += taglen;
+ return skip;
+}
+
+/* Parses the WPS.
+ data is the pointer to the structure where the parsed WPS should be stored.
+ It is initialised.
+ wps_bufptr points to the string containing the WPS tags */
+static bool wps_parse(struct wps_data *data, const char *wps_bufptr)
+{
+ if (!data || !wps_bufptr || !*wps_bufptr)
+ return false;
+
+ char *stringbuf = data->string_buffer;
+ int stringbuf_used = 0;
+ enum wps_parse_error fail = PARSE_OK;
+ int ret;
+ line = 1;
+ level = -1;
+
+ while(*wps_bufptr && !fail && data->num_tokens < WPS_MAX_TOKENS - 1
+ && data->num_viewports < WPS_MAX_VIEWPORTS
+ && data->num_lines < WPS_MAX_LINES)
+ {
+ switch(*wps_bufptr++)
+ {
+
+ /* Regular tag */
+ case '%':
+ if ((ret = parse_token(wps_bufptr, data)) < 0)
+ {
+ fail = PARSE_FAIL_COND_INVALID_PARAM;
+ break;
+ }
+ else if (level >= WPS_MAX_COND_LEVEL - 1)
+ {
+ fail = PARSE_FAIL_LIMITS_EXCEEDED;
+ break;
+ }
+ wps_bufptr += ret;
+ break;
+
+ /* Alternating sublines separator */
+ case ';':
+ if (level >= 0) /* there are unclosed conditionals */
+ {
+ fail = PARSE_FAIL_UNCLOSED_COND;
+ break;
+ }
+
+ if (data->num_sublines+1 < WPS_MAX_SUBLINES)
+ wps_start_new_subline(data);
+ else
+ fail = PARSE_FAIL_LIMITS_EXCEEDED;
+
+ break;
+
+ /* Conditional list start */
+ case '<':
+ if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL)
+ {
+ fail = PARSE_FAIL_COND_SYNTAX_ERROR;
+ break;
+ }
+
+ 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, invalid char */
+ {
+ fail = PARSE_FAIL_INVALID_CHAR;
+ break;
+ }
+
+ data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
+ if (lastcond[level])
+ data->tokens[lastcond[level]].value.i = data->num_tokens;
+ else
+ {
+ fail = PARSE_FAIL_COND_SYNTAX_ERROR;
+ break;
+ }
+
+ 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, invalid char */
+ {
+ fail = PARSE_FAIL_INVALID_CHAR;
+ break;
+ }
+
+ data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
+ if (lastcond[level])
+ data->tokens[lastcond[level]].value.i = data->num_tokens;
+ else
+ {
+ fail = PARSE_FAIL_COND_SYNTAX_ERROR;
+ break;
+ }
+
+ lastcond[level] = data->num_tokens;
+ numoptions[level]++;
+ data->num_tokens++;
+ break;
+
+ /* Comment */
+ case '#':
+ if (level >= 0) /* there are unclosed conditionals */
+ {
+ fail = PARSE_FAIL_UNCLOSED_COND;
+ break;
+ }
+
+ wps_bufptr += skip_end_of_line(wps_bufptr);
+ break;
+
+ /* End of this line */
+ case '\n':
+ if (level >= 0) /* there are unclosed conditionals */
+ {
+ fail = PARSE_FAIL_UNCLOSED_COND;
+ break;
+ }
+
+ line++;
+ wps_start_new_subline(data);
+ data->num_lines++; /* Start a new line */
+
+ if ((data->num_lines < WPS_MAX_LINES) &&
+ (data->num_sublines < WPS_MAX_SUBLINES))
+ {
+ data->lines[data->num_lines].first_subline_idx =
+ data->num_sublines;
+
+ data->sublines[data->num_sublines].first_token_idx =
+ data->num_tokens;
+ }
+
+ break;
+
+ /* String */
+ default:
+ {
+ unsigned int len = 1;
+ const char *string_start = wps_bufptr - 1;
+
+ /* find the length of the string */
+ while (*wps_bufptr && *wps_bufptr != '#' &&
+ *wps_bufptr != '%' && *wps_bufptr != ';' &&
+ *wps_bufptr != '<' && *wps_bufptr != '>' &&
+ *wps_bufptr != '|' && *wps_bufptr != '\n')
+ {
+ wps_bufptr++;
+ len++;
+ }
+
+ /* look if we already have that string */
+ char **str;
+ int i;
+ bool found;
+ for (i = 0, str = data->strings, found = false;
+ i < data->num_strings &&
+ !(found = (strlen(*str) == len &&
+ strncmp(string_start, *str, len) == 0));
+ i++, str++);
+ /* If a matching string is found, found is true and i is
+ the index of the string. If not, found is false */
+
+ if (!found)
+ {
+ /* new string */
+
+ if (stringbuf_used + len > STRING_BUFFER_SIZE - 1
+ || data->num_strings >= WPS_MAX_STRINGS)
+ {
+ /* too many strings or characters */
+ fail = PARSE_FAIL_LIMITS_EXCEEDED;
+ break;
+ }
+
+ strlcpy(stringbuf, string_start, len+1);
+
+ data->strings[data->num_strings] = stringbuf;
+ stringbuf += len + 1;
+ stringbuf_used += len + 1;
+ data->tokens[data->num_tokens].value.i =
+ data->num_strings;
+ data->num_strings++;
+ }
+ else
+ {
+ /* another occurrence of an existing string */
+ data->tokens[data->num_tokens].value.i = i;
+ }
+ data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
+ data->num_tokens++;
+ }
+ break;
+ }
+ }
+
+ if (!fail && level >= 0) /* there are unclosed conditionals */
+ fail = PARSE_FAIL_UNCLOSED_COND;
+
+ if (*wps_bufptr && !fail)
+ /* one of the limits of the while loop was exceeded */
+ fail = PARSE_FAIL_LIMITS_EXCEEDED;
+
+ data->viewports[data->num_viewports].last_line = data->num_lines - 1;
+
+ /* We have finished with the last viewport, so increment count */
+ data->num_viewports++;
+
+#if defined(DEBUG) || defined(SIMULATOR)
+ print_debug_info(data, fail, line);
+#endif
+
+ return (fail == 0);
+}
+
+#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 = -1;
+ data->img[i].always_display = false;
+ data->img[i].num_subimages = 1;
+ }
+}
+#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;
+ /* progress bars */
+ wps_data->progressbar_count = 0;
+#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->button_time_volume = 0;
+ wps_data->wps_loaded = false;
+}
+
+static void wps_reset(struct wps_data *data)
+{
+#ifdef HAVE_REMOTE_LCD
+ bool rwps = data->remote_wps; /* remember whether the data is for a RWPS */
+#endif
+ memset(data, 0, sizeof(*data));
+ wps_data_init(data);
+#ifdef HAVE_REMOTE_LCD
+ data->remote_wps = rwps;
+#endif
+}
+
+#ifdef HAVE_LCD_BITMAP
+
+static bool load_wps_bitmaps(struct wps_data *wps_data, char *bmpdir)
+{
+ char img_path[MAX_PATH];
+ struct bitmap *bitmap;
+ bool *loaded;
+ int n;
+ for (n = 0; n < BACKDROP_BMP; n++)
+ {
+ if (bmp_names[n])
+ {
+ get_image_filename(bmp_names[n], bmpdir,
+ img_path, sizeof(img_path));
+
+ if (n >= PROGRESSBAR_BMP ) {
+ /* progressbar bitmap */
+ bitmap = &wps_data->progressbar[n-PROGRESSBAR_BMP].bm;
+ loaded = &wps_data->progressbar[n-PROGRESSBAR_BMP].have_bitmap_pb;
+ } else {
+ /* regular bitmap */
+ bitmap = &wps_data->img[n].bm;
+ loaded = &wps_data->img[n].loaded;
+ }
+
+ /* load the image */
+ bitmap->data = wps_data->img_buf_ptr;
+ if (load_bitmap(wps_data, img_path, bitmap))
+ {
+ *loaded = true;
+
+ /* Calculate and store height if this image has sub-images */
+ if (n < MAX_IMAGES)
+ wps_data->img[n].subimage_height = wps_data->img[n].bm.height /
+ wps_data->img[n].num_subimages;
+ }
+ else
+ {
+ /* Abort if we can't load an image */
+ DEBUGF("ERR: Failed to load image %d - %s\n",n,img_path);
+ return false;
+ }
+ }
+ }
+
+#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
+ if (bmp_names[BACKDROP_BMP])
+ {
+ get_image_filename(bmp_names[BACKDROP_BMP], bmpdir,
+ img_path, sizeof(img_path));
+
+#if defined(HAVE_REMOTE_LCD)
+ /* We only need to check LCD type if there is a remote LCD */
+ if (!wps_data->remote_wps)
+#endif
+ {
+ /* Load backdrop for the main LCD */
+ if (!load_wps_backdrop(img_path))
+ return false;
+ }
+#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
+ else
+ {
+ /* Load backdrop for the remote LCD */
+ if (!load_remote_wps_backdrop(img_path))
+ return false;
+ }
+#endif
+ }
+#endif /* has backdrop support */
+
+ /* If we got here, everything was OK */
+ return true;
+}
+
+#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,
+ struct screen *display,
+ const char *buf,
+ bool isfile)
+{
+#ifdef HAVE_ALBUMART
+ struct mp3entry *curtrack;
+ long offset;
+ int status;
+ int wps_uses_albumart = wps_data->wps_uses_albumart;
+ int albumart_max_height = wps_data->albumart_max_height;
+ int albumart_max_width = wps_data->albumart_max_width;
+#endif
+ if (!wps_data || !buf)
+ return false;
+
+ wps_reset(wps_data);
+
+ /* Initialise the first (default) viewport */
+ wps_data->viewports[0].vp.x = 0;
+ wps_data->viewports[0].vp.width = display->getwidth();
+ wps_data->viewports[0].vp.height = display->getheight();
+ switch (statusbar_position(display->screen_type))
+ {
+ case STATUSBAR_OFF:
+ wps_data->viewports[0].vp.y = 0;
+ break;
+ case STATUSBAR_TOP:
+ wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
+ wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
+ break;
+ case STATUSBAR_BOTTOM:
+ wps_data->viewports[0].vp.y = 0;
+ wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
+ break;
+ }
+#ifdef HAVE_LCD_BITMAP
+ wps_data->viewports[0].vp.font = FONT_UI;
+ wps_data->viewports[0].vp.drawmode = DRMODE_SOLID;
+#endif
+#if LCD_DEPTH > 1
+ if (display->depth > 1)
+ {
+ wps_data->viewports[0].vp.fg_pattern = display->get_foreground();
+ wps_data->viewports[0].vp.bg_pattern = display->get_background();
+ }
+#endif
+ 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.
+ */
+#ifndef __PCTOOL__
+ 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
+#endif /* __PCTOOL__ */
+
+ int fd = open_utf8(buf, O_RDONLY);
+
+ if (fd < 0)
+ return false;
+
+ /* get buffer space from the plugin buffer */
+ size_t 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,
+ ensuring that every line ends with a newline char. */
+ 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
+ /* Set all filename pointers to NULL */
+ memset(bmp_names, 0, sizeof(bmp_names));
+#endif
+
+ /* parse the WPS source */
+ if (!wps_parse(wps_data, wps_buffer)) {
+ wps_reset(wps_data);
+ return false;
+ }
+
+ wps_data->wps_loaded = true;
+
+#ifdef HAVE_LCD_BITMAP
+ /* get the bitmap dir */
+ char bmpdir[MAX_PATH];
+ char *dot = strrchr(buf, '.');
+
+ strlcpy(bmpdir, buf, dot - buf + 1);
+
+ /* load the bitmaps that were found by the parsing */
+ if (!load_wps_bitmaps(wps_data, bmpdir)) {
+ wps_reset(wps_data);
+ return false;
+ }
+#endif
+#ifdef HAVE_ALBUMART
+ status = audio_status();
+ if (((!wps_uses_albumart && wps_data->wps_uses_albumart) ||
+ (wps_data->wps_uses_albumart &&
+ (albumart_max_height != wps_data->albumart_max_height ||
+ albumart_max_width != wps_data->albumart_max_width))) &&
+ status & AUDIO_STATUS_PLAY)
+ {
+ curtrack = audio_current_track();
+ offset = curtrack->offset;
+ audio_stop();
+ if (!(status & AUDIO_STATUS_PAUSE))
+ audio_play(offset);
+ }
+#endif
+ return true;
+ }
+}
+
+int wps_subline_index(struct wps_data *data, int line, int subline)
+{
+ return data->lines[line].first_subline_idx + subline;
+}
+
+int wps_first_token_index(struct wps_data *data, int line, int subline)
+{
+ int first_subline_idx = data->lines[line].first_subline_idx;
+ return data->sublines[first_subline_idx + subline].first_token_idx;
+}
+
+int wps_last_token_index(struct wps_data *data, int line, int subline)
+{
+ int first_subline_idx = data->lines[line].first_subline_idx;
+ int idx = first_subline_idx + subline;
+ if (idx < data->num_sublines - 1)
+ {
+ /* This subline ends where the next begins */
+ return data->sublines[idx+1].first_token_idx - 1;
+ }
+ else
+ {
+ /* The last subline goes to the end */
+ return data->num_tokens - 1;
+ }
+}
diff --git a/apps/gui/wps_engine/wps_tokens.c b/apps/gui/wps_engine/wps_tokens.c
new file mode 100644
index 0000000000..3852251916
--- /dev/null
+++ b/apps/gui/wps_engine/wps_tokens.c
@@ -0,0 +1,807 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002-2007 Björn Stenberg
+ * Copyright (C) 2007-2008 Nicolas Pennequin
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "font.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "action.h"
+#include "system.h"
+#include "settings.h"
+#include "settings_list.h"
+#include "rbunicode.h"
+#include "timefuncs.h"
+#include "audio.h"
+#include "status.h"
+#include "power.h"
+#include "powermgmt.h"
+#include "sound.h"
+#include "debug.h"
+#ifdef HAVE_LCD_CHARCELLS
+#include "hwcompat.h"
+#endif
+#include "abrepeat.h"
+#include "mp3_playback.h"
+#include "lang.h"
+#include "misc.h"
+#include "led.h"
+#ifdef HAVE_LCD_BITMAP
+/* Image stuff */
+#include "albumart.h"
+#endif
+#include "dsp.h"
+#include "playlist.h"
+#if CONFIG_CODEC == SWCODEC
+#include "playback.h"
+#endif
+#include "viewport.h"
+
+#include "wps_internals.h"
+#include "music_screen.h"
+
+static char* get_codectype(const struct mp3entry* id3)
+{
+ if (id3->codectype < AFMT_NUM_CODECS) {
+ return (char*)audio_formats[id3->codectype].label;
+ } else {
+ return NULL;
+ }
+}
+
+/* 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);
+ strlcpy(buf, sep + 1, len + 1);
+ 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 conditionals/enums: when this function is called,
+ intval should contain the number of options in the conditional/enum.
+ When this function returns, intval is -1 if the tag is non numeric or,
+ if the tag is numeric, *intval is the enum case we want to go to (between 1
+ and the original value of *intval, inclusive).
+ When not treating a conditional/enum, intval should be NULL.
+*/
+const char *get_token_value(struct gui_wps *gwps,
+ struct wps_token *token,
+ 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 (token->next)
+ id3 = state->nid3;
+ else
+ id3 = state->id3;
+
+ if (!id3)
+ return NULL;
+
+#if CONFIG_RTC
+ struct tm* tm = NULL;
+
+ /* if the token is an RTC one, update the time
+ and do the necessary checks */
+ if (token->type >= WPS_TOKENS_RTC_BEGIN
+ && token->type <= WPS_TOKENS_RTC_END)
+ {
+ tm = get_time();
+
+ if (!valid_time(tm))
+ return NULL;
+ }
+#endif
+
+ int limit = 1;
+ if (intval)
+ {
+ limit = *intval;
+ *intval = -1;
+ }
+
+ switch (token->type)
+ {
+ case WPS_TOKEN_CHARACTER:
+ return &(token->value.c);
+
+ case WPS_TOKEN_STRING:
+ return data->strings[token->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)
+ {
+ if (global_settings.volume == sound_min(SOUND_VOLUME))
+ {
+ *intval = 1;
+ }
+ else if (global_settings.volume == 0)
+ {
+ *intval = limit - 1;
+ }
+ else if (global_settings.volume > 0)
+ {
+ *intval = limit;
+ }
+ else
+ {
+ *intval = (limit - 3) * (global_settings.volume
+ - sound_min(SOUND_VOLUME) - 1)
+ / (-1 - sound_min(SOUND_VOLUME)) + 2;
+ }
+ }
+ return buf;
+
+ case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
+ if (id3->length <= 0)
+ return NULL;
+
+ if (intval)
+ {
+ *intval = limit * (id3->elapsed + state->ff_rewind_count)
+ / id3->length + 1;
+ }
+ snprintf(buf, buf_size, "%d",
+ 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
+ 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_GROUPING:
+ return id3->grouping;
+
+ case WPS_TOKEN_METADATA_GENRE:
+ return id3->genre_string;
+
+ case WPS_TOKEN_METADATA_DISC_NUMBER:
+ if (id3->disc_string)
+ return id3->disc_string;
+ if (id3->discnum) {
+ snprintf(buf, buf_size, "%d", id3->discnum);
+ return buf;
+ }
+ return NULL;
+
+ 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;
+
+#ifdef HAVE_ALBUMART
+ case WPS_TOKEN_ALBUMART_DISPLAY:
+ draw_album_art(gwps, audio_current_aa_hid(), false);
+ return NULL;
+
+ case WPS_TOKEN_ALBUMART_FOUND:
+ if (audio_current_aa_hid() >= 0) {
+ return "C";
+ }
+ return NULL;
+#endif
+
+ case WPS_TOKEN_FILE_BITRATE:
+ if(id3->bitrate)
+ snprintf(buf, buf_size, "%d", id3->bitrate);
+ else
+ return "?";
+ return buf;
+
+ case WPS_TOKEN_FILE_CODEC:
+ if (intval)
+ {
+ if(id3->codectype == AFMT_UNKNOWN)
+ *intval = AFMT_NUM_CODECS;
+ else
+ *intval = id3->codectype;
+ }
+ return get_codectype(id3);
+
+ case WPS_TOKEN_FILE_FREQUENCY:
+ snprintf(buf, buf_size, "%ld", id3->frequency);
+ return buf;
+
+ case WPS_TOKEN_FILE_FREQUENCY_KHZ:
+ /* ignore remainders < 100, so 22050 Hz becomes just 22k */
+ if ((id3->frequency % 1000) < 100)
+ snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
+ else
+ snprintf(buf, buf_size, "%ld.%d",
+ id3->frequency / 1000,
+ (id3->frequency % 1000) / 100);
+ 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, token->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 / 1000, (v % 1000) / 10);
+ 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
+ return "?h ?m";
+ 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_BATTERY_SLEEPTIME:
+ {
+ if (get_sleep_timer() == 0)
+ return NULL;
+ else
+ {
+ format_time(buf, buf_size, get_sleep_timer() * 1000);
+ return buf;
+ }
+ }
+
+ case WPS_TOKEN_PLAYBACK_STATUS:
+ {
+ int status = audio_status();
+ int mode = 1;
+ if (status == AUDIO_STATUS_PLAY)
+ mode = 2;
+ if (is_wps_fading() ||
+ (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-1);
+ return buf;
+ }
+
+ case WPS_TOKEN_REPEAT_MODE:
+ if (intval)
+ *intval = global_settings.repeat_mode + 1;
+ snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
+ return buf;
+
+ case WPS_TOKEN_RTC_PRESENT:
+#if CONFIG_RTC
+ return "c";
+#else
+ return NULL;
+#endif
+
+#if CONFIG_RTC
+ case WPS_TOKEN_RTC_12HOUR_CFG:
+ if (intval)
+ *intval = global_settings.timeformat + 1;
+ snprintf(buf, buf_size, "%d", global_settings.timeformat);
+ return buf;
+
+ case WPS_TOKEN_RTC_DAY_OF_MONTH:
+ /* d: day of month (01..31) */
+ 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) */
+ snprintf(buf, buf_size, "%2d", tm->tm_mday);
+ return buf;
+
+ case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
+ /* H: hour (00..23) */
+ snprintf(buf, buf_size, "%02d", tm->tm_hour);
+ return buf;
+
+ case WPS_TOKEN_RTC_HOUR_24:
+ /* k: hour ( 0..23) */
+ snprintf(buf, buf_size, "%2d", tm->tm_hour);
+ return buf;
+
+ case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
+ /* I: hour (01..12) */
+ 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) */
+ 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 (intval)
+ *intval = tm->tm_mon + 1;
+ snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
+ return buf;
+
+ case WPS_TOKEN_RTC_MINUTE:
+ /* M: minute (00..59) */
+ snprintf(buf, buf_size, "%02d", tm->tm_min);
+ return buf;
+
+ case WPS_TOKEN_RTC_SECOND:
+ /* S: second (00..59) */
+ 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...) */
+ 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 */
+ return tm->tm_hour/12 == 0 ? "AM" : "PM";
+
+ case WPS_TOKEN_RTC_AM_PM_LOWER:
+ /* P: lower case am or pm indicator */
+ return tm->tm_hour/12 == 0 ? "am" : "pm";
+
+ case WPS_TOKEN_RTC_WEEKDAY_NAME:
+ /* a: abbreviated weekday name (Sun..Sat) */
+ return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
+
+ case WPS_TOKEN_RTC_MONTH_NAME:
+ /* b: abbreviated month name (Jan..Dec) */
+ return str(LANG_MONTH_JANUARY + tm->tm_mon);
+
+ case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
+ /* u: day of week (1..7); 1 is Monday */
+ if (intval)
+ *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
+ 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 (intval)
+ *intval = tm->tm_wday + 1;
+ snprintf(buf, buf_size, "%1d", tm->tm_wday);
+ return buf;
+#else
+ 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_AM_PM_UPPER:
+ case WPS_TOKEN_RTC_AM_PM_LOWER:
+ case WPS_TOKEN_RTC_YEAR_2_DIGITS:
+ return "--";
+ case WPS_TOKEN_RTC_YEAR_4_DIGITS:
+ return "----";
+ case WPS_TOKEN_RTC_WEEKDAY_NAME:
+ case WPS_TOKEN_RTC_MONTH_NAME:
+ return "---";
+ case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
+ case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
+ return "-";
+#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 */
+ strlcpy(buf, " ", buf_size);
+ }
+ else
+ {
+ /* Tell the user if we have an OldPlayer */
+ strlcpy(buf, " <Old LCD> ", buf_size);
+ }
+ return buf;
+#endif
+
+#ifdef HAVE_TAGCACHE
+ 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;
+
+ case WPS_TOKEN_DATABASE_AUTOSCORE:
+ if (intval)
+ *intval = id3->score + 1;
+
+ snprintf(buf, buf_size, "%d", id3->score);
+ return buf;
+#endif
+
+#if (CONFIG_CODEC == SWCODEC)
+ case WPS_TOKEN_CROSSFADE:
+ if (intval)
+ *intval = global_settings.crossfade + 1;
+ snprintf(buf, buf_size, "%d", global_settings.crossfade);
+ return buf;
+
+ case WPS_TOKEN_REPLAYGAIN:
+ {
+ int val;
+
+ if (global_settings.replaygain_type == REPLAYGAIN_OFF)
+ 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:
+ strlcpy(buf, id3->track_gain_string, buf_size);
+ break;
+ case 3:
+ case 5:
+ strlcpy(buf, id3->album_gain_string, buf_size);
+ break;
+ }
+ return buf;
+ }
+#endif /* (CONFIG_CODEC == SWCODEC) */
+
+#if (CONFIG_CODEC != MAS3507D)
+ case WPS_TOKEN_SOUND_PITCH:
+ {
+ int val = sound_get_pitch();
+ snprintf(buf, buf_size, "%d.%d",
+ val / 10, val % 10);
+ return buf;
+ }
+#endif
+
+ case WPS_TOKEN_MAIN_HOLD:
+#ifdef HAS_BUTTON_HOLD
+ if (button_hold())
+#else
+ if (is_keys_locked())
+#endif /*hold switch or softlock*/
+ return "h";
+ else
+ return NULL;
+
+#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
+ case WPS_TOKEN_BUTTON_VOLUME:
+ if (data->button_time_volume &&
+ TIME_BEFORE(current_tick, data->button_time_volume +
+ token->value.i * TIMEOUT_UNIT))
+ return "v";
+ return NULL;
+ case WPS_TOKEN_LASTTOUCH:
+#ifdef HAVE_TOUCHSCREEN
+ if (TIME_BEFORE(current_tick, token->value.i * TIMEOUT_UNIT +
+ touchscreen_last_touch()))
+ return "t";
+#endif
+ return NULL;
+
+ case WPS_TOKEN_SETTING:
+ {
+ if (intval)
+ {
+ /* Handle contionals */
+ const struct settings_list *s = settings+token->value.i;
+ switch (s->flags&F_T_MASK)
+ {
+ case F_T_INT:
+ case F_T_UINT:
+ if (s->flags&F_RGB)
+ /* %?St|name|<#000000|#000001|...|#FFFFFF> */
+ /* shouldn't overflow since colors are stored
+ * on 16 bits ...
+ * but this is pretty useless anyway */
+ *intval = *(int*)s->setting + 1;
+ else if (s->cfg_vals == NULL)
+ /* %?St|name|<1st choice|2nd choice|...> */
+ *intval = (*(int*)s->setting-s->int_setting->min)
+ /s->int_setting->step + 1;
+ else
+ /* %?St|name|<1st choice|2nd choice|...> */
+ /* Not sure about this one. cfg_name/vals are
+ * indexed from 0 right? */
+ *intval = *(int*)s->setting + 1;
+ break;
+ case F_T_BOOL:
+ /* %?St|name|<if true|if false> */
+ *intval = *(bool*)s->setting?1:2;
+ break;
+ case F_T_CHARPTR:
+ /* %?St|name|<if non empty string|if empty>
+ * The string's emptyness discards the setting's
+ * prefix and suffix */
+ *intval = ((char*)s->setting)[0]?1:2;
+ break;
+ default:
+ /* This shouldn't happen ... but you never know */
+ *intval = -1;
+ break;
+ }
+ }
+ cfg_to_string(token->value.i,buf,buf_size);
+ return buf;
+ }
+
+ default:
+ return NULL;
+ }
+}