Step 4 of voice UI:
- "talkbox" functionality added, but yet without thumbnail recording
- menu under "general settings" to configure voice
- directories and folders can be voiced as a numbers


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@4440 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 5af10d4..ad98416 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -2373,3 +2373,57 @@
 
 #carry on adding normal LANG_ strings below
 
+id: LANG_VOICE
+desc: root of voice menu
+eng: "Voice"
+voice: "Voice"
+new:
+
+id: LANG_VOICE_MENU
+desc: item of voice menu, enable/disable the voice UI
+eng: "Voice Menus"
+voice: "Voice Menus"
+new:
+
+id: LANG_VOICE_DIR
+desc: item of voice menu, set the "talkbox" mode for directories
+eng: "Voice Directories"
+voice: "Voice Directories"
+new:
+
+id: LANG_VOICE_FILE
+desc: item of voice menu, set the voive mode for files
+eng: "Voice Filenames"
+voice: "Voice Filenames"
+new:
+
+id: LANG_VOICE_NUMBER
+desc: "talkbox" mode for files+directories
+eng: "Numbers"
+voice: "Numbers"
+new:
+
+id: LANG_VOICE_DIR_ENTER
+desc: "talkbox" mode for directories
+eng: "on enter"
+voice: "on enter"
+new:
+
+id: LANG_VOICE_DIR_HOVER
+desc: "talkbox" mode for directories
+eng: "while hovering"
+voice: "while hovering"
+new:
+
+id: VOICE_FILE
+desc: spoken only, prefix for file number
+eng: ""
+voice: "file"
+new:
+
+id: VOICE_DIR
+desc: spoken only, prefix for directory number
+eng: ""
+voice: "folder"
+new:
+
diff --git a/apps/menu.c b/apps/menu.c
index d1f0736..eae5ca2 100644
--- a/apps/menu.c
+++ b/apps/menu.c
@@ -243,7 +243,7 @@
     if (do_update)
     {   /* "say" the entry under the cursor */
         int voice_id = menus[m].items[menus[m].cursor].voice_id;
-        if (voice_id >= 0) /* valid ID given? */
+        if (voice_id >= 0 && global_settings.talk_menu) /* valid ID given? */
             talk_id(voice_id, false); /* say it */
     }
 
@@ -320,7 +320,7 @@
 
     /* say current entry */
     voice_id = menus[m].items[menus[m].cursor].voice_id;
-    if (voice_id >= 0) /* valid ID given? */
+    if (voice_id >= 0 && global_settings.talk_menu) /* valid ID given? */
         talk_id(voice_id, false); /* say it */
 
     while (!exit) {
diff --git a/apps/settings.c b/apps/settings.c
index 2bc7f45..a9691f3 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -456,6 +456,9 @@
 
     config_block[0xf4]=((unsigned char)global_settings.rec_prerecord_time |
                         ((unsigned char)global_settings.rec_directory << 5));
+    config_block[0xf5] = (global_settings.talk_dir & 7) |
+                         ((global_settings.talk_file & 3) << 3) | 
+                         ((global_settings.talk_menu & 1) << 5);
 
     if(save_config_buffer())
     {
@@ -793,6 +796,11 @@
             global_settings.rec_prerecord_time = config_block[0xf4] & 0x1f;
             global_settings.rec_directory = (config_block[0xf4] >> 5) & 3;
         }
+        if (config_block[0xf5] != 0xff) {
+            global_settings.talk_dir = config_block[0xf5] & 7;
+            global_settings.talk_file = (config_block[0xf5] >> 3) & 3;
+            global_settings.talk_menu = (config_block[0xf5] >> 5) & 1;
+        }
         
 #ifdef HAVE_LCD_CHARCELLS
         if (config_block[0xa8] != 0xff)
@@ -1193,6 +1201,20 @@
             set_cfg_option(&global_settings.playlist_viewer_track_display,
                 value, options, 2);
         }
+        else if (!strcasecmp(name, "talk dir"))
+        {
+            static char* options[] = {"off", "number", "enter", "hover"};
+            set_cfg_option(&global_settings.talk_dir, value, options, 4);
+        }
+        else if (!strcasecmp(name, "talk file"))
+        {
+            static char* options[] = {"off", "number"};
+            set_cfg_option(&global_settings.talk_dir, value, options, 2);
+        }
+        else if (!strcasecmp(name, "talk menu"))
+        {
+            set_cfg_bool(&global_settings.talk_menu, value);
+        }
     }
 
     close(fd);
@@ -1539,6 +1561,16 @@
                 options[global_settings.playlist_viewer_track_display]);
         }
     }
+    fprintf(fd, "#\r\n# Voice\r\n#\r\n");
+    {
+        static char* options[] = {"off", "number", "enter", "hover"};
+        fprintf(fd, "talk dir: %s\r\n",
+            options[global_settings.talk_dir]);
+        fprintf(fd, "talk file: %s\r\n", /* recycle the options, */
+            options[global_settings.talk_file]); /* first 2 are alike */
+        fprintf(fd, "talk menu: %s\r\n",
+            boolopt[global_settings.talk_menu]);
+    }
 
     close(fd);
 
@@ -1646,6 +1678,10 @@
     global_settings.playlist_viewer_icons = true;
     global_settings.playlist_viewer_indices = true;
     global_settings.playlist_viewer_track_display = 0;
+    /* talking menu on by default, to help the blind (if voice file present) */
+    global_settings.talk_menu = 1; 
+    global_settings.talk_dir = 0;
+    global_settings.talk_file = 0;
 }
 
 bool set_bool(char* string, bool* variable )
@@ -1713,7 +1749,7 @@
 #endif
         lcd_update();
 
-        if (*variable != last_value)
+        if (global_settings.talk_menu && *variable != last_value)
         {
             if (voice_unit < UNIT_LAST)
             {   /* use the available unit definition */
@@ -1829,7 +1865,7 @@
     while ( !done ) {
         index = type==INT ? *intvar : (int)*boolvar;
         lcd_puts(0, 1, options[index].string);
-        if (index != oldindex)
+        if (global_settings.talk_menu && index != oldindex)
         {
             talk_id(options[index].voice_id, false);
             oldindex = index;
diff --git a/apps/settings.h b/apps/settings.h
index 350d14a..0bf67b4 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -202,6 +202,11 @@
     bool playlist_viewer_icons; /* display icons on viewer */
     bool playlist_viewer_indices; /* display playlist indices on viewer */
     int playlist_viewer_track_display; /* how to display tracks in viewer */
+
+    /* voice UI settings */
+    bool talk_menu; /* enable voice UI */
+    int talk_dir; /* talkbox mode: 0=off 1=number 2=clip@enter 3=clip@hover */
+    int talk_file; /* voice filename mode: 0=off, 1=number, other t.b.d. */ 
 };
 
 enum optiontype { INT, BOOL };
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index 35602f0..a0f039b 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -876,6 +876,56 @@
     return rockbox_browse(ROCKBOX_DIR LANG_DIR, SHOW_LNG);
 }
 
+static bool voice_menus(void)
+{
+    bool ret;
+    bool temp = global_settings.talk_menu;
+    /* work on a temp variable first, avoid "life" disabling */
+    ret = set_bool( str(LANG_VOICE_MENU), &temp );
+    global_settings.talk_menu = temp;
+    return ret;
+}
+
+static bool voice_dirs(void)
+{
+    struct opt_items names[] = {
+        { STR(LANG_OFF) }, 
+        { STR(LANG_VOICE_NUMBER) },
+        { STR(LANG_VOICE_DIR_ENTER) },
+        { STR(LANG_VOICE_DIR_HOVER) }
+    };
+    return set_option( str(LANG_VOICE_DIR), 
+                       &global_settings.talk_dir, INT, names, 4, NULL);
+}
+
+static bool voice_files(void)
+{
+    struct opt_items names[] = {
+        { STR(LANG_OFF) }, 
+        { STR(LANG_VOICE_NUMBER) }
+    };
+    return set_option( str(LANG_VOICE_DIR), 
+                       &global_settings.talk_file, INT, names, 2, NULL);
+}
+
+static bool voice_menu(void)
+{
+    int m;
+    bool result;
+
+    struct menu_item items[] = {
+        { STR(LANG_VOICE_MENU), voice_menus },
+        { STR(LANG_VOICE_DIR),  voice_dirs  },
+        { STR(LANG_VOICE_FILE),  voice_files }
+    };
+
+    m=menu_init( items, sizeof(items) / sizeof(*items), NULL,
+                 NULL, NULL, NULL);
+    result = menu_run(m);
+    menu_exit(m);
+    return result;
+}
+
 #ifdef HAVE_LCD_BITMAP
 static bool font_browse(void)
 {
@@ -1283,6 +1333,7 @@
         { STR(LANG_SYSTEM),           system_settings_menu   },
         { STR(LANG_BOOKMARK_SETTINGS),bookmark_settings_menu },
         { STR(LANG_LANGUAGE),         language_browse        },
+        { STR(LANG_VOICE),            voice_menu             },
     };
     
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL,
diff --git a/apps/sleeptimer.c b/apps/sleeptimer.c
index 1a6e3ec..a236ed9 100644
--- a/apps/sleeptimer.c
+++ b/apps/sleeptimer.c
@@ -144,7 +144,7 @@
                      hours, minutes);
             lcd_puts(0, 1, buf);
 
-            if (sayit)
+            if (sayit && global_settings.talk_menu)
             {
                 bool enqueue = false; /* first one should not ne queued */
 
diff --git a/apps/sound_menu.c b/apps/sound_menu.c
index 67e6999..23d4c81 100644
--- a/apps/sound_menu.c
+++ b/apps/sound_menu.c
@@ -87,7 +87,8 @@
             {
                 snprintf(str,sizeof str,"%d %s  ", val, unit);
             }
-            talk_value(val, talkunit, false); /* speak it */
+            if (global_settings.talk_menu)
+                talk_value(val, talkunit, false); /* speak it */
         }
         lcd_puts(0,1,str);
         status_draw(true);
diff --git a/apps/talk.c b/apps/talk.c
index 3849f0e..6dd0076 100644
--- a/apps/talk.c
+++ b/apps/talk.c
@@ -30,14 +30,14 @@
 #include "mpeg.h"
 #include "lang.h"
 #include "talk.h"
-#include "screens.h" /* test hack */
-#include "kernel.h"
+#include "id3.h"
 extern void bitswap(unsigned char *data, int length); /* no header for this */
 
 /***************** Constants *****************/
 
 #define QUEUE_SIZE 20
 const char* voicefont_file = "/.rockbox/langs/english.voice";
+const char* dir_thumbnail_name = ".dirname.mp3";
 
 
 /***************** Data types *****************/
@@ -317,6 +317,7 @@
 {
     int fd;
     int size;
+    struct mp3entry info;
 
     if (mpeg_status()) /* busy, buffer in use */
         return -1; 
@@ -324,12 +325,19 @@
     if (p_thumbnail == NULL || size_for_thumbnail <= 0)
         return -1;
 
+    if(mp3info(&info, filename)) /* use this to find real start */
+    {   
+        return 0; /* failed to open, or invalid */
+    }
+
     fd = open(filename, O_RDONLY);
     if (fd < 0) /* failed to open */
     {
         return 0;
     }
 
+    lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */
+
     size = read(fd, p_thumbnail, size_for_thumbnail);
     close(fd);
 
diff --git a/apps/talk.h b/apps/talk.h
index 518549e..f3c1366 100644
--- a/apps/talk.h
+++ b/apps/talk.h
@@ -54,6 +54,9 @@
 /* convenience macro to have both string and ID as arguments */
 #define STR(id) str(id), id
 
+/* publish this string, so it's stored only once (better than #define) */
+extern const char* dir_thumbnail_name;
+
 
 void talk_init(void);
 int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */
diff --git a/apps/tree.c b/apps/tree.c
index d56dacc..9d6a666 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -55,6 +55,7 @@
 #include "plugin.h"
 #include "power.h"
 #include "action.h"
+#include "talk.h"
 
 #ifdef HAVE_LCD_BITMAP
 #include "widgets.h"
@@ -112,6 +113,7 @@
 static int dirstart;
 static int dirlevel;
 static int filesindir;
+static int dirsindir; /* how many of the dircache entries are directories */
 static int dirpos[MAX_DIR_LEVELS];
 static int cursorpos[MAX_DIR_LEVELS];
 static char lastdir[MAX_PATH];
@@ -122,6 +124,7 @@
 static int boot_size = 0;
 static int boot_cluster;
 static bool boot_changed = false;
+static bool enqueue_next = false;
 
 static bool start_wps = false;
 static bool dirbrowse(char *root, int *dirfilter);
@@ -199,7 +202,8 @@
 
     for(i = 0;i < filesindir;i++)
     {
-        if((dircache[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
+        if((dircache[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA 
+            && (strcmp(dircache[i].name, dir_thumbnail_name) != 0))
         {
             DEBUGF("Adding %s\n", dircache[i].name);
             if (playlist_add(dircache[i].name) < 0)
@@ -216,6 +220,36 @@
     return start_index;
 }
 
+
+static int play_dirname(int start_index, bool enqueue)
+{
+    int fd;
+    char dirname_mp3_filename[MAX_PATH+1];
+
+    if (mpeg_status() & MPEG_STATUS_PLAY)
+        return 0;
+
+    snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
+		 currdir, dircache[start_index].name, dir_thumbnail_name);
+
+    DEBUGF("Checking for %s\n", dirname_mp3_filename);
+
+    fd = open(dirname_mp3_filename, O_RDONLY);
+    if (fd < 0)
+    {
+	    DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
+	    return -1;
+    }
+
+    close(fd);
+  
+    DEBUGF("Found: %s\n", dirname_mp3_filename);
+
+    talk_file(dirname_mp3_filename, enqueue);
+    return 1;
+}
+
+
 static int compare(const void* p1, const void* p2)
 {
     struct entry* e1 = (struct entry*)p1;
@@ -278,6 +312,7 @@
         return NULL; /* not a directory */
 
     name_buffer_length = 0;
+    dirsindir = 0;
     *buffer_full = false;
 
     for ( i=0; i < max_files_in_dir; i++ ) {
@@ -369,6 +404,9 @@
         dptr->name = &name_buffer[name_buffer_length];
         strcpy(dptr->name,entry->d_name);
         name_buffer_length += len + 1;
+
+        if (dptr->attr & ATTR_DIRECTORY) /* count the remaining dirs */
+            dirsindir++; 
     }
     *num_files = i;
     closedir(dir);
@@ -1019,6 +1057,14 @@
                     snprintf(buf,sizeof(buf),"/%s",file->name);
 
                 if (file->attr & ATTR_DIRECTORY) {
+                    if (global_settings.talk_dir == 2) /* enter */
+                    {
+                        /* play_dirname */
+                        DEBUGF("Playing directory thumbnail: %s", currdir);
+                        play_dirname(dircursor+dirstart, false);
+                        /* avoid reading getting cut by next filename */
+                        enqueue_next = true;
+                    }
                     memcpy(currdir,buf,sizeof(currdir));
                     if ( dirlevel < MAX_DIR_LEVELS ) {
                         dirpos[dirlevel] = dirstart;
@@ -1425,6 +1471,32 @@
 
                 showfileline(dircursor, i, true, dirfilter); /* scroll please */
                 need_update = true;
+
+                if (dircache[i].attr & ATTR_DIRECTORY) /* directory? */
+                {
+                    int ret = 0;
+                    /* play directory thumbnail */
+                    if (global_settings.talk_dir == 3) /* hover */
+                    {
+                        DEBUGF("Playing directory thumbnail: %s", currdir);
+                        ret = play_dirname(dircursor+dirstart, false);
+                    }
+
+                    if (global_settings.talk_dir == 1 /* dirs as numbers */
+                        || ret == -1) /* or no thumbnail found above */
+                    {
+                        talk_id(VOICE_DIR, false);
+                        talk_number(i+1, true);
+                    }
+                }
+                else if (global_settings.talk_file == 1) /* files as numbers */
+                {
+                    /* enqueue_next is true if still talking the dir name */
+                    talk_id(VOICE_FILE, enqueue_next);
+                    talk_number(i-dirsindir+1, true);
+                    enqueue_next = false;
+                }
+
             }
         }