Index: apps/bookmark.c =================================================================== --- apps/bookmark.c (revision 14907) +++ apps/bookmark.c (working copy) @@ -73,7 +73,7 @@ char* items[]; }; -static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, +static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, bool most_recent); static bool check_bookmark(const char* bookmark); static char* create_bookmark(void); @@ -137,7 +137,7 @@ if (generate_bookmark_file_name(name)) { char* bookmark = select_bookmark(global_bookmark_file_name, false); - + if (bookmark != NULL) { return play_bookmark(bookmark); @@ -253,7 +253,7 @@ /* ----------------------------------------------------------------------- */ /* This function adds a bookmark to a file. */ /* ------------------------------------------------------------------------*/ -static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, +static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, bool most_recent) { int temp_bookmark_file = 0; @@ -297,7 +297,7 @@ /* This keeps it from getting too large */ if (most_recent && (bookmark_count >= MAX_BOOKMARKS)) break; - + cp = strchr(global_read_buffer,'/'); tmp = strrchr(global_read_buffer,';'); if (check_bookmark(global_read_buffer) && @@ -400,7 +400,7 @@ else { char* bookmark = select_bookmark(global_bookmark_file_name, true); - + if (bookmark != NULL) { return play_bookmark(bookmark); @@ -456,7 +456,7 @@ { read_count++; } - + close(file); return read_count; } @@ -478,25 +478,25 @@ /* Entire file fits in buffer */ first_line = 0; } - + bookmarks->start = first_line; bookmarks->count = 0; bookmarks->reload = false; - + while(read_line(file, global_read_buffer, sizeof(global_read_buffer)) > 0) { read_count++; - + if (read_count >= first_line) { dest -= strlen(global_read_buffer) + 1; - + if (dest < ((char*) bookmarks) + sizeof(*bookmarks) + (sizeof(char*) * (bookmarks->count + 1))) { break; } - + strcpy(dest, global_read_buffer); bookmarks->items[bookmarks->count] = dest; bookmarks->count++; @@ -519,22 +519,22 @@ { if (index == 0) { - return list_index % 2 == 0 + return list_index % 2 == 0 ? (char*) str(LANG_BOOKMARK_DONT_RESUME) : " "; } - + index--; } - if (bookmarks->reload || (index >= bookmarks->start + bookmarks->count) + if (bookmarks->reload || (index >= bookmarks->start + bookmarks->count) || (index < bookmarks->start)) { int read_index = index; - + /* Using count as a guide on how far to move could possibly fail * sometimes. Use byte count if that is a problem? */ - + if (read_index != 0) { /* Move count * 3 / 4 items in the direction the user is moving, @@ -542,33 +542,33 @@ */ int offset = bookmarks->count; int max = bookmarks->total_count - (bookmarks->count / 2); - + if (read_index < bookmarks->start) { offset *= 3; } - + read_index = index - offset / 4; if (read_index > max) { read_index = max; } - + if (read_index < 0) { read_index = 0; } } - + if (buffer_bookmarks(bookmarks, read_index) <= index) { return ""; } } - + if (!parse_bookmark(bookmarks->items[index - bookmarks->start], - &resume_index, NULL, NULL, NULL, NULL, 0, &resume_time, NULL, + &resume_index, NULL, NULL, NULL, NULL, 0, &resume_time, NULL, &shuffle, global_filename)) { return list_index % 2 == 0 ? (char*) str(LANG_BOOKMARK_INVALID) : " "; @@ -596,6 +596,11 @@ } } + +MENUITEM_STRINGLIST(bookmark_menu_items, ID2P(LANG_BOOKMARK_CONTEXT_MENU), + NULL, ID2P(LANG_BOOKMARK_CONTEXT_RESUME), + ID2P(LANG_BOOKMARK_CONTEXT_DELETE)); + /* ----------------------------------------------------------------------- */ /* This displays a the bookmarks in a file and allows the user to */ /* select one to play. */ @@ -617,14 +622,14 @@ bookmarks->filename = bookmark_file_name; bookmarks->start = 0; gui_synclist_init(&list, &get_bookmark_info, (void*) bookmarks, false, 2); - gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), + gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), Icon_Bookmark); gui_syncstatusbar_draw(&statusbars, true); while (!exit) { gui_syncstatusbar_draw(&statusbars, false); - + if (refresh) { int count = get_bookmark_count(bookmark_file_name); @@ -666,11 +671,11 @@ { item--; } - + if (item != last_item && talk_menus_enabled()) { last_item = item; - + if (item == -1) { talk_id(LANG_BOOKMARK_DONT_RESUME, true); @@ -680,21 +685,18 @@ say_bookmark(bookmarks->items[item - bookmarks->start], item); } } - + if (action == ACTION_STD_CONTEXT) { - MENUITEM_STRINGLIST(menu_items, ID2P(LANG_BOOKMARK_CONTEXT_MENU), - NULL, ID2P(LANG_BOOKMARK_CONTEXT_RESUME), - ID2P(LANG_BOOKMARK_CONTEXT_DELETE)); - static const int menu_actions[] = + static const int menu_actions[] = { ACTION_STD_OK, ACTION_BMS_DELETE }; - int selection = do_menu(&menu_items, NULL); - + int selection = do_menu(&bookmark_menu_items, NULL); + refresh = true; - if (selection >= 0 && selection <= + if (selection >= 0 && selection <= (int) (sizeof(menu_actions) / sizeof(menu_actions[0]))) { action = menu_actions[selection]; @@ -708,7 +710,7 @@ { return bookmarks->items[item - bookmarks->start]; } - + /* Else fall through */ case ACTION_TREE_WPS: @@ -794,7 +796,7 @@ char dir[MAX_PATH]; bool enqueue = false; /* only the first voice is not queued */ - if (!parse_bookmark(bookmark, &resume_index, NULL, NULL, NULL, + if (!parse_bookmark(bookmark, &resume_index, NULL, NULL, NULL, dir, sizeof(dir), &ms, NULL, NULL, NULL)) { talk_id(LANG_BOOKMARK_INVALID, true); @@ -802,7 +804,7 @@ } /* disabled, because transition between talkbox and voice UI clip is not nice */ -#if 0 +#if 0 if (global_settings.talk_dir >= 3) { /* "talkbox" enabled */ char* last = strrchr(dir, '/'); @@ -849,7 +851,7 @@ global_filename); return true; } - + return false; } @@ -859,12 +861,12 @@ { s++; } - + if (*s) { s++; } - + return s; } @@ -874,7 +876,7 @@ { *dest = atoi(s); } - + return skip_token(s); } @@ -916,7 +918,7 @@ { const char* s = bookmark; const char* end; - + s = int_token(s, resume_index); s = int_token(s, resume_offset); s = int_token(s, resume_seed); @@ -929,7 +931,7 @@ { return false; } - + end = strchr(s, ';'); if (resume_file != NULL) @@ -947,7 +949,7 @@ strncpy(file_name, end, MAX_PATH - 1); file_name[MAX_PATH - 1] = 0; } - + return true; } @@ -1018,7 +1020,7 @@ if (!(audio_status() && audio_current_track())) { /* no track playing */ - return false; + return false; } /* Checking to see if playing a queued track */ Index: apps/Makefile =================================================================== --- apps/Makefile (revision 14907) +++ apps/Makefile (working copy) @@ -56,7 +56,7 @@ CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(BUILDDATE) $(DEFINES) -DTARGET_ID=$(TARGET_ID) \ -DAPPSVERSION=\"$(VERSION)\" $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE} \ - -DTARGET_NAME=\"$(ARCHOS)\" + -DTARGET_NAME=\"$(ARCHOS)\" -DGATHER_MENU_CALL_STATS -save-temps OBJS2 := $(OBJDIR)/lang.o $(patsubst %.c, $(OBJDIR)/%.o, $(SRC)) OBJS = $(patsubst %.S, $(OBJDIR)/%.o, $(OBJS2)) Index: apps/menu.c =================================================================== --- apps/menu.c (revision 14907) +++ apps/menu.c (working copy) @@ -67,11 +67,11 @@ static int current_subitems_count = 0; static void get_menu_callback(const struct menu_item_ex *m, - menu_callback_type *menu_callback) + menu_callback_type *menu_callback) { if (m->flags&(MENU_HAS_DESC|MENU_DYNAMIC_DESC)) *menu_callback= m->callback_and_desc->menu_callback; - else + else *menu_callback = m->menu_callback; } @@ -95,7 +95,7 @@ const struct menu_item_ex *menu = (const struct menu_item_ex *)data; int type = (menu->flags&MENU_TYPE_MASK); selected_item = get_menu_selection(selected_item, menu); - + (void)buffer; /* only MT_MENU or MT_RETURN_ID is allowed in here */ if (type == MT_RETURN_ID) @@ -105,13 +105,13 @@ menu->menu_get_name_and_icon->list_get_name_data, buffer); return (char*)menu->strings[selected_item]; } - + menu = menu->submenus[selected_item]; - + if ((menu->flags&MENU_DYNAMIC_DESC) && (type != MT_SETTING_W_TEXT)) return menu->menu_get_name_and_icon->list_get_name(selected_item, menu->menu_get_name_and_icon->list_get_name_data, buffer); - + type = (menu->flags&MENU_TYPE_MASK); if ((type == MT_SETTING) || (type == MT_SETTING_W_TEXT)) { @@ -129,7 +129,7 @@ const struct menu_item_ex *menu = (const struct menu_item_ex *)data; int menu_icon = Icon_NOICON; selected_item = get_menu_selection(selected_item, menu); - + if ((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID) { return Icon_Menu_functioncall; @@ -139,7 +139,7 @@ menu_icon = menu->callback_and_desc->icon_id; else if (menu->flags&MENU_DYNAMIC_DESC) menu_icon = menu->menu_get_name_and_icon->icon_id; - + if (menu_icon == Icon_NOICON) { switch (menu->flags&MENU_TYPE_MASK) @@ -161,6 +161,64 @@ } #endif +/* The array (and the var for its length) is defined and initialized + in an automatically generated file 'menu_call_stats_list.c' and + contains pointers to the call statistics for all menu items + in the core Rockbox */ +extern struct s_call_stats * const menu_call_stats_list[]; +extern const int menu_call_stats_list_length; +void get_menu_call_stats_list(struct s_call_stats * const **list, int *list_length) +{ + (*list) = menu_call_stats_list; + (*list_length) = menu_call_stats_list_length; +} + +static void reorder_menu_items_by_freq(const struct menu_item_ex *menu) +{ + int type; + struct s_call_stats *stats; + const struct menu_item_ex **items; + const struct menu_item_ex *tmp; + int item_count; + int i, j, max, max_index; + + type = menu->flags & MENU_TYPE_MASK; + + DEBUGF("Reordering menu with id='%s', type=%d\n", + (menu->call_stats!=0?menu->call_stats->item_id:"---"), + type); + /* TODO: sort other menu as well (e.g. items within sound settings) */ + /* Is this a reordable menu? */ + if (type != MT_MENU) + { + return; + } + + items = menu->submenus; + item_count = MENU_GET_COUNT(menu->flags); + for (i=0; icall_stats; + if ((stats != NULL) && (stats->call_count > max)) + { + max = stats->call_count; + max_index = j; + } + } + if (max_index >= 0) + { + tmp = items[i]; + items[i] = items[max_index]; + items[max_index] = tmp; + } + } +} + + static void init_menu_lists(const struct menu_item_ex *menu, struct gui_synclist *lists, int selected, bool callback) { @@ -170,6 +228,9 @@ int icon; current_subitems_count = 0; + /* Reorder menu items (most frequently selected go to the top) */ + reorder_menu_items_by_freq(menu); + if (type == MT_RETURN_ID) get_menu_callback(menu, &menu_callback); @@ -187,7 +248,7 @@ current_subitems_count++; } } - else + else { current_subitems[current_subitems_count] = i; current_subitems_count++; @@ -201,7 +262,7 @@ icon = Icon_Submenu_Entered; else icon = menu->callback_and_desc->icon_id; - gui_synclist_set_title(lists, P2STR(menu->callback_and_desc->desc), icon); + gui_synclist_set_title(lists, P2STR(menu->callback_and_desc->desc), icon); gui_synclist_set_icon_callback(lists, menu_get_icon); #else (void)icon; @@ -210,7 +271,7 @@ gui_synclist_set_nb_items(lists,current_subitems_count); gui_synclist_limit_scroll(lists,true); gui_synclist_select_item(lists, find_menu_selection(selected)); - + get_menu_callback(menu,&menu_callback); if (callback && menu_callback) menu_callback(ACTION_ENTER_MENUITEM,menu); @@ -224,7 +285,7 @@ int type; unsigned char *str; int sel; - + if (talk_menus_enabled()) { sel = get_menu_selection(gui_synclist_get_sel_pos(lists),menu); @@ -233,7 +294,7 @@ type = menu->submenus[sel]->flags&MENU_TYPE_MASK; if ((type == MT_SETTING) || (type == MT_SETTING_W_TEXT)) talk_setting(menu->submenus[sel]->variable); - else + else { if (menu->submenus[sel]->flags&(MENU_DYNAMIC_DESC)) { @@ -263,6 +324,7 @@ } } } + #define MAX_OPTIONS 32 bool do_setting_from_menu(const struct menu_item_ex *temp) { @@ -275,7 +337,7 @@ title = temp->callback_and_desc->desc; else title = ID2P(setting->lang_id); - option_screen((struct settings_list *)setting, + option_screen((struct settings_list *)setting, setting->flags&F_TEMPVAR, title); return false; } @@ -307,27 +369,30 @@ gui_buttonbar_set(&buttonbar, "<<<", "", ""); gui_buttonbar_draw(&buttonbar); #endif + + DEBUGF("Drawing menu of type %d\n", menu->flags&MENU_TYPE_MASK); + 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,HZ); /* HZ so the status bar redraws corectly */ if (action == ACTION_NONE) { continue; } - + if (menu_callback) { int old_action = action; @@ -350,7 +415,7 @@ talk_menu_item(menu, &lists); continue; } - + #ifdef HAVE_RECORDING if (action == ACTION_STD_REC) { @@ -373,7 +438,7 @@ { ret = GO_TO_ROOTITEM_CONTEXT; done = true; - } + } else if (action == ACTION_STD_MENU) { if (menu != &root_menu_) @@ -387,7 +452,7 @@ in_stringlist = false; if (menu_callback) menu_callback(ACTION_EXIT_MENUITEM, menu); - + if (menu->flags&MENU_EXITAFTERTHISMENU) done = true; if (stack_top > 0) @@ -397,7 +462,7 @@ if (menu->flags&MENU_EXITAFTERTHISMENU) done = true; else - init_menu_lists(menu, &lists, + init_menu_lists(menu, &lists, menu_stack_selected_item[stack_top], false); /* new menu, so reload the callback */ get_menu_callback(menu, &menu_callback); @@ -418,10 +483,17 @@ #endif selected = get_menu_selection(gui_synclist_get_sel_pos(&lists), menu); temp = menu->submenus[selected]; + if (temp->call_stats != NULL) + { + temp->call_stats->call_count++; + DEBUGF("Selected item: id='%s', call_cnt=%d, type=%d\n", + temp->call_stats->item_id, temp->call_stats->call_count, + temp->flags&MENU_TYPE_MASK); + } redraw_lists = true; if (in_stringlist) type = (menu->flags&MENU_TYPE_MASK); - else + else { type = (temp->flags&MENU_TYPE_MASK); get_menu_callback(temp, &menu_callback); @@ -453,7 +525,7 @@ if (temp->flags&MENU_FUNC_USEPARAM) return_value = temp->function->function_w_param( temp->function->param); - else + else return_value = temp->function->function(); if (temp->flags&MENU_FUNC_CHECK_RETVAL) { @@ -468,11 +540,9 @@ case MT_SETTING: case MT_SETTING_W_TEXT: { - if (do_setting_from_menu(temp)) - { - init_menu_lists(menu, &lists, selected, true); - redraw_lists = false; /* above does the redraw */ - } + do_setting_from_menu(temp); + init_menu_lists(menu, &lists, selected, true); + redraw_lists = false; /* above does the redraw */ talk_item = true; break; } @@ -508,7 +578,7 @@ init_menu_lists(menu,&lists,selected,true); /* callback was changed, so reload the menu's callback */ get_menu_callback(menu, &menu_callback); - if ((menu->flags&MENU_EXITAFTERTHISMENU) && + if ((menu->flags&MENU_EXITAFTERTHISMENU) && !(temp->flags&MENU_EXITAFTERTHISMENU)) { done = true; @@ -526,7 +596,7 @@ } if (talk_item && !done) talk_menu_item(menu, &lists); - + if (redraw_lists) gui_synclist_draw(&lists); } Index: apps/menu.h =================================================================== --- apps/menu.h (revision 14907) +++ apps/menu.h (working copy) @@ -25,6 +25,11 @@ #include "icons.h" #include "root_menu.h" /* needed for MENU_* return codes */ +/* Define the following if the statistics about the selected menu + items should be gathered */ +/* GATHER_MENU_CALL_STATS must be defined in the Makefile -- for core only. + For plugins and codecs, it should NOT be defined */ +/*#define GATHER_MENU_CALL_STATS*/ enum menu_item_type { MT_MENU = 0, @@ -80,38 +85,44 @@ int (*menu_callback)(int action, const struct menu_item_ex *this_item); /* For everything else, except if the text is dynamic */ const struct menu_callback_with_desc { - int (*menu_callback)(int action, + int (*menu_callback)(int action, const struct menu_item_ex *this_item); unsigned char *desc; /* string or ID */ int icon_id; /* from icons_6x8 in icons.h */ } *callback_and_desc; /* For when the item text is dynamic */ const struct menu_get_name_and_icon { - int (*menu_callback)(int action, + int (*menu_callback)(int action, const struct menu_item_ex *this_item); char *(*list_get_name)(int selected_item, void * data, char *buffer); void *list_get_name_data; int icon_id; } *menu_get_name_and_icon; }; + /* The following pointer will be initialized with NULL if + GATHER_MENU_CALL_STATS is undefined */ + struct s_call_stats { + char *item_id; /* id of the item, should be unique across all menues */ + int call_count; /* How many times this item was selected */ + } * const call_stats; }; typedef int (*menu_callback_type)(int action, const struct menu_item_ex *this_item); bool do_setting_from_menu(const struct menu_item_ex *temp); -/* +/* int do_menu(const struct menu_item_ex *menu, int *start_selected) - + Return value - usually one of the GO_TO_* values from root_menu.h, - however, some of the following defines can cause this to + however, some of the following defines can cause this to return a different value. - + *menu - The menu to run, can be a pointer to a MAKE_MENU() variable, MENUITEM_STRINGLIST() or MENUITEM_RETURNVALUE() variable. - + *start_selected - the item to select when the menu is first run. - When do_menu() returns, this will be set to the + When do_menu() returns, this will be set to the index of the selected item at the time of the exit. This is always set, even if the menu was cancelled. If NULL it is ignored and the firs item starts selected @@ -124,23 +135,47 @@ - callback: The callback function to call for this menu item. */ +#ifdef GATHER_MENU_CALL_STATS +#define STATS_STRUCT(name) \ + struct s_call_stats item_call_stats_##name = {#name, 0} +#define ITEMS_STATS(name) \ + &item_call_stats_##name +#else +#define STATS_STRUCT(name) +#define ITEMS_STATS(name) \ + NULL +#endif /* GATHER_MENU_CALL_STATS */ + + +/* Function for getting information about selecting of menu items */ +void get_menu_call_stats_list(struct s_call_stats * const **list, int *list_length); + +/* + * Important: all the MENUITEM_xxx macros should only be used at the top + * level, i.e. NOT within a function body. + */ + /* Use this to put a setting into a menu. The setting must appear in settings_list.c. If the setting is not configured properly, the menu will display "Not Done yet!" When the user selects this item the setting select screen will load, when that screen exits the user wll be back in the menu */ #define MENUITEM_SETTING(name,var,callback) \ + STATS_STRUCT(name); \ static const struct menu_item_ex name = \ - {MT_SETTING, {.variable = (void*)var},{callback}}; + {MT_SETTING, {.variable = (void*)var},{callback}, \ + ITEMS_STATS(name)} /* Use this for settings which have a differnt title in their setting screen than in the menu (e.g scroll options */ #define MENUITEM_SETTING_W_TEXT(name, var, str, callback ) \ static const struct menu_callback_with_desc name##__ = \ {callback,str, Icon_NOICON}; \ + STATS_STRUCT(name); \ static const struct menu_item_ex name = \ {MT_SETTING_W_TEXT|MENU_HAS_DESC, {.variable = (void*)var }, \ - {.callback_and_desc = & name##__}}; + {.callback_and_desc = & name##__}, \ + ITEMS_STATS(name)} /* Use this To create a list of Strings (or ID2P()'s ) When the user enters this list and selects one, the menu will exit @@ -150,28 +185,34 @@ static const char *name##_[] = {__VA_ARGS__}; \ static const struct menu_callback_with_desc name##__ = \ {callback,str, Icon_NOICON}; \ + STATS_STRUCT(name); \ static const struct menu_item_ex name = \ {MT_RETURN_ID|MENU_HAS_DESC| \ MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ - { .strings = name##_},{.callback_and_desc = & name##__}}; + { .strings = name##_},{.callback_and_desc = & name##__}, \ + ITEMS_STATS(name)} - + /* causes do_menu() to return a value associated with the item */ -#define MENUITEM_RETURNVALUE(name, str, val, cb, icon) \ - static const struct menu_callback_with_desc name##_ = {cb,str,icon}; \ - static const struct menu_item_ex name = \ - { MT_RETURN_VALUE|MENU_HAS_DESC, { .value = val}, \ - {.callback_and_desc = & name##_}}; - +#define MENUITEM_RETURNVALUE(name, str, val, cb, icon) \ + static const struct menu_callback_with_desc name##_ = {cb,str,icon}; \ + STATS_STRUCT(name); \ + static const struct menu_item_ex name = \ + { MT_RETURN_VALUE|MENU_HAS_DESC, { .value = val}, \ + {.callback_and_desc = & name##_}, \ + ITEMS_STATS(name)} + /* same as above, except the item name is dynamic */ #define MENUITEM_RETURNVALUE_DYNTEXT(name, val, cb, text_callback, \ text_cb_data, icon) \ - static const struct menu_get_name_and_icon name##_ \ + static const struct menu_get_name_and_icon name##_ \ = {cb,text_callback,text_cb_data,icon}; \ - static const struct menu_item_ex name = \ + STATS_STRUCT(name); \ + static const struct menu_item_ex name = \ { MT_RETURN_VALUE|MENU_DYNAMIC_DESC, { .value = val}, \ - {.menu_get_name_and_icon = & name##_}}; - + {.menu_get_name_and_icon = & name##_}, \ + ITEMS_STATS(name)} + /* Use this to put a function call into the menu. When the user selects this item the function will be run, if MENU_FUNC_CHECK_RETVAL is set, the return value @@ -182,30 +223,36 @@ static const struct menu_callback_with_desc name##_ = {callback,str,icon}; \ static const struct menu_func name##__ = {{(void*)func}, param}; \ /* should be const, but recording_settings wont let us do that */ \ + STATS_STRUCT(name); \ const struct menu_item_ex name = \ { MT_FUNCTION_CALL|MENU_HAS_DESC|flags, \ - { .function = & name##__}, {.callback_and_desc = & name##_}}; - + { .function = & name##__}, {.callback_and_desc = & name##_}, \ + ITEMS_STATS(name)} + /* As above, except the text is dynamic */ #define MENUITEM_FUNCTION_DYNTEXT(name, flags, func, param, \ text_callback, text_cb_data, callback, icon) \ static const struct menu_get_name_and_icon name##_ \ = {callback,text_callback,text_cb_data,icon}; \ static const struct menu_func name##__ = {{(void*)func}, param}; \ + STATS_STRUCT(name); \ static const struct menu_item_ex name = \ { MT_FUNCTION_CALL|MENU_DYNAMIC_DESC|flags, \ - { .function = & name##__}, {.menu_get_name_and_icon = & name##_}}; + { .function = & name##__}, {.menu_get_name_and_icon = & name##_}, \ + ITEMS_STATS(name)} -/* Use this to actually create a menu. the ... argument is a list of pointers - to any of the above macro'd variables. +/* Use this to actually create a menu. the ... argument is a list of pointers + to any of the above macro'd variables. (It can also have other menus in the list.) */ #define MAKE_MENU( name, str, callback, icon, ... ) \ static const struct menu_item_ex *name##_[] = {__VA_ARGS__}; \ static const struct menu_callback_with_desc name##__ = {callback,str,icon};\ + STATS_STRUCT(name); \ const struct menu_item_ex name = \ {MT_MENU|MENU_HAS_DESC| \ MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ - { (void*)name##_},{.callback_and_desc = & name##__}}; - + { .submenus = name##_},{.callback_and_desc = & name##__}, \ + ITEMS_STATS(name)} + #endif /* End __MENU_H__ */ Index: apps/onplay.c =================================================================== --- apps/onplay.c (revision 14907) +++ apps/onplay.c (working copy) @@ -81,12 +81,14 @@ #define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \ static const struct menu_item_ex *name##_[] = {__VA_ARGS__}; \ static const struct menu_callback_with_desc name##__ = {callback,str,icon};\ + STATS_STRUCT(name); \ static const struct menu_item_ex name = \ {MT_MENU|MENU_HAS_DESC|MENU_EXITAFTERTHISMENU| \ MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ - { (void*)name##_},{.callback_and_desc = & name##__}}; - + { (void*)name##_},{.callback_and_desc = & name##__}, \ + ITEMS_STATS(name)}; + /* ----------------------------------------------------------------------- */ /* Displays the bookmark menu options for the user to decide. This is an */ /* interface function. */ @@ -94,12 +96,12 @@ static int bookmark_menu_callback(int action, const struct menu_item_ex *this_item); -MENUITEM_FUNCTION(bookmark_create_menu_item, 0, - ID2P(LANG_BOOKMARK_MENU_CREATE), +MENUITEM_FUNCTION(bookmark_create_menu_item, 0, + ID2P(LANG_BOOKMARK_MENU_CREATE), bookmark_create_menu, NULL, NULL, Icon_Bookmark); -MENUITEM_FUNCTION(bookmark_load_menu_item, 0, - ID2P(LANG_BOOKMARK_MENU_LIST), - bookmark_load_menu, NULL, +MENUITEM_FUNCTION(bookmark_load_menu_item, 0, + ID2P(LANG_BOOKMARK_MENU_LIST), + bookmark_load_menu, NULL, bookmark_menu_callback, Icon_Bookmark); MAKE_MENU(bookmark_menu, ID2P(LANG_BOOKMARK_MENU), bookmark_menu_callback, Icon_Bookmark, &bookmark_create_menu_item, &bookmark_load_menu_item); @@ -166,7 +168,7 @@ struct text_message message={lines, 2}; gui_syncsplash(0, ID2P(LANG_WAIT)); - + if (new_playlist) playlist_create(NULL, NULL); @@ -187,7 +189,7 @@ else if (selected_file_attr & ATTR_DIRECTORY) { bool recurse = false; - + if (global_settings.recursive_dir_insert != RECURSE_ASK) recurse = (bool)global_settings.recursive_dir_insert; else @@ -195,14 +197,14 @@ /* Ask if user wants to recurse directory */ recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES); } - + playlist_insert_directory(NULL, selected_file, position, queue, recurse); } else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U) playlist_insert_playlist(NULL, selected_file, position, queue); } - + if (new_playlist && (playlist_amount() > 0)) { /* nothing is currently playing so begin playing what we just @@ -249,14 +251,14 @@ MENUITEM_FUNCTION(cat_view_lists, 0, ID2P(LANG_CATALOG_VIEW), catalog_view_playlists, 0, cat_playlist_callback, Icon_Playlist); -MENUITEM_FUNCTION(cat_add_to_list, 0, ID2P(LANG_CATALOG_ADD_TO), +MENUITEM_FUNCTION(cat_add_to_list, 0, ID2P(LANG_CATALOG_ADD_TO), cat_add_to_a_playlist, 0, NULL, Icon_Playlist); -MENUITEM_FUNCTION(cat_add_to_new, 0, ID2P(LANG_CATALOG_ADD_TO_NEW), +MENUITEM_FUNCTION(cat_add_to_new, 0, ID2P(LANG_CATALOG_ADD_TO_NEW), cat_add_to_a_new_playlist, 0, NULL, Icon_Playlist); MAKE_MENU( cat_playlist_menu, ID2P(LANG_CATALOG), cat_playlist_callback, - Icon_Playlist, &cat_view_lists, + Icon_Playlist, &cat_view_lists, &cat_add_to_list, &cat_add_to_new ); - + static int cat_playlist_callback(int action, const struct menu_item_ex *this_item) { @@ -285,22 +287,22 @@ /* CONTEXT_WPS playlist options */ -MENUITEM_FUNCTION(playlist_viewer_item, 0, +MENUITEM_FUNCTION(playlist_viewer_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer, NULL, NULL, Icon_Playlist); -MENUITEM_FUNCTION(search_playlist_item, 0, +MENUITEM_FUNCTION(search_playlist_item, 0, ID2P(LANG_SEARCH_IN_PLAYLIST), search_playlist, NULL, NULL, Icon_Playlist); MENUITEM_FUNCTION(playlist_save_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist, NULL, NULL, Icon_Playlist); MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_PLAYLIST), shuffle_playlist, NULL, NULL, Icon_Playlist); -MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_PLAYLIST), - NULL, Icon_Playlist, +MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_PLAYLIST), + NULL, Icon_Playlist, &playlist_viewer_item, &search_playlist_item, &playlist_save_item, &reshuffle_item ); - + /* CONTEXT_[TREE|ID3DB] playlist options */ static int playlist_insert_func(void *param) { @@ -313,7 +315,7 @@ return 0; } static int treeplaylist_wplayback_callback(int action, - const struct menu_item_ex* + const struct menu_item_ex* this_item) { (void)this_item; @@ -322,7 +324,7 @@ case ACTION_REQUEST_MENUITEM: if (audio_status() & AUDIO_STATUS_PLAY) return action; - else + else return ACTION_EXIT_MENUITEM; break; } @@ -373,20 +375,20 @@ view_playlist, NULL, treeplaylist_callback, Icon_Playlist); -MAKE_ONPLAYMENU( tree_playlist_menu, ID2P(LANG_PLAYLIST), +MAKE_ONPLAYMENU( tree_playlist_menu, ID2P(LANG_PLAYLIST), treeplaylist_callback, Icon_Playlist, - + /* view */ &view_playlist_item, - + /* insert */ &i_pl_item_no_play, &i_pl_item, &i_first_pl_item, &i_last_pl_item, &i_shuf_pl_item, - + /* queue */ &q_pl_item, &q_first_pl_item, &q_last_pl_item, &q_shuf_pl_item, - + /* replace */ &replace_pl_item ); @@ -399,14 +401,14 @@ case ACTION_REQUEST_MENUITEM: if (this_item == &tree_playlist_menu) { - if (((selected_file_attr & FILE_ATTR_MASK) == + if (((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) || ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)|| (selected_file_attr & ATTR_DIRECTORY)) { return action; } - else + else return ACTION_EXIT_MENUITEM; } else if (this_item == &view_playlist_item) @@ -414,7 +416,7 @@ if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U && context == CONTEXT_TREE) return action; - else + else return ACTION_EXIT_MENUITEM; } else if (this_item == &i_pl_item_no_play) @@ -423,17 +425,17 @@ { return action; } - else + else return ACTION_EXIT_MENUITEM; } else if (this_item == &i_shuf_pl_item) { - + if (audio_status() & AUDIO_STATUS_PLAY) { return action; } - else if ((this_item == &i_shuf_pl_item) && + else if ((this_item == &i_shuf_pl_item) && ((selected_file_attr & ATTR_DIRECTORY) || ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U))) @@ -470,7 +472,7 @@ dirname[dirlen] ='\0'; FOR_NB_SCREENS(i) screens[i].puts(0,1,dirname); - + /* append name to current directory */ snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name); if (entry->attribute & ATTR_DIRECTORY) @@ -480,7 +482,7 @@ continue; /* skip these */ /* inform the user which dir we're deleting */ - + result = remove_dir(dirname, len); /* recursion */ if (result) break; /* or better continue, delete what we can? */ @@ -921,7 +923,7 @@ { strncpy(srcpath, clipboard_selection, sizeof srcpath); strncpy(targetpath, target, sizeof targetpath); - + success = clipboard_pastedirectory(srcpath, sizeof(srcpath), target, sizeof(targetpath), clipboard_is_copy); } @@ -977,13 +979,13 @@ static bool set_rating_inline(void) { struct mp3entry* id3 = audio_current_track(); - if (id3 && id3->tagcache_idx) + if (id3 && id3->tagcache_idx) { - if (id3->rating<10) + if (id3->rating<10) id3->rating++; else id3->rating=0; - + tagcache_update_numeric(id3->tagcache_idx, tag_rating, id3->rating); } return false; @@ -1001,7 +1003,7 @@ return action; } MENUITEM_FUNCTION_DYNTEXT(rating_item, 0, set_rating_inline, - NULL, rating_name, NULL, + NULL, rating_name, NULL, ratingitem_callback, Icon_Questionmark); #endif @@ -1104,7 +1106,7 @@ action : ACTION_EXIT_MENUITEM; } else if ((this_item == &create_dir_item) || - (this_item == &properties_item) || + (this_item == &properties_item) || (this_item == &rename_file_item) || (this_item == &clipboard_cut_item) || (this_item == &clipboard_copy_item) || @@ -1144,11 +1146,11 @@ else if (selected_file #ifdef HAVE_MULTIVOLUME /* no rename+delete for volumes */ - && !(selected_file_attr & ATTR_VOLUME) + && !(selected_file_attr & ATTR_VOLUME) #endif ) { - if ((this_item == &delete_file_item) || + if ((this_item == &delete_file_item) || (this_item == &list_viewers_item)) { return action; @@ -1161,12 +1163,12 @@ } /* used when onplay() is called in the CONTEXT_WPS context */ - -MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE), + +MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE), onplaymenu_callback, Icon_Audio, &sound_settings, &wps_playlist_menu, &cat_playlist_menu, #ifdef HAVE_TAGCACHE - &rating_item, + &rating_item, #endif &bookmark_menu, &browse_id3_item, &delete_file_item, &view_cue_item, #ifdef HAVE_PITCHSCREEN @@ -1177,7 +1179,7 @@ #endif ); /* used when onplay() is not called in the CONTEXT_WPS context */ -MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE), +MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE), onplaymenu_callback, Icon_file_view_menu, &tree_playlist_menu, &cat_playlist_menu, &rename_file_item, &clipboard_cut_item, &clipboard_copy_item, @@ -1194,7 +1196,7 @@ int onplay(char* file, int attr, int from) { int menu_result; - int selected_item = 0; /* this is a bit of a hack to reopen + int selected_item = 0; /* this is a bit of a hack to reopen the menu if certain items are selected */ onplay_result = ONPLAY_OK; context = from; Index: apps/playlist_viewer.c =================================================================== --- apps/playlist_viewer.c (revision 14907) +++ apps/playlist_viewer.c (working copy) @@ -283,7 +283,7 @@ } if (!have_list && (playlist_amount() > 0)) { - /*If dynamic playlist still exists, view it anyway even + /*If dynamic playlist still exists, view it anyway even if playback has reached the end of the playlist */ have_list = true; } @@ -431,17 +431,17 @@ /* Menu of playlist commands. Invoked via ON+PLAY on main viewer screen. Returns -1 if USB attached, 0 if no playlist change, and 1 if playlist changed. */ +MENUITEM_STRINGLIST(playlist_viewer_menu_items, ID2P(LANG_PLAYLIST), NULL, + ID2P(LANG_REMOVE), ID2P(LANG_MOVE), + ID2P(LANG_CATALOG_ADD_TO), ID2P(LANG_CATALOG_ADD_TO_NEW)); static int onplay_menu(int index) { int result, ret = 0; struct playlist_entry * current_track= playlist_buffer_get_track(&viewer.buffer, index); - MENUITEM_STRINGLIST(menu_items, ID2P(LANG_PLAYLIST), NULL, - ID2P(LANG_REMOVE), ID2P(LANG_MOVE), - ID2P(LANG_CATALOG_ADD_TO), ID2P(LANG_CATALOG_ADD_TO_NEW)); bool current = (current_track->index == viewer.current_playing_track); - result = do_menu(&menu_items, NULL); + result = do_menu(&playlist_viewer_menu_items, NULL); if (result == MENU_ATTACHED_USB) { ret = -1; @@ -497,15 +497,15 @@ } /* Menu of viewer options. Invoked via F1(r) or Menu(p). */ -MENUITEM_SETTING(show_icons, &global_settings.playlist_viewer_icons, NULL); -MENUITEM_SETTING(show_indices, &global_settings.playlist_viewer_indices, NULL); -MENUITEM_SETTING(track_display, +MENUITEM_SETTING(vo_show_icons, &global_settings.playlist_viewer_icons, NULL); +MENUITEM_SETTING(vo_show_indices, &global_settings.playlist_viewer_indices, NULL); +MENUITEM_SETTING(vo_track_display, &global_settings.playlist_viewer_track_display, NULL); -MENUITEM_FUNCTION(save_playlist_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), +MENUITEM_FUNCTION(vo_save_playlist_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist_func, 0, NULL, Icon_NOICON); -MAKE_MENU(viewer_settings_menu, ID2P(LANG_PLAYLISTVIEWER_SETTINGS), +MAKE_MENU(viewer_settings_menu, ID2P(LANG_PLAYLISTVIEWER_SETTINGS), NULL, Icon_Playlist, - &show_icons, &show_indices, &track_display, &save_playlist_item); + &vo_show_icons, &vo_show_indices, &vo_track_display, &vo_save_playlist_item); static bool viewer_menu(void) { return do_menu(&viewer_settings_menu, NULL) == MENU_ATTACHED_USB; Index: apps/plugin.c =================================================================== --- apps/plugin.c (revision 14907) +++ apps/plugin.c (working copy) @@ -514,6 +514,8 @@ flush_icache, invalidate_icache, #endif + + get_menu_call_stats_list, }; int plugin_load(const char* plugin, void* parameter) Index: apps/plugin.h =================================================================== --- apps/plugin.h (revision 14907) +++ apps/plugin.h (working copy) @@ -112,7 +112,7 @@ #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 78 +#define PLUGIN_API_VERSION 79 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -502,7 +502,7 @@ /* scroll bar */ struct gui_syncstatusbar *statusbars; void (*gui_syncstatusbar_draw)(struct gui_syncstatusbar * bars, bool force_redraw); - + /* options */ const struct settings_list* (*find_setting)(void* variable, int *id); bool (*option_screen)(struct settings_list *setting, @@ -632,6 +632,9 @@ void (*flush_icache)(void); void (*invalidate_icache)(void); #endif + /* Statistics about the menu item selection (array of addresses of stat structs + * and the number of elements in the array) */ + void (*get_menu_call_stats_list)(struct s_call_stats * const **list, int *list_length); }; /* plugin header */ Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 14907) +++ apps/plugins/CATEGORIES (working copy) @@ -36,6 +36,7 @@ maze,games mazezam,games mem_mon,apps +menu_call_stats,apps metronome,apps midi2wav,viewers midiplay,viewers Index: apps/plugins/dice.c =================================================================== --- apps/plugins/dice.c (revision 14907) +++ apps/plugins/dice.c (working copy) @@ -79,7 +79,7 @@ dice_init(&dice); rb->srand(*rb->current_tick); - + configfile_init(rb); configfile_load(CFG_FILE, config, 2, 0); dice.nb_sides = nb_sides_values[sides_index]; @@ -110,7 +110,7 @@ void dice_init(struct dices* dice){ dice->nb_dices=INITIAL_NB_DICES; sides_index=INITIAL_NB_SIDES; - + } void dice_roll(struct dices* dice) { @@ -168,14 +168,13 @@ display->update(); } +MENUITEM_STRINGLIST(menu,"Dice Menu",NULL,"Roll Dice","Number of Dice", + "Number of Sides","Quit"); + bool dice_menu(struct dices * dice) { int selection; bool menu_quit = false, result = false; - MENUITEM_STRINGLIST(menu,"Dice Menu",NULL,"Roll Dice","Number of Dice", - "Number of Sides","Quit"); - - while (!menu_quit) { switch(rb->do_menu(&menu, &selection)){ case 0: @@ -189,7 +188,7 @@ break; case 2: - rb->set_option("Number of Sides", &sides_index, INT, + rb->set_option("Number of Sides", &sides_index, INT, nb_sides_option, sizeof(nb_sides_values)/sizeof(int), NULL); dice->nb_sides=nb_sides_values[sides_index]; Index: apps/plugins/disktidy.c =================================================================== --- apps/plugins/disktidy.c (revision 14907) +++ apps/plugins/disktidy.c (working copy) @@ -93,7 +93,7 @@ rb->lcd_update(); } -void tidy_get_absolute_path(struct dirent *entry, char *fullname, +void tidy_get_absolute_path(struct dirent *entry, char *fullname, const char* name) { /* gets absolute path using dirent and name */ @@ -113,12 +113,12 @@ int button; DIR *dir; char fullname[MAX_PATH]; - + /* display status text */ tidy_lcd_status(name, removed); - + rb->yield(); - + dir = rb->opendir(name); if (dir) { @@ -137,12 +137,12 @@ rb->closedir(dir); return TIDY_RETURN_USB; } - + rb->yield(); - + /* get absolute path */ tidy_get_absolute_path(entry, fullname, name); - + if (entry->attribute & ATTR_DIRECTORY) { /* dir ignore "." and ".." */ @@ -181,12 +181,12 @@ int del; /* has the item been deleted */ DIR *dir; char fullname[MAX_PATH]; - + /* display status text */ tidy_lcd_status(name, removed); - + rb->yield(); - + dir = rb->opendir(name); if (dir) { @@ -205,9 +205,9 @@ rb->closedir(dir); return TIDY_RETURN_USB; } - + rb->yield(); - + if (entry->attribute & ATTR_DIRECTORY) { /* directory ignore "." and ".." */ @@ -215,10 +215,10 @@ (rb->strcmp(entry->d_name, "..") != 0)) { del = 0; - + /* get absolute path */ tidy_get_absolute_path(entry, fullname, name); - + /* check if we are in root directory "/" */ if (rb->strcmp(name, "/") == 0) { @@ -232,7 +232,7 @@ del = 1; } } - + if (del == 0) { if ((system == TIDY_WIN) || (system == TIDY_BOTH)) @@ -248,7 +248,7 @@ } } } - + if (del == 0) { /* dir not deleted so clean it */ @@ -260,7 +260,7 @@ { /* file */ del = 0; - + if ((system == TIDY_MAC) || (system == TIDY_BOTH)) { /* remove mac files */ @@ -268,17 +268,17 @@ (rb->strncmp(entry->d_name, "._", 2) == 0)) { *removed += 1; /* increment removed files counter */ - + /* get absolute path */ char fullname[MAX_PATH]; tidy_get_absolute_path(entry, fullname, name); - + /* delete file */ rb->remove(fullname); del = 1; } } - + if (del == 0) { if ((system == TIDY_WIN) || (system == TIDY_BOTH)) @@ -287,18 +287,18 @@ if ((rb->strcmp(entry->d_name, "Thumbs.db") == 0)) { *removed += 1; /* increment removed files counter */ - + /* get absolute path */ char fullname[MAX_PATH]; tidy_get_absolute_path(entry, fullname, name); - + /* delete file */ rb->remove(fullname); del = 1; } - } + } } - } + } } rb->closedir(dir); return status; @@ -315,17 +315,17 @@ int removed = 0; enum tidy_return status; char text[24]; /* "Cleaned up nnnnn items" */ - + #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif - + status = tidy_clean("/", &removed, system); - + #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif - + if ((status == TIDY_RETURN_OK) || (status == TIDY_RETURN_ABORT)) { rb->lcd_clear_display(); @@ -340,34 +340,34 @@ return status; } +MENUITEM_STRINGLIST(menu,"Disktidy Menu",NULL,"Start Cleaning", + "Files to Clean","Quit"); + int tidy_lcd_menu(void) { int selection, ret = 2; bool menu_quit = false; - - MENUITEM_STRINGLIST(menu,"Disktidy Menu",NULL,"Start Cleaning", - "Files to Clean","Quit"); - - static const struct opt_items system_option[3] = + + static const struct opt_items system_option[3] = { { "Mac", -1 }, { "Windows", -1 }, { "Both", -1 } }; - + while (!menu_quit) - { + { switch(rb->do_menu(&menu, &selection)) { - + case 0: menu_quit = true; /* start cleaning */ break; - + case 1: rb->set_option("Files to Clean", &ret, INT, system_option, 3, NULL); break; - + default: ret = 99; /* exit plugin */ menu_quit = true; @@ -386,7 +386,7 @@ (void)parameter; rb = api; - + switch(tidy_lcd_menu()) { case 0: @@ -417,15 +417,15 @@ case TIDY_RETURN_USB: return PLUGIN_USB_CONNECTED; case TIDY_RETURN_ABORT: - return PLUGIN_OK; + return PLUGIN_OK; } } - + if (rb->default_event_handler(rb->button_get(false)) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; - + rb->yield(); - + return PLUGIN_OK; } Index: apps/plugins/menu_call_stats.c =================================================================== --- apps/plugins/menu_call_stats.c (revision 0) +++ apps/plugins/menu_call_stats.c (revision 0) @@ -0,0 +1,289 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: dice.c 13984 2007-07-25 13:08:12Z jdgordon $ + * + * Copyright (c) 2005 by Alexander Levin + * + * 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. + * + ****************************************************************************/ + +/* + * Plugin for loading of menu statistics from or writing them to a file + */ + +#include "plugin.h" + +PLUGIN_HEADER + +#define FILENAME "/menustats.mnu" + +static struct plugin_api* rb; +MEM_FUNCTION_WRAPPERS(rb); + +#define ERR_NO_FILE -1 +#define ERR_UNKNOWN_ID -2 +#define ERR_FILE_NOT_COMPLETE -3 +#define ERR_BAD_RB_STATE -4 +#define ERR_BAD_ITEM_CALL_CNT -5 +#define ERR_BAD_SYNTAX -6 +#define ERR_OTHER -99 + + +typedef struct s_call_stats * const *list_type; + +static bool dump_stats(char *filename) +{ + list_type list; + int size; + struct s_call_stats *stats; + int i; + int fd; + + fd = rb->open(filename, O_WRONLY|O_TRUNC); + if (fd < 0) { + rb->splash(HZ*2, "Could not open file %s for writing", filename); + return false; + } + + rb->get_menu_call_stats_list(&list, &size); + + for (i=0; ifdprintf(fd, "%s: %d\n", stats->item_id, stats->call_count); + } + + rb->close(fd); + DEBUGF("Dumped %d entries to the file '%s'\n", size, filename); + + return true; +} + +/* Returns the pointer to the stats for the item with the specified id + or NULL if not found */ +static struct s_call_stats *find_item(char *id, list_type list, int size) +{ + struct s_call_stats *item; + int i; + for (i=0; istrcmp(id, item->item_id) == 0) + return item; + } + return NULL; +} + +/* Replaces the counter according to new = -(old+1) + * This transformation has two properties: + * 1. If the old value is not negative the new value is guaranteed + * to be negative + * 2. After another transformation, old value is restored + */ +static void replace_call_counter(struct s_call_stats *item) +{ + item->call_count = -(item->call_count+1); +} + +static void replace_call_counters(list_type list, int size, + bool only_negative) +{ + struct s_call_stats *stats; + int i; + for (i=0; icall_count >= 0 && only_negative)) + stats->call_count = -(stats->call_count+1); + } +} + +/* Checks that all counters are non negative */ +static bool has_negative_counters(list_type list, int size) +{ + int i; + for (i=0; icall_count < 0) { + DEBUGF("Negative RB counter for entry '%s'\n", list[i]->item_id); + return true; + } + } + return false; +} + +static bool parse_entry(char *line, char *item_id, int *item_counter) +{ + char *sep; + sep = rb->strchr(line, ':'); + if (sep == NULL) + return false; + rb->strncpy(item_id, line, sep - line); + item_id[sep-line] = '\0'; + + sep++; /* Skip colon */ + + /* Skip spaces */ + while (*sep == ' ') + sep++; + + if (*sep == '\0') + return false; /* No value */ + + *item_counter = rb->atoi(sep); + return true; +} + + +/* Loads stats from the file. Returns the number of entries read (>=0 ) if + everything wenk OK or a negative number in the case of an error. + + Following errors are detected: + -1 : file could not be opened for reading + -2 : unknown entry id in the file + -3 : file contains entries not for all menu items + -4 : Rockbox state is not consistent + -5 : entry has a negative counter in the file + -6 : bad entry syntax + -99 : other errors + */ +static int load_stats(char *filename) +{ + list_type list; + int size; + struct s_call_stats *stats; + int i; + int fd; + char file_line[100]; + char nc; + char item_id[100]; + int item_counter; + bool file_opened = false; + bool cnt_replaced = false; + int ret_val; + + rb->get_menu_call_stats_list(&list, &size); + + /* Check that all entries have non-negative counts (should alway be). + Only then can we safely do some tricks (see later) */ + if (has_negative_counters(list, size)) { + ret_val = ERR_BAD_RB_STATE; + goto end_proc; + } + + fd = rb->open(filename, O_RDONLY); + if (fd < 0) { + rb->splash(HZ*2, "Could not open file %s for reading", filename); + ret_val = ERR_NO_FILE; + goto end_proc; + } + file_opened = true; + + replace_call_counters(list, size, false); + cnt_replaced = true; + + /* Now read the file and check that the sets in RB and in + the file are the same */ + + /* First pass: check that all entries are known and have valid values + and that all RB entries are covered */ + while ((nc=rb->read_line(fd, file_line, sizeof(file_line)))) { + if (!parse_entry(file_line, item_id, &item_counter)) { + /* Could not parse the entry */ + DEBUGF("Could not parse '%s'\n", file_line); + ret_val = ERR_BAD_SYNTAX; + goto end_proc; + } + stats = find_item(item_id, list, size); + if (stats == NULL) { + DEBUGF("Unknown item '%s'\n", item_id); + ret_val = ERR_UNKNOWN_ID; + goto end_proc; + } + if (item_counter < 0) { + DEBUGF("Negative counter for '%s'\n", item_id); + ret_val = ERR_BAD_ITEM_CALL_CNT; + goto end_proc; + } + /* Item was OK, convert it back thus marking it as covered */ + replace_call_counter(stats); + } + + /* Check that all items have been covered */ + for (i=0; icall_count < 0) { + DEBUGF("Item '%s' is not covered\n", stats->item_id); + ret_val = ERR_FILE_NOT_COMPLETE; + goto end_proc; + } + } + + /* Now we start the second pass and really replace values */ + rb->lseek(fd, 0, SEEK_SET); + while ((nc=rb->read_line(fd, file_line, sizeof(file_line)))) { + if (!parse_entry(file_line, item_id, &item_counter)) { + /* Could not parse the entry */ + DEBUGF("Could not parse '%s'\n", file_line); + ret_val = ERR_OTHER; + goto end_proc; + } + stats = find_item(item_id, list, size); + if (stats == NULL) { + DEBUGF("Unknown item '%s'\n", item_id); + ret_val = ERR_OTHER; + goto end_proc; + } + if (item_counter < 0) { + DEBUGF("Negative counter for '%s'\n", item_id); + ret_val = ERR_OTHER; + goto end_proc; + } + stats->call_count = item_counter; + } + +end_proc: + if (file_opened) { + rb->close(fd); + file_opened = false; + } + if ((ret_val < 0) && cnt_replaced) { + replace_call_counters(list, size, true); + cnt_replaced = false; + } + DEBUGF("Loaded entries from the file '%s', ret_val=%d\n", + filename, ret_val); + + return ret_val; +} + +/* plugin entry point */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { + char *filename; + int size; + + rb = api; + + if (parameter == NULL) { + /* No file specified -> dump */ + dump_stats(FILENAME); + return PLUGIN_OK; + } else { + filename = (char*) parameter; + size = load_stats(filename); + if (size >= 0) { + rb->splash(HZ*2, "Loaded statistics for %d entries", size); + return PLUGIN_OK; + } else { + rb->splash(HZ*2, "Could not load statistics from %s, error code=%d", + filename, size); + return PLUGIN_ERROR; + } + } +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 14907) +++ apps/plugins/SOURCES (working copy) @@ -7,6 +7,7 @@ firmware_flash.c jackpot.c logo.c +menu_call_stats.c mosaique.c properties.c random_folder_advance_config.c Index: apps/root_menu.c =================================================================== --- apps/root_menu.c (revision 14907) +++ apps/root_menu.c (working copy) @@ -90,18 +90,18 @@ #ifdef HAVE_TAGCACHE static int last_db_dirlevel = 0, last_db_selection = 0; #endif - + switch ((intptr_t)param) { case GO_TO_FILEBROWSER: filter = global_settings.dirfilter; - if (global_settings.browse_current && + if (global_settings.browse_current && last_screen == GO_TO_WPS && audio_status() && wps_state.current_track_path[0] != '\0') { strcpy(folder, wps_state.current_track_path); } -#ifdef HAVE_HOTSWAP /* quick hack to stop crashing if you try entering +#ifdef HAVE_HOTSWAP /* quick hack to stop crashing if you try entering the browser from the menu when you were in the card and it was removed */ else if (strchr(last_folder, '<') && (card_detect() == false)) @@ -120,8 +120,8 @@ while(!tagcache_is_usable()) { gui_syncstatusbar_draw(&statusbars, false); - struct tagcache_stat *stat = tagcache_get_stat(); - + struct tagcache_stat *stat = tagcache_get_stat(); + /* Allow user to exit */ if (action_userabort(HZ/2)) break; @@ -139,9 +139,9 @@ gui_syncsplash(0, str(LANG_TAGCACHE_BUSY)); continue; } - + /* Re-init if required */ - if (!reinit_attempted && !stat->ready && + if (!reinit_attempted && !stat->ready && stat->processed_entries == 0 && stat->commit_step == 0) { /* Prompt the user */ @@ -180,7 +180,7 @@ if (stat->commit_step > 0) { gui_syncsplash(0, "%s [%d/%d]", - str(LANG_TAGCACHE_INIT), stat->commit_step, + str(LANG_TAGCACHE_INIT), stat->commit_step, tagcache_get_max_commit_step()); } else @@ -216,13 +216,13 @@ #endif } return ret_val; -} +} static int menu(void* param) { (void)param; return do_menu(NULL, 0); - + } #ifdef HAVE_RECORDING static int recscrn(void* param) @@ -279,12 +279,13 @@ bookmark_mrb_load(); return GO_TO_PREVIOUS; } + +MENUITEM_STRINGLIST(plugins_menu_items, ID2P(LANG_PLUGINS), NULL, + ID2P(LANG_PLUGIN_GAMES), + ID2P(LANG_PLUGIN_APPS), ID2P(LANG_PLUGIN_DEMOS)); static int plugins_menu(void* param) { (void)param; - MENUITEM_STRINGLIST(plugins_menu_items, ID2P(LANG_PLUGINS), NULL, - ID2P(LANG_PLUGIN_GAMES), - ID2P(LANG_PLUGIN_APPS), ID2P(LANG_PLUGIN_DEMOS)); char *folder; int retval = GO_TO_PREVIOUS; int selection = 0, current = 0; @@ -312,8 +313,8 @@ /* These are all static const'd from apps/menus/ *.c so little hack so we can use them */ -extern struct menu_item_ex - file_menu, +extern struct menu_item_ex + file_menu, #ifdef HAVE_TAGCACHE tagcache_menu, #endif @@ -329,18 +330,18 @@ #endif [GO_TO_WPS] = { wpsscrn, NULL, &playback_menu_item }, [GO_TO_MAINMENU] = { menu, NULL, &manage_settings }, - + #ifdef HAVE_RECORDING [GO_TO_RECSCREEN] = { recscrn, NULL, &recording_settings_menu }, #endif - + #if CONFIG_TUNER [GO_TO_FM] = { radio, NULL, &radio_settings_menu }, #endif - - [GO_TO_RECENTBMARKS] = { load_bmarks, NULL, &bookmark_settings_menu }, - [GO_TO_BROWSEPLUGINS] = { plugins_menu, NULL, NULL }, - + + [GO_TO_RECENTBMARKS] = { load_bmarks, NULL, &bookmark_settings_menu }, + [GO_TO_BROWSEPLUGINS] = { plugins_menu, NULL, NULL }, + }; static const int nb_items = sizeof(items)/sizeof(*items); @@ -349,10 +350,10 @@ MENUITEM_RETURNVALUE(file_browser, ID2P(LANG_DIR_BROWSER), GO_TO_FILEBROWSER, NULL, Icon_file_view_menu); #ifdef HAVE_TAGCACHE -MENUITEM_RETURNVALUE(db_browser, ID2P(LANG_TAGCACHE), GO_TO_DBBROWSER, +MENUITEM_RETURNVALUE(db_browser, ID2P(LANG_TAGCACHE), GO_TO_DBBROWSER, NULL, Icon_Audio); #endif -MENUITEM_RETURNVALUE(rocks_browser, ID2P(LANG_PLUGINS), GO_TO_BROWSEPLUGINS, +MENUITEM_RETURNVALUE(rocks_browser, ID2P(LANG_PLUGINS), GO_TO_BROWSEPLUGINS, NULL, Icon_Plugin); char *get_wps_item_name(int selected_item, void * data, char *buffer) { @@ -361,20 +362,20 @@ return ID2P(LANG_NOW_PLAYING); return ID2P(LANG_RESUME_PLAYBACK); } -MENUITEM_RETURNVALUE_DYNTEXT(wps_item, GO_TO_WPS, NULL, get_wps_item_name, +MENUITEM_RETURNVALUE_DYNTEXT(wps_item, GO_TO_WPS, NULL, get_wps_item_name, NULL, Icon_Playback_menu); #ifdef HAVE_RECORDING -MENUITEM_RETURNVALUE(rec, ID2P(LANG_RECORDING), GO_TO_RECSCREEN, +MENUITEM_RETURNVALUE(rec, ID2P(LANG_RECORDING), GO_TO_RECSCREEN, NULL, Icon_Recording); #endif #if CONFIG_TUNER -MENUITEM_RETURNVALUE(fm, ID2P(LANG_FM_RADIO), GO_TO_FM, +MENUITEM_RETURNVALUE(fm, ID2P(LANG_FM_RADIO), GO_TO_FM, item_callback, Icon_Radio_screen); #endif -MENUITEM_RETURNVALUE(menu_, ID2P(LANG_SETTINGS), GO_TO_MAINMENU, +MENUITEM_RETURNVALUE(menu_, ID2P(LANG_SETTINGS), GO_TO_MAINMENU, NULL, Icon_Submenu_Entered); MENUITEM_RETURNVALUE(bookmarks, ID2P(LANG_BOOKMARK_MENU_RECENT_BOOKMARKS), - GO_TO_RECENTBMARKS, item_callback, + GO_TO_RECENTBMARKS, item_callback, Icon_Bookmark); #ifdef HAVE_LCD_CHARCELLS static int do_shutdown(void) @@ -392,13 +393,13 @@ #endif MAKE_MENU(root_menu_, ID2P(LANG_ROCKBOX_TITLE), item_callback, Icon_Rockbox, - &bookmarks, &file_browser, + &bookmarks, &file_browser, #ifdef HAVE_TAGCACHE &db_browser, #endif - &wps_item, &menu_, + &wps_item, &menu_, #ifdef HAVE_RECORDING - &rec, + &rec, #endif #if CONFIG_TUNER &fm, @@ -410,7 +411,7 @@ #endif ); -int item_callback(int action, const struct menu_item_ex *this_item) +int item_callback(int action, const struct menu_item_ex *this_item) { switch (action) { @@ -423,7 +424,7 @@ if (radio_hardware_present() == 0) return ACTION_EXIT_MENUITEM; } - else + else #endif if (this_item == &bookmarks) { @@ -439,7 +440,7 @@ unsigned int i; for(i=0; i< sizeof(root_menu__)/sizeof(*root_menu__); i++) { - if (((root_menu__[i]->flags&MENU_TYPE_MASK) == MT_RETURN_VALUE) && + if (((root_menu__[i]->flags&MENU_TYPE_MASK) == MT_RETURN_VALUE) && (root_menu__[i]->value == last_screen)) { return i; @@ -479,7 +480,7 @@ { context_menu = &system_menu; } - + if (context_menu) return do_menu(context_menu, NULL); else @@ -495,9 +496,9 @@ if (global_settings.start_in_screen == 0) next_screen = (int)global_status.last_screen; else next_screen = global_settings.start_in_screen - 2; - + #ifdef HAVE_RTC_ALARM - if ( rtc_check_alarm_started(true) ) + if ( rtc_check_alarm_started(true) ) { rtc_enable_alarm(false); next_screen = GO_TO_WPS; @@ -516,7 +517,7 @@ #endif /* HAVE_RTC_ALARM */ #ifdef HAVE_HEADPHONE_DETECTION - if (next_screen == GO_TO_WPS && + if (next_screen == GO_TO_WPS && (global_settings.unplug_autoresume && !headphones_inserted() )) next_screen = GO_TO_ROOT; #endif @@ -551,13 +552,13 @@ next_screen = load_context_screen(selected); break; default: - if (next_screen == GO_TO_FILEBROWSER + if (next_screen == GO_TO_FILEBROWSER #ifdef HAVE_TAGCACHE || next_screen == GO_TO_DBBROWSER #endif ) previous_browser = next_screen; - if (next_screen == GO_TO_WPS + if (next_screen == GO_TO_WPS #if CONFIG_TUNER || next_screen == GO_TO_FM #endif Index: apps/SOURCES =================================================================== --- apps/SOURCES (revision 14907) +++ apps/SOURCES (working copy) @@ -156,3 +156,4 @@ #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD keymaps/keymap-av300.c #endif +menu_call_stats_list.c