From 3e7444ff8770678b563af6b3f9f6b05521cac959 Mon Sep 17 00:00:00 2001 From: Jonathan Gordon Date: Mon, 3 Aug 2009 04:43:34 +0000 Subject: part two of the grand overall wps/skinning engine cleanup work: * rename wps_engine to skin_engine as that was agreed on * rename music_screen back to wps * clean up the skin display/update functions a bit * make skin_data_load setup the hardcoded default if a skin cant be loaded for whatever reason instead of doing it when it is first displayed ignore any gui_wps or wps_ or gwps_ nameing in skin_engine/ ... these will be renamed as this work gets finished git-svn-id: svn://svn.rockbox.org/rockbox/trunk@22135 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 10 +- apps/filetree.c | 6 +- apps/gui/skin_engine/wps_debug.c | 641 ++++++++++++ apps/gui/skin_engine/wps_display.c | 1058 +++++++++++++++++++ apps/gui/skin_engine/wps_internals.h | 550 ++++++++++ apps/gui/skin_engine/wps_parser.c | 1892 ++++++++++++++++++++++++++++++++++ apps/gui/skin_engine/wps_tokens.c | 807 +++++++++++++++ apps/gui/wps_engine/wps_debug.c | 641 ------------ apps/gui/wps_engine/wps_display.c | 1099 -------------------- apps/gui/wps_engine/wps_engine.h | 49 - apps/gui/wps_engine/wps_internals.h | 561 ---------- apps/gui/wps_engine/wps_parser.c | 1858 --------------------------------- apps/gui/wps_engine/wps_tokens.c | 807 --------------- apps/iap.c | 2 +- apps/main.c | 2 +- apps/menus/main_menu.c | 2 +- apps/misc.c | 2 +- apps/recorder/albumart.h | 2 +- apps/root_menu.c | 2 +- apps/settings.c | 8 +- apps/tree.c | 2 +- 21 files changed, 4968 insertions(+), 5033 deletions(-) create mode 100644 apps/gui/skin_engine/wps_debug.c create mode 100644 apps/gui/skin_engine/wps_display.c create mode 100644 apps/gui/skin_engine/wps_internals.h create mode 100644 apps/gui/skin_engine/wps_parser.c create mode 100644 apps/gui/skin_engine/wps_tokens.c delete mode 100644 apps/gui/wps_engine/wps_debug.c delete mode 100644 apps/gui/wps_engine/wps_display.c delete mode 100644 apps/gui/wps_engine/wps_engine.h delete mode 100644 apps/gui/wps_engine/wps_internals.h delete mode 100644 apps/gui/wps_engine/wps_parser.c delete mode 100644 apps/gui/wps_engine/wps_tokens.c diff --git a/apps/SOURCES b/apps/SOURCES index e1d85a75f0..12ac54f38d 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -77,17 +77,17 @@ gui/pitchscreen.c gui/quickscreen.c #endif -gui/music_screen.c +gui/wps.c gui/scrollbar.c gui/splash.c gui/statusbar.c gui/yesno.c gui/viewport.c -gui/wps_engine/wps_debug.c -gui/wps_engine/wps_display.c -gui/wps_engine/wps_parser.c -gui/wps_engine/wps_tokens.c +gui/skin_engine/wps_debug.c +gui/skin_engine/wps_display.c +gui/skin_engine/wps_parser.c +gui/skin_engine/wps_tokens.c #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) gui/backdrop.c diff --git a/apps/filetree.c b/apps/filetree.c index 458d553e27..8e97a0d13c 100644 --- a/apps/filetree.c +++ b/apps/filetree.c @@ -51,7 +51,7 @@ #if CONFIG_TUNER #include "radio.h" #endif -#include "wps_engine/wps_internals.h" /* FIXME: REMOVE ME */ +#include "skin_engine/skin_engine.h" #include "backdrop.h" static int compare_sort_dir; /* qsort key for sorting directories */ @@ -484,7 +484,7 @@ int ft_enter(struct tree_context* c) #if LCD_DEPTH > 1 unload_wps_backdrop(); #endif - wps_data_load(gui_wps[0].data, &screens[0], buf, true); + skin_data_load(gui_wps[0].data, &screens[0], buf, true); set_file(buf, (char *)global_settings.wps_file, MAX_FILENAME); break; @@ -496,7 +496,7 @@ int ft_enter(struct tree_context* c) #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1 unload_remote_wps_backdrop(); #endif - wps_data_load(gui_wps[1].data, &screens[1], buf, true); + skin_data_load(gui_wps[1].data, &screens[1], buf, true); set_file(buf, (char *)global_settings.rwps_file, MAX_FILENAME); break; diff --git a/apps/gui/skin_engine/wps_debug.c b/apps/gui/skin_engine/wps_debug.c new file mode 100644 index 0000000000..a89f61af9d --- /dev/null +++ b/apps/gui/skin_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 +#include +#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/skin_engine/wps_display.c b/apps/gui/skin_engine/wps_display.c new file mode 100644 index 0000000000..98050093c4 --- /dev/null +++ b/apps/gui/skin_engine/wps_display.c @@ -0,0 +1,1058 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include +#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 "skin_engine.h" + +static bool gui_wps_redraw(struct gui_wps *gwps, unsigned refresh_mode); + + +bool gui_wps_display(struct gui_wps *gwps) +{ + struct screen *display = gwps->display; + + /* 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) + { + gwps->data->viewports[0].vp.fg_pattern = display->get_foreground(); + gwps->data->viewports[0].vp.bg_pattern = display->get_background(); + } +#endif + display->clear_display(); +#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1 + if (display->screen_type == SCREEN_REMOTE) + show_remote_wps_backdrop(); + else if (display->screen_type == SCREEN_MAIN) +#endif +#if LCD_DEPTH > 1 + show_wps_backdrop(); +#endif + return gui_wps_redraw(gwps, WPS_REFRESH_ALL); +} + +/* update a skinned screen, update_type is WPS_REFRESH_* values. + * Usually it should only be WPS_REFRESH_NON_STATIC + * A full update will be done if required (state.do_full_update == true) + */ +bool skin_update(struct gui_wps *gwps, unsigned int update_type) +{ + bool retval; + /* This maybe shouldnt be here, but while the skin is only used to + * display the music screen this is better than whereever we are being + * called from. This is also safe for skined screen which dont use the id3 */ + struct mp3entry *id3 = gwps->state->id3; + 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, gwps->state->do_full_update ? + WPS_REFRESH_ALL : update_type); + 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 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;jnum_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, + ¢er_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); + } + } +} + +static bool gui_wps_redraw(struct gui_wps *gwps, 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 + + /* 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/skin_engine/wps_internals.h b/apps/gui/skin_engine/wps_internals.h new file mode 100644 index 0000000000..2dcaa504bb --- /dev/null +++ b/apps/gui/skin_engine/wps_internals.h @@ -0,0 +1,550 @@ +/*************************************************************************** + * __________ __ ___. + * 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); + + +/* 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/skin_engine/wps_parser.c b/apps/gui/skin_engine/wps_parser.c new file mode 100644 index 0000000000..a3e5f6861d --- /dev/null +++ b/apps/gui/skin_engine/wps_parser.c @@ -0,0 +1,1892 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include +#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 +#include +#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|