/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007 Jonathan Gordon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include "string-extra.h" #include "config.h" #include "appevents.h" #include "menu.h" #include "root_menu.h" #include "lang.h" #include "settings.h" #include "screens.h" #include "kernel.h" #include "debug.h" #include "misc.h" #include "open_plugin.h" #include "rolo.h" #include "powermgmt.h" #include "power.h" #include "talk.h" #include "audio.h" #include "shortcuts.h" #ifdef HAVE_HOTSWAP #include "storage.h" #include "mv.h" #endif /* gui api */ #include "list.h" #include "splash.h" #include "action.h" #include "yesno.h" #include "viewport.h" #include "tree.h" #if CONFIG_TUNER #include "radio.h" #endif #ifdef HAVE_RECORDING #include "recording.h" #endif #include "wps.h" #include "bookmark.h" #include "playlist.h" #include "playlist_viewer.h" #include "playlist_catalog.h" #include "menus/exported_menus.h" #ifdef HAVE_RTC_ALARM #include "rtc.h" #endif #ifdef HAVE_TAGCACHE #include "tagcache.h" #endif #include "language.h" #include "plugin.h" #include "disk.h" struct root_items { int (*function)(void* param); void* param; const struct menu_item_ex *context_menu; }; static int next_screen = GO_TO_ROOT; /* holding info about the upcoming screen * which is the current screen for the * rest of the code after load_screen * is called */ static int last_screen = GO_TO_ROOT; /* unfortunatly needed so we can resume or goto current track based on previous screen */ static int previous_music = GO_TO_WPS; /* Toggles behavior of the return-to * playback-button depending * on FM radio */ #if (CONFIG_TUNER) static void rootmenu_start_playback_callback(unsigned short id, void *param) { (void) id; (void) param; /* Cancel FM radio selection as previous music. For cases where we start playback without going to the WPS, such as playlist insert or playlist catalog. */ previous_music = GO_TO_WPS; } #endif static char current_track_path[MAX_PATH]; static void rootmenu_track_changed_callback(unsigned short id, void* param) { (void)id; struct mp3entry *id3 = ((struct track_event *)param)->id3; strlcpy(current_track_path, id3->path, MAX_PATH); } static int browser(void* param) { int ret_val; #ifdef HAVE_TAGCACHE struct tree_context* tc = tree_get_context(); #endif struct browse_context browse; int filter = SHOW_SUPPORTED; char folder[MAX_PATH] = "/"; /* stuff needed to remember position in file browser */ static char last_folder[MAX_PATH] = "/"; /* and stuff for the database browser */ #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 && last_screen == GO_TO_WPS && current_track_path[0]) { strcpy(folder, current_track_path); } else if (!strcmp(last_folder, "/")) { strcpy(folder, global_settings.start_directory); } else { #ifdef HAVE_HOTSWAP bool in_hotswap = false; /* handle entering an ejected drive */ int i; for (i = 0; i < NUM_VOLUMES; i++) { char vol_string[VOL_MAX_LEN + 1]; if (!volume_removable(i)) continue; get_volume_name(i, vol_string); /* test whether we would browse the external card */ if (!volume_present(i) && (strstr(last_folder, vol_string) #ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN || (i == 0) #endif )) { /* leave folder as "/" to avoid crash when trying * to access an ejected drive */ strcpy(folder, "/"); in_hotswap = true; break; } } if (!in_hotswap) #endif strcpy(folder, last_folder); } push_current_activity(ACTIVITY_FILEBROWSER); break; #ifdef HAVE_TAGCACHE case GO_TO_DBBROWSER: if (!tagcache_is_usable()) { bool reinit_attempted = false; /* Now display progress until it's ready or the user exits */ while(!tagcache_is_usable()) { struct tagcache_stat *stat = tagcache_get_stat(); /* Allow user to exit */ if (action_userabort(HZ/2)) break; /* Maybe just needs to reboot due to delayed commit */ if (stat->commit_delayed) { splash(HZ*2, ID2P(LANG_PLEASE_REBOOT)); break; } /* Check if ready status is known */ if (!stat->readyvalid) { splash(0, ID2P(LANG_TAGCACHE_BUSY)); continue; } /* Re-init if required */ if (!reinit_attempted && !stat->ready && stat->processed_entries == 0 && stat->commit_step == 0) { /* Prompt the user */ reinit_attempted = true; static const char *lines[]={ ID2P(LANG_TAGCACHE_BUSY), ID2P(LANG_TAGCACHE_FORCE_UPDATE)}; static const struct text_message message={lines, 2}; if(gui_syncyesno_run(&message, NULL, NULL) == YESNO_NO) break; FOR_NB_SCREENS(i) screens[i].clear_display(); /* Start initialisation */ tagcache_rebuild(); } /* Display building progress */ static long talked_tick = 0; if(global_settings.talk_menu && (talked_tick == 0 || TIME_AFTER(current_tick, talked_tick+7*HZ))) { talked_tick = current_tick; if (stat->commit_step > 0) { talk_id(LANG_TAGCACHE_INIT, false); talk_number(stat->commit_step, true); talk_id(VOICE_OF, true); talk_number(tagcache_get_max_commit_step(), true); } else if(stat->processed_entries) { talk_number(stat->processed_entries, false); talk_id(LANG_BUILDING_DATABASE, true); } } if (stat->commit_step > 0) { if (lang_is_rtl()) { splashf(0, "[%d/%d] %s", stat->commit_step, tagcache_get_max_commit_step(), str(LANG_TAGCACHE_INIT)); } else { splashf(0, "%s [%d/%d]", str(LANG_TAGCACHE_INIT), stat->commit_step, tagcache_get_max_commit_step()); } } else { splashf(0, str(LANG_BUILDING_DATABASE), stat->processed_entries); } } } if (!tagcache_is_usable()) return GO_TO_PREVIOUS; filter = SHOW_ID3DB; tc->dirlevel = last_db_dirlevel; tc->selected_item = last_db_selection; push_current_activity(ACTIVITY_DATABASEBROWSER); break; #endif } browse_context_init(&browse, filter, 0, NULL, NOICON, folder, NULL); ret_val = rockbox_browse(&browse); pop_current_activity(); switch ((intptr_t)param) { case GO_TO_FILEBROWSER: if (!get_current_file(last_folder, MAX_PATH) || (!strchr(&last_folder[1], '/') && global_settings.start_directory[1] != '\0')) { last_folder[0] = '/'; last_folder[1] = '\0'; } break; #ifdef HAVE_TAGCACHE case GO_TO_DBBROWSER: last_db_dirlevel = tc->dirlevel; last_db_selection = tc->selected_item; break; #endif } return ret_val; } #ifdef HAVE_RECORDING static int recscrn(void* param) { (void)param; recording_screen(false); return GO_TO_ROOT; } #endif static int wpsscrn(void* param) { int ret_val = GO_TO_PREVIOUS; (void)param; push_current_activity(ACTIVITY_WPS); if (audio_status()) { talk_shutup(); ret_val = gui_wps_show(); } else if ( global_status.resume_index != -1 ) { DEBUGF("Resume index %X crc32 %lX offset %lX\n", global_status.resume_index, (unsigned long)global_status.resume_crc32, (unsigned long)global_status.resume_offset); if (playlist_resume() != -1) { playlist_resume_track(global_status.resume_index, global_status.resume_crc32, global_status.resume_elapsed, global_status.resume_offset); ret_val = gui_wps_show(); } } else { splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME)); } pop_current_activity(); return ret_val; } #if CONFIG_TUNER static int radio(void* param) { (void)param; radio_screen(); return GO_TO_ROOT; } #endif static int miscscrn(void * param) { const struct menu_item_ex *menu = (const struct menu_item_ex*)param; int result = do_menu(menu, NULL, NULL, false); switch (result) { case GO_TO_PLUGIN: case GO_TO_PLAYLIST_VIEWER: case GO_TO_WPS: return result; default: return GO_TO_ROOT; } } static int playlist_view_catalog(void * param) { /* kludge untill catalog_view_playlists() returns something useful */ int old_playstatus = audio_status(); (void)param; push_current_activity(ACTIVITY_PLAYLISTBROWSER); catalog_view_playlists(); pop_current_activity(); if (!old_playstatus && audio_status()) return GO_TO_WPS; return GO_TO_PREVIOUS; } static int playlist_view(void * param) { (void)param; int val; push_current_activity(ACTIVITY_PLAYLISTVIEWER); val = playlist_viewer(); pop_current_activity(); switch (val) { case PLAYLIST_VIEWER_MAINMENU: case PLAYLIST_VIEWER_USB: return GO_TO_ROOT; case PLAYLIST_VIEWER_OK: return GO_TO_PREVIOUS; } return GO_TO_PREVIOUS; } static int load_bmarks(void* param) { (void)param; if(bookmark_mrb_load()) return GO_TO_WPS; return GO_TO_PREVIOUS; } /* 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, #ifdef HAVE_TAGCACHE tagcache_menu, #endif main_menu_, manage_settings, plugin_menu, playlist_options, info_menu, system_menu; static const struct root_items items[] = { [GO_TO_FILEBROWSER] = { browser, (void*)GO_TO_FILEBROWSER, &file_menu}, #ifdef HAVE_TAGCACHE [GO_TO_DBBROWSER] = { browser, (void*)GO_TO_DBBROWSER, &tagcache_menu }, #endif [GO_TO_WPS] = { wpsscrn, NULL, &playback_settings }, [GO_TO_MAINMENU] = { miscscrn, (struct menu_item_ex*)&main_menu_, &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] = { miscscrn, &plugin_menu, NULL }, [GO_TO_PLAYLISTS_SCREEN] = { playlist_view_catalog, NULL, &playlist_options }, [GO_TO_PLAYLIST_VIEWER] = { playlist_view, NULL, &playlist_options }, [GO_TO_SYSTEM_SCREEN] = { miscscrn, &info_menu, &system_menu }, [GO_TO_SHORTCUTMENU] = { do_shortcut_menu, NULL, NULL }, }; static const int nb_items = sizeof(items)/sizeof(*items); static int item_callback(int action, const struct menu_item_ex *this_item, struct gui_synclist *this_list); MENUITEM_RETURNVALUE(shortcut_menu, ID2P(LANG_SHORTCUTS), GO_TO_SHORTCUTMENU, NULL, Icon_Bookmark); 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, NULL, Icon_Audio); #endif MENUITEM_RETURNVALUE(rocks_browser, ID2P(LANG_PLUGINS), GO_TO_BROWSEPLUGINS, NULL, Icon_Plugin); MENUITEM_RETURNVALUE(playlist_browser, ID2P(LANG_CATALOG), GO_TO_PLAYLIST_VIEWER, NULL, Icon_Playlist); static char *get_wps_item_name(int selected_item, void * data, char *buffer, size_t buffer_len) { (void)selected_item; (void)data; (void)buffer; (void)buffer_len; if (audio_status()) return ID2P(LANG_NOW_PLAYING); return ID2P(LANG_RESUME_PLAYBACK); } MENUITEM_RETURNVALUE_DYNTEXT(wps_item, GO_TO_WPS, NULL, get_wps_item_name, NULL, NULL, Icon_Playback_menu); #ifdef HAVE_RECORDING 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, item_callback, Icon_Radio_screen); #endif 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, Icon_Bookmark); MENUITEM_RETURNVALUE(playlists, ID2P(LANG_CATALOG), GO_TO_PLAYLISTS_SCREEN, NULL, Icon_Playlist); MENUITEM_RETURNVALUE(system_menu_, ID2P(LANG_SYSTEM), GO_TO_SYSTEM_SCREEN, NULL, Icon_System_menu); struct menu_item_ex root_menu_; static struct menu_callback_with_desc root_menu_desc = { item_callback, ID2P(LANG_ROCKBOX_TITLE), Icon_Rockbox }; static struct menu_table menu_table[] = { /* Order here represents the default ordering */ { "bookmarks", &bookmarks }, { "files", &file_browser }, #ifdef HAVE_TAGCACHE { "database", &db_browser }, #endif { "wps", &wps_item }, { "settings", &menu_ }, #ifdef HAVE_RECORDING { "recording", &rec }, #endif #if CONFIG_TUNER { "radio", &fm }, #endif { "playlists", &playlists }, { "plugins", &rocks_browser }, { "system_menu", &system_menu_ }, { "shortcuts", &shortcut_menu }, }; #define MAX_MENU_ITEMS (sizeof(menu_table) / sizeof(struct menu_table)) static struct menu_item_ex *root_menu__[MAX_MENU_ITEMS]; struct menu_table *root_menu_get_options(int *nb_options) { *nb_options = MAX_MENU_ITEMS; return menu_table; } void root_menu_load_from_cfg(void* setting, char *value) { char *next = value, *start, *end; unsigned int menu_item_count = 0, i; bool main_menu_added = false; if (*value == '-') { root_menu_set_default(setting, NULL); return; } root_menu_.flags = MENU_HAS_DESC | MT_MENU; root_menu_.submenus = (const struct menu_item_ex **)&root_menu__; root_menu_.callback_and_desc = &root_menu_desc; while (next && menu_item_count < MAX_MENU_ITEMS) { start = next; next = strchr(next, ','); if (next) { *next = '\0'; next++; } start = skip_whitespace(start); if ((end = strchr(start, ' '))) *end = '\0'; for (i=0; iflags&MENU_TYPE_MASK) == MT_RETURN_VALUE) && (root_menu__[i]->value == last_screen)) { return i; } } return 0; } static inline int load_screen(int screen) { /* set the global_status.last_screen before entering, if we dont we will always return to the wrong screen on boot */ int old_previous = last_screen; int ret_val; enum current_activity activity = ACTIVITY_UNKNOWN; if (screen <= GO_TO_ROOT) return screen; if (screen == old_previous) old_previous = GO_TO_ROOT; global_status.last_screen = (char)screen; status_save(); if (screen == GO_TO_BROWSEPLUGINS) activity = ACTIVITY_PLUGINBROWSER; else if (screen == GO_TO_MAINMENU) activity = ACTIVITY_SETTINGS; else if (screen == GO_TO_SYSTEM_SCREEN) activity = ACTIVITY_SYSTEMSCREEN; if (activity != ACTIVITY_UNKNOWN) push_current_activity(activity); ret_val = items[screen].function(items[screen].param); if (activity != ACTIVITY_UNKNOWN) pop_current_activity(); last_screen = screen; if (ret_val == GO_TO_PREVIOUS) last_screen = old_previous; return ret_val; } static int load_context_screen(int selection) { const struct menu_item_ex *context_menu = NULL; int retval = GO_TO_PREVIOUS; push_current_activity(ACTIVITY_CONTEXTMENU); if ((root_menu__[selection]->flags&MENU_TYPE_MASK) == MT_RETURN_VALUE) { int item = root_menu__[selection]->value; context_menu = items[item].context_menu; } /* special cases */ else if (root_menu__[selection] == &info_menu) { context_menu = &system_menu; } if (context_menu) retval = do_menu(context_menu, NULL, NULL, false); pop_current_activity(); return retval; } static int load_plugin_screen(char *plug_path, void* plug_param) { int ret_val; int old_previous = last_screen; last_screen = next_screen; global_status.last_screen = (char)next_screen; status_save(); switch (plugin_load(plug_path, plug_param)) { case PLUGIN_GOTO_WPS: ret_val = GO_TO_WPS; break; case PLUGIN_GOTO_PLUGIN: ret_val = GO_TO_PLUGIN; break; case PLUGIN_OK: ret_val = audio_status() ? GO_TO_PREVIOUS : GO_TO_ROOT; break; default: ret_val = GO_TO_PREVIOUS; break; } if (ret_val == GO_TO_PREVIOUS) last_screen = (old_previous == next_screen) ? GO_TO_ROOT : old_previous; return ret_val; } void root_menu(void) { int previous_browser = GO_TO_FILEBROWSER; int selected = 0; push_current_activity(ACTIVITY_MAINMENU); if (global_settings.start_in_screen == 0) next_screen = (int)global_status.last_screen; else next_screen = global_settings.start_in_screen - 2; #if CONFIG_TUNER add_event(PLAYBACK_EVENT_START_PLAYBACK, rootmenu_start_playback_callback); #endif add_event(PLAYBACK_EVENT_TRACK_CHANGE, rootmenu_track_changed_callback); #ifdef HAVE_RTC_ALARM if ( rtc_check_alarm_started(true) ) { rtc_enable_alarm(false); next_screen = GO_TO_WPS; #if CONFIG_TUNER if (global_settings.alarm_wake_up_screen == ALARM_START_FM) next_screen = GO_TO_FM; #endif #ifdef HAVE_RECORDING if (global_settings.alarm_wake_up_screen == ALARM_START_REC) { recording_start_automatic = true; next_screen = GO_TO_RECSCREEN; } #endif } #endif /* HAVE_RTC_ALARM */ #ifdef HAVE_HEADPHONE_DETECTION if (next_screen == GO_TO_WPS && (global_settings.unplug_autoresume && !headphones_inserted() )) next_screen = GO_TO_ROOT; #endif while (true) { switch (next_screen) { case MENU_ATTACHED_USB: case MENU_SELECTED_EXIT: /* fall through */ case GO_TO_ROOT: if (last_screen != GO_TO_ROOT) selected = get_selection(last_screen); #if (CONFIG_PLATFORM&PLATFORM_ANDROID) /* When we are in the main menu we want the hardware BACK * button to be handled by Android instead of rockbox */ android_ignore_back_button(true); #endif next_screen = do_menu(&root_menu_, &selected, NULL, false); #if (CONFIG_PLATFORM&PLATFORM_ANDROID) android_ignore_back_button(false); #endif if (next_screen != GO_TO_PREVIOUS) last_screen = GO_TO_ROOT; break; case GO_TO_PREVIOUS: next_screen = last_screen; break; case GO_TO_PREVIOUS_BROWSER: next_screen = previous_browser; break; case GO_TO_PREVIOUS_MUSIC: next_screen = previous_music; break; case GO_TO_ROOTITEM_CONTEXT: next_screen = load_context_screen(selected); break; case GO_TO_PLUGIN: { char *key; switch (last_screen) { case GO_TO_ROOT: key = ID2P(LANG_START_SCREEN); break; case GO_TO_WPS: key = ID2P(LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN); break; case GO_TO_SHORTCUTMENU: key = ID2P(LANG_SHORTCUTS); break; default: key = ID2P(LANG_OPEN_PLUGIN); break; } open_plugin_get_entry(key, &open_plugin_entry); char *path = open_plugin_entry.path; char *param = open_plugin_entry.param; if (param[0] == '\0') param = NULL; next_screen = load_plugin_screen(path, param); previous_browser = (next_screen != GO_TO_WPS) ? GO_TO_FILEBROWSER : GO_TO_PLUGIN; break; } default: #ifdef HAVE_TAGCACHE /* With !HAVE_TAGCACHE previous_browser is always GO_TO_FILEBROWSER */ if (next_screen == GO_TO_FILEBROWSER || next_screen == GO_TO_DBBROWSER) previous_browser = next_screen; #endif #if CONFIG_TUNER /* With !CONFIG_TUNER previous_music is always GO_TO_WPS */ if (next_screen == GO_TO_WPS || next_screen == GO_TO_FM) previous_music = next_screen; #endif next_screen = load_screen(next_screen); break; } /* switch() */ } }