Index: apps/SOURCES
===================================================================
RCS file: /cvsroot/rockbox/apps/SOURCES,v
retrieving revision 1.58
diff -u -b -B -p -r1.58 SOURCES
--- apps/SOURCES	19 Nov 2006 14:11:40 -0000	1.58
+++ apps/SOURCES	18 Dec 2006 23:37:37 -0000
@@ -23,6 +23,7 @@ settings.c
 settings_menu.c
 sound_menu.c
 status.c
+cuesheet.c
 #if !defined(SIMULATOR) || CONFIG_CODEC == SWCODEC
 talk.c
 #endif
Index: apps/cuesheet.c
===================================================================
RCS file: apps/cuesheet.c
diff -N apps/cuesheet.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/cuesheet.c	18 Dec 2006 23:37:38 -0000
@@ -0,0 +1,372 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id: cuesheet.c,v 1.17 2006-12-05 10:09:06 markun Exp $
+ *
+ *
+ * Copyright (C) 2006 Nicolas Pennequin, Jonathan Gordon
+ *
+ * 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <atoi.h>
+#include <string.h>
+#include "system.h"
+#include "audio.h"
+#include "kernel.h"
+#include "logf.h"
+#include "sprintf.h"
+#include "misc.h"
+#include "screens.h"
+#include "splash.h"
+#include "list.h"
+#include "action.h"
+#include "lang.h"
+#include "debug.h"
+
+#include "cuesheet.h"
+
+/* struct mp3entry* id3; */
+
+bool is_cuesheet(char *filename)
+{
+    char *suffix;
+    suffix = strrchr(filename, '.');
+    if (suffix)
+        return (strcasecmp(suffix, ".cue") == 0);
+    else
+        return false;
+}
+
+char *skip_whitespace(char* buf)
+{
+    char *r = buf;
+    while (*r && (*r < 33))
+        r++;
+    return r;
+}
+
+/* Copy the name of the audio track associated to a cuesheet.
+ * The name is placed in "buf".
+ * Returns false if "filename" isn't a cuesheet or if there was an error.
+ */
+bool get_trackname_from_cuesheet(char *filename, char *buf)
+{
+    if (!is_cuesheet(filename))
+        return false;
+
+    char line[MAX_PATH], audio_filename[MAX_PATH];
+    char *s, *start, *end, *slash;
+    int fd = open(filename,O_RDONLY);
+    if (fd<0)
+    {
+        gui_syncsplash(HZ*2,true,"Couldnt open file: %s",filename);
+        return false;
+    }
+
+    while (read_line(fd,line,MAX_PATH))
+    {
+        s = skip_whitespace(line);
+        if (MATCH(s, "FILE"))
+        {
+            start = strchr(s,'"');
+            if (!start)
+                break;
+            end = strchr(++start,'"');
+            if (!end)
+                break;
+            *end = '\0';
+            strncpy(audio_filename,start,MAX_PATH);
+
+            strncpy(buf,filename,MAX_PATH);
+            slash = strrchr(buf,'/');
+            strncpy(slash+1,audio_filename,MAX_NAME);
+        }
+    }
+
+    close(fd);
+    return true;
+}
+
+/* parse cuesheet "file" and store the information in "cue" */
+bool parse_cuesheet(char *file, struct cuesheet *cue)
+{
+    char line[MAX_PATH];
+    char *s, *start, *end, *slash;
+    int fd = open(file,O_RDONLY);
+    if (fd<0)
+    {
+        gui_syncsplash(HZ*2,true,"Couldnt open file: %s",file);
+        return false;
+    }
+
+    /* strncpy(cue_current,file,MAX_PATH); */
+
+    cue->track_count = -1;
+    while (read_line(fd,line,MAX_PATH))
+    {
+        s = skip_whitespace(line);
+        if (MATCH(s, "TITLE"))
+        {
+            start = strchr(s,'"');
+            if (!start)
+                break;
+            end = strchr(++start,'"');
+            if (!end)
+                break;
+            *end = '\0';
+            if (cue->track_count < 0)
+                strncpy(cue->title,start,MAX_NAME);
+            else strncpy(cue->tracks[cue->track_count].title,
+                         start,MAX_NAME);
+        }
+        else if (MATCH(s, "PERFORMER"))
+        {
+            start = strchr(s,'"');
+            if (!start)
+                break;
+            end = strchr(++start,'"');
+            if (!end)
+                break;
+            *end = '\0';
+            if (cue->track_count < 0)
+                strncpy(cue->performer,start,MAX_NAME);
+            else strncpy(cue->tracks[cue->track_count].performer,
+                         start,MAX_NAME);
+        }
+        else if (MATCH(s, "FILE"))
+        {
+            start = strchr(s,'"');
+            if (!start)
+                break;
+            end = strchr(++start,'"');
+            if (!end)
+                break;
+            *end = '\0';
+
+            strncpy(cue->audio_filename,file,MAX_PATH);
+            slash = strrchr(cue->audio_filename,'/');
+            strncpy(slash+1,start,MAX_NAME);
+        }
+        else if (MATCH(s, "TRACK"))
+            cue->track_count++;
+        else if (MATCH(s, "INDEX"))
+        {
+            char min[3], sec[3], fr[3];
+            s = strchr(s,' ');
+            s = skip_whitespace(s);
+            s = strchr(s,' ');
+            s = skip_whitespace(s);
+            strncpy(min,s,3);
+            s += 3;min[2] ='\0';
+            strncpy(sec,s,3);
+            s += 3;sec[2] ='\0';
+            strncpy(fr,s,3);fr[2] ='\0';
+            cue->tracks[cue->track_count].offset = (13*atoi(fr))
+                + (1000*atoi(sec))
+                + (1000*atoi(min)*60);
+            logf("%d %ld",cue->track_count,cue->tracks[cue->track_count].offset);
+        }
+    }
+    close(fd);
+    /* cue_loaded = true; */
+    return true;
+}
+
+/* Replace the filename of a cuesheet by the name of the audio track it plays
+ * and replace the file descriptor to that of the new opened file.
+ * Returns true if the file was a cuesheet and the filename was replaced,
+ * or false if the file wasn't a cuesheet or if there was an error.
+ * allows for parsing the cuesheet at the same time, storing its information
+ * in the gloabl cuesheet struct.
+ */
+bool cue_spoof_track(char *trackname, int *current_fd, bool parse)
+{
+    if (is_cuesheet(trackname))
+    {
+        if (parse)
+        {
+            parse_cuesheet(trackname, &curr_cue);
+            strncpy(trackname,curr_cue.audio_filename,MAX_PATH);
+        }
+        else
+        {
+            char buf[MAX_PATH];
+            get_trackname_from_cuesheet(trackname,buf);
+            strncpy(trackname,buf,MAX_PATH);
+        }
+        close(*current_fd);
+        *current_fd = open(trackname, O_RDONLY);
+        DEBUGF("spoofed trackname and the file descriptor for a cuesheet\n");
+        return true;
+    }
+    else
+    {
+        /* the track wasn't a cuesheet or there was an error */
+        return false;
+    }
+}
+
+/* tajes care of seeking to a track in a playlist
+ * returns false if audio  isn't playing or if we try to seek too far */
+bool seek(struct mp3entry *id3, unsigned int pos)
+{
+    if (!(audio_status() & AUDIO_STATUS_PLAY) || pos > id3->length)
+    {
+        return false;
+    }
+    else
+    {
+        audio_pre_ff_rewind();
+        audio_ff_rewind(pos);
+        return true;
+    }
+}
+
+/* returns the index of the track currently being played.
+ * TODO: add some checks */
+int find_current_track(struct cuesheet cue, struct mp3entry *id3)
+{
+    int i=0;
+    while (cue.tracks[i+1].offset < id3->elapsed)
+    {
+        i++;
+    }
+    return i;
+}
+
+char *list_get_name_cb(int selected_item,
+                             void *data,
+                             char *buffer)
+{
+    struct cuesheet *cue = (struct cuesheet *)data;
+
+    if (selected_item % 2)
+    {
+        snprintf(buffer,MAX_PATH,(selected_item+1)/2>9 ? "   %s" : "  %s",
+                     cue->tracks[selected_item/2].title);
+    }
+    else
+    {
+        snprintf(buffer,MAX_PATH,"%d %s",
+                     selected_item/2+1,cue->tracks[selected_item/2].performer);
+    }
+    return buffer;
+}
+
+void browse(struct cuesheet cue)
+{
+    struct gui_synclist lists;
+    int action;
+    bool done = false;
+    int sel;
+    char title[MAX_PATH];
+    struct mp3entry *id3 = audio_current_track();
+
+    snprintf(title,MAX_PATH,"%s: %s",cue.performer, cue.title);
+    gui_synclist_init(&lists,list_get_name_cb,&cue,false,2);
+    gui_synclist_set_nb_items(&lists,2*(cue.track_count+1));
+    gui_synclist_set_title(&lists,title,0);
+
+    if (!strcmp(id3->path,cue.audio_filename))
+    {
+        gui_synclist_select_item(&lists,2*find_current_track(cue,id3));
+    }
+    else
+    {
+        gui_syncsplash(HZ, true, "Read-only mode");
+    }
+
+    while (!done)
+    {
+        gui_synclist_draw(&lists);
+        action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
+        if (gui_synclist_do_button(&lists,action,LIST_WRAP_UNLESS_HELD))
+            continue;
+        switch (action)
+        {
+            case ACTION_STD_OK:
+                id3 = audio_current_track();
+                if (!strcmp(id3->path,cue.audio_filename))
+                {
+                    sel = gui_synclist_get_sel_pos(&lists);
+                    /*gui_synclist_select_item(&lists,2*find_current_track(cue,id3));*/
+                    if (!seek(id3,cue.tracks[sel/2].offset))
+                    {
+                        gui_syncsplash(HZ, true, "Failed to seek");
+                    }
+                }
+                break;
+            case ACTION_STD_CANCEL:
+                done = true;
+        }
+    }
+}
+
+
+bool display_cuesheet_content(char* filename)
+{
+    struct cuesheet cue;
+    if (!parse_cuesheet(filename, &cue))
+        return false;
+
+    browse(cue);
+    return true;
+}
+
+/* skips backwards or forward in the current cuesheet
+ * the return value indicates whether we're still in a cusheet after skipping
+ * it also returns false if we weren't in a cuesheet.
+ */
+bool cuesheet_skip(int direction)
+{
+    struct mp3entry *id3 = audio_current_track();
+
+    if (strcmp(id3->path,curr_cue.audio_filename))
+        return false;
+
+    int curr_track = find_current_track(curr_cue,id3);
+
+    if (direction >= 0)
+    {
+        if (curr_track > curr_cue.track_count - 1)
+        {
+            /* we've gotten out of the cuesheet */
+            DEBUGF("No longer playing a cuesheet\n");
+            strcpy(curr_cue.audio_filename,"\0");
+            return false;
+        }
+
+        DEBUGF("skip to track index %d out of %d\n",
+               curr_track+1, curr_cue.track_count);
+        seek(id3, curr_cue.tracks[curr_track+1].offset);
+
+    }
+    else
+    {
+        if (curr_track <= 1)
+        {
+            seek(id3, 0);
+            DEBUGF("skip to beginning\n");
+        }
+        else
+        {
+            DEBUGF("skip to track index %d out of %d\n",
+                   curr_track-1, curr_cue.track_count);
+            seek(id3, curr_cue.tracks[curr_track-1].offset);
+        }
+    }
+    return true;
+}
Index: apps/cuesheet.h
===================================================================
RCS file: apps/cuesheet.h
diff -N apps/cuesheet.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/cuesheet.h	18 Dec 2006 23:37:38 -0000
@@ -0,0 +1,72 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $$
+ *
+ * Copyright (C) 2005 Nicolas Pennequin, Jonathan Gordon
+ *
+ * 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _CUESHEET_H_
+#define _CUESHEET_H_
+
+#include <stdbool.h>
+
+#define MAX_NAME 64
+#define MAX_TRACKS 99
+
+#define MATCH(l,s) !strncmp(l,s,strlen(s))
+
+
+struct cuesheet {
+    char audio_filename[MAX_PATH];
+
+    char title[MAX_NAME];
+    char performer[MAX_NAME];
+
+    int track_count;
+
+    struct cue_track_info {
+        char title[MAX_NAME];
+        char performer[MAX_NAME];
+        unsigned long offset; /* ms from start of track */
+    } tracks[MAX_TRACKS];
+};
+
+/* the currently loaded cuesheet */
+struct cuesheet curr_cue;
+
+/* indicates wether the currently playing file is a cusehet
+ * (currently this is not updated, and maybe won't be used at all) */
+bool curr_track_is_cuesheet;
+
+/* returns true if is "filename" a cuesheet */
+bool is_cuesheet(char *filename);
+
+/* reads a cuesheet to find the audio track associated to it */
+bool get_trackname_from_cuesheet(char *filename, char *buf);
+
+/* if "trackname" is a cuesheet, open the associated audio
+ * file instead. This is used in the playback code.
+ * Also loads the cuesheet in curr_cue if "parse" is true. */
+bool cue_spoof_track(char *trackname, int *current_fd, bool parse);
+
+/* displays a cuesheet to the screen without loading it to curr_cue */
+bool display_cuesheet_content(char* filename);
+
+/* finds the current track played within a cuesheet */
+int find_current_track(struct cuesheet cue, struct mp3entry *id3);
+
+/* skip to next track in the cuesheet towards "direction" */
+bool cuesheet_skip(int direction);
+
+#endif
Index: apps/onplay.c
===================================================================
RCS file: /cvsroot/rockbox/apps/onplay.c,v
retrieving revision 1.94
diff -u -b -B -p -r1.94 onplay.c
--- apps/onplay.c	14 Dec 2006 22:43:16 -0000	1.94
+++ apps/onplay.c	18 Dec 2006 23:37:38 -0000
@@ -50,6 +50,7 @@
 #include "action.h"
 #include "splash.h"
 #include "yesno.h"
+#include "cuesheet.h"
 #if LCD_DEPTH > 1
 #include "backdrop.h"
 #endif
@@ -519,6 +520,11 @@ static bool set_backdrop(void)
 }
 #endif
 
+static bool view_cuesheet(void)
+{
+    return display_cuesheet_content(selected_file);
+}
+
 static bool rename_file(void)
 {
     char newname[MAX_PATH];
@@ -950,6 +956,12 @@ int onplay(char* file, int attr, int fro
                     }
                 }
 #endif
+                if (is_cuesheet(selected_file))
+                {
+                    items[i].desc = ID2P(LANG_VIEW_CUESHEET);
+                    items[i].function = view_cuesheet;
+                    i++;
+                }
             }
             else
             {
Index: apps/playback.c
===================================================================
RCS file: /cvsroot/rockbox/apps/playback.c,v
retrieving revision 1.406
diff -u -b -B -p -r1.406 playback.c
--- apps/playback.c	17 Dec 2006 14:47:12 -0000	1.406
+++ apps/playback.c	18 Dec 2006 23:37:39 -0000
@@ -57,6 +57,7 @@
 #include "buffer.h"
 #include "dsp.h"
 #include "abrepeat.h"
+#include "cuesheet.h"
 #ifdef HAVE_TAGCACHE
 #include "tagcache.h"
 #endif
@@ -2688,6 +2689,9 @@ static bool audio_load_track(int offset,
         return false;
     }
 
+    /* if the file is a cuesheet, we need to replace trackname and current_fd */
+    cue_spoof_track(trackname, &current_fd, true);
+
     /* Initialize track entry. */
     size = filesize(current_fd);
     tracks[track_widx].filerem = size;
@@ -2829,6 +2833,9 @@ static bool audio_read_next_metadata(voi
     if (!trackname)
         return false;
 
+    /* if the file is a cuesheet, we need to replace trackname and current_fd */
+    cue_spoof_track(trackname, &current_fd, false);
+
     fd = open(trackname, O_RDONLY);
     if (fd < 0)
         return false;
@@ -3144,6 +3151,10 @@ static int audio_rebuffer_and_seek(size_
 
     /* (Re-)open current track's file handle. */
     trackname = playlist_peek(0);
+
+    /* if the file is a cuesheet, we need to replace trackname and current_fd */
+    cue_spoof_track(trackname, &current_fd, true);
+
     fd = open(trackname, O_RDONLY);
     if (fd < 0) 
     {
Index: apps/tree.c
===================================================================
RCS file: /cvsroot/rockbox/apps/tree.c,v
retrieving revision 1.458
diff -u -b -B -p -r1.458 tree.c
--- apps/tree.c	12 Dec 2006 10:45:34 -0000	1.458
+++ apps/tree.c	18 Dec 2006 23:37:39 -0000
@@ -89,6 +89,8 @@ const struct filetype filetypes[] = {
     { "mp3", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "mp2", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "mpa", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
+    { "cue", TREE_ATTR_MPA, Icon_Bookmark, LANG_CUESHEET },
+                        /* TODO: find out why it get Icon_Audio */
 #if CONFIG_CODEC == SWCODEC
     /* Temporary hack to allow playlist creation */
     { "mp1", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
Index: apps/gui/gwps.c
===================================================================
RCS file: /cvsroot/rockbox/apps/gui/gwps.c,v
retrieving revision 1.56
diff -u -b -B -p -r1.56 gwps.c
--- apps/gui/gwps.c	13 Dec 2006 11:52:21 -0000	1.56
+++ apps/gui/gwps.c	18 Dec 2006 23:37:39 -0000
@@ -54,6 +54,7 @@
 #include "abrepeat.h"
 #include "playback.h"
 #include "splash.h"
+#include "cuesheet.h"
 #if LCD_DEPTH > 1
 #include "backdrop.h"
 #endif
@@ -356,6 +357,11 @@ long gui_wps_show(void)
                     audio_prev();
                 }
                 else {
+
+                    /* take care of if we're playing a cuesheet */
+                    if (cuesheet_skip(-1))
+                        break;
+
                     if (!wps_state.paused)
 #if (CONFIG_CODEC == SWCODEC)
                         audio_pre_ff_rewind();
@@ -396,6 +402,11 @@ long gui_wps_show(void)
                 }
                 /* ...otherwise, do it normally */
 #endif
+
+                /* take care of if we're playing a cuesheet */
+                if (cuesheet_skip(1))
+                    break;
+
                 audio_next();
                 break;
                 /* next / prev directories */
Index: apps/lang/english.lang
===================================================================
RCS file: /cvsroot/rockbox/apps/lang/english.lang,v
retrieving revision 1.304
diff -u -b -B -p -r1.304 english.lang
--- apps/lang/english.lang	10 Dec 2006 10:47:11 -0000	1.304
+++ apps/lang/english.lang	18 Dec 2006 23:37:41 -0000
@@ -10372,3 +10372,31 @@
     *: "Properties"
   </voice>
 </phrase>
+<phrase>
+  id: LANG_CUESHEET
+  desc: 
+  user:
+  <source>
+    *: "Cuesheet"
+  </source>
+  <dest>
+    *: "Cuesheet"
+  </dest>
+  <voice>
+    *: "Cuesheet"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_VIEW_CUESHEET
+  desc: cuesheet viewer
+  user:
+  <source>
+    *: "View cuesheet"
+  </source>
+  <dest>
+    *: "View cuesheet"
+  </dest>
+  <voice>
+    *: "View cuesheet"
+  </voice>
+</phrase>
