Index: apps/lang/english.lang
===================================================================
--- apps/lang/english.lang	(rÃ©vision 13003)
+++ apps/lang/english.lang	(copie de travail)
@@ -7286,13 +7286,13 @@
 </phrase>
 <phrase>
   id: LANG_END_PLAYLIST_PLAYER
-  desc: when playlist has finished
+  desc: DEPRECATED
   user:
   <source>
-    *: "End of List"
+    *: ""
   </source>
   <dest>
-    *: "End of List"
+    *: deprecated
   </dest>
   <voice>
     *: ""
@@ -7304,9 +7304,11 @@
   user:
   <source>
     *: "End of Song List"
+    player: "End of List"
   </source>
   <dest>
     *: "End of Song List"
+    player: "End of List"
   </dest>
   <voice>
     *: ""
Index: apps/gui/gwps-common.c
===================================================================
--- apps/gui/gwps-common.c	(rÃ©vision 13003)
+++ apps/gui/gwps-common.c	(copie de travail)
@@ -7,7 +7,7 @@
  *                     \/            \/     \/    \/            \/
  * $Id$
  *
- * Copyright (C) 2002 Björn Stenberg
+ * Copyright (C) 2007 Nicolas Pennequin
  *
  * All files in this archive are subject to the GNU General Public License.
  * See the file COPYING in the source tree root for full license agreement.
@@ -56,16 +56,11 @@
 #include "action.h"
 #include "cuesheet.h"
 
-#ifdef HAVE_LCD_CHARCELLS
-static bool draw_player_progress(struct gui_wps *gwps);
-static void draw_player_fullbar(struct gui_wps *gwps,
-                                char* buf, int buf_size);
-#endif
-
 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ 
                                 /* 3% of 30min file == 54s step size */
 #define MIN_FF_REWIND_STEP 500
 
+#if 0
 /* Skip leading UTF-8 BOM, if present. */
 static char* skip_utf8_bom(char* buf)
 {
@@ -78,287 +73,14 @@
     
     return buf;
 }
-
-/*
- * returns the image_id between
- * a..z and A..Z
- */
-#ifdef HAVE_LCD_BITMAP
-static int get_image_id(int c)
-{
-    if(c >= 'a' && c <= 'z')
-        return c - 'a';
-    else if(c >= 'A' && c <= 'Z')
-        return c - 'A' + 26;
-    else
-        return -1;
-}
 #endif
-    
-/*
- * parse the given buffer for following static tags:
- * %x    -   load image for always display
- * %X    -   load backdrop image
- * %xl   -   preload image
- * %we   -   enable statusbar on wps regardless of the global setting
- * %wd   -   disable statusbar on wps regardless of the global setting
- * and also for:
- * #     -   a comment line
- *
- * it returns true if one of these tags is found and handled
- * false otherwise
- */
-bool wps_data_preload_tags(struct wps_data *data, char *buf,
-                            const char *bmpdir, size_t bmpdirlen)
-{
-    if(!data || !buf) return false;
-    
-    char c;
-#ifndef HAVE_LCD_BITMAP
-    /* no bitmap-lcd == no bitmap loading */
-    (void)bmpdir;
-    (void)bmpdirlen;
-#endif
-    buf = skip_utf8_bom(buf);
-    
-    if(*buf == '#')
-        return true;
-    if('%' != *buf)
-        return false;
-    buf++;
-    
-    c  = *buf;
-    switch (c)
-    {
-#ifdef HAVE_LCD_BITMAP
-        case 'w':
-            /* 
-             * if tag found then return because these two tags must be on 
-             * must be on their own line
-             */
-            if(*(buf+1) == 'd' || *(buf+1) == 'e')
-            {
-                data->wps_sb_tag = true;
-                if( *(buf+1) == 'e' )
-                    data->show_sb_on_wps = true;
-                return true;
-            }
-        break;
 
-#if LCD_DEPTH > 1
-        case 'X':
-            /* Backdrop image - must be the same size as the LCD */
-        {
-            char *ptr = buf+2;
-            char *pos = NULL;
-            char imgname[MAX_PATH];
-
-            /* format: %X|filename.bmp| */
-
-            /* get filename */
-            pos = strchr(ptr, '|');
-            if ((pos - ptr) <
-                (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
-            {
-                memcpy(imgname, bmpdir, bmpdirlen);
-                imgname[bmpdirlen] = '/';
-                memcpy(&imgname[bmpdirlen+1],
-                        ptr, pos - ptr);
-                imgname[bmpdirlen+1+pos-ptr] = 0;
-            }
-            else
-            {
-                /* filename too long */
-                imgname[0] = 0;
-            }
-
-            /* load the image */
-            return load_wps_backdrop(imgname);
-        }
-
-        break;
-#endif
-
-        case 'P':
-            /* progress bar image */
-        {
-            int ret = 0;
-            char *ptr = buf+2;
-            char *pos = NULL;
-            char imgname[MAX_PATH];
-
-            /* format: %P|filename.bmp| */
-            {
-                /* get filename */
-                pos = strchr(ptr, '|');
-                if ((pos - ptr) <
-                    (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
-                {
-                    memcpy(imgname, bmpdir, bmpdirlen);
-                    imgname[bmpdirlen] = '/';
-                    memcpy(&imgname[bmpdirlen+1],
-                           ptr, pos - ptr);
-                    imgname[bmpdirlen+1+pos-ptr] = 0;
-                }
-                else
-                    /* filename too long */
-                    imgname[0] = 0;
-
-                ptr = pos+1;
-
-                /* load the image */
-                data->progressbar.bm.data=data->img_buf_ptr;
-                ret = read_bmp_file(imgname, &data->progressbar.bm,
-                                    data->img_buf_free,
-                                    FORMAT_ANY|FORMAT_TRANSPARENT);
-                        
-                if (ret > 0)
-                {
-#if LCD_DEPTH == 16
-                     if (ret % 2) ret++; 
-                     /* Always consume an even number of bytes */
-#endif
-
-                     data->img_buf_ptr += ret;
-                     data->img_buf_free -= ret;
-
-                     if (data->progressbar.bm.width <= LCD_WIDTH) {
-                          data->progressbar.have_bitmap_pb=true;
-                          return true;
-                     } else
-                          return false;
-                }
-                
-            }
-        }
-
-        break;
-        
-        case 'x':
-            /* Preload images so the %xd# tag can display it */
-        {
-            int ret = 0;
-            int n;
-            char *ptr = buf+1;
-            char *pos = NULL;
-            char imgname[MAX_PATH];
-            char qual = *ptr;
-
-            if (qual == 'l' || qual == '|')  /* format:
-                                                %x|n|filename.bmp|x|y|
-                                                or
-                                                %xl|n|filename.bmp|x|y|
-                                             */
-            {
-                ptr = strchr(ptr, '|') + 1;
-                pos = strchr(ptr, '|');
-                if (pos)
-                {
-                    /* get the image ID */
-                    n = get_image_id(*ptr);
-                    
-                    if(n < 0 || n >= MAX_IMAGES)
-                    {
-                        /* Skip the rest of the line */
-                        while(*buf != '\n')
-                            buf++;
-                        return false;
-                    }
-                    ptr = pos+1;
-
-                    /* check the image number and load state */
-                    if (data->img[n].loaded)
-                    {
-                        /* Skip the rest of the line */
-                        while(*buf != '\n')
-                            buf++;
-                        return false;
-                    }
-                    else
-                    {
-                        /* get filename */
-                        pos = strchr(ptr, '|');
-
-                        if (pos == NULL)
-                            return false;
-
-                        if ((pos - ptr) <
-                            (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
-                        {
-                            memcpy(imgname, bmpdir, bmpdirlen);
-                            imgname[bmpdirlen] = '/';
-                            memcpy(&imgname[bmpdirlen+1],
-                                   ptr, pos - ptr);
-                            imgname[bmpdirlen+1+pos-ptr] = 0;
-                        }
-                        else
-                            /* filename too long */
-                            imgname[0] = 0;
-
-                        ptr = pos+1;
-
-                        /* get x-position */
-                        pos = strchr(ptr, '|');
-                        if (pos)
-                            data->img[n].x = atoi(ptr);
-                        else
-                        {
-                            /* weird syntax, bail out */
-                            buf++;
-                            return false;
-                        }
-
-                        /* get y-position */
-                        ptr = pos+1;
-                        pos = strchr(ptr, '|');
-                        if (pos)
-                            data->img[n].y = atoi(ptr);
-                        else
-                        {
-                            /* weird syntax, bail out */
-                            buf++;
-                            return false;
-                        }
-
-                        /* load the image */
-                        data->img[n].bm.data = data->img_buf_ptr;
-                        ret = read_bmp_file(imgname, &data->img[n].bm,
-                                            data->img_buf_free,
-                                            FORMAT_ANY|FORMAT_TRANSPARENT);
-                        
-                        if (ret > 0)
-                        {
-#if LCD_DEPTH == 16
-                            if (ret % 2) ret++; 
-                            /* Always consume an even number of bytes */
-#endif
-
-                            data->img_buf_ptr += ret;
-                            data->img_buf_free -= ret;
-                            data->img[n].loaded = true;
-                            if(qual == '|')
-                                data->img[n].always_display = true;
-                        }
-                        return true;
-                    }
-                }
-            }
-        }
-
-        break;
-#endif
-    }
-    /* no of these tags found */
-    return false;
-}
-
-
 /* draws the statusbar on the given wps-screen */
 #ifdef HAVE_LCD_BITMAP
-static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
+void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
 {
     bool draw = global_settings.statusbar;
-    
+
     if(wps->data->wps_sb_tag 
         && wps->data->show_sb_on_wps)
         draw = true;
@@ -372,1235 +94,449 @@
     gui_statusbar_draw((wps)->statusbar, (force))
 #endif
 
-/* Extract a part from a path.
- *
- * buf      - buffer extract part to.
- * buf_size - size of buffer.
- * path     - path to extract from.
- * level    - what to extract. 0 is file name, 1 is parent of file, 2 is
- *            parent of parent, etc.
- *
- * Returns buf if the desired level was found, NULL otherwise.
- */
-static char* get_dir(char* buf, int buf_size, const char* path, int level)
+/* fades the volume */
+void fade(bool fade_in)
 {
-    const char* sep;
-    const char* last_sep;
-    int len;
+    int fp_global_vol = global_settings.volume << 8;
+    int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
+    int fp_step = (fp_global_vol - fp_min_vol) / 30;
 
-    sep = path + strlen(path);
-    last_sep = sep;
+    if (fade_in) {
+        /* fade in */
+        int fp_volume = fp_min_vol;
 
-    while (sep > path)
-    {
-        if ('/' == *(--sep))
-        {
-            if (!level)
-            {
-                break;
-            }
+        /* zero out the sound */
+        sound_set_volume(fp_min_vol >> 8);
 
-            level--;
-            last_sep = sep - 1;
+        sleep(HZ/10); /* let audio thread run */
+        audio_resume();
+        
+        while (fp_volume < fp_global_vol - fp_step) {
+            fp_volume += fp_step;
+            sound_set_volume(fp_volume >> 8);
+            sleep(1);
         }
+        sound_set_volume(global_settings.volume);
     }
+    else {
+        /* fade out */
+        int fp_volume = fp_global_vol;
 
-    if (level || (last_sep <= sep))
-    {
-        return NULL;
-    }
+        while (fp_volume > fp_min_vol + fp_step) {
+            fp_volume -= fp_step;
+            sound_set_volume(fp_volume >> 8);
+            sleep(1);
+        }
+        audio_pause();
+#ifndef SIMULATOR
+        /* let audio thread run and wait for the mas to run out of data */
+        while (!mp3_pause_done())
+#endif
+            sleep(HZ/10);
 
-    len = MIN(last_sep - sep, buf_size - 1);
-    strncpy(buf, sep + 1, len);
-    buf[len] = 0;
-    return buf;
+        /* reset volume to what it was before the fade */
+        sound_set_volume(global_settings.volume);
+    }
 }
 
-/* Get the tag specified by the two characters at fmt.
- *
- * cid3      - ID3 data to get tag values from.
- * nid3     - next-song ID3 data to get tag values from.
- * tag      - string (of two characters) specifying the tag to get.
- * buf      - buffer to certain tags, such as track number, play time or
- *           directory name.
- * buf_size - size of buffer.
- * flags    - returns the type of the line. See constants i wps-display.h
- *
- * Returns the tag. NULL indicates the tag wasn't available.
- */
-static char* get_tag(struct wps_data* wps_data,
-                     struct mp3entry* cid3,
-                     struct mp3entry* nid3,
-                     const char* tag,
-                     char* buf,
-                     int buf_size,
-                     unsigned char* tag_len,
-                     unsigned short* subline_time_mult,
-                     unsigned char* flags,
-                     int *intval)
+/* set volume */
+void setvol(void)
 {
-    struct mp3entry *id3 = cid3; /* default to current song */
-    int limit = *intval;
-#ifndef HAVE_LCD_CHARCELLS
-    (void)wps_data;
+    if (global_settings.volume < sound_min(SOUND_VOLUME))
+        global_settings.volume = sound_min(SOUND_VOLUME);
+    if (global_settings.volume > sound_max(SOUND_VOLUME))
+        global_settings.volume = sound_max(SOUND_VOLUME);
+    sound_set_volume(global_settings.volume);
+    settings_save();
+}
+/* return true if screen restore is needed
+   return false otherwise
+*/
+bool update_onvol_change(struct gui_wps * gwps)
+{
+    gui_wps_statusbar_draw(gwps, false);
+    gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
+
+#ifdef HAVE_LCD_CHARCELLS
+    gui_splash(gwps->display, 0, "Vol: %3d dB",
+               sound_val2phys(SOUND_VOLUME, global_settings.volume));
+    return true;
 #endif
-    if ((0 == tag[0]) || (0 == tag[1]))
-    {
-        *tag_len = 0;
-        return NULL;
-    }
+    return false;
+}
 
-    *tag_len = 2;
+bool ffwd_rew(int button)
+{
+    static const int ff_rew_steps[] = {
+      1000, 2000, 3000, 4000,
+      5000, 6000, 8000, 10000,
+      15000, 20000, 25000, 30000,
+      45000, 60000
+    };
 
-    *intval = 0;
-    
-    switch (tag[0])
-    {
-        case 'I':  /* ID3 Information */
-            id3 = nid3; /* display next-song data */
-            *flags |= WPS_REFRESH_DYNAMIC;
-            if(!id3)
-                return NULL; /* no such info (yet) */
-            /* fall-through */
-        case 'i':  /* ID3 Information */
-            *flags |= WPS_REFRESH_STATIC;
-            switch (tag[1])
-            {
-                case 't':  /* ID3 Title */
-                    return id3->title;
+    unsigned int step = 0;     /* current ff/rewind step */ 
+    unsigned int max_step = 0; /* maximum ff/rewind step */ 
+    int ff_rewind_count = 0;   /* current ff/rewind count (in ticks) */
+    int direction = -1;         /* forward=1 or backward=-1 */
+    long accel_tick = 0;       /* next time at which to bump the step size */
+    bool exit = false;
+    bool usb = false;
+    int i = 0;
 
-                case 'a':  /* ID3 Artist */
-                    return id3->artist;
-
-                case 'n':  /* ID3 Track Number */
-                    if (id3->track_string)
-                        return id3->track_string;
-
-                    if (id3->tracknum) {
-                        snprintf(buf, buf_size, "%d", id3->tracknum);
-                        return buf;
-                    }
-                    return NULL;
-
-                case 'd':  /* ID3 Album/Disc */
-                    return id3->album;
-
-                case 'c':  /* ID3 Composer */
-                    return id3->composer;
-
-                case 'C':  /* ID3 Comment */
-                    return id3->comment;
-
-                case 'A':  /* ID3 Albumartist */
-                    return id3->albumartist;
-
-                case 'y':  /* year */
-                    if( id3->year_string )
-                        return id3->year_string;
-
-                    if (id3->year) {
-                        snprintf(buf, buf_size, "%d", id3->year);
-                        return buf;
-                    }
-                    return NULL;
-
-                case 'g':  /* genre */
-                    return id3->genre_string;
-
-                case 'v': /* id3 version */
-                    switch (id3->id3version)
+    if (button == ACTION_NONE)
+    {
+        status_set_ffmode(0);
+        return usb;
+    }
+    while (!exit)
+    {
+        switch ( button )
+        {
+            case ACTION_WPS_SEEKFWD:
+                 direction = 1;
+            case ACTION_WPS_SEEKBACK:
+                if (wps_state.ff_rewind)
+                {
+                    if (direction == 1)
                     {
-                        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;
+                        /* fast forwarding, calc max step relative to end */
+                        max_step = (wps_state.id3->length - 
+                                    (wps_state.id3->elapsed +
+                                     ff_rewind_count)) *
+                                     FF_REWIND_MAX_PERCENT / 100;
                     }
-            }
-            break;
-
-        case 'F':  /* File Information */
-            id3 = nid3;
-            *flags |= WPS_REFRESH_DYNAMIC;
-            if(!id3)
-                return NULL; /* no such info (yet) */
-            /* fall-through */
-        case 'f':  /* File Information */
-            *flags |= WPS_REFRESH_STATIC;
-            switch(tag[1])
-            {
-                case 'v':  /* VBR file? */
-                    return id3->vbr ? "(avg)" : NULL;
-
-                case 'b':  /* File Bitrate */
-                    if(id3->bitrate)
-                        snprintf(buf, buf_size, "%d", id3->bitrate);
                     else
-                        snprintf(buf, buf_size, "?");
-                    return buf;
-
-                case 'f':  /* File Frequency */
-                    snprintf(buf, buf_size, "%ld", id3->frequency);
-                    return buf;
-
-                case 'p':  /* File Path */
-                    return id3->path;
-
-                case 'm':  /* File Name - With Extension */
-                    return get_dir(buf, buf_size, id3->path, 0);
-
-                case 'n':  /* File Name */
-                    if (get_dir(buf, buf_size, id3->path, 0))
                     {
-                        /* Remove extension */
-                        char* sep = strrchr(buf, '.');
-
-                        if (NULL != sep)
-                        {
-                            *sep = 0;
-                        }
-
-                        return buf;
+                        /* rewinding, calc max step relative to start */
+                        max_step = (wps_state.id3->elapsed + ff_rewind_count) *
+                                    FF_REWIND_MAX_PERCENT / 100;
                     }
-                    else
-                    {
-                        return NULL;
-                    }
 
-                case 's':  /* File Size (in kilobytes) */
-                    snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
-                    return buf;
+                    max_step = MAX(max_step, MIN_FF_REWIND_STEP);
 
-                case 'c':  /* File Codec */
-                    if(id3->codectype == AFMT_UNKNOWN)
-                        *intval = AFMT_NUM_CODECS;
-                    else
-                        *intval = id3->codectype;
-                    return id3_get_codec(id3);
-            }
-            break;
+                    if (step > max_step)
+                        step = max_step;
 
-        case 'p':  /* Playlist/Song Information */
-            switch(tag[1])
-            {
-                case 'b':  /* progress bar */
-                    *flags |= WPS_REFRESH_PLAYER_PROGRESS;
-#ifdef HAVE_LCD_CHARCELLS
-                    char *end = utf8encode(wps_data->wps_progress_pat[0], buf);
-                    *end = '\0';
-                    wps_data->full_line_progressbar=0;
-                    return buf;
-#else
-                    /* default values : */
-                    wps_data->progress_top = -1;
-                    wps_data->progress_height = 6;
-                    wps_data->progress_start = 0;
-                    wps_data->progress_end = 0;
+                    ff_rewind_count += step * direction;
 
-                    char *prev=strchr(tag, '|');
-                    if (prev) {
-                        char *p=strchr(prev+1, '|');
-                        if (p) {
-                            wps_data->progress_height=atoi(++prev);
-                            prev=strchr(prev, '|');
-                            p=strchr(++p, '|');
-                            if (p) {
-                                wps_data->progress_start=atoi(++prev);
-                                prev=strchr(prev, '|');
-                                p=strchr(++p, '|');
-                                if (p) {
-                                    wps_data->progress_end=atoi(++prev);
-                                    prev=strchr(prev, '|');
-                                    p=strchr(++p, '|');
-                                    if(p)
-                                        wps_data->progress_top = atoi(++prev);
-                                }
-
-                            if (wps_data->progress_height<3)
-                                wps_data->progress_height=3;
-                            if (wps_data->progress_end<wps_data->progress_start+3)
-                                wps_data->progress_end=0;
-                            }
-                        }
-                    }
-                    return "\x01";
-#endif
-                case 'f':  /* full-line progress bar */
-#ifdef HAVE_LCD_CHARCELLS
-                    if(is_new_player()) {
-                        *flags |= WPS_REFRESH_PLAYER_PROGRESS;
-                        *flags |= WPS_REFRESH_DYNAMIC;
-                        wps_data->full_line_progressbar=1;
-                        /* we need 11 characters (full line) for
-                           progress-bar */
-                        snprintf(buf, buf_size, "           ");
-                    }
-                    else
+                    if (global_settings.ff_rewind_accel != 0 && 
+                        current_tick >= accel_tick)
+                    { 
+                        step *= 2;
+                        accel_tick = current_tick +
+                            global_settings.ff_rewind_accel*HZ; 
+                    } 
+                }
+                else
+                {
+                    if ( (audio_status() & AUDIO_STATUS_PLAY) &&
+                          wps_state.id3 && wps_state.id3->length )
                     {
-                        /* Tell the user if we have an OldPlayer */
-                        snprintf(buf, buf_size, " <Old LCD> ");
-                    }
-                    return buf;
-#endif
-                case 'p':  /* Playlist Position */
-                    *flags |= WPS_REFRESH_STATIC;
-                    snprintf(buf, buf_size, "%d",
-                             playlist_get_display_index());
-                    return buf;
-
-                case 'n':  /* Playlist Name (without path) */
-                    *flags |= WPS_REFRESH_STATIC;
-                    return playlist_name(NULL, buf, buf_size);
-
-                case 'e':  /* Playlist Total Entries */
-                    *flags |= WPS_REFRESH_STATIC;
-                    snprintf(buf, buf_size, "%d", playlist_amount());
-                    return buf;
-
-                case 'c':  /* Current Time in Song */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    format_time(buf, buf_size,
-                                id3->elapsed + wps_state.ff_rewind_count);
-                    return buf;
-
-                case 'r': /* Remaining Time in Song */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    format_time(buf, buf_size,
-                                id3->length - id3->elapsed -
-                                wps_state.ff_rewind_count);
-                    return buf;
-
-                case 't':  /* Total Time */
-                    *flags |= WPS_REFRESH_STATIC;
-                    format_time(buf, buf_size, id3->length);
-                    return buf;
-
-#ifdef HAVE_LCD_BITMAP
-                case 'm': /* Peak Meter */
-                    *flags |= WPS_REFRESH_PEAK_METER;
-                    return "\x01";
-#endif
-                case 's': /* shuffle */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    if ( global_settings.playlist_shuffle )
-                        return "s";
-                    else
-                        return NULL;
-                    break;
-
-                case 'v': /* volume */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    snprintf(buf, buf_size, "%d", global_settings.volume);
-                    *intval = limit * (global_settings.volume 
-                                    - sound_min(SOUND_VOLUME))
-                                 / (sound_max(SOUND_VOLUME)
-                                    - sound_min(SOUND_VOLUME)) + 1;
-                    return buf;
-
-            }
-            break;
-
+                        if (!wps_state.paused)
 #if (CONFIG_CODEC == SWCODEC)
-        case 'S': /* DSP/Equalizer/Sound settings */
-            switch (tag[1])
-            {
-                case 'p': /* pitch */
-                    *intval = sound_get_pitch();
-                    snprintf(buf, buf_size, "%d.%d",
-                            *intval / 10, *intval % 10);
-                    return buf;
-            }
-            break;
+                            audio_pre_ff_rewind();
+#else
+                            audio_pause();
 #endif
-            
-        case 'm':
-            switch (tag[1])
-            {
-                case 'm': /* playback repeat mode */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    *intval = global_settings.repeat_mode + 1;
-                    snprintf(buf, buf_size, "%d", *intval);
-                    return buf;
-                    
-                /* playback status */
-                case 'p': /* play */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    int status = audio_status();
-                    *intval = 1;
-                    if (status == AUDIO_STATUS_PLAY && \
-                        !(status & AUDIO_STATUS_PAUSE))
-                        *intval = 2;
-                    if (audio_status() & AUDIO_STATUS_PAUSE && \
-                        (! status_get_ffmode()))
-                        *intval = 3;
-                    if (status_get_ffmode() == STATUS_FASTFORWARD)
-                        *intval = 4;
-                    if (status_get_ffmode() == STATUS_FASTBACKWARD)
-                        *intval = 5;
-                    snprintf(buf, buf_size, "%d", *intval);
-                    return buf;
-
-#ifdef HAS_BUTTON_HOLD
-                case 'h': /* hold */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    if (button_hold())
-                        return "h";
-                    else
-                        return NULL;
+#if CONFIG_KEYPAD == PLAYER_PAD
+                        FOR_NB_SCREENS(i)
+                            gui_wps[i].display->stop_scroll();
 #endif
-#ifdef HAS_REMOTE_BUTTON_HOLD
-                case 'r': /* remote hold */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    if (remote_button_hold())
-                        return "r";
-                    else
-                        return NULL;
-#endif
-            }
-            break;
+                        if (direction > 0) 
+                            status_set_ffmode(STATUS_FASTFORWARD);
+                        else
+                            status_set_ffmode(STATUS_FASTBACKWARD);
 
-        case 'b': /* battery info */
-            *flags |= WPS_REFRESH_DYNAMIC;
-            switch (tag[1])
-            {
-                case 'l': /* battery level */
-                {
-                    int l = battery_level();
-                    limit = MAX(limit, 2);
-                    if (l > -1)
-                    {
-                        snprintf(buf, buf_size, "%d", l);
-                        /* First enum is used for "unknown level". */
-                        *intval = (limit - 1) * l / 100 + 1 + 1;
-                    }
-                    else
-                    {
-                        *intval = 1;
-                        return "?";
-                    }
-                    return buf;
-                }
+                        wps_state.ff_rewind = true;
 
-                case 'v': /* battery voltage */
-                {
-                    unsigned int v = battery_voltage();
-                    snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
-                    return buf;
-                }
+                        step = ff_rew_steps[global_settings.ff_rewind_min_step];
 
-                case 't': /* estimated battery time */
-                {
-                    int t = battery_time();
-                    if (t >= 0)
-                        snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
-                    else
-                        strncpy(buf, "?h ?m", buf_size);
-                    return buf;
-                }
-
-                case 's': /* sleep timer */
-                {
-                    if (get_sleep_timer() == 0)
-                    {
-                        return NULL;
+                        accel_tick = current_tick +
+                            global_settings.ff_rewind_accel*HZ; 
                     }
                     else
-                    {
-                        format_time(buf, buf_size, \
-                                    get_sleep_timer() * 1000);
-                        return buf;
-                    }
+                        break;
                 }
-                
-#if CONFIG_CHARGING
-                case 'p': /* External power plugged in? */
-                {
-                    if(charger_input_state==CHARGER)
-                        return "p";
-                    else
-                        return NULL;
-                }
-#endif
-#if CONFIG_CHARGING >= CHARGING_MONITOR
-                case 'c': /* Charging */
-                {
-                    if (charge_state == CHARGING || charge_state == TOPOFF) {
-                        return "c";
-                    } else {
-                        return NULL;
-                    }
-                }
-#endif
-            }
-            break;
 
-#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
-        case 'l': /* VIRTUAL_LED */
-        {
-            switch(tag[1])
-            {
-                case 'h': /* Only one we have so far HDD LED */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    if(led_read(HZ/2))
-                        return "h";
-                    else
-                        return NULL;
-            }
-        }
-        break;
-#endif
-
-        case 'D': /* Directory path information */
-            id3 = nid3; /* next song please! */
-            *flags |= WPS_REFRESH_DYNAMIC;
-            if(!id3)
-                return NULL; /* no such info (yet) */
-            /* fall-through */
-        case 'd': /* Directory path information */
-            {
-                int level = tag[1] - '0';
-                *flags |= WPS_REFRESH_STATIC;
-                /* d1 through d9 */
-                if ((0 < level) && (9 > level))
-                {
-                    return get_dir(buf, buf_size, id3->path, level);
+                if (direction > 0) {
+                    if ((wps_state.id3->elapsed + ff_rewind_count) >
+                        wps_state.id3->length)
+                        ff_rewind_count = wps_state.id3->length -
+                            wps_state.id3->elapsed;
                 }
-            }
-            break;
-
-        case 't': /* set sub line time multiplier */
-            {
-                int d = 1;
-                int time_mult = 0;
-                bool have_point = false;
-                bool have_tenth = false;
-
-                while (((tag[d] >= '0') &&
-                        (tag[d] <= '9')) ||
-                       (tag[d] == '.'))
-                {
-                    if (tag[d] != '.')
-                    {
-                        time_mult = time_mult * 10;
-                        time_mult = time_mult + tag[d] - '0';
-                        if (have_point)
-                        {
-                            have_tenth = true;
-                            d++;
-                            break;
-                        }
-                    }
-                    else
-                    {
-                        have_point = true;
-                    }
-                    d++;
+                else {
+                    if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
+                        ff_rewind_count = -wps_state.id3->elapsed;
                 }
 
-                if (have_tenth == false)
-                    time_mult *= 10;
+                FOR_NB_SCREENS(i)
+                    gui_wps_refresh(&gui_wps[i],
+                                    (wps_state.wps_time_countup == false)?
+                                    ff_rewind_count:-ff_rewind_count,
+                                    WPS_REFRESH_PLAYER_PROGRESS |
+                                    WPS_REFRESH_DYNAMIC);
 
-                *subline_time_mult = time_mult;
-                *tag_len = d;
+                break;
 
-                buf[0] = 0;
-                return buf;
-            }
-            break;
-       case 'r':  /* Runtime database Information and Replaygain */
-            switch(tag[1])
-            {
-                case 'p':  /* Playcount */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    *intval = cid3->playcount+1;
-                    snprintf(buf, buf_size, "%ld", cid3->playcount);
-                    return buf;
-                case 'r':  /* Rating */
-                    *flags |= WPS_REFRESH_DYNAMIC;
-                    *intval = cid3->rating+1;
-                    snprintf(buf, buf_size, "%d", cid3->rating);
-                    return buf;
-#if CONFIG_CODEC == SWCODEC
-                case 'g': /* ReplayGain */
-                    *flags |= WPS_REFRESH_STATIC;
-                    if (global_settings.replaygain == 0)
-                        *intval = 1; /* off */
-                    else
-                    {
-                        int type = get_replaygain_mode(
-                            id3->track_gain_string != NULL,
-                            id3->album_gain_string != NULL);
-                        
-                        if (type < 0)
-                            *intval = 6;    /* no tag */
-                        else
-                            *intval = type + 2;
-                            
-                        if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
-                            *intval += 2;
-                    }
-
-                    switch (*intval)
-                    {
-                        case 1:
-                        case 6:
-                            return "+0.00 dB";
-                            break;
-                        case 2:
-                        case 4:
-                            strncpy(buf, id3->track_gain_string, buf_size);
-                            break;
-                        case 3:
-                        case 5:
-                            strncpy(buf, id3->album_gain_string, buf_size);
-                            break;
-                    }
-                    return buf;
+            case ACTION_WPS_STOPSEEK:
+                wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
+                audio_ff_rewind(wps_state.id3->elapsed);
+                ff_rewind_count = 0;
+                wps_state.ff_rewind = false;
+                status_set_ffmode(0);
+#if (CONFIG_CODEC != SWCODEC)
+                if (!wps_state.paused)
+                    audio_resume();
 #endif
-            }
-            break;
-#if CONFIG_RTC
-       case 'c':  /* Real Time Clock display */
-            *flags |= WPS_REFRESH_DYNAMIC;
-            {
-                int value;
-                char *format = 0;
-                char *bufptr = buf;
-                struct tm* tm = get_time();
-                int i;
-                for (i=1;/*break*/;i++) {
-                    switch(tag[i])
-                    {
-                        case 'a': /* abbreviated weekday name (Sun..Sat) */
-                            value = tm->tm_wday;
-                            if (value > 6 || value < 0) continue;
-                            value = snprintf(
-                                    bufptr,buf_size,"%s",str(dayname[value]));
-                            bufptr += value;
-                            buf_size -= value;
-                            continue;
-                        case 'b': /* abbreviated month name (Jan..Dec) */
-                            value = tm->tm_mon;
-                            if (value > 11 || value < 0) continue;
-                            value = snprintf(
-                                    bufptr,buf_size,"%s",str(monthname[value]));
-                            bufptr += value;
-                            buf_size -= value;
-                            continue;
-                        case 'd': /* day of month (01..31) */
-                            value = tm->tm_mday;
-                            if (value > 31 || value < 1) continue;
-                            format = "%02d";
-                            break;
-                        case 'e': /* day of month, blank padded ( 1..31) */
-                            value = tm->tm_mday;
-                            if (value > 31 || value < 1) continue;
-                            format = "%2d";
-                            break;
-                        case 'H': /* hour (00..23) */
-                            value = tm->tm_hour;
-                            if (value > 23) continue;
-                            format = "%02d";
-                            break;
-                        case 'k': /* hour ( 0..23) */
-                            value = tm->tm_hour;
-                            if (value > 23) continue;
-                            format = "%2d";
-                            break;
-                        case 'I': /* hour (01..12) */
-                            value = tm->tm_hour;
-                            if (value > 23) continue;
-                            value %= 12;
-                            if (value == 0) value = 12;
-                            format = "%02d";
-                            break;
-                        case 'l': /* hour ( 1..12) */
-                            value = tm->tm_hour;
-                            if (value > 23 || value < 0) continue;
-                            value %= 12;
-                            if (value == 0) value = 12;
-                            format = "%2d";
-                            break;
-                        case 'm': /* month (01..12) */
-                            value = tm->tm_mon;
-                            if (value > 11 || value < 0) continue;
-                            value++;
-                            format = "%02d";
-                            break;
-                        case 'M': /* minute (00..59) */
-                            value = tm->tm_min;
-                            if (value > 59 || value < 0) continue;
-                            format = "%02d";
-                            break;
-                        case 'S': /* second (00..59) */
-                            value = tm->tm_sec;
-                            if (value > 59 || value < 0) continue;
-                            format = "%02d";
-                            break;
-                        case 'y': /* last two digits of year (00..99) */
-                            value = tm->tm_year;
-                            value %= 100;
-                            format = "%02d";
-                            break;
-                        case 'Y': /* year (1970...) */
-                            value = tm->tm_year;
-                            if (value > 199 || value < 100) continue;
-                            value += 1900;
-                            format = "%04d";
-                            break;
-                        case 'p': /* upper case AM or PM indicator */
-                            if (tm->tm_hour/12 == 0) format = "AM";
-                            else format = "PM";
-                            snprintf(bufptr,buf_size,"%s",format);
-                            bufptr += 2;
-                            buf_size -= 2;
-                            continue;
-                        case 'P': /* lower case am or pm indicator */
-                            if (tm->tm_hour/12 == 0) format = "am";
-                            else format = "pm";
-                            snprintf(bufptr,buf_size,"%s",format);
-                            bufptr += 2;
-                            buf_size -= 2;
-                            continue;
-                        case 'u': /* day of week (1..7); 1 is Monday */
-                            value = tm->tm_wday;
-                            if (value < 0 || value > 6) continue;
-                            value++;
-                            format = "%1d";
-                            break;
-                        case 'w': /* day of week (0..6); 0 is Sunday */
-                            value = tm->tm_wday;
-                            if (value < 0 || value > 6) continue;
-                            format = "%1d";
-                            break;
-                        default:
-                            if (tag[i] == 'c') {
-                                i++;
-                                value = -1;
-                                break;
-                            } else if (tag[i] == '\n') {
-                                value = -1;
-                                break;
-                            }
-                            snprintf(bufptr,buf_size,"%c",tag[i]);
-                            bufptr++;
-                            buf_size--;
-                            continue;
-                    } /* switch */
-                    if (value < 0) break;
-
-                    value = snprintf(bufptr, buf_size, format, value);
-                    bufptr += value;
-                    buf_size -= value;
-                } /* while */
-                *tag_len = i;
-                return buf;
-            }
-#endif /* CONFIG_RTC */
-#if CONFIG_CODEC == SWCODEC
-        case 'x':
-            *flags |= WPS_REFRESH_DYNAMIC;
-            switch(tag[1])
-            {
-                case 'd': /* crossfeed */
-                    if(global_settings.crossfeed)
-                        return "d";
-                    else
-                        return NULL;
-                case 'f': /* crossfade */
-                     *intval = global_settings.crossfade+1;
-                     snprintf(buf, buf_size, "%d", global_settings.crossfade);
-                return buf;
-            }
-            break;
+#ifdef HAVE_LCD_CHARCELLS
+                gui_wps_display();
 #endif
+                exit = true;
+                break;
+
+            default:
+                if(default_event_handler(button) == SYS_USB_CONNECTED) {
+                    status_set_ffmode(0);
+                    usb = true;
+                    exit = true;
+                }
+                break;
+        }
+        if (!exit)
+            button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK);
     }
-    return NULL;
+    action_signalscreenchange();
+    return usb;
 }
 
-#ifdef HAVE_LCD_BITMAP
-/* clears the area where the image was shown */
-static void clear_image_pos(struct gui_wps *gwps, int n)
+bool gui_wps_display(void)
 {
-    if(!gwps)
-        return;
-    struct wps_data *data = gwps->data;
-    gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
-    gwps->display->fillrect(data->img[n].x, data->img[n].y, 
-                            data->img[n].bm.width, data->img[n].bm.height);
-    gwps->display->set_drawmode(DRMODE_SOLID);
-}
-#endif
-
-/* Skip to the end of the current %? conditional.
- *
- * fmt     - string to skip it. Should point to somewhere after the leading
- *           "<" char (and before or at the last ">").
- * num     - number of |'s to skip, or 0 to skip to the end (the ">").
- * enums   - If not NULL, set to the number of |'s found in the current 
- *           conditional (sub-conditionals are ignored). num should be 0
- *           to find all |'s.
- *
- * Returns the new position in fmt.
- */
-static const char* skip_conditional(struct gui_wps *gwps, const char* fmt,
-                                    int num, int *enums)
-{
-    int level = 1;
-    int count = num;
-    const char *last_alternative = NULL;
+    int i;
+    if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
+    {
+        global_status.resume_index = -1;
 #ifdef HAVE_LCD_BITMAP
-    struct wps_data *data = NULL;
-    int last_x=-1, last_y=-1, last_w=-1, last_h=-1;
-    if(gwps)
-        data = gwps->data;
-    if (enums)
-        *enums = 0;
-#else
-    (void)gwps;
+        gui_syncstatusbar_draw(&statusbars, true);
 #endif
-    while (*fmt)
+        gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
+        return true;
+    }
+    else
     {
-        switch (*fmt++)
+        FOR_NB_SCREENS(i)
         {
-            case '%':
-#ifdef HAVE_LCD_BITMAP
-                if(data && *(fmt) == 'x' && *(fmt+1) == 'd' )
-                {
-                    fmt +=2;
-                    int n = *fmt;
-                    if(n >= 'a' && n <= 'z')
-                        n -= 'a';
-                    if(n >= 'A' && n <= 'Z')
-                        n = n - 'A' + 26;
-                    if(last_x != data->img[n].x || last_y != data->img[n].y
-                       || last_w != data->img[n].bm.width
-                       || last_h != data->img[n].bm.height)
+            gui_wps[i].display->clear_display();
+            if (!gui_wps[i].data->wps_loaded) {
+                if ( !gui_wps[i].data->format_buffer[0] ) {
+                    /* set the default wps for the main-screen */
+                    if(i == 0)
                     {
-                        last_x = data->img[n].x;
-                        last_y = data->img[n].y;
-                        last_w = data->img[n].bm.width;
-                        last_h = data->img[n].bm.height;
-                        clear_image_pos(gwps,n);
-                    }
-                }
+#ifdef HAVE_LCD_BITMAP
+#if LCD_DEPTH > 1
+                        unload_wps_backdrop();
 #endif
-                break;
-
-            case '|':
-                if(1 == level) {
-                    if (enums)
-                        (*enums)++;
-                    last_alternative = fmt;
-                    if(num) {
-                        count--;
-                        if(count == 0)
-                            return fmt;
-                        continue;
+                        wps_data_load(gui_wps[i].data,
+                                      "%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
+                        wps_data_load(gui_wps[i].data,
+                                      "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
+                                      "%pc%?ps<*|/>%pt\n", false);
+#endif
                     }
+#if NB_SCREENS == 2
+                     /* set the default wps for the remote-screen */
+                     else if(i == 1)
+                     {
+                         wps_data_load(gui_wps[i].data,
+                                       "%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", false);
+                     }
+#endif
                 }
-                continue;
-
-            case '>':
-                if (0 == --level)
-                {
-                    /* We're just skipping to the end */
-                    if(num == 0)
-                        return fmt;
-
-                    /* If we are parsing an enum, we'll return the selected
-                       item. If there weren't enough items in the enum, we'll
-                       return the last one found. */
-                    if(count && last_alternative)
-                    {
-                        return last_alternative;
-                    }
-                    return fmt - 1;
-                }
-                continue;
-
-            default:
-                continue;
+            }
         }
-
-        switch (*fmt++)
-        {
-            case 0:
-            case '%':
-            case '|':
-            case '<':
-            case '>':
-                break;
-
-            case '?':
-                while (*fmt && ('<' != *fmt))
-                    fmt++;
-
-                if ('<' == *fmt)
-                    fmt++;
-
-                level++;
-                break;
-
-            default:
-                break;
-        }
     }
-
-    return fmt;
+    yield();
+    FOR_NB_SCREENS(i)
+    {
+        gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
+    }
+    return false;
 }
 
-/* Generate the display based on id3 information and format string.
- *
- * buf      - char buffer to write the display to.
- * buf_size - the size of buffer.
- * id3      - the ID3 data to format with.
- * nid3     - the ID3 data of the next song (might by NULL)
- * fmt      - format description.
- * flags    - returns the type of the line. See constants i wps-display.h
- */
-static void format_display(struct gui_wps *gwps, char* buf,
-                           int buf_size,
-                           struct mp3entry* id3,
-                           struct mp3entry* nid3, /* next song's id3 */
-                           const char* fmt,
-                           struct align_pos* align,
-                           unsigned short* subline_time_mult,
-                           unsigned char* flags)
+bool update(struct gui_wps *gwps)
 {
-    char temp_buf[128];
-    char* buf_start = buf;
-    char* buf_end = buf + buf_size - 1;   /* Leave room for end null */
-    char* value = NULL;
-    int level = 0;
-    unsigned char tag_length;
-    int intval;
-    int cur_align;
-    char* cur_align_start;
-#ifdef HAVE_LCD_BITMAP
-    struct gui_img *img = gwps->data->img;
-    int n;
-#endif
-    
-    cur_align_start = buf;
-    cur_align = WPS_ALIGN_LEFT;
-    *subline_time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
+    bool track_changed = audio_has_changed_track();
+    bool retcode = false;
 
-    align->left = 0;
-    align->center = 0;
-    align->right = 0;
-
-    while (fmt && *fmt && buf < buf_end)
+    gwps->state->nid3 = audio_next_track();
+    if (track_changed)
     {
-        switch (*fmt)
-        {
-            case '%':
-                ++fmt;
-                break;
+        gwps->display->stop_scroll();
+        gwps->state->id3 = audio_current_track();
 
-            case '|':
-            case '>':
-                if (level > 0)
-                {
-                    fmt = skip_conditional(NULL, fmt, 0, NULL);
-                    level--;
-                    continue;
-                }
-                /* Else fall through */
-
-            default:
-                *buf++ = *fmt++;
-                continue;
-        }
-
-        switch (*fmt)
+        if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
+            && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
         {
-            case 0:
-                *buf++ = '%';
-                break;
-            case 'a':
-                ++fmt;
-                /* remember where the current aligned text started */
-                switch (cur_align)
-                {
-                    case WPS_ALIGN_LEFT:
-                        align->left = cur_align_start;
-                        break;
+            /* the current cuesheet isn't the right one any more */
 
-                    case WPS_ALIGN_CENTER:
-                        align->center = cur_align_start;
-                        break;
+            if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
+                /* We have the new cuesheet in memory (temp_cue),
+                   let's make it the current one ! */
+                memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
+            }
+            else {
+                /* We need to parse the new cuesheet */
 
-                    case WPS_ALIGN_RIGHT:
-                        align->right = cur_align_start;
-                        break;
-                }
-                /* start a new alignment */
-                switch (*fmt)
-                {
-                    case 'l':
-                        cur_align = WPS_ALIGN_LEFT;
-                        break;
-                    case 'c':
-                        cur_align = WPS_ALIGN_CENTER;
-                        break;
-                    case 'r':
-                        cur_align = WPS_ALIGN_RIGHT;
-                        break;
-                } 
-                *buf++=0;
-                cur_align_start = buf;
-                ++fmt;
-                break;
-            case 's':
-                *flags |= WPS_REFRESH_SCROLL;
-                ++fmt;
-                break;
+                char cuepath[MAX_PATH];
+                strncpy(cuepath, gwps->state->id3->path, MAX_PATH);
+                char *dot = strrchr(cuepath, '.');
+                strcpy(dot, ".cue");
 
-            case 'x': /* image support */
-#ifdef HAVE_LCD_BITMAP
-                if ('d' == *(fmt+1) )
+                if (parse_cuesheet(cuepath, curr_cue))
                 {
-                    fmt+=2;
-
-                    /* get the image ID */
-                    n = *fmt;
-                    if(n >= 'a' && n <= 'z')
-                        n -= 'a';
-                    if(n >= 'A' && n <= 'Z')
-                        n = n - 'A' + 26;
-                    if (n >= 0 && n < MAX_IMAGES && img[n].loaded) {
-                        img[n].display = true;
-                    }
+                    gwps->state->id3->cuesheet_type = 1;
+                    strcpy(curr_cue->audio_filename, gwps->state->id3->path);
                 }
+            }
 
-#endif
-                fmt++;
-                break;
+            cue_spoof_id3(curr_cue, gwps->state->id3);
+        }
 
+        if (gui_wps_display())
+            retcode = true;
+        else{
+            gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
+        }
 
-            case '%':
-            case '|':
-            case '<':
-            case '>':
-            case ';':
-                *buf++ = *fmt++;
-                break;
+        if (gwps->state->id3)
+            memcpy(gwps->state->current_track_path, gwps->state->id3->path,
+                   sizeof(gwps->state->current_track_path));
+    }
 
-            case '?':
-                fmt++;
-                /* Get number of "|" chars in the current conditional;
-                 * used by get_tag when calculating levels.
-                 */
-                skip_conditional(gwps, fmt, 0, &intval);
-                value = get_tag(gwps->data, id3, nid3, fmt, temp_buf,
-                                sizeof(temp_buf),&tag_length,
-                                subline_time_mult, flags, &intval);
+    if (gwps->state->id3)
+    {
+        if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
+            && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
+                || (curr_cue->curr_track_idx < curr_cue->track_count - 1
+                    && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
+        {
+            /* We've changed tracks within the cuesheet :
+               we need to update the ID3 info and refresh the WPS */
 
-                while (*fmt && ('<' != *fmt))
-                    fmt++;
+            cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
+            cue_spoof_id3(curr_cue, gwps->state->id3);
 
-                if ('<' == *fmt)
-                    fmt++;
-
-                /* No value, so skip to else part, using a sufficiently high
-                   value to "hit" the last part of the conditional */
-                if ((!value) || (!strlen(value)))
-                    fmt = skip_conditional(NULL, fmt, 1000, NULL);
-                else
-                    if(intval > 1) /* enum */
-                        fmt = skip_conditional(NULL, fmt, intval - 1, NULL);
-
-                level++;
-                break;
-
-            default:
-                intval = 1;
-                value = get_tag(gwps->data, id3, nid3, fmt, temp_buf,
-                                sizeof(temp_buf), &tag_length,
-                                subline_time_mult, flags,&intval);
-                fmt += tag_length;
-
-                if (value)
-                {
-                    while (*value && (buf < buf_end))
-                        *buf++ = *value++;
-                }
+            gwps->display->stop_scroll();
+            if (gui_wps_display())
+                retcode = true;
+            else
+                gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
         }
+        else
+            gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
     }
 
-    /* remember where the current aligned text started */
-    switch (cur_align)
-    {
-        case WPS_ALIGN_LEFT:
-            align->left = cur_align_start;
-            break;
+    gui_wps_statusbar_draw(gwps, false);
 
-        case WPS_ALIGN_CENTER:
-            align->center = cur_align_start;
-            break;
+    return retcode;
+}
 
-        case WPS_ALIGN_RIGHT:
-            align->right = cur_align_start;
-            break;
-    }
 
-    *buf = 0;
+void display_keylock_text(bool locked)
+{
+    char* s;
+    int i;
+    FOR_NB_SCREENS(i)
+        gui_wps[i].display->stop_scroll();
 
-    /* if resulting line is an empty line, set the subline time to 0 */
-    if (buf - buf_start == 0)
-        *subline_time_mult = 0;
-
-    /* If no flags have been set, the line didn't contain any format codes.
-       We still want to refresh it. */
-    if(*flags == 0)
-        *flags = WPS_REFRESH_STATIC;
+#ifdef HAVE_LCD_CHARCELLS
+    if(locked)
+        s = str(LANG_KEYLOCK_ON_PLAYER);
+    else
+        s = str(LANG_KEYLOCK_OFF_PLAYER);
+#else
+    if(locked)
+        s = str(LANG_KEYLOCK_ON_RECORDER);
+    else
+        s = str(LANG_KEYLOCK_OFF_RECORDER);
+#endif
+    gui_syncsplash(HZ, s);
 }
 
-/* fades the volume */
-void fade(bool fade_in)
+#ifdef HAVE_LCD_BITMAP
+
+static void draw_progressbar(struct gui_wps *gwps, int line)
 {
-    int fp_global_vol = global_settings.volume << 8;
-    int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
-    int fp_step = (fp_global_vol - fp_min_vol) / 30;
+    struct wps_data *data = gwps->data;
+    struct screen *display = gwps->display;
+    struct wps_state *state = gwps->state;
+    int h = font_get(FONT_UI)->height;
 
-    if (fade_in) {
-        /* fade in */
-        int fp_volume = fp_min_vol;
+    int sb_y;
+    if (data->progress_top < 0)
+        sb_y = line*h + display->getymargin() +
+               ((h > data->progress_height + 1)
+                ? (h - data->progress_height) / 2 : 1);
+    else
+        sb_y = data->progress_top;
 
-        /* zero out the sound */
-        sound_set_volume(fp_min_vol >> 8);
+    if (!data->progress_end)
+        data->progress_end=display->width;
 
-        sleep(HZ/10); /* let audio thread run */
-        audio_resume();
-        
-        while (fp_volume < fp_global_vol - fp_step) {
-            fp_volume += fp_step;
-            sound_set_volume(fp_volume >> 8);
-            sleep(1);
-        }
-        sound_set_volume(global_settings.volume);
-    }
-    else {
-        /* fade out */
-        int fp_volume = fp_global_vol;
+    if (gwps->data->progressbar.have_bitmap_pb)
+        gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
+                                  data->progress_start, sb_y,
+                                  data->progress_end-data->progress_start,
+                                  data->progressbar.bm.height,
+                                  state->id3->length ? state->id3->length : 1, 0,
+                                  state->id3->length ? state->id3->elapsed
+                                                       + state->ff_rewind_count : 0,
+                                  HORIZONTAL);
+    else
+        gui_scrollbar_draw(display, data->progress_start, sb_y,
+                           data->progress_end-data->progress_start,
+                           data->progress_height,
+                           state->id3->length ? state->id3->length : 1, 0,
+                           state->id3->length ? state->id3->elapsed
+                                                + state->ff_rewind_count : 0,
+                           HORIZONTAL);
 
-        while (fp_volume > fp_min_vol + fp_step) {
-            fp_volume -= fp_step;
-            sound_set_volume(fp_volume >> 8);
-            sleep(1);
-        }
-        audio_pause();
-#ifndef SIMULATOR
-        /* let audio thread run and wait for the mas to run out of data */
-        while (!mp3_pause_done())
+#ifdef AB_REPEAT_ENABLE
+    if ( ab_repeat_mode_enabled() )
+        ab_draw_markers(display, state->id3->length,
+                        data->progress_start, data->progress_end, sb_y,
+                        data->progress_height);
 #endif
-            sleep(HZ/10);
 
-        /* reset volume to what it was before the fade */
-        sound_set_volume(global_settings.volume);
-    }
+    if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
+        cue_draw_markers(display, state->id3->length,
+                         data->progress_start, data->progress_end,
+                         sb_y+1, data->progress_height-2);
 }
 
-/* Set format string to use for WPS, splitting it into lines */
-void gui_wps_format(struct wps_data *data)
+/* clears the area where the image was shown */
+static void clear_image_pos(struct gui_wps *gwps, int n)
 {
-    char* buf = data->format_buffer;
-    char* start_of_line = data->format_buffer;
-    int line = 0;
-    int subline;
-    char c;
-    if(!data)
+    if(!gwps)
         return;
-
-    for (line=0; line<WPS_MAX_LINES; line++)
-    {
-        for (subline=0; subline<WPS_MAX_SUBLINES; subline++)
-        {
-            data->format_lines[line][subline] = 0;
-            data->time_mult[line][subline] = 0;
-        }
-        data->subline_expire_time[line] = 0;
-        data->curr_subline[line] = SUBLINE_RESET;
-    }
-    
-    line = 0;
-    subline = 0;
-    buf = skip_utf8_bom(buf);
-    data->format_lines[line][subline] = buf;
-
-    while ((*buf) && (line < WPS_MAX_LINES))
-    {
-        c = *buf;
-        
-        switch (c)
-        {
-            /*
-             * skip % sequences so "%;" doesn't start a new subline
-             * don't skip %x lines (pre-load bitmaps)
-             */
-            case '%':
-                buf++;
-                break;
-
-            case '\r': /* CR */
-                *buf = 0;
-                break;
-
-            case '\n': /* LF */
-                *buf = 0;
-
-                if (*start_of_line != '#') /* A comment? */
-                    line++;
-
-                if (line < WPS_MAX_LINES)
-                {
-                    /* the next line starts on the next byte */
-                    subline = 0;
-                    data->format_lines[line][subline] = buf+1;
-                    start_of_line = data->format_lines[line][subline];
-                }
-                break;
-
-            case ';': /* start a new subline */
-                *buf = 0;
-                subline++;
-                if (subline < WPS_MAX_SUBLINES)
-                {
-                    data->format_lines[line][subline] = buf+1;
-                }
-                else /* exceeded max sublines, skip rest of line */
-                {
-                    while (*(++buf))
-                    {
-                        if  ((*buf == '\r') || (*buf == '\n'))
-                        {
-                            break;
-                        }
-                    }
-                    buf--;
-                    subline = 0;
-                }
-                break;
-        }
-        buf++;
-    }
+    struct wps_data *data = gwps->data;
+    gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
+    gwps->display->fillrect(data->img[n].x, data->img[n].y,
+                            data->img[n].bm.width, data->img[n].bm.height);
+    gwps->display->set_drawmode(DRMODE_SOLID);
 }
 
-#ifdef HAVE_LCD_BITMAP
-/* Display images */
 static void wps_draw_image(struct gui_wps *gwps, int n)
 {
     struct screen *display = gwps->display;
@@ -1625,509 +561,29 @@
     }
 #endif
 }
-static void wps_display_images(struct gui_wps *gwps, bool always)
-{
-    if(!gwps || !gwps->data || !gwps->display) return;
-    int n;
-    struct wps_data *data = gwps->data;
-    struct screen *display = gwps->display;
-    for (n = 0; n < MAX_IMAGES; n++) {
-        if (data->img[n].loaded) {
-            if( (!always && data->img[n].display)
-                || (always && data->img[n].always_display) )
-                wps_draw_image(gwps, n);
-        }
-    }
-    display->set_drawmode(DRMODE_SOLID);
-}
-#endif
 
-#if 0 /* currently unused */
-void gui_wps_reset(struct gui_wps *gui_wps)
+static void wps_display_images(struct gui_wps *gwps)
 {
-    if(!gui_wps || !gui_wps->data)
+    if(!gwps || !gwps->data || !gwps->display)
         return;
-    gui_wps->data->wps_loaded = false;
-    memset(&gui_wps->data->format_buffer, 0,
-           sizeof(gui_wps->data->format_buffer));
-}
-#endif
 
-bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset,
-                     unsigned char refresh_mode)
-{
-    char buf[MAX_PATH];
-    unsigned char flags;
-    int i;
-    bool update_line;
-    bool only_one_subline;
-    bool new_subline_refresh;
-    bool reset_subline;
-    int search;
-    int search_start;
-    struct align_pos format_align;
+    int n;
     struct wps_data *data = gwps->data;
-    struct wps_state *state = gwps->state;
     struct screen *display = gwps->display;
-    if(!gwps || !data || !state || !display)
-    {
-        return false;
-    }
-#ifdef HAVE_LCD_BITMAP
-    int h = font_get(FONT_UI)->height;
-    int offset = 0;
-    gui_wps_statusbar_draw(gwps, true);
-    if(data->wps_sb_tag && data->show_sb_on_wps)
-        offset = STATUSBAR_HEIGHT;
-    else if ( global_settings.statusbar && !data->wps_sb_tag)
-        offset = STATUSBAR_HEIGHT;
-    
-    /* to find out wether the peak meter is enabled we
-       assume it wasn't until we find a line that contains
-       the peak meter. We can't use peak_meter_enabled itself
-       because that would mean to turn off the meter thread
-       temporarily. (That shouldn't matter unless yield
-       or sleep is called but who knows...)
-    */
-    bool enable_pm = false;
 
-    /* Set images to not to be displayed */
-    for (i = 0; i < MAX_IMAGES; i++) {
-        data->img[i].display = false;
-    }
-#endif
-    /* reset to first subline if refresh all flag is set */
-    if (refresh_mode == WPS_REFRESH_ALL)
+    for (n = 0; n < MAX_IMAGES; n++)
     {
-        for (i=0; i<WPS_MAX_LINES; i++)
+        if (data->img[n].loaded &&
+            (data->img[n].display || data->img[n].always_display))
         {
-            data->curr_subline[i] = SUBLINE_RESET;
+            wps_draw_image(gwps, n);
         }
     }
-
-#ifdef HAVE_LCD_CHARCELLS
-    for (i=0; i<8; i++) {
-       if (data->wps_progress_pat[i]==0)
-           data->wps_progress_pat[i]=display->get_locked_pattern();
-    }
-#endif
-
-    if (!state->id3)
-    {
-        display->stop_scroll();
-        return false;
-    }
-
-    state->ff_rewind_count = ffwd_offset;
-
-    for (i = 0; i < WPS_MAX_LINES; i++)
-    {
-        reset_subline = (data->curr_subline[i] == SUBLINE_RESET);
-        new_subline_refresh = false;
-        only_one_subline = false;
-
-        /* if time to advance to next sub-line  */
-        if (TIME_AFTER(current_tick,  data->subline_expire_time[i] - 1) ||
-           reset_subline)
-        {
-            /* search all sublines until the next subline with time > 0
-               is found or we get back to the subline we started with */
-            if (reset_subline)
-                search_start = 0;
-            else
-                search_start = data->curr_subline[i];
-            for (search=0; search<WPS_MAX_SUBLINES; search++)
-            {
-                data->curr_subline[i]++;
-
-                /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
-                if ((!data->format_lines[i][data->curr_subline[i]]) ||
-                    (data->curr_subline[i] == WPS_MAX_SUBLINES))
-                {
-                    if (data->curr_subline[i] == 1)
-                        only_one_subline = true;
-                    data->curr_subline[i] = 0;
-                }
-
-                /* if back where we started after search or
-                   only one subline is defined on the line */
-                if (((search > 0) && (data->curr_subline[i] == search_start)) ||
-                    only_one_subline)
-                {
-                    /* no other subline with a time > 0 exists */
-                    data->subline_expire_time[i] = (reset_subline?
-                        current_tick : data->subline_expire_time[i]) + 100 * HZ;
-                    break;
-                }
-                else
-                {
-                    /* get initial time multiplier and
-                       line type flags for this subline */
-                    format_display(gwps, buf, sizeof(buf),
-                                   state->id3, state->nid3,
-                                   data->format_lines[i][data->curr_subline[i]],
-                                   &format_align,
-                                   &data->time_mult[i][data->curr_subline[i]],
-                                   &data->line_type[i][data->curr_subline[i]]);
-
-                    /* only use this subline if subline time > 0 */
-                    if (data->time_mult[i][data->curr_subline[i]] > 0)
-                    {
-                        new_subline_refresh = true;
-                        data->subline_expire_time[i] = (reset_subline?
-                            current_tick : data->subline_expire_time[i]) +
-                            BASE_SUBLINE_TIME * data->time_mult[i][data->curr_subline[i]];
-                        break;
-                    }
-                }
-            }
-
-        }
-
-        update_line = false;
-
-        if ( !data->format_lines[i][data->curr_subline[i]] )
-            break;
-
-        if ((data->line_type[i][data->curr_subline[i]] & refresh_mode) ||
-            (refresh_mode == WPS_REFRESH_ALL) ||
-            new_subline_refresh)
-        {
-            flags = 0;
-            int left_width,   left_xpos;
-            int center_width, center_xpos;
-            int right_width,  right_xpos;
-            int space_width;
-            int string_height;
-            int ypos;
-
-            format_display(gwps, buf, sizeof(buf),
-                           state->id3, state->nid3,
-                           data->format_lines[i][data->curr_subline[i]],
-                           &format_align,
-                           &data->time_mult[i][data->curr_subline[i]],
-                           &flags);
-            data->line_type[i][data->curr_subline[i]] = flags;
-
-#ifdef HAVE_LCD_BITMAP
-            /* progress */
-            if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) 
-            {
-                int sb_y;
-                if (data->progress_top == -1)
-                    sb_y = i*h + offset + ((h > data->progress_height + 1)
-                                           ? (h - data->progress_height) / 2 : 1);
-                else
-                    sb_y = data->progress_top;
-
-                if (!data->progress_end)
-                    data->progress_end=display->width;
-                
-                if (gwps->data->progressbar.have_bitmap_pb)
-                    gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
-                                        data->progress_start, sb_y, 
-                                        data->progress_end-data->progress_start,
-                                        data->progressbar.bm.height,
-                                        state->id3->length?state->id3->length:1, 0,
-                                        state->id3->length?state->id3->elapsed + state->ff_rewind_count:0,
-                                        HORIZONTAL);
-                else
-                    gui_scrollbar_draw(display, data->progress_start, sb_y, 
-                                        data->progress_end-data->progress_start,
-                                        data->progress_height,
-                                        state->id3->length?state->id3->length:1, 0,
-                                        state->id3->length?state->id3->elapsed + state->ff_rewind_count:0,
-                                        HORIZONTAL);
-#ifdef AB_REPEAT_ENABLE
-                if ( ab_repeat_mode_enabled() )
-                    ab_draw_markers(display, state->id3->length,
-                            data->progress_start, data->progress_end, sb_y,
-                            data->progress_height);
-#endif
-
-                if (cuesheet_is_enabled() && state->id3->cuesheet_type)
-                {
-                    cue_draw_markers(display, state->id3->length,
-                                     data->progress_start, data->progress_end,
-                                     sb_y+1, data->progress_height-2);
-                }
-
-                update_line = true;
-            }
-            if (flags & refresh_mode & WPS_REFRESH_PEAK_METER) {
-                /* peak meter */
-                int peak_meter_y;
-
-                update_line = true;
-                peak_meter_y = i * h + offset;
-
-                /* The user might decide to have the peak meter in the last
-                   line so that it is only displayed if no status bar is
-                   visible. If so we neither want do draw nor enable the
-                   peak meter. */
-                if (peak_meter_y + h <= display->height) {
-                    /* found a line with a peak meter -> remember that we must
-                       enable it later */
-                    enable_pm = true;
-                    peak_meter_screen(gwps->display, 0, peak_meter_y,
-                                    MIN(h, display->height - peak_meter_y));
-                }
-            }
-#else
-            /* progress */
-            if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) {
-                if (data->full_line_progressbar)
-                    draw_player_fullbar(gwps, buf, sizeof(buf));
-                else
-                    draw_player_progress(gwps);
-            }
-#endif
-            /* calculate different string sizes and positions */
-            display->getstringsize((unsigned char *)" ", &space_width, &string_height);
-            if (format_align.left != 0) {
-                display->getstringsize((unsigned char *)format_align.left,
-                                       &left_width, &string_height);
-            }
-            else {
-                left_width = 0;
-            }
-            left_xpos = 0;
-
-            if (format_align.center != 0) {
-                display->getstringsize((unsigned char *)format_align.center,
-                                       &center_width, &string_height);
-            }
-            else {
-                center_width = 0;
-            }
-            center_xpos=(display->width - center_width) / 2;
-
-            if (format_align.right != 0) {
-                display->getstringsize((unsigned char *)format_align.right,
-                                       &right_width, &string_height);
-            }
-            else {
-                right_width = 0;
-            }
-            right_xpos = (display->width - right_width);
-
-            /* Checks for overlapping strings.
-               If needed the overlapping strings will be merged, separated by a
-               space */
-
-            /* CASE 1: left and centered string overlap */
-            /* there is a left string, need to merge left and center */
-            if ((left_width != 0 && center_width != 0) &&
-                (left_xpos + left_width + space_width > center_xpos)) {
-                /* replace the former separator '\0' of left and 
-                   center string with a space */
-                *(--format_align.center) = ' ';
-                /* calculate the new width and position of the merged string */
-                left_width = left_width + space_width + center_width;
-                left_xpos = 0;
-                /* there is no centered string anymore */
-                center_width = 0;
-            }
-            /* there is no left string, move center to left */
-            if ((left_width == 0 && center_width != 0) &&
-                (left_xpos + left_width > center_xpos)) {
-                /* move the center string to the left string */
-                format_align.left = format_align.center;
-                /* calculate the new width and position of the string */
-                left_width = center_width;
-                left_xpos = 0;
-                /* there is no centered string anymore */
-                center_width = 0;
-            }
-
-            /* CASE 2: centered and right string overlap */
-            /* there is a right string, need to merge center and right */
-            if ((center_width != 0 && right_width != 0) &&
-                (center_xpos + center_width + space_width > right_xpos)) {
-                /* replace the former separator '\0' of center and 
-                   right string with a space */
-                *(--format_align.right) = ' ';
-                /* move the center string to the right after merge */
-                format_align.right = format_align.center;
-                /* calculate the new width and position of the merged string */
-                right_width = center_width + space_width + right_width;
-                right_xpos = (display->width - right_width);
-                /* there is no centered string anymore */
-                center_width = 0;
-            }
-            /* there is no right string, move center to right */
-            if ((center_width != 0 && right_width == 0) &&
-                (center_xpos + center_width > right_xpos)) {
-                /* move the center string to the right string */
-                format_align.right = format_align.center;
-                /* calculate the new width and position of the string */
-                right_width = center_width;
-                right_xpos = (display->width - right_width);
-                /* there is no centered string anymore */
-                center_width = 0;
-            }
-
-            /* CASE 3: left and right overlap
-               There is no center string anymore, either there never
-               was one or it has been merged in case 1 or 2 */
-            /* there is a left string, need to merge left and right */
-            if ((left_width != 0 && center_width == 0 && right_width != 0) &&
-                (left_xpos + left_width + space_width > right_xpos)) {
-                /* replace the former separator '\0' of left and 
-                   right string with a space */
-                *(--format_align.right) = ' ';
-                /* calculate the new width and position of the string */
-                left_width = left_width + space_width + right_width;
-                left_xpos = 0;
-                /* there is no right string anymore */
-                right_width = 0;
-            }
-            /* there is no left string, move right to left */
-            if ((left_width == 0 && center_width == 0 && right_width != 0) &&
-                (left_xpos + left_width > right_xpos)) {
-                /* move the right string to the left string */
-                format_align.left = format_align.right;
-                /* calculate the new width and position of the string */
-                left_width = right_width;
-                left_xpos = 0;
-                /* there is no right string anymore */
-                right_width = 0;
-            }
-
-            if (flags & WPS_REFRESH_SCROLL) {
-
-                /* scroll line */
-                if ((refresh_mode & WPS_REFRESH_SCROLL) ||
-                    new_subline_refresh) {
-
-                    ypos = (i*string_height)+display->getymargin();
-                    update_line = true;
-                    
-                    if (left_width>display->width) {
-                        display->puts_scroll(0, i,
-                                             (unsigned char *)format_align.left);
-                    } else {
-                        /* clear the line first */
-#ifdef HAVE_LCD_BITMAP
-                        display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
-                        display->fillrect(0, ypos, display->width, string_height);
-                        display->set_drawmode(DRMODE_SOLID);
-#endif
-                        /* Nasty hack: we output an empty scrolling string,
-                           which will reset the scroller for that line */
-                        display->puts_scroll(0, i, (unsigned char *)"");
-
-                        /* print aligned strings */
-                        if (left_width != 0)
-                        {
-                            display->putsxy(left_xpos, ypos,
-                                            (unsigned char *)format_align.left);
-                        }
-                        if (center_width != 0)
-                        {
-                            display->putsxy(center_xpos, ypos, 
-                                            (unsigned char *)format_align.center);
-                        }
-                        if (right_width != 0)
-                        {
-                            display->putsxy(right_xpos, ypos,
-                                            (unsigned char *)format_align.right);
-                        }
-                    }
-                }
-            }
-            else if (flags & (WPS_REFRESH_DYNAMIC | WPS_REFRESH_STATIC))
-            {
-                /* dynamic / static line */
-                if ((refresh_mode & (WPS_REFRESH_DYNAMIC|WPS_REFRESH_STATIC)) ||
-                    new_subline_refresh)
-                {
-                    ypos = (i*string_height)+display->getymargin();
-                    update_line = true;
-
-#ifdef HAVE_LCD_BITMAP
-                    /* clear the line first */
-                    display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
-                    display->fillrect(0, ypos, display->width, string_height);
-                    display->set_drawmode(DRMODE_SOLID);
-#endif
-
-                    /* Nasty hack: we output an empty scrolling string,
-                       which will reset the scroller for that line */
-                    display->puts_scroll(0, i, (unsigned char *)"");
-                        
-                    /* print aligned strings */
-                    if (left_width != 0)
-                    {
-                        display->putsxy(left_xpos, ypos,
-                                        (unsigned char *)format_align.left);
-                    }
-                    if (center_width != 0)
-                    {
-                        display->putsxy(center_xpos, ypos,
-                                        (unsigned char *)format_align.center);
-                    }
-                    if (right_width != 0)
-                    {
-                        display->putsxy(right_xpos, ypos,
-                                        (unsigned char *)format_align.right);
-                    }
-                }
-            }
-        }
-#ifdef HAVE_LCD_BITMAP
-        if (update_line) {
-            wps_display_images(gwps,false);
-        }
-#endif
-    }
-
-#ifdef HAVE_LCD_BITMAP
-    /* Display all images */
-    wps_display_images(gwps,true);
-    display->update();
-    /* Now we know wether the peak meter is used.
-       So we can enable / disable the peak meter thread */
-    data->peak_meter_enabled = enable_pm;
-#endif
-
-#if CONFIG_BACKLIGHT
-    if (global_settings.caption_backlight && state->id3) {
-        /* turn on backlight n seconds before track ends, and turn it off n
-           seconds into the new track. n == backlight_timeout, or 5s */
-        int n = backlight_timeout_value[global_settings.backlight_timeout] 
-              * 1000;
-
-        if ( n < 1000 )
-            n = 5000; /* use 5s if backlight is always on or off */
-
-        if (((state->id3->elapsed < 1000) ||
-            ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
-            (state->paused == false))
-            backlight_on();
-    }
-#endif
-#ifdef HAVE_REMOTE_LCD
-    if (global_settings.remote_caption_backlight && state->id3) {
-        /* turn on remote backlight n seconds before track ends, and turn it
-           off n seconds into the new track. n == remote_backlight_timeout,
-           or 5s */
-        int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
-              * 1000;
-
-        if ( n < 1000 )
-            n = 5000; /* use 5s if backlight is always on or off */
-
-        if (((state->id3->elapsed < 1000) ||
-            ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
-            (state->paused == false))
-            remote_backlight_on();
-    }
-#endif
-    return true;
+    display->set_drawmode(DRMODE_SOLID);
 }
 
-#ifdef HAVE_LCD_CHARCELLS
+#else /* HAVE_LCD_CHARCELL */
+
 static bool draw_player_progress(struct gui_wps *gwps)
 {
     char player_progressbar[7];
@@ -2150,7 +606,7 @@
             songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) /
                             state->id3->length;
         else
-            songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / 
+            songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) /
                             state->id3->length;
     }
     for (i=0; i < songpos; i++)
@@ -2215,10 +671,10 @@
         songpos = 55;
     else {
         if(state->wps_time_countup == false)
-            songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / 
+            songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) /
                                 state->id3->length;
         else
-            songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / 
+            songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) /
                                 state->id3->length;
     }
 
@@ -2304,342 +760,1183 @@
         *buf = '\0';
     }
 }
-#endif
 
-/* set volume */
-void setvol(void)
+#endif /* HAVE_LCD_CHARCELL */
+
+/* Extract a part from a path.
+ *
+ * buf      - buffer extract part to.
+ * buf_size - size of buffer.
+ * path     - path to extract from.
+ * level    - what to extract. 0 is file name, 1 is parent of file, 2 is
+ *            parent of parent, etc.
+ *
+ * Returns buf if the desired level was found, NULL otherwise.
+ */
+static char* get_dir(char* buf, int buf_size, const char* path, int level)
 {
-    if (global_settings.volume < sound_min(SOUND_VOLUME))
-        global_settings.volume = sound_min(SOUND_VOLUME);
-    if (global_settings.volume > sound_max(SOUND_VOLUME))
-        global_settings.volume = sound_max(SOUND_VOLUME);
-    sound_set_volume(global_settings.volume);
-    settings_save();
+    const char* sep;
+    const char* last_sep;
+    int len;
+
+    sep = path + strlen(path);
+    last_sep = sep;
+
+    while (sep > path)
+    {
+        if ('/' == *(--sep))
+        {
+            if (!level)
+                break;
+
+            level--;
+            last_sep = sep - 1;
+        }
+    }
+
+    if (level || (last_sep <= sep))
+        return NULL;
+
+    len = MIN(last_sep - sep, buf_size - 1);
+    strncpy(buf, sep + 1, len);
+    buf[len] = 0;
+    return buf;
 }
-/* return true if screen restore is needed
-   return false otherwise
+
+/* Return the tag found at index i and write its value in buf.
+   The return value is buf if the tag had a value, or NULL if not.
+
+   intval is used with enums: when this function is called, it should contain
+   the number of options in the enum. When this function returns, it will
+   contain the enum case we are actually in.
+   When not treating an enum, intval should be NULL.
 */
-bool update_onvol_change(struct gui_wps * gwps)
+static char *get_tag(struct gui_wps *gwps,
+                     int i,
+                     char *buf,
+                     int buf_size,
+                     int *intval)
 {
-    gui_wps_statusbar_draw(gwps, false);
-    gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
+    if (!gwps)
+        return NULL;
 
-#ifdef HAVE_LCD_CHARCELLS
-    gui_splash(gwps->display, 0, "Vol: %3d dB",
-               sound_val2phys(SOUND_VOLUME, global_settings.volume));
-    return true;
-#endif
-    return false;
-}
+    struct wps_data *data = gwps->data;
+    struct wps_state *state = gwps->state;
 
-bool ffwd_rew(int button)
-{
-    static const int ff_rew_steps[] = {
-      1000, 2000, 3000, 4000,
-      5000, 6000, 8000, 10000,
-      15000, 20000, 25000, 30000,
-      45000, 60000
-    };
+    if (!data || !state)
+        return NULL;
 
-    unsigned int step = 0;     /* current ff/rewind step */ 
-    unsigned int max_step = 0; /* maximum ff/rewind step */ 
-    int ff_rewind_count = 0;   /* current ff/rewind count (in ticks) */
-    int direction = -1;         /* forward=1 or backward=-1 */
-    long accel_tick = 0;       /* next time at which to bump the step size */
-    bool exit = false;
-    bool usb = false;
-    int i = 0;
+    struct mp3entry *id3;
 
-    if (button == ACTION_NONE)
+    if (data->tokens[i].next)
+        id3 = state->nid3;
+    else
+        id3 = state->id3;
+
+    if (!id3)
+        return NULL;
+
+    int limit = 1;
+    if (intval)
+        limit = *intval;
+
+#if CONFIG_RTC
+    static struct tm* tm;
+#endif
+
+    switch (data->tokens[i].type)
     {
-        status_set_ffmode(0);
-        return usb;
-    }
-    while (!exit)
-    {
-        switch ( button )
-        {
-            case ACTION_WPS_SEEKFWD:
-                 direction = 1;
-            case ACTION_WPS_SEEKBACK:
-                if (wps_state.ff_rewind)
-                {
-                    if (direction == 1)
-                    {
-                        /* fast forwarding, calc max step relative to end */
-                        max_step = (wps_state.id3->length - 
-                                    (wps_state.id3->elapsed +
-                                     ff_rewind_count)) *
-                                     FF_REWIND_MAX_PERCENT / 100;
-                    }
-                    else
-                    {
-                        /* rewinding, calc max step relative to start */
-                        max_step = (wps_state.id3->elapsed + ff_rewind_count) *
-                                    FF_REWIND_MAX_PERCENT / 100;
-                    }
+        case WPS_TOKEN_CHARACTER:
+            return &(data->tokens[i].value.c);
 
-                    max_step = MAX(max_step, MIN_FF_REWIND_STEP);
+        case WPS_TOKEN_STRING:
+            return data->strings[data->tokens[i].value.i];
 
-                    if (step > max_step)
-                        step = max_step;
+        case WPS_TOKEN_TRACK_TIME_ELAPSED:
+            format_time(buf, buf_size,
+                        id3->elapsed + state->ff_rewind_count);
+            return buf;
 
-                    ff_rewind_count += step * direction;
+        case WPS_TOKEN_TRACK_TIME_REMAINING:
+            format_time(buf, buf_size,
+                        id3->length - id3->elapsed -
+                        state->ff_rewind_count);
+            return buf;
 
-                    if (global_settings.ff_rewind_accel != 0 && 
-                        current_tick >= accel_tick)
-                    { 
-                        step *= 2;
-                        accel_tick = current_tick +
-                            global_settings.ff_rewind_accel*HZ; 
-                    } 
-                }
+        case WPS_TOKEN_TRACK_LENGTH:
+            format_time(buf, buf_size, id3->length);
+            return buf;
+
+        case WPS_TOKEN_PLAYLIST_ENTRIES:
+            snprintf(buf, buf_size, "%d", playlist_amount());
+            return buf;
+
+        case WPS_TOKEN_PLAYLIST_NAME:
+            return playlist_name(NULL, buf, buf_size);
+
+        case WPS_TOKEN_PLAYLIST_POSITION:
+            snprintf(buf, buf_size, "%d",
+                        playlist_get_display_index());
+            return buf;
+
+        case WPS_TOKEN_PLAYLIST_SHUFFLE:
+            if ( global_settings.playlist_shuffle )
+                return "s";
+            else
+                return NULL;
+            break;
+
+        case WPS_TOKEN_VOLUME:
+            snprintf(buf, buf_size, "%d", global_settings.volume);
+            if (intval)
+            {
+                *intval = limit * (global_settings.volume
+                                    - sound_min(SOUND_VOLUME))
+                                    / (sound_max(SOUND_VOLUME)
+                                        - sound_min(SOUND_VOLUME)) + 1;
+            }
+            return buf;
+
+        case WPS_TOKEN_METADATA_ARTIST:
+            return id3->artist;
+
+        case WPS_TOKEN_METADATA_COMPOSER:
+            return id3->composer;
+
+        case WPS_TOKEN_METADATA_ALBUM:
+            return id3->album;
+
+        case WPS_TOKEN_METADATA_ALBUM_ARTIST:
+            return id3->albumartist;
+
+        case WPS_TOKEN_METADATA_GENRE:
+            return id3->genre_string;
+
+        case WPS_TOKEN_METADATA_TRACK_NUMBER:
+            if (id3->track_string)
+                return id3->track_string;
+
+            if (id3->tracknum) {
+                snprintf(buf, buf_size, "%d", id3->tracknum);
+                return buf;
+            }
+            return NULL;
+
+        case WPS_TOKEN_METADATA_TRACK_TITLE:
+            return id3->title;
+
+        case WPS_TOKEN_METADATA_VERSION:
+            switch (id3->id3version)
+            {
+                case ID3_VER_1_0:
+                    return "1";
+
+                case ID3_VER_1_1:
+                    return "1.1";
+
+                case ID3_VER_2_2:
+                    return "2.2";
+
+                case ID3_VER_2_3:
+                    return "2.3";
+
+                case ID3_VER_2_4:
+                    return "2.4";
+
+                default:
+                    return NULL;
+            }
+
+        case WPS_TOKEN_METADATA_YEAR:
+            if( id3->year_string )
+                return id3->year_string;
+
+            if (id3->year) {
+                snprintf(buf, buf_size, "%d", id3->year);
+                return buf;
+            }
+            return NULL;
+
+        case WPS_TOKEN_METADATA_COMMENT:
+            return id3->comment;
+
+        case WPS_TOKEN_FILE_BITRATE:
+            if(id3->bitrate)
+                snprintf(buf, buf_size, "%d", id3->bitrate);
+            else
+                snprintf(buf, buf_size, "?");
+            return buf;
+
+        case WPS_TOKEN_FILE_CODEC:
+            if (intval)
+            {
+                if(id3->codectype == AFMT_UNKNOWN)
+                    *intval = AFMT_NUM_CODECS;
                 else
-                {
-                    if ( (audio_status() & AUDIO_STATUS_PLAY) &&
-                          wps_state.id3 && wps_state.id3->length )
-                    {
-                        if (!wps_state.paused)
-#if (CONFIG_CODEC == SWCODEC)
-                            audio_pre_ff_rewind();
-#else
-                            audio_pause();
+                    *intval = id3->codectype;
+            }
+            return id3_get_codec(id3);
+
+        case WPS_TOKEN_FILE_FREQUENCY:
+            snprintf(buf, buf_size, "%ld", id3->frequency);
+            return buf;
+
+        case WPS_TOKEN_FILE_NAME:
+            if (get_dir(buf, buf_size, id3->path, 0)) {
+                /* Remove extension */
+                char* sep = strrchr(buf, '.');
+                if (NULL != sep) {
+                    *sep = 0;
+                }
+                return buf;
+            }
+            else {
+                return NULL;
+            }
+
+        case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
+            return get_dir(buf, buf_size, id3->path, 0);
+
+        case WPS_TOKEN_FILE_PATH:
+            return id3->path;
+
+        case WPS_TOKEN_FILE_SIZE:
+            snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
+            return buf;
+
+        case WPS_TOKEN_FILE_VBR:
+            return id3->vbr ? "(avg)" : NULL;
+
+        case WPS_TOKEN_FILE_DIRECTORY:
+            return get_dir(buf, buf_size, id3->path, data->tokens[i].value.i);
+
+        case WPS_TOKEN_BATTERY_PERCENT:
+        {
+            int l = battery_level();
+
+            if (intval)
+            {
+                limit = MAX(limit, 2);
+                if (l > -1) {
+                    /* First enum is used for "unknown level". */
+                    *intval = (limit - 1) * l / 100 + 2;
+                } else {
+                    *intval = 1;
+                }
+            }
+
+            if (l > -1) {
+                snprintf(buf, buf_size, "%d", l);
+                return buf;
+            } else {
+                return "?";
+            }
+        }
+
+        case WPS_TOKEN_BATTERY_VOLTS:
+        {
+            unsigned int v = battery_voltage();
+            snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
+            return buf;
+        }
+
+        case WPS_TOKEN_BATTERY_TIME:
+        {
+            int t = battery_time();
+            if (t >= 0)
+                snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
+            else
+                strncpy(buf, "?h ?m", buf_size);
+            return buf;
+        }
+
+#if CONFIG_CHARGING
+        case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
+        {
+            if(charger_input_state==CHARGER)
+                return "p";
+            else
+                return NULL;
+        }
 #endif
-#if CONFIG_KEYPAD == PLAYER_PAD
-                        FOR_NB_SCREENS(i)
-                            gui_wps[i].display->stop_scroll();
+#if CONFIG_CHARGING >= CHARGING_MONITOR
+        case WPS_TOKEN_BATTERY_CHARGING:
+        {
+            if (charge_state == CHARGING || charge_state == TOPOFF) {
+                return "c";
+            } else {
+                return NULL;
+            }
+        }
 #endif
-                        if (direction > 0) 
-                            status_set_ffmode(STATUS_FASTFORWARD);
-                        else
-                            status_set_ffmode(STATUS_FASTBACKWARD);
 
-                        wps_state.ff_rewind = true;
+        case WPS_TOKEN_PLAYBACK_STATUS:
+        {
+            int status = audio_status();
+            int mode = 1;
+            if (status == AUDIO_STATUS_PLAY && \
+                !(status & AUDIO_STATUS_PAUSE))
+                mode = 2;
+            if (audio_status() & AUDIO_STATUS_PAUSE && \
+                (! status_get_ffmode()))
+                mode = 3;
+            if (status_get_ffmode() == STATUS_FASTFORWARD)
+                mode = 4;
+            if (status_get_ffmode() == STATUS_FASTBACKWARD)
+                mode = 5;
 
-                        step = ff_rew_steps[global_settings.ff_rewind_min_step];
+            if (intval) {
+                *intval = mode;
+            }
 
-                        accel_tick = current_tick +
-                            global_settings.ff_rewind_accel*HZ; 
-                    }
-                    else
-                        break;
-                }
+            snprintf(buf, buf_size, "%d", mode);
+            return buf;
+        }
 
-                if (direction > 0) {
-                    if ((wps_state.id3->elapsed + ff_rewind_count) >
-                        wps_state.id3->length)
-                        ff_rewind_count = wps_state.id3->length -
-                            wps_state.id3->elapsed;
-                }
-                else {
-                    if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
-                        ff_rewind_count = -wps_state.id3->elapsed;
-                }
+        case WPS_TOKEN_REPEAT_MODE:
+            if (intval)
+                *intval = global_settings.repeat_mode + 1;
+            snprintf(buf, buf_size, "%d", *intval);
+            return buf;
 
-                FOR_NB_SCREENS(i)
-                    gui_wps_refresh(&gui_wps[i],
-                                    (wps_state.wps_time_countup == false)?
-                                    ff_rewind_count:-ff_rewind_count,
-                                    WPS_REFRESH_PLAYER_PROGRESS |
-                                    WPS_REFRESH_DYNAMIC);
+#if CONFIG_RTC
+        case WPS_TOKEN_RTC:
+            tm = get_time();
+            return NULL;
 
-                break;
+        case WPS_TOKEN_RTC_DAY_OF_MONTH:
+            /* d: day of month (01..31) */
+            if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
+            snprintf(buf, buf_size, "%02d", tm->tm_mday);
+            return buf;
 
-            case ACTION_WPS_STOPSEEK:
-                wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
-                audio_ff_rewind(wps_state.id3->elapsed);
-                ff_rewind_count = 0;
-                wps_state.ff_rewind = false;
-                status_set_ffmode(0);
-#if (CONFIG_CODEC != SWCODEC)
-                if (!wps_state.paused)
-                    audio_resume();
+        case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
+            /* e: day of month, blank padded ( 1..31) */
+            if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
+            snprintf(buf, buf_size, "%2d", tm->tm_mday);
+            return buf;
+
+        case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
+            /* H: hour (00..23) */
+            if (tm->tm_hour > 23) return NULL;
+            snprintf(buf, buf_size, "%02d", tm->tm_hour);
+            return buf;
+
+        case WPS_TOKEN_RTC_HOUR_24:
+            /* k: hour ( 0..23) */
+            if (tm->tm_hour > 23) return NULL;
+            snprintf(buf, buf_size, "%2d", tm->tm_hour);
+            return buf;
+
+        case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
+            /* I: hour (01..12) */
+            if (tm->tm_hour > 23) return NULL;
+            snprintf(buf, buf_size, "%02d",
+                     (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
+            return buf;
+
+        case WPS_TOKEN_RTC_HOUR_12:
+            /* l: hour ( 1..12) */
+            if (tm->tm_hour > 23) return NULL;
+            snprintf(buf, buf_size, "%2d",
+                     (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
+            return buf;
+
+        case WPS_TOKEN_RTC_MONTH:
+            /* m: month (01..12) */
+            if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
+            snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
+            return buf;
+
+        case WPS_TOKEN_RTC_MINUTE:
+            /* M: minute (00..59) */
+            if (tm->tm_min > 59 || tm->tm_min < 0) return NULL;
+            snprintf(buf, buf_size, "%02d", tm->tm_min);
+            return buf;
+
+        case WPS_TOKEN_RTC_SECOND:
+            /* S: second (00..59) */
+            if (tm->tm_sec > 59 || tm->tm_sec < 0) return NULL;
+            snprintf(buf, buf_size, "%02d", tm->tm_sec);
+            return buf;
+
+        case WPS_TOKEN_RTC_YEAR_2_DIGITS:
+            /* y: last two digits of year (00..99) */
+            snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
+            return buf;
+
+        case WPS_TOKEN_RTC_YEAR_4_DIGITS:
+            /* Y: year (1970...) */
+            if (tm->tm_year > 199 || tm->tm_year < 100) return NULL;
+            snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
+            return buf;
+
+        case WPS_TOKEN_RTC_AM_PM_UPPER:
+            /* p: upper case AM or PM indicator */
+            snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
+            return buf;
+
+        case WPS_TOKEN_RTC_AM_PM_LOWER:
+            /* P: lower case am or pm indicator */
+            snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
+            return buf;
+
+        case WPS_TOKEN_RTC_WEEKDAY_NAME:
+            /* a: abbreviated weekday name (Sun..Sat) */
+            if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
+            snprintf(buf, buf_size, "%s",str(dayname[tm->tm_wday]));
+            return buf;
+
+        case WPS_TOKEN_RTC_MONTH_NAME:
+            /* b: abbreviated month name (Jan..Dec) */
+            if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
+            snprintf(buf, buf_size, "%s",str(monthname[tm->tm_mon]));
+            return buf;
+
+        case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
+            /* u: day of week (1..7); 1 is Monday */
+            if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
+            snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
+            return buf;
+
+        case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
+            /* w: day of week (0..6); 0 is Sunday */
+            if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
+            snprintf(buf, buf_size, "%1d", tm->tm_wday);
+            return buf;
 #endif
+
 #ifdef HAVE_LCD_CHARCELLS
-                gui_wps_display();
+        case WPS_TOKEN_PROGRESSBAR:
+        {
+            char *end = utf8encode(data->wps_progress_pat[0], buf);
+            *end = '\0';
+            return buf;
+        }
+
+        case WPS_TOKEN_PLAYER_PROGRESSBAR:
+            if(is_new_player())
+            {
+                /* we need 11 characters (full line) for
+                    progress-bar */
+                snprintf(buf, buf_size, "           ");
+            }
+            else
+            {
+                /* Tell the user if we have an OldPlayer */
+                snprintf(buf, buf_size, " <Old LCD> ");
+            }
+            return buf;
 #endif
-                exit = true;
-                break;
 
-            default:
-                if(default_event_handler(button) == SYS_USB_CONNECTED) {
-                    status_set_ffmode(0);
-                    usb = true;
-                    exit = true;
-                }
-                break;
+        case WPS_TOKEN_DATABASE_PLAYCOUNT:
+            if (intval) {
+                *intval = id3->playcount + 1;
+            }
+            snprintf(buf, buf_size, "%ld", id3->playcount);
+            return buf;
+
+        case WPS_TOKEN_DATABASE_RATING:
+            if (intval) {
+                *intval = id3->rating + 1;
+            }
+            snprintf(buf, buf_size, "%d", id3->rating);
+            return buf;
+
+#if (CONFIG_CODEC == SWCODEC)
+        case WPS_TOKEN_REPLAYGAIN:
+        {
+            int val;
+
+            if (global_settings.replaygain == 0)
+                val = 1; /* off */
+            else
+            {
+                int type =
+                    get_replaygain_mode(id3->track_gain_string != NULL,
+                                        id3->album_gain_string != NULL);
+                if (type < 0)
+                    val = 6;    /* no tag */
+                else
+                    val = type + 2;
+
+                if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
+                    val += 2;
+            }
+
+            if (intval)
+                *intval = val;
+
+            switch (val)
+            {
+                case 1:
+                case 6:
+                    return "+0.00 dB";
+                    break;
+                case 2:
+                case 4:
+                    strncpy(buf, id3->track_gain_string, buf_size);
+                    break;
+                case 3:
+                case 5:
+                    strncpy(buf, id3->album_gain_string, buf_size);
+                    break;
+            }
+            return buf;
         }
-        if (!exit)
-            button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK);
+
+        case WPS_TOKEN_SOUND_PITCH:
+            snprintf(buf, buf_size, "%d.%d",
+                    *intval / 10, *intval % 10);
+            return buf;
+
+#endif
+
+#ifdef HAS_BUTTON_HOLD
+        case WPS_TOKEN_MAIN_HOLD:
+            if (button_hold())
+                return "h";
+            else
+                return NULL;
+#endif
+#ifdef HAS_REMOTE_BUTTON_HOLD
+        case WPS_TOKEN_REMOTE_HOLD:
+            if (remote_button_hold())
+                return "r";
+            else
+                return NULL;
+#endif
+
+#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
+        case WPS_TOKEN_VLED_HDD:
+            if(led_read(HZ/2))
+                return "h";
+            else
+                return NULL;
+#endif
+
+        default:
+            return NULL;
     }
-    action_signalscreenchange();
-    return usb;
 }
 
-bool gui_wps_display(void)
+/* Return the index to the end token for the conditional token at index.
+   The conditional token can be either a start token or a separator
+   (i.e. option) token.
+*/
+static int find_conditional_end(struct wps_data *data, int index)
 {
+    int type = data->tokens[index].type;
+
+    if (type != WPS_TOKEN_CONDITIONAL_START
+        && type != WPS_TOKEN_CONDITIONAL_OPTION)
+    {
+        /* this function should only be used with "index" pointing to a
+           WPS_TOKEN_CONDITIONAL_START or a WPS_TOKEN_CONDITIONAL_OPTION */
+        return index + 1;
+    }
+
+    int ret = index;
+    do
+        ret = data->tokens[ret].value.i;
+    while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END);
+
+    /* ret now is the index to the end token for the conditional. */
+    return ret;
+}
+
+/* Return the index of the appropriate case for the conditional
+   that starts at cond_index.
+*/
+static int evaluate_conditional(struct gui_wps *gwps, int cond_index)
+{
+    if (!gwps)
+        return 0;
+
+    struct wps_data *data = gwps->data;
+
+    int ret;
+    int num_options = data->tokens[cond_index].value.i;
+    char result[128], *value;
+    int cond_start = cond_index;
+
+    /* find the index of the conditional start token */
+    while (data->tokens[cond_start].type != WPS_TOKEN_CONDITIONAL_START
+           && cond_start < data->num_tokens)
+        cond_start++;
+
+    if (num_options > 2)  /* enum */
+    {
+        int intval = num_options;
+        /* get_tag needs to know the number of options in the enum */
+        get_tag(gwps, cond_index + 1, result, sizeof(result), &intval);
+        /* intval is now the number of the enum option we want to read,
+           starting from 1 */
+        if (intval > num_options || intval < 1)
+            intval = num_options;
+
+        int next = cond_start;
+        int i;
+        for (i = 1; i < intval; i++)
+        {
+            next = data->tokens[next].value.i;
+        }
+        ret = next;
+    }
+    else  /* %?xx<true|false> or %?<true> */
+    {
+        value = get_tag(gwps, cond_index + 1, result, sizeof(result), NULL);
+        ret = value ? cond_start : data->tokens[cond_start].value.i;
+    }
+
+#ifdef HAVE_LCD_BITMAP
+    /* clear all pictures in the conditional */
     int i;
-    if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
+    for (i=0; i < MAX_IMAGES; i++)
     {
-        global_status.resume_index = -1;
-#ifdef HAVE_LCD_CHARCELLS
-        gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER));
-#else
-        gui_syncstatusbar_draw(&statusbars, true);
-        gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
-#endif
-        return true;
+        if (data->img[i].cond_index == cond_index)
+            clear_image_pos(gwps, i);
     }
-    else
+#endif
+
+    return ret;
+}
+
+/* Read a (sub)line to the given alignment format buffer.
+   linebuf is the buffer where the data is actually stored.
+   align is the alignment format that'll be used to display the text.
+   The return value indicates whether the line needs to be updated.
+*/
+static bool get_line(struct gui_wps *gwps,
+                     int line, int subline,
+                     struct align_pos *align,
+                     char *linebuf,
+                     int linebuf_size)
+{
+    struct wps_data *data = gwps->data;
+
+    char temp_buf[128];
+    char *buf = linebuf;  /* will always point to the writing position */
+    char *linebuf_end = linebuf + linebuf_size - 1;
+    bool update = false;
+
+    /* alignment-related variables */
+    int cur_align;
+    char* cur_align_start;
+    cur_align_start = buf;
+    cur_align = WPS_ALIGN_LEFT;
+    align->left = 0;
+    align->center = 0;
+    align->right = 0;
+
+    /* start at the beginning of the current (sub)line */
+    int i = data->format_lines[line][subline];
+
+    while (data->tokens[i].type != WPS_TOKEN_EOL
+           && data->tokens[i].type != WPS_TOKEN_SUBLINE_SEPARATOR
+           && i < data->num_tokens)
     {
-        FOR_NB_SCREENS(i)
+        switch(data->tokens[i].type)
         {
-            gui_wps[i].display->clear_display();
-            if (!gui_wps[i].data->wps_loaded) {
-                if ( !gui_wps[i].data->format_buffer[0] ) {
-                    /* set the default wps for the main-screen */
-                    if(i == 0)
-                    {
+            case WPS_TOKEN_CONDITIONAL:
+                /* place ourselves in the right conditional case */
+                i = evaluate_conditional(gwps, i);
+                break;
+
+            case WPS_TOKEN_CONDITIONAL_OPTION:
+                /* we've finished in the curent conditional case,
+                    skip to the end of the conditional structure */
+                i = find_conditional_end(data, i);
+                break;
+
 #ifdef HAVE_LCD_BITMAP
-#if LCD_DEPTH > 1
-                        unload_wps_backdrop();
+            case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
+            {
+                struct gui_img *img = data->img;
+                int n = get_image_id(data->tokens[i].value.c);
+                if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
+                    img[n].display = true;
+                break;
+            }
 #endif
-                        wps_data_load(gui_wps[i].data,
-                                      "%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
-                        wps_data_load(gui_wps[i].data,
-                                      "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
-                                      "%pc%?ps<*|/>%pt\n", false);
-#endif
-                    }
-#if NB_SCREENS == 2
-                     /* set the default wps for the remote-screen */
-                     else if(i == 1)
-                     {
-                         wps_data_load(gui_wps[i].data,
-                                       "%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", false);
-                     }
-#endif
+
+            case WPS_TOKEN_ALIGN_LEFT:
+            case WPS_TOKEN_ALIGN_CENTER:
+            case WPS_TOKEN_ALIGN_RIGHT:
+                /* remember where the current aligned text started */
+                switch (cur_align)
+                {
+                    case WPS_ALIGN_LEFT:
+                        align->left = cur_align_start;
+                        break;
+
+                    case WPS_ALIGN_CENTER:
+                        align->center = cur_align_start;
+                        break;
+
+                    case WPS_ALIGN_RIGHT:
+                        align->right = cur_align_start;
+                        break;
                 }
+                /* start a new alignment */
+                switch (data->tokens[i].type)
+                {
+                    case WPS_TOKEN_ALIGN_LEFT:
+                        cur_align = WPS_ALIGN_LEFT;
+                        break;
+                    case WPS_TOKEN_ALIGN_CENTER:
+                        cur_align = WPS_ALIGN_CENTER;
+                        break;
+                    case WPS_TOKEN_ALIGN_RIGHT:
+                        cur_align = WPS_ALIGN_RIGHT;
+                        break;
+                    default:
+                        break;
+                }
+                *buf++ = 0;
+                cur_align_start = buf;
+                break;
+
+            default:
+            {
+                /* get the value of the tag and copy it to the buffer */
+                char *value = get_tag(gwps, i, temp_buf,
+                                      sizeof(temp_buf), NULL);
+                if (value)
+                {
+                    update = true;
+                    while (*value && (buf < linebuf_end))
+                        *buf++ = *value++;
+                }
+                break;
             }
         }
+        i++;
     }
-    yield();
-    FOR_NB_SCREENS(i)
+
+    /* close the current alignment */
+    switch (cur_align)
     {
-        gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
+        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 false;
+
+    return update;
 }
 
-bool update(struct gui_wps *gwps)
+/* Calculate which subline should be displayed for each line */
+static bool get_curr_subline(struct wps_data *data, int line)
 {
-    bool track_changed = audio_has_changed_track();
-    bool retcode = false;
+    int search, search_start;
+    bool reset_subline;
+    bool new_subline_refresh;
+    bool only_one_subline;
 
-    gwps->state->nid3 = audio_next_track();
-    if (track_changed)
+    reset_subline = (data->curr_subline[line] == SUBLINE_RESET);
+    new_subline_refresh = false;
+    only_one_subline = false;
+
+    /* if time to advance to next sub-line  */
+    if (TIME_AFTER(current_tick, data->subline_expire_time[line] - 1) ||
+        reset_subline)
     {
-        gwps->display->stop_scroll();
-        gwps->state->id3 = audio_current_track();
+        /* search all sublines until the next subline with time > 0
+            is found or we get back to the subline we started with */
+        if (reset_subline)
+            search_start = 0;
+        else
+            search_start = data->curr_subline[line];
 
-        if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
-            && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
+        for (search = 0; search < WPS_MAX_SUBLINES; search++)
         {
-            /* the current cuesheet isn't the right one any more */
+            data->curr_subline[line]++;
 
-            if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
-                /* We have the new cuesheet in memory (temp_cue),
-                   let's make it the current one ! */
-                memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
+            /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
+            if ((!data->format_lines[line][data->curr_subline[line]]) ||
+                (data->curr_subline[line] == WPS_MAX_SUBLINES))
+            {
+                if (data->curr_subline[line] == 1)
+                    only_one_subline = true;
+                data->curr_subline[line] = 0;
             }
-            else {
-                /* We need to parse the new cuesheet */
 
-                char cuepath[MAX_PATH];
-                strncpy(cuepath, gwps->state->id3->path, MAX_PATH);
-                char *dot = strrchr(cuepath, '.');
-                strcpy(dot, ".cue");
-
-                if (parse_cuesheet(cuepath, curr_cue))
+            /* if back where we started after search or
+                only one subline is defined on the line */
+            if (((search > 0) && (data->curr_subline[line] == search_start)) ||
+                only_one_subline)
+            {
+                /* no other subline with a time > 0 exists */
+                data->subline_expire_time[line] = (reset_subline?
+                    current_tick : data->subline_expire_time[line]) + 100 * HZ;
+                break;
+            }
+            else
+            {
+                /* only use this subline if subline time > 0 */
+                if (data->time_mult[line][data->curr_subline[line]] > 0)
                 {
-                    gwps->state->id3->cuesheet_type = 1;
-                    strcpy(curr_cue->audio_filename, gwps->state->id3->path);
+                    new_subline_refresh = true;
+                    data->subline_expire_time[line] = (reset_subline ?
+                        current_tick : data->subline_expire_time[line]) +
+                        BASE_SUBLINE_TIME * data->time_mult[line][data->curr_subline[line]];
+                    break;
                 }
             }
+        }
+    }
 
-            cue_spoof_id3(curr_cue, gwps->state->id3);
+    return new_subline_refresh;
+}
+
+/* Display a line appropriately according to its alignment format.
+   format_align contains the text, separated between left, center and right.
+   line is the index of the line on the screen.
+   scroll indicates whether the line is a scrolling one or not.
+*/
+static void write_line(struct screen *display,
+                       struct align_pos *format_align,
+                       int line,
+                       bool scroll)
+{
+
+    int left_width,   left_xpos;
+    int center_width, center_xpos;
+    int right_width,  right_xpos;
+    int ypos;
+    int space_width;
+    int string_height;
+
+    /* calculate different string sizes and positions */
+    display->getstringsize((unsigned char *)" ", &space_width, &string_height);
+    if (format_align->left != 0) {
+        display->getstringsize((unsigned char *)format_align->left,
+                                &left_width, &string_height);
+    }
+    else {
+        left_width = 0;
+    }
+    left_xpos = 0;
+
+    if (format_align->center != 0) {
+        display->getstringsize((unsigned char *)format_align->center,
+                                &center_width, &string_height);
+    }
+    else {
+        center_width = 0;
+    }
+    center_xpos=(display->width - center_width) / 2;
+
+    if (format_align->right != 0) {
+        display->getstringsize((unsigned char *)format_align->right,
+                                &right_width, &string_height);
+    }
+    else {
+        right_width = 0;
+    }
+    right_xpos = (display->width - right_width);
+
+    /* Checks for overlapping strings.
+        If needed the overlapping strings will be merged, separated by a
+        space */
+
+    /* CASE 1: left and centered string overlap */
+    /* there is a left string, need to merge left and center */
+    if ((left_width != 0 && center_width != 0) &&
+        (left_xpos + left_width + space_width > center_xpos)) {
+        /* replace the former separator '\0' of left and
+            center string with a space */
+        *(--format_align->center) = ' ';
+        /* calculate the new width and position of the merged string */
+        left_width = left_width + space_width + center_width;
+        left_xpos = 0;
+        /* there is no centered string anymore */
+        center_width = 0;
+    }
+    /* there is no left string, move center to left */
+    if ((left_width == 0 && center_width != 0) &&
+        (left_xpos + left_width > center_xpos)) {
+        /* move the center string to the left string */
+        format_align->left = format_align->center;
+        /* calculate the new width and position of the string */
+        left_width = center_width;
+        left_xpos = 0;
+        /* there is no centered string anymore */
+        center_width = 0;
+    }
+
+    /* CASE 2: centered and right string overlap */
+    /* there is a right string, need to merge center and right */
+    if ((center_width != 0 && right_width != 0) &&
+        (center_xpos + center_width + space_width > right_xpos)) {
+        /* replace the former separator '\0' of center and
+            right string with a space */
+        *(--format_align->right) = ' ';
+        /* move the center string to the right after merge */
+        format_align->right = format_align->center;
+        /* calculate the new width and position of the merged string */
+        right_width = center_width + space_width + right_width;
+        right_xpos = (display->width - right_width);
+        /* there is no centered string anymore */
+        center_width = 0;
+    }
+    /* there is no right string, move center to right */
+    if ((center_width != 0 && right_width == 0) &&
+        (center_xpos + center_width > right_xpos)) {
+        /* move the center string to the right string */
+        format_align->right = format_align->center;
+        /* calculate the new width and position of the string */
+        right_width = center_width;
+        right_xpos = (display->width - right_width);
+        /* there is no centered string anymore */
+        center_width = 0;
+    }
+
+    /* CASE 3: left and right overlap
+        There is no center string anymore, either there never
+        was one or it has been merged in case 1 or 2 */
+    /* there is a left string, need to merge left and right */
+    if ((left_width != 0 && center_width == 0 && right_width != 0) &&
+        (left_xpos + left_width + space_width > right_xpos)) {
+        /* replace the former separator '\0' of left and
+            right string with a space */
+        *(--format_align->right) = ' ';
+        /* calculate the new width and position of the string */
+        left_width = left_width + space_width + right_width;
+        left_xpos = 0;
+        /* there is no right string anymore */
+        right_width = 0;
+    }
+    /* there is no left string, move right to left */
+    if ((left_width == 0 && center_width == 0 && right_width != 0) &&
+        (left_xpos + left_width > right_xpos)) {
+        /* move the right string to the left string */
+        format_align->left = format_align->right;
+        /* calculate the new width and position of the string */
+        left_width = right_width;
+        left_xpos = 0;
+        /* there is no right string anymore */
+        right_width = 0;
+    }
+
+    ypos = (line * string_height) + display->getymargin();
+
+
+    if (scroll && left_width > display->width)
+    {
+        display->puts_scroll(0, line,
+                             (unsigned char *)format_align->left);
+    }
+    else
+    {
+#ifdef HAVE_LCD_BITMAP
+        /* clear the line first */
+        display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
+        display->fillrect(0, ypos, display->width, string_height);
+        display->set_drawmode(DRMODE_SOLID);
+#endif
+
+        /* Nasty hack: we output an empty scrolling string,
+        which will reset the scroller for that line */
+        display->puts_scroll(0, line, (unsigned char *)"");
+
+        /* print aligned strings */
+        if (left_width != 0)
+        {
+            display->putsxy(left_xpos, ypos,
+                            (unsigned char *)format_align->left);
         }
+        if (center_width != 0)
+        {
+            display->putsxy(center_xpos, ypos,
+                            (unsigned char *)format_align->center);
+        }
+        if (right_width != 0)
+        {
+            display->putsxy(right_xpos, ypos,
+                            (unsigned char *)format_align->right);
+        }
+    }
+}
 
-        if (gui_wps_display())
-            retcode = true;
-        else{
-            gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
+/* Refresh the WPS according to refresh_mode. */
+bool gui_wps_refresh(struct gui_wps *gwps,
+                     int ffwd_offset,
+                     unsigned char refresh_mode)
+{
+    struct wps_data *data = gwps->data;
+    struct screen *display = gwps->display;
+    struct wps_state *state = gwps->state;
+
+    if(!gwps || !data || !state || !display)
+        return false;
+
+    int line, i;
+    unsigned char flags;
+    char linebuf[MAX_PATH];
+
+    struct align_pos align;
+    align.left = NULL;
+    align.center = NULL;
+    align.right = NULL;
+
+    bool update_line, new_subline_refresh;
+
+#ifdef HAVE_LCD_BITMAP
+    gui_wps_statusbar_draw(gwps, true);
+
+    /* to find out wether the peak meter is enabled we
+       assume it wasn't until we find a line that contains
+       the peak meter. We can't use peak_meter_enabled itself
+       because that would mean to turn off the meter thread
+       temporarily. (That shouldn't matter unless yield
+       or sleep is called but who knows...)
+    */
+    bool enable_pm = false;
+
+    /* Set images to not to be displayed */
+    for (i = 0; i < MAX_IMAGES; i++)
+    {
+        data->img[i].display = false;
+    }
+#endif
+
+    /* reset to first subline if refresh all flag is set */
+    if (refresh_mode == WPS_REFRESH_ALL)
+    {
+        for (i = 0; i < data->num_lines; i++)
+        {
+            data->curr_subline[i] = SUBLINE_RESET;
         }
+    }
 
-        if (gwps->state->id3)
-            memcpy(gwps->state->current_track_path, gwps->state->id3->path,
-                   sizeof(gwps->state->current_track_path));
+#ifdef HAVE_LCD_CHARCELLS
+    for (i = 0; i < 8; i++)
+    {
+       if (data->wps_progress_pat[i] == 0)
+           data->wps_progress_pat[i] = display->get_locked_pattern();
     }
+#endif
 
-    if (gwps->state->id3)
+    if (!state->id3)
     {
-        if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
-            && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
-                || (curr_cue->curr_track_idx < curr_cue->track_count - 1
-                    && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
+        display->stop_scroll();
+        return false;
+    }
+
+    state->ff_rewind_count = ffwd_offset;
+
+    for (line = 0; line < data->num_lines; line++)
+    {
+        memset(linebuf, 0, sizeof(linebuf));
+        update_line = false;
+
+        /* get current subline for the line */
+        new_subline_refresh = get_curr_subline(data, line);
+
+        flags = data->line_type[line][data->curr_subline[line]];
+
+        if (refresh_mode == WPS_REFRESH_ALL || flags & refresh_mode
+            || new_subline_refresh)
         {
-            /* We've changed tracks within the cuesheet :
-               we need to update the ID3 info and refresh the WPS */
+            /* get_line tells us if we need to update the line */
+            update_line = get_line(gwps, line, data->curr_subline[line],
+                                   &align, linebuf, sizeof(linebuf));
+        }
 
-            cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
-            cue_spoof_id3(curr_cue, gwps->state->id3);
+#ifdef HAVE_LCD_BITMAP
+        /* progressbar */
+        if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
+            draw_progressbar(gwps, line);
 
-            gwps->display->stop_scroll();
-            if (gui_wps_display())
-                retcode = true;
+        /* peakmeter */
+        if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
+        {
+            int h = font_get(FONT_UI)->height;
+            int peak_meter_y = display->getymargin() + line * h;
+
+            /* The user might decide to have the peak meter in the last
+                line so that it is only displayed if no status bar is
+                visible. If so we neither want do draw nor enable the
+                peak meter. */
+            if (peak_meter_y + h <= display->height) {
+                /* found a line with a peak meter -> remember that we must
+                    enable it later */
+                enable_pm = true;
+                peak_meter_screen(gwps->display, 0, peak_meter_y,
+                                  MIN(h, display->height - peak_meter_y));
+            }
+        }
+
+#else /* HAVE_LCD_CHARCELL */
+
+        /* progressbar */
+        if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
+        {
+            if (data->full_line_progressbar)
+                draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
             else
-                gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
+                draw_player_progress(gwps);
         }
-        else
-            gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
+#endif
+
+        if (update_line)
+        {
+            /* calculate alignment and draw the strings */
+            write_line(display, &align, line, flags & WPS_REFRESH_SCROLL);
+        }
     }
 
-    gui_wps_statusbar_draw(gwps, false);
+#ifdef HAVE_LCD_BITMAP
+    data->peak_meter_enabled = enable_pm;
+    wps_display_images(gwps);
+#endif
 
-    return retcode;
-}
+    display->update();
 
+#if CONFIG_BACKLIGHT
+    if (global_settings.caption_backlight && state->id3)
+    {
+        /* turn on backlight n seconds before track ends, and turn it off n
+           seconds into the new track. n == backlight_timeout, or 5s */
+        int n = backlight_timeout_value[global_settings.backlight_timeout]
+                * 1000;
 
-void display_keylock_text(bool locked)
-{
-    char* s;
-    int i;
-    FOR_NB_SCREENS(i)
-        gui_wps[i].display->stop_scroll();
+        if ( n < 1000 )
+            n = 5000; /* use 5s if backlight is always on or off */
 
-#ifdef HAVE_LCD_CHARCELLS
-    if(locked)
-        s = str(LANG_KEYLOCK_ON_PLAYER);
-    else
-        s = str(LANG_KEYLOCK_OFF_PLAYER);
-#else
-    if(locked)
-        s = str(LANG_KEYLOCK_ON_RECORDER);
-    else
-        s = str(LANG_KEYLOCK_OFF_RECORDER);
+        if (((state->id3->elapsed < 1000) ||
+             ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
+            (state->paused == false))
+            backlight_on();
+    }
 #endif
-    gui_syncsplash(HZ, s);
-}
+#ifdef HAVE_REMOTE_LCD
+    if (global_settings.remote_caption_backlight && state->id3)
+    {
+        /* turn on remote backlight n seconds before track ends, and turn it
+           off n seconds into the new track. n == remote_backlight_timeout,
+           or 5s */
+        int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
+                * 1000;
 
+        if ( n < 1000 )
+            n = 5000; /* use 5s if backlight is always on or off */
+
+        if (((state->id3->elapsed < 1000) ||
+             ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
+            (state->paused == false))
+            remote_backlight_on();
+    }
+#endif
+
+    return true;
+}
Index: apps/gui/gwps-common.h
===================================================================
--- apps/gui/gwps-common.h	(rÃ©vision 13003)
+++ apps/gui/gwps-common.h	(copie de travail)
@@ -7,7 +7,7 @@
  *                     \/            \/     \/    \/            \/
  * $Id$
  *
- * Copyright (C) 2002 Björn Stenberg
+ * Copyright (C) 2007 Nicolas Pennequin
  *
  * All files in this archive are subject to the GNU General Public License.
  * See the file COPYING in the source tree root for full license agreement.
@@ -24,16 +24,15 @@
 #include "gwps.h"
 
 void fade(bool fade_in);
-void gui_wps_format(struct wps_data *data);
-bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset,
-                     unsigned char refresh_mode);
 bool gui_wps_display(void);
 void setvol(void);
 bool update_onvol_change(struct gui_wps * gwps);
 bool update(struct gui_wps *gwps);
 bool ffwd_rew(int button);
-bool wps_data_preload_tags(struct wps_data *data, char *buf,
-                            const char *bmpdir, size_t bmpdirlen);
 void display_keylock_text(bool locked);
+
+bool gui_wps_refresh(struct gui_wps *gwps,
+                     int ffwd_offset,
+                     unsigned char refresh_mode);
 #endif
 
Index: apps/gui/gwps.c
===================================================================
--- apps/gui/gwps.c	(rÃ©vision 13003)
+++ apps/gui/gwps.c	(copie de travail)
@@ -81,7 +81,6 @@
 /* connects a wps with a statusbar*/
 static void gui_wps_set_statusbar(struct gui_wps *gui_wps, struct gui_statusbar *statusbar);
 
-
 #ifdef HAVE_LCD_BITMAP
 static void gui_wps_set_margin(struct gui_wps *gwps)
 {
@@ -673,8 +672,19 @@
 
 /* wps_data*/
 #ifdef HAVE_LCD_BITMAP
+
+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;
+}
+
 /* Clear the WPS image cache */
-static void wps_clear(struct wps_data *data )
+static void wps_images_clear(struct wps_data *data )
 {
     int i;
     /* set images to unloaded and not displayed */
@@ -688,14 +698,15 @@
     data->progressbar.have_bitmap_pb=false;
 }
 #else
-#define wps_clear(a)
+#define wps_images_clear(a)
 #endif
 
 /* initial setup of wps_data */
 void wps_data_init(struct wps_data *wps_data)
 {
 #ifdef HAVE_LCD_BITMAP
-    wps_clear(wps_data);
+    wps_images_clear(wps_data);
+    wps_data->peak_meter_enabled = false;
 #else /* HAVE_LCD_CHARCELLS */
     {
         int i;
@@ -706,13 +717,12 @@
 #endif
     wps_data->format_buffer[0] = '\0';
     wps_data->wps_loaded = false;
-    wps_data->peak_meter_enabled = false;
 }
 
 static void wps_reset(struct wps_data *data)
 {
+    memset(data, 0, sizeof(*data));
     data->wps_loaded = false;
-    memset(&data->format_buffer, 0, sizeof data->format_buffer);
     wps_data_init(data);
 }
 
@@ -727,12 +737,13 @@
     if(!wps_data || !buf)
         return false;
 
+    wps_reset(wps_data);
+
     if(!isfile)
     {
-        wps_clear(wps_data);
         strncpy(wps_data->format_buffer, buf, sizeof(wps_data->format_buffer));
         wps_data->format_buffer[sizeof(wps_data->format_buffer) - 1] = 0;
-        gui_wps_format(wps_data);
+        wps_load(wps_data);
         return true;
     }
     else
@@ -744,7 +755,6 @@
          */
         if (! strcmp(buf, WPS_DEFAULTCFG) )
         {
-            wps_reset(wps_data);
             global_settings.wps_file[0] = 0;
             return false;
         }
@@ -752,24 +762,24 @@
 #ifdef HAVE_REMOTE_LCD
         if (! strcmp(buf, RWPS_DEFAULTCFG) )
         {
-            wps_reset(wps_data);
             global_settings.rwps_file[0] = 0;
             return false;
         }
 #endif
 
-        size_t bmpdirlen;
-        char *bmpdir = strrchr(buf, '.');
-        bmpdirlen = bmpdir - buf;
-
         fd = open(buf, O_RDONLY);
 
         if (fd >= 0)
         {
             unsigned int start = 0;
 
-            wps_reset(wps_data);
 #ifdef HAVE_LCD_BITMAP
+            size_t bmpdirlen;
+            char *bmpdir = strrchr(buf, '.');
+            bmpdirlen = bmpdir - buf;
+            strncpy(wps_data->bmpdir, buf, bmpdir - buf);
+            wps_data->bmpdir[bmpdirlen] = 0;
+
             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 */
@@ -777,23 +787,19 @@
             while( ( read_line(fd, &wps_data->format_buffer[start],
                     sizeof(wps_data->format_buffer)-start) ) > 0 )
             {
-                if(!wps_data_preload_tags(wps_data,
-                                          &wps_data->format_buffer[start],
-                                          buf, bmpdirlen))
+
+                start += strlen(&wps_data->format_buffer[start]);
+
+                if (start < sizeof(wps_data->format_buffer) - 1)
                 {
-                    start += strlen(&wps_data->format_buffer[start]);
-
-                    if (start < sizeof(wps_data->format_buffer) - 1)
-                    {
-                        wps_data->format_buffer[start++] = '\n';
-                        wps_data->format_buffer[start] = 0;
-                    }
+                    wps_data->format_buffer[start++] = '\n';
+                    wps_data->format_buffer[start] = 0;
                 }
             }
 
             if (start > 0)
             {
-                gui_wps_format(wps_data);
+                wps_load(wps_data);
             }
 
             close(fd);
Index: apps/gui/gwps.h
===================================================================
--- apps/gui/gwps.h	(rÃ©vision 13003)
+++ apps/gui/gwps.h	(copie de travail)
@@ -7,7 +7,7 @@
  *                     \/            \/     \/    \/            \/
  * $Id$
  *
- * Copyright (C) 2002 Jerome Kuptz
+ * Copyright (C) 2007 Nicolas Pennequin
  *
  * All files in this archive are subject to the GNU General Public License.
  * See the file COPYING in the source tree root for full license agreement.
@@ -49,6 +49,7 @@
     bool loaded;            /* load state */
     bool display;           /* is to be displayed */
     bool always_display;    /* not using the preload/display mechanism */
+    int cond_index;         /* the index of the conditional the image is in */
 };
 
 struct prog_img{ /*progressbar image*/
@@ -64,23 +65,191 @@
 };
 
 #ifdef HAVE_LCD_BITMAP
+
 #define MAX_IMAGES (26*2) /* a-z and A-Z */
 #define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \
                    + (2*LCD_HEIGHT*LCD_WIDTH/8))
 #define WPS_MAX_LINES (LCD_HEIGHT/5+1)
 #define FORMAT_BUFFER_SIZE 3072
+#define WPS_MAX_TOKENS 1024
+#define WPS_MAX_STRINGS 128
+#define STRING_BUFFER_SIZE 512
+#define WPS_MAX_COND_LEVEL 10
+
 #else
+
 #define WPS_MAX_LINES 2
 #define FORMAT_BUFFER_SIZE 400
+#define WPS_MAX_TOKENS 64
+#define WPS_MAX_STRINGS 16
+#define STRING_BUFFER_SIZE 61
+#define WPS_MAX_COND_LEVEL 5
+
 #endif
+
 #define WPS_MAX_SUBLINES 12
 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* (10ths of sec) */
 #define BASE_SUBLINE_TIME 10 /* base time that multiplier is applied to
                                 (1/HZ sec, or 100ths of sec) */
 #define SUBLINE_RESET -1
 
+enum wps_token_type {
+    WPS_NO_TOKEN,   /* for WPS tags we don't want to save as tokens */
+    WPS_TOKEN_UNKNOWN,
+
+    /* Markers */
+    WPS_TOKEN_CHARACTER,
+    WPS_TOKEN_STRING,
+    WPS_TOKEN_EOL,
+
+    /* Alignment */
+    WPS_TOKEN_ALIGN_LEFT,
+    WPS_TOKEN_ALIGN_CENTER,
+    WPS_TOKEN_ALIGN_RIGHT,
+
+    /* Scrolling */
+    WPS_TOKEN_SCROLL,
+
+    /* Alternating sublines */
+    WPS_TOKEN_SUBLINE_SEPARATOR,
+    WPS_TOKEN_SUBLINE_TIMEOUT,
+
+    /* Battery */
+    WPS_TOKEN_BATTERY_PERCENT,
+    WPS_TOKEN_BATTERY_VOLTS,
+    WPS_TOKEN_BATTERY_TIME,
+    WPS_TOKEN_BATTERY_CHARGER_CONNECTED,
+    WPS_TOKEN_BATTERY_CHARGING,
+    WPS_TOKEN_BATTERY_SLEEPTIME,
+
+#if (CONFIG_CODEC == SWCODEC)
+    /* Sound */
+    WPS_TOKEN_SOUND_PITCH,
+    WPS_TOKEN_REPLAYGAIN,
+#endif
+
+#if CONFIG_RTC
+    /* Time */
+    WPS_TOKEN_RTC,
+    WPS_TOKEN_RTC_DAY_OF_MONTH,
+    WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,
+    WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED,
+    WPS_TOKEN_RTC_HOUR_24,
+    WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED,
+    WPS_TOKEN_RTC_HOUR_12,
+    WPS_TOKEN_RTC_MONTH,
+    WPS_TOKEN_RTC_MINUTE,
+    WPS_TOKEN_RTC_SECOND,
+    WPS_TOKEN_RTC_YEAR_2_DIGITS,
+    WPS_TOKEN_RTC_YEAR_4_DIGITS,
+    WPS_TOKEN_RTC_AM_PM_UPPER,
+    WPS_TOKEN_RTC_AM_PM_LOWER,
+    WPS_TOKEN_RTC_WEEKDAY_NAME,
+    WPS_TOKEN_RTC_MONTH_NAME,
+    WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON,
+    WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN,
+#endif
+
+    /* Conditional */
+    WPS_TOKEN_CONDITIONAL,
+    WPS_TOKEN_CONDITIONAL_START,
+    WPS_TOKEN_CONDITIONAL_OPTION,
+    WPS_TOKEN_CONDITIONAL_END,
+
+    /* Database */
+    WPS_TOKEN_DATABASE_PLAYCOUNT,
+    WPS_TOKEN_DATABASE_RATING,
+
+    /* File */
+    WPS_TOKEN_FILE_BITRATE,
+    WPS_TOKEN_FILE_CODEC,
+    WPS_TOKEN_FILE_FREQUENCY,
+    WPS_TOKEN_FILE_NAME,
+    WPS_TOKEN_FILE_NAME_WITH_EXTENSION,
+    WPS_TOKEN_FILE_PATH,
+    WPS_TOKEN_FILE_SIZE,
+    WPS_TOKEN_FILE_VBR,
+    WPS_TOKEN_FILE_DIRECTORY,
+
+#ifdef HAVE_LCD_BITMAP
+    /* Image */
+    WPS_TOKEN_IMAGE_BACKDROP,
+    WPS_TOKEN_IMAGE_PROGRESS_BAR,
+    WPS_TOKEN_IMAGE_PRELOAD,
+    WPS_TOKEN_IMAGE_PRELOAD_DISPLAY,
+    WPS_TOKEN_IMAGE_DISPLAY,
+#endif
+
+    /* Metadata */
+    WPS_TOKEN_METADATA_ARTIST,
+    WPS_TOKEN_METADATA_COMPOSER,
+    WPS_TOKEN_METADATA_ALBUM_ARTIST,
+    WPS_TOKEN_METADATA_ALBUM,
+    WPS_TOKEN_METADATA_GENRE,
+    WPS_TOKEN_METADATA_TRACK_NUMBER,
+    WPS_TOKEN_METADATA_TRACK_TITLE,
+    WPS_TOKEN_METADATA_VERSION,
+    WPS_TOKEN_METADATA_YEAR,
+    WPS_TOKEN_METADATA_COMMENT,
+
+    /* Mode */
+    WPS_TOKEN_REPEAT_MODE,
+    WPS_TOKEN_PLAYBACK_STATUS,
+
+#ifdef HAS_BUTTON_HOLD
+    WPS_TOKEN_MAIN_HOLD,
+#endif
+#ifdef HAS_REMOTE_BUTTON_HOLD
+    WPS_TOKEN_REMOTE_HOLD,
+#endif
+
+    /* Progressbar */
+    WPS_TOKEN_PROGRESSBAR,
+    WPS_TOKEN_PLAYER_PROGRESSBAR,
+
+#ifdef HAVE_LCD_BITMAP
+    /* Peakmeter */
+    WPS_TOKEN_PEAKMETER,
+#endif
+
+    /* Volume level */
+    WPS_TOKEN_VOLUME,
+
+    /* Current track */
+    WPS_TOKEN_TRACK_TIME_ELAPSED,
+    WPS_TOKEN_TRACK_TIME_REMAINING,
+    WPS_TOKEN_TRACK_LENGTH,
+
+    /* Playlist */
+    WPS_TOKEN_PLAYLIST_ENTRIES,
+    WPS_TOKEN_PLAYLIST_NAME,
+    WPS_TOKEN_PLAYLIST_POSITION,
+    WPS_TOKEN_PLAYLIST_SHUFFLE,
+
+#ifdef HAVE_LCD_BITMAP
+    /* Statusbar */
+    WPS_TOKEN_STATUSBAR_ENABLED,
+    WPS_TOKEN_STATUSBAR_DISABLED,
+#endif
+
+#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
+    /* Virtual LED */
+    WPS_TOKEN_VLED_HDD
+#endif
+};
+
+struct wps_token {
+    enum wps_token_type type;   /* the first bit is used as 'bool next' */
+    bool next;
+    union {
+        char c;
+        unsigned short i;
+    } value;
+};
+
+
 /* wps_data
-   this struct old all necessary data which describes the
+   this struct holds all necessary data which describes the
    viewable content of a wps */
 struct wps_data
 {
@@ -89,26 +258,36 @@
     struct prog_img progressbar;
     unsigned char img_buf[IMG_BUFSIZE];
     unsigned char* img_buf_ptr;
+    char bmpdir[MAX_PATH];
     int img_buf_free;
     bool wps_sb_tag;
     bool show_sb_on_wps;
-#endif
-#ifdef HAVE_LCD_CHARCELLS
+
+    int progress_top;
+    int progress_height;
+    int progress_start;
+    int progress_end;
+    bool peak_meter_enabled;
+#else /*HAVE_LCD_CHARCELLS */
     unsigned short wps_progress_pat[8];
     bool full_line_progressbar;
 #endif
     char format_buffer[FORMAT_BUFFER_SIZE];
-    char* format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES];
+    int format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES];
     unsigned char line_type[WPS_MAX_LINES][WPS_MAX_SUBLINES];
     unsigned short time_mult[WPS_MAX_LINES][WPS_MAX_SUBLINES];
     long subline_expire_time[WPS_MAX_LINES];
     int curr_subline[WPS_MAX_LINES];
-    int progress_top;
-    int progress_height;
-    int progress_start;
-    int progress_end;
+    int num_lines;
+    int num_sublines[WPS_MAX_LINES];
+
+    char string_buffer[STRING_BUFFER_SIZE];
+    char *strings[WPS_MAX_STRINGS];
+    int num_strings;
+    struct wps_token tokens[WPS_MAX_TOKENS];
+    int num_tokens;
+
     bool wps_loaded;
-    bool peak_meter_enabled;
 };
 
 /* initial setup of wps_data */
@@ -120,6 +299,8 @@
                    const char *buf,
                    bool isfile);
 
+bool wps_load(struct wps_data *wps_data);
+
 /* wps_data end */
 
 /* wps_state
@@ -170,4 +351,6 @@
 void gui_sync_wps_init(void);
 void gui_sync_wps_screen_init(void);
 
+int get_image_id(int c);
+
 #endif
Index: apps/SOURCES
===================================================================
--- apps/SOURCES	(rÃ©vision 13003)
+++ apps/SOURCES	(copie de travail)
@@ -58,6 +58,8 @@
 gui/statusbar.c
 gui/textarea.c
 gui/yesno.c
+gui/wps_debug.c
+gui/wps_parser.c
 
 #ifdef HAVE_LCD_CHARCELLS
 player/icons.c
