Index: trunk/apps/lang/english.lang =================================================================== --- trunk.orig/apps/lang/english.lang +++ trunk/apps/lang/english.lang @@ -11417,3 +11417,17 @@ *: "Blank" + + id: VOICE_EMPTY_LIST + desc: spoken only, when a list dialog contains no elements + user: + + *: "" + + + *: "" + + + *: "Empty list" + + Index: trunk/apps/gui/list.c =================================================================== --- trunk.orig/apps/gui/list.c +++ trunk/apps/gui/list.c @@ -36,6 +36,7 @@ #include "lang.h" #include "sound.h" #include "misc.h" +#include "talk.h" #ifdef HAVE_LCD_CHARCELLS #define SCROLL_LIMIT 1 @@ -78,6 +79,7 @@ static void gui_list_init(struct gui_lis { gui_list->callback_get_item_icon = NULL; gui_list->callback_get_item_name = callback_get_item_name; + gui_list->callback_speak_item = NULL; gui_list->display = NULL; gui_list_set_nb_items(gui_list, 0); gui_list->selected_item = 0; @@ -96,6 +98,7 @@ static void gui_list_init(struct gui_lis gui_list->last_displayed_selected_item = -1 ; gui_list->last_displayed_start_item = -1 ; + gui_list->scheduled_talk_tick = gui_list->last_talked_tick = 0; gui_list->show_selection_marker = true; #ifdef HAVE_LCD_COLOR @@ -825,6 +828,12 @@ void gui_synclist_set_icon_callback(stru } } +void gui_synclist_set_voice_callback(struct gui_synclist * lists, + list_speak_item voice_callback) +{ + gui_list_set_voice_callback(&(lists->gui_list[0]), voice_callback); +} + void gui_synclist_draw(struct gui_synclist * lists) { int i; @@ -909,6 +918,43 @@ static void gui_synclist_scroll_left(str } #endif /* HAVE_LCD_BITMAP */ +static void _gui_synclist_speak_item(struct gui_synclist *lists, bool repeating) +{ + struct gui_list *l = &lists->gui_list[0]; + list_speak_item *cb = l->callback_speak_item; + if(cb && gui_synclist_get_nb_items(lists) != 0) + { + int sel = gui_synclist_get_sel_pos(lists); + shutup(); + /* If we got a repeating key action, or we have just very + recently started talking, then we want to stay silent for a + while until things settle. Likewise if we already had a + pending scheduled announcement not yet due: we need to + reschedule it. */ + if(repeating + || (l->scheduled_talk_tick + && TIME_BEFORE(current_tick, l->scheduled_talk_tick)) + || (l->last_talked_tick + && TIME_BEFORE(current_tick, l->last_talked_tick +HZ/4))) + { + l->scheduled_talk_tick = current_tick +HZ/4; + return; + } else { + l->scheduled_talk_tick = 0; /* work done */ + cb(sel, l->data); + l->last_talked_tick = current_tick; + } + } +} +void gui_synclist_speak_item(struct gui_synclist * lists) +/* The list user should call this to speak the first item on entering + the list, and whenever the list is updated. */ +{ + if(gui_synclist_get_nb_items(lists) == 0 && talk_menus_enabled()) + talk_id(VOICE_EMPTY_LIST, true); + else _gui_synclist_speak_item(lists, false); +} + extern intptr_t get_action_data(void); bool gui_synclist_do_button(struct gui_synclist * lists, @@ -987,9 +1033,10 @@ bool gui_synclist_do_button(struct gui_s #ifndef HAVE_SCROLLWHEEL if (queue_count(&button_queue) < FRAMEDROP_TRIGGER) #endif - { gui_synclist_draw(lists); - } + _gui_synclist_speak_item(lists, + action == ACTION_STD_PREVREPEAT + || next_item_modifier >1); yield(); *actionptr = ACTION_STD_PREV; return true; @@ -1001,9 +1048,10 @@ bool gui_synclist_do_button(struct gui_s #ifndef HAVE_SCROLLWHEEL if (queue_count(&button_queue) < FRAMEDROP_TRIGGER) #endif - { gui_synclist_draw(lists); - } + _gui_synclist_speak_item(lists, + action == ACTION_STD_NEXTREPEAT + || next_item_modifier >1); yield(); *actionptr = ACTION_STD_NEXT; return true; @@ -1052,6 +1100,7 @@ bool gui_synclist_do_button(struct gui_s SCREEN_MAIN; gui_synclist_select_previous_page(lists, screen); gui_synclist_draw(lists); + _gui_synclist_speak_item(lists, false); yield(); *actionptr = ACTION_STD_NEXT; } @@ -1067,10 +1116,44 @@ bool gui_synclist_do_button(struct gui_s SCREEN_MAIN; gui_synclist_select_next_page(lists, screen); gui_synclist_draw(lists); + _gui_synclist_speak_item(lists, false); yield(); *actionptr = ACTION_STD_PREV; } return true; } + if(lists->gui_list[0].scheduled_talk_tick + && TIME_AFTER(current_tick, lists->gui_list[0].scheduled_talk_tick)) + /* scheduled postponed item announcement is due */ + _gui_synclist_speak_item(lists, false); return false; } + +int list_do_action_timeout(struct gui_synclist *lists, int timeout) +/* Returns the lowest of timeout or the delay until a postponed + scheduled announcement is due (if any). */ +{ + if(lists->gui_list[0].scheduled_talk_tick) + { + long delay = lists->gui_list[0].scheduled_talk_tick -current_tick +1; + /* +1 because the trigger condition uses TIME_AFTER(), which + is implemented as strictly greater than. */ + if(delay < 0) + delay = 0; + if(timeout > delay || timeout == TIMEOUT_BLOCK) + timeout = delay; + } + return timeout; +} + +bool list_do_action(int context, int timeout, + struct gui_synclist *lists, int *action, + enum list_wrap wrap) +/* Combines the get_action() (with possibly overridden timeout) and + gui_synclist_do_button() calls. Returns the list action from + do_button, and places the action from get_action in *action. */ +{ + timeout = list_do_action_timeout(lists, timeout); + *action = get_action(context, timeout); + return gui_synclist_do_button(lists, action, wrap); +} Index: trunk/apps/gui/list.h =================================================================== --- trunk.orig/apps/gui/list.h +++ trunk/apps/gui/list.h @@ -62,6 +62,14 @@ typedef enum themable_icons list_get_ico * Returns a pointer to a string that contains the text to display */ typedef char * list_get_name(int selected_item, void * data, char * buffer); +/* + * Voice callback + * - selected_item : an integer that tells the number of the item to speak + * - data : a void pointer to the data you gave to the list when you + * initialized it + * Returns an integer, 0 means success, ignored really... + */ +typedef int list_speak_item(int selected_item, void * data); #ifdef HAVE_LCD_COLOR /* * Color callback @@ -98,9 +106,11 @@ struct gui_list #endif /* Cache the width of the title string in pixels/characters */ int title_width; + long scheduled_talk_tick, last_talked_tick; list_get_icon *callback_get_item_icon; list_get_name *callback_get_item_name; + list_speak_item *callback_speak_item; struct screen * display; /* The data that will be passed to the callback function YOU implement */ @@ -141,6 +151,14 @@ struct gui_list #define gui_list_set_icon_callback(gui_list, _callback) \ (gui_list)->callback_get_item_icon=_callback +/* + * Sets the voice callback function + * - gui_list : the list structure + * - _callback : the callback function + */ +#define gui_list_set_voice_callback(gui_list, _callback) \ + (gui_list)->callback_speak_item=_callback + #ifdef HAVE_LCD_COLOR /* * Sets the color callback function @@ -201,6 +219,8 @@ extern void gui_synclist_init( ); extern void gui_synclist_set_nb_items(struct gui_synclist * lists, int nb_items); extern void gui_synclist_set_icon_callback(struct gui_synclist * lists, list_get_icon icon_callback); +extern void gui_synclist_set_voice_callback(struct gui_synclist * lists, list_speak_item voice_callback); +extern void gui_synclist_speak_item(struct gui_synclist * lists); extern int gui_synclist_get_nb_items(struct gui_synclist * lists); extern int gui_synclist_get_sel_pos(struct gui_synclist * lists); @@ -225,4 +245,15 @@ extern bool gui_synclist_do_button(struc unsigned *action, enum list_wrap); +/* If the list has a pending postponed scheduled announcement, that + may become due before the next get_action tmieout. This function + adjusts the get_action timeout appropriately. */ +extern int list_do_action_timeout(struct gui_synclist *lists, int timeout); +/* This one combines a get_action call (with timeout overridden by + list_do_action_timeout) with the gui_synclist_do_button call, for + convenience. */ +extern bool list_do_action(int context, int timeout, + struct gui_synclist *lists, int *action, + enum list_wrap wrap); + #endif /* _GUI_LIST_H_ */ Index: trunk/apps/tree.c =================================================================== --- trunk.orig/apps/tree.c +++ trunk/apps/tree.c @@ -111,9 +111,9 @@ static bool start_wps = false; static int curr_context = false;/* id3db or tree*/ static int dirbrowse(void); -static int ft_play_filenumber(int pos, int attr); static int ft_play_dirname(char* name); static void ft_play_filename(char *dir, char *file); +static void say_filetype(int attr); /* * removes the extension of filename (if it doesn't start with a .) @@ -216,6 +216,69 @@ static int tree_get_fileicon(int selecte } } +static int tree_voice_cb(int selected_item, void * data) +{ + struct tree_context * local_tc=(struct tree_context *)data; + char *name; + int attr=0; +#ifdef HAVE_TAGCACHE + bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; + + if (id3db) + { + attr = tagtree_get_attr(local_tc); + name = tagtree_get_entry(local_tc, selected_item)->name; + } + else +#endif + { + struct entry* dc = local_tc->dircache; + struct entry* e = &dc[selected_item]; + name = e->name; + attr = e->attr; + } + bool is_dir = (attr & ATTR_DIRECTORY); + bool did_clip = false; + /* First the .talk clip case */ + if(is_dir) + { + if(global_settings.talk_dir_clip) + { + DEBUGF("Playing directory thumbnail: %s", local_tc->currdir); + did_clip = true; + if(ft_play_dirname(name) <0) + /* failed, not existing */ + did_clip = false; + } + } else { /* it's a file */ + if (global_settings.talk_file_clip && (attr & FILE_ATTR_THUMBNAIL)) + { + did_clip = true; + DEBUGF("Playing file thumbnail: %s/%s%s\n", + local_tc->currdir, name, file_thumbnail_ext); + ft_play_filename(local_tc->currdir, name); + } + } + if(!did_clip) + { + /* say the number or spell if required or as a fallback */ + switch (is_dir ? global_settings.talk_dir : global_settings.talk_file) + { + case 1: /* as numbers */ + talk_id(is_dir ? VOICE_DIR : VOICE_FILE, false); + talk_number(selected_item+1 - (is_dir ? 0 : local_tc->dirsindir), + true); + if(!is_dir) + say_filetype(attr); + break; + case 2: /* spelled */ + talk_spell(name, false); + break; + } + } + return 0; +} + bool check_rockboxdir(void) { DIR *dir = opendir(ROCKBOX_DIR); @@ -255,6 +318,9 @@ void tree_gui_init(void) gui_buttonbar_set_display(&tree_buttonbar, &(screens[SCREEN_MAIN]) ); #endif gui_synclist_init(&tree_lists, &tree_get_filename, &tc, false, 1); + if(global_settings.talk_dir || global_settings.talk_dir_clip + || global_settings.talk_file || global_settings.talk_file_clip) + gui_synclist_set_voice_callback(&tree_lists, tree_voice_cb); gui_synclist_set_icon_callback(&tree_lists, &tree_get_fileicon); #ifdef HAVE_LCD_COLOR gui_list_set_color_callback(&tree_lists.gui_list[SCREEN_MAIN], @@ -269,8 +335,6 @@ struct tree_context* tree_get_context(vo return &tc; } -/* talkbox hovering delay, to avoid immediate disk activity */ -#define HOVER_DELAY (HZ/2) /* * Returns the position of a given file in the current directory * returns -1 if not found @@ -424,6 +488,7 @@ static int update_dir(void) } #endif gui_synclist_draw(&tree_lists); + gui_synclist_speak_item(&tree_lists); gui_syncstatusbar_draw(&statusbars, true); return tc.filesindir; } @@ -551,14 +616,11 @@ static int dirbrowse() { int numentries=0; char buf[MAX_PATH]; - int lasti = -1; unsigned button, oldbutton; bool reload_root = false; int lastfilter = *tc.dirfilter; bool lastsortcase = global_settings.sort_case; - bool need_update = true; bool exit_func = false; - long thumbnail_time = -1; /* for delaying a thumbnail */ char* currdir = tc.currdir; /* just a shortcut */ #ifdef HAVE_TAGCACHE @@ -580,6 +642,7 @@ static int dirbrowse() start_wps = false; numentries = update_dir(); + reload_dir = false; if (numentries == -1) return false; /* currdir is not a directory */ @@ -604,9 +667,10 @@ static int dirbrowse() boot_changed = false; } #endif - button = get_action(CONTEXT_TREE,HZ/5); + button = get_action(CONTEXT_TREE, + list_do_action_timeout(&tree_lists, HZ/2)); oldbutton = button; - need_update = gui_synclist_do_button(&tree_lists, &button,LIST_WRAP_UNLESS_HELD); + gui_synclist_do_button(&tree_lists, &button,LIST_WRAP_UNLESS_HELD); tc.selected_item = gui_synclist_get_sel_pos(&tree_lists); switch ( button ) { case ACTION_STD_OK: @@ -750,57 +814,6 @@ static int dirbrowse() } case ACTION_NONE: - if (thumbnail_time != -1 && - TIME_AFTER(current_tick, thumbnail_time)) - { /* a delayed hovering thumbnail is due now */ - int res; - int attr; - char* name; - -#ifdef HAVE_TAGCACHE - if (id3db) - { - attr = tagtree_get_attr(&tc); - name = tagtree_get_entry(&tc, lasti)->name; - } - else -#endif - { - attr = dircache[lasti].attr; - name = dircache[lasti].name; - } - - if (attr & ATTR_DIRECTORY) - { - DEBUGF("Playing directory thumbnail: %s", currdir); - res = ft_play_dirname(name); - if (res < 0) /* failed, not existing */ - { - /* say the number or spell if required as a fallback */ - switch (global_settings.talk_dir) - { - case 1: /* dirs as numbers */ - talk_id(VOICE_DIR, false); - talk_number(lasti+1, true); - break; - - case 2: /* dirs spelled */ - talk_spell(name, false); - break; - } - } - } - else - { - DEBUGF("Playing file thumbnail: %s/%s%s\n", - currdir, name, - file_thumbnail_ext); - /* no fallback necessary, we knew in advance - that the file exists */ - ft_play_filename(currdir, name); - } - thumbnail_time = -1; /* job done */ - } gui_syncstatusbar_draw(&statusbars, false); break; @@ -872,87 +885,12 @@ static int dirbrowse() if (restore || reload_dir) { /* restore display */ numentries = update_dir(); + reload_dir = false; if (currdir[1] && (numentries < 0)) { /* not in root and reload failed */ reload_root = true; /* try root */ - reload_dir = false; goto check_rescan; } - need_update = true; - reload_dir = false; - } - - if(need_update) { - need_update=false; - if ( numentries > 0 ) { - /* Voice the file if changed */ - if(lasti != tc.selected_item || restore) { - int attr; - char* name; - - lasti = tc.selected_item; - thumbnail_time = -1; /* Cancel whatever we were - about to say */ - -#ifdef HAVE_TAGCACHE - if (id3db) - { - attr = tagtree_get_attr(&tc); - name = tagtree_get_entry(&tc, tc.selected_item)->name; - } - else -#endif - { - attr = dircache[tc.selected_item].attr; - name = dircache[tc.selected_item].name; - } - - /* Directory? */ - if (attr & ATTR_DIRECTORY) - { - /* schedule thumbnail playback if required */ - if (global_settings.talk_dir_clip) - thumbnail_time = current_tick + HOVER_DELAY; - else - { - /* talk directly */ - switch (global_settings.talk_dir) - { - case 1: /* dirs as numbers */ - talk_id(VOICE_DIR, false); - talk_number(tc.selected_item+1, true); - break; - - case 2: /* dirs spelled */ - talk_spell(name, false); - break; - } - } - } - else /* file */ - { - /* schedule thumbnail playback if required */ - if (global_settings.talk_file_clip && (attr & FILE_ATTR_THUMBNAIL)) - thumbnail_time = current_tick + HOVER_DELAY; - else - { - /* talk directly */ - switch (global_settings.talk_file) - { - case 1: /* files as numbers */ - ft_play_filenumber( - tc.selected_item-tc.dirsindir+1, - attr & FILE_ATTR_MASK); - break; - - case 2: /* files spelled */ - talk_spell(name, false); - break; - } - } - } - } - } } } return true; @@ -1226,24 +1164,17 @@ void bookmark_play(char *resume_file, in start_wps=true; } -static int ft_play_filenumber(int pos, int attr) +static void say_filetype(int attr) { /* try to find a voice ID for the extension, if known */ int j; - int ext_id = -1; /* default to none */ + attr &= FILE_ATTR_MASK; /* file type */ for (j=0; jflags & F_BOOL_SETTING) == F_BOOL_SETTING) { bool val = temp_var==1?true:false; @@ -167,6 +169,7 @@ static void option_talk(struct settings_ talk_id(P2ID(setting->choice_setting->desc[value]), false); } } + return 0; } #if 0 int option_select_next_val(struct settings_list *setting, @@ -319,6 +322,8 @@ bool option_screen(struct settings_list gui_synclist_set_title(&lists, title, Icon_Questionmark); gui_synclist_set_icon_callback(&lists, NULL); + if(talk_menus_enabled()) + gui_synclist_set_voice_callback(&lists, option_talk); /* set the number of items and current selection */ if (var_type == F_T_INT || var_type == F_T_UINT) @@ -375,13 +380,11 @@ bool option_screen(struct settings_list gui_synclist_limit_scroll(&lists, true); gui_synclist_draw(&lists); /* talk the item */ - option_talk(setting, *variable); + gui_synclist_speak_item(&lists); while (!done) { - action = get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - if (action == ACTION_NONE) - continue; - if (gui_synclist_do_button(&lists, &action, + if (list_do_action(CONTEXT_LIST, TIMEOUT_BLOCK, + &lists, &action, allow_wrap? LIST_WRAP_UNLESS_HELD: LIST_WRAP_OFF)) { selected = gui_synclist_get_sel_pos(&lists); @@ -391,9 +394,9 @@ bool option_screen(struct settings_list if (!use_temp_var) *(bool*)setting->setting = selected==1?true:false; } - /* talk */ - option_talk(setting, *variable); } + else if (action == ACTION_NONE) + continue; else if (action == ACTION_STD_CANCEL) { bool show_cancel = false; Index: trunk/apps/menu.c =================================================================== --- trunk.orig/apps/menu.c +++ trunk/apps/menu.c @@ -65,6 +65,7 @@ static struct menu_item_ex *current_submenus_menu; static int current_subitems[MAX_MENU_SUBITEMS]; static int current_subitems_count = 0; +static int talk_menu_item(int selected_item, void *data); static void get_menu_callback(const struct menu_item_ex *m, menu_callback_type *menu_callback) @@ -207,6 +208,8 @@ static void init_menu_lists(const struct (void)icon; gui_synclist_set_icon_callback(lists, NULL); #endif + if(talk_menus_enabled()) + gui_synclist_set_voice_callback(lists, talk_menu_item); gui_synclist_set_nb_items(lists,current_subitems_count); gui_synclist_limit_scroll(lists,true); gui_synclist_select_item(lists, find_menu_selection(selected)); @@ -215,19 +218,17 @@ static void init_menu_lists(const struct if (callback && menu_callback) menu_callback(ACTION_ENTER_MENUITEM,menu); gui_synclist_draw(lists); + gui_synclist_speak_item(lists); } -static void talk_menu_item(const struct menu_item_ex *menu, - struct gui_synclist *lists) +static int talk_menu_item(int selected_item, void *data) { + const struct menu_item_ex *menu = (const struct menu_item_ex *)data; int id = -1; int type; unsigned char *str; - int sel; + int sel = get_menu_selection(selected_item, menu); - if (talk_menus_enabled()) - { - sel = get_menu_selection(gui_synclist_get_sel_pos(lists),menu); if ((menu->flags&MENU_TYPE_MASK) == MT_MENU) { type = menu->submenus[sel]->flags&MENU_TYPE_MASK; @@ -271,7 +272,7 @@ static void talk_menu_item(const struct talk_id(id,false); } } - } + return 0; } #define MAX_OPTIONS 32 bool do_setting_from_menu(const struct menu_item_ex *temp) @@ -307,7 +308,6 @@ int do_menu(const struct menu_item_ex *s int stack_top = 0; bool in_stringlist, done = false; menu_callback_type menu_callback = NULL; - bool talk_item = false; if (start_menu == NULL) menu = &main_menu_; else menu = start_menu; @@ -320,25 +320,18 @@ int do_menu(const struct menu_item_ex *s init_menu_lists(menu,&lists,selected,true); in_stringlist = ((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID); - talk_menu_item(menu, &lists); - /* load the callback, and only reload it if menu changes */ get_menu_callback(menu, &menu_callback); - gui_synclist_draw(&lists); while (!done) { - talk_item = false; redraw_lists = false; gui_syncstatusbar_draw(&statusbars, true); - action = get_action(CONTEXT_MAINMENU,HZ); + action = get_action(CONTEXT_MAINMENU, + list_do_action_timeout(&lists, HZ)); /* HZ so the status bar redraws corectly */ - if (action == ACTION_NONE) - { - continue; - } - if (menu_callback) + if (action != ACTION_NONE && menu_callback) { int old_action = action; action = menu_callback(action, menu); @@ -356,10 +349,9 @@ int do_menu(const struct menu_item_ex *s } if (gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD)) - { - talk_menu_item(menu, &lists); continue; - } + if (action == ACTION_NONE) + continue; #ifdef HAVE_RECORDING if (action == ACTION_STD_REC) @@ -411,7 +403,6 @@ int do_menu(const struct menu_item_ex *s menu_stack_selected_item[stack_top], false); /* new menu, so reload the callback */ get_menu_callback(menu, &menu_callback); - talk_item = true; } else if (menu != &root_menu_) { @@ -453,13 +444,11 @@ int do_menu(const struct menu_item_ex *s init_menu_lists(temp, &lists, 0, true); redraw_lists = false; /* above does the redraw */ menu = temp; - talk_item = true; } break; case MT_FUNCTION_CALL: { int return_value; - talk_item = true; if (temp->flags&MENU_FUNC_USEPARAM) return_value = temp->function->function_w_param( temp->function->param); @@ -483,7 +472,6 @@ int do_menu(const struct menu_item_ex *s init_menu_lists(menu, &lists, selected, true); redraw_lists = false; /* above does the redraw */ } - talk_item = true; break; } case MT_RETURN_ID: @@ -500,7 +488,6 @@ int do_menu(const struct menu_item_ex *s menu = temp; init_menu_lists(menu,&lists,0,false); redraw_lists = false; /* above does the redraw */ - talk_item = true; in_stringlist = true; } break; @@ -534,11 +521,12 @@ int do_menu(const struct menu_item_ex *s ret = MENU_ATTACHED_USB; done = true; } - if (talk_item && !done) - talk_menu_item(menu, &lists); - if (redraw_lists) + if (redraw_lists && !done) + { gui_synclist_draw(&lists); + gui_synclist_speak_item(&lists); + } } if (start_selected) {