/*************************************************************************** * * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2003 Hardeep Sidhu * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include "playlist.h" #include "audio.h" #include "screens.h" #include "status.h" #include "settings.h" #include "icons.h" #include "menu.h" #include "plugin.h" #include "keyboard.h" #include "tree.h" #include "onplay.h" #include "talk.h" #include "misc.h" #include "action.h" #ifdef HAVE_LCD_BITMAP #include "widgets.h" #endif #include "lang.h" #include "playlist_viewer.h" /* Defines for LCD display purposes. Taken from tree.c */ #ifdef HAVE_LCD_BITMAP #define CURSOR_X (global_settings.scrollbar && \ viewer.num_tracks>viewer.num_display_lines?1:0) #define CURSOR_Y 0 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4) #define ICON_WIDTH ((viewer.char_width > 6) ? viewer.char_width : 6) #define MARGIN_X ((global_settings.scrollbar && \ viewer.num_tracks > viewer.num_display_lines ? \ SCROLLBAR_WIDTH : 0) + CURSOR_WIDTH + \ (global_settings.playlist_viewer_icons ? \ ICON_WIDTH : 0)) #define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0) #define LINE_X 0 #define LINE_Y (global_settings.statusbar ? 1 : 0) #define SCROLLBAR_X 0 #define SCROLLBAR_Y lcd_getymargin() #define SCROLLBAR_WIDTH 6 #else #define MARGIN_X 0 #define MARGIN_Y 0 #define LINE_X 2 #define LINE_Y 0 #define CURSOR_X 0 #define CURSOR_Y 0 #endif /* Maximum number of tracks we can have loaded at one time */ #define MAX_PLAYLIST_ENTRIES 200 /* Default playlist name for saving */ #define DEFAULT_PLAYLIST_NAME "/viewer.m3u" /* Index of track on display line _pos */ #define INDEX(_pos) (viewer.first_display_index - viewer.first_index + (_pos)) /* Global playlist viewer settings */ struct playlist_viewer_info { struct playlist_info* playlist; /* playlist being viewed */ char *name_buffer; /* Buffer used to store track names */ int buffer_size; /* Size of name buffer */ int num_display_lines; /* Number of lines on lcd */ int line_height; /* Height (in pixels) of display line */ int char_width; /* Width (in pixels) of a character */ int num_tracks; /* Number of tracks in playlist */ int current_playing_track; /* Index of current playing track */ int num_loaded; /* Number of track entries loaded in viewer */ int first_index; /* Index of first loaded track */ int last_index; /* Index of last loaded track */ int first_display_index; /* Index of first track on display */ int last_display_index; /* Index of last track on display */ int cursor_pos; /* Line number of cursor */ int move_track; /* Playlist index of track to move or -1 */ }; /* Information about a specific track */ struct playlist_entry { char *name; /* Formatted track name */ int index; /* Playlist index */ int display_index; /* Display index */ bool queued; /* Is track queued? */ }; static struct playlist_viewer_info viewer; static struct playlist_entry tracks[MAX_PLAYLIST_ENTRIES]; /* Used when viewing playlists on disk */ static struct playlist_info temp_playlist; static bool initialize(char* filename, bool reload); static void load_playlist_entries(int start_index); static void load_playlist_entries_r(int end_index); static int load_entry(int index, int pos, char* p, int size); static void format_name(char* dest, const char* src); static void format_line(const struct playlist_entry* track, char* str, int len); static void display_playlist(void); static void update_display_line(int line, bool scroll); static void scroll_display(int lines); static void update_first_index(void); static bool update_playlist(bool force); static int onplay_menu(int index); static bool viewer_menu(void); static bool show_icons(void); static bool show_indices(void); static bool track_display(void); static bool save_playlist(void); /* Initialize the playlist viewer. */ static bool initialize(char* filename, bool reload) { char* buffer; int buffer_size; bool is_playing = audio_status() & AUDIO_STATUS_PLAY; if (!filename && !is_playing) /* Nothing is playing, exit */ return false; buffer = plugin_get_buffer(&buffer_size); if (!buffer) return false; if (!filename) viewer.playlist = NULL; else { /* Viewing playlist on disk */ char *dir, *file, *temp_ptr; char *index_buffer = NULL; int index_buffer_size = 0; viewer.playlist = &temp_playlist; /* Separate directory from filename */ temp_ptr = strrchr(filename+1,'/'); if (temp_ptr) { *temp_ptr = 0; dir = filename; file = temp_ptr + 1; } else { dir = "/"; file = filename+1; } if (is_playing) { /* Something is playing, use half the plugin buffer for playlist indices */ index_buffer_size = buffer_size / 2; index_buffer = buffer; } playlist_create_ex(viewer.playlist, dir, file, index_buffer, index_buffer_size, buffer+index_buffer_size, buffer_size-index_buffer_size); if (temp_ptr) *temp_ptr = '/'; buffer += index_buffer_size; buffer_size -= index_buffer_size; } viewer.name_buffer = buffer; viewer.buffer_size = buffer_size; #ifdef HAVE_LCD_BITMAP { char icon_chars[] = "MQ"; /* characters used as icons */ unsigned int i; viewer.char_width = 0; viewer.line_height = 0; /* Use icon characters to calculate largest possible width/height so that we set proper margins */ for (i=0; i viewer.char_width) viewer.char_width = w; if (h > viewer.line_height) { viewer.line_height = h; viewer.num_display_lines = (LCD_HEIGHT - MARGIN_Y)/h; } } } #else viewer.num_display_lines = 2; viewer.char_width = 1; viewer.line_height = 1; #endif viewer.move_track = -1; if (!reload) { viewer.cursor_pos = 0; if (!viewer.playlist) /* Start displaying at current playing track */ viewer.first_display_index = playlist_get_display_index() - 1; else viewer.first_display_index = 0; update_first_index(); } if (!update_playlist(true)) return false; return true; } /* Load tracks starting at start_index */ static void load_playlist_entries(int start_index) { int num_entries = viewer.num_tracks - start_index; char* p = viewer.name_buffer; int remaining = viewer.buffer_size; int i; viewer.first_index = start_index; if (num_entries > MAX_PLAYLIST_ENTRIES) num_entries = MAX_PLAYLIST_ENTRIES; for(i=0; i= MAX_PLAYLIST_ENTRIES) num_entries = MAX_PLAYLIST_ENTRIES-1; for(i=num_entries; i>=0; i--, end_index--) { int len = load_entry(end_index, i, p, remaining); if (len < 0) { int j; /* Out of name buffer space */ num_entries -= i; /* Shift loaded tracks up such that first track is index 0 */ for (j=0; j MAX_PLAYLIST_ENTRIES) max = MAX_PLAYLIST_ENTRIES; for (i = num_entries; iname); if (global_settings.playlist_viewer_indices) /* Display playlist index */ snprintf(str, len, "%d. %s", track->display_index, name); else snprintf(str, len, "%s", name); } /* Display tracks on screen */ static void display_playlist(void) { int i; int num_display_tracks = viewer.last_display_index - viewer.first_display_index; lcd_clear_display(); #ifdef HAVE_LCD_BITMAP lcd_setmargins(MARGIN_X, MARGIN_Y); lcd_setfont(FONT_UI); #endif for (i=0; i<=num_display_tracks; i++) { if (global_settings.playlist_viewer_icons) { /* Icons */ if (tracks[INDEX(i)].index == viewer.current_playing_track) { /* Current playing track */ #ifdef HAVE_LCD_BITMAP int offset=0; if ( viewer.line_height > 8 ) offset = (viewer.line_height - 8) / 2; lcd_bitmap(bitmap_icons_6x8[File], CURSOR_X * 6 + CURSOR_WIDTH, MARGIN_Y+(i*viewer.line_height) + offset, 6, 8, true); #else lcd_putc(LINE_X-1, i, File); #endif } else if (tracks[INDEX(i)].index == viewer.move_track) { /* Track we are moving */ #ifdef HAVE_LCD_BITMAP lcd_putsxy(CURSOR_X * 6 + CURSOR_WIDTH, MARGIN_Y+(i*viewer.line_height), "M"); #else lcd_putc(LINE_X-1, i, 'M'); #endif } else if (tracks[INDEX(i)].queued) { /* Queued track */ #ifdef HAVE_LCD_BITMAP lcd_putsxy(CURSOR_X * 6 + CURSOR_WIDTH, MARGIN_Y+(i*viewer.line_height), "Q"); #else lcd_putc(LINE_X-1, i, 'Q'); #endif } } update_display_line(i, false); } #ifdef HAVE_LCD_BITMAP if (global_settings.scrollbar && (viewer.num_tracks > viewer.num_display_lines)) scrollbar(SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH - 1, LCD_HEIGHT - SCROLLBAR_Y, viewer.num_tracks-1, viewer.first_display_index, viewer.last_display_index, VERTICAL); #endif put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, true); status_draw(true); } /* Scroll cursor or display by num lines */ static void scroll_display(int lines) { int new_index = viewer.first_display_index + viewer.cursor_pos + lines; bool pagescroll = false; bool wrap = false; put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, false); if (lines > 1 || lines < -1) pagescroll = true; if (new_index < 0) { /* Wrap around if not pageup */ if (pagescroll) new_index = 0; else { new_index += viewer.num_tracks; viewer.cursor_pos = viewer.num_display_lines-1; wrap = true; } } else if (new_index >= viewer.num_tracks) { /* Wrap around if not pagedown */ if (pagescroll) new_index = viewer.num_tracks - 1; else { new_index -= viewer.num_tracks; viewer.cursor_pos = 0; wrap = true; } } if (new_index >= viewer.first_display_index && new_index <= viewer.last_display_index) { /* Just update the cursor */ viewer.cursor_pos = new_index - viewer.first_display_index; } else { /* New track is outside of display */ if (wrap) viewer.first_display_index = new_index; else viewer.first_display_index = viewer.first_display_index + lines; if (viewer.first_display_index < 0) viewer.first_display_index = 0; viewer.last_display_index = viewer.first_display_index + (viewer.num_display_lines - 1); if (viewer.last_display_index >= viewer.num_tracks) { /* display as many tracks as possible on screen */ if (viewer.first_display_index > 0) { viewer.first_display_index -= (viewer.last_display_index - viewer.num_tracks + 1); if (viewer.first_display_index < 0) viewer.first_display_index = 0; } viewer.last_display_index = viewer.num_tracks - 1; } if (viewer.cursor_pos > (viewer.last_display_index - viewer.first_display_index)) viewer.cursor_pos = viewer.last_display_index - viewer.first_display_index; /* Load more data if needed */ if (viewer.first_display_index < viewer.first_index) load_playlist_entries_r(viewer.last_display_index); else if (viewer.last_display_index > viewer.last_index) load_playlist_entries(viewer.first_display_index); display_playlist(); } put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, true); } /* Update lcd line. Scroll line if requested */ static void update_display_line(int line, bool scroll) { char str[MAX_PATH + 16]; format_line(&tracks[INDEX(line)], str, sizeof(str)); if (scroll) { #ifdef HAVE_LCD_BITMAP if (global_settings.invert_cursor) lcd_puts_scroll_style(LINE_X, line, str, STYLE_INVERT); else #endif lcd_puts_scroll(LINE_X, line, str); } else lcd_puts(LINE_X, line, str); } /* Update first index, if necessary, to put as much as possible on the screen */ static void update_first_index(void) { /* viewer.num_tracks may be invalid at this point */ int num_tracks = playlist_amount_ex(viewer.playlist); if ((num_tracks - viewer.first_display_index) < viewer.num_display_lines) { /* Try to display as much as possible */ int old_index = viewer.first_display_index; viewer.first_display_index = num_tracks - viewer.num_display_lines; if (viewer.first_display_index < 0) viewer.first_display_index = 0; /* Cursor should still point at current track */ viewer.cursor_pos += old_index - viewer.first_display_index; } } /* Update playlist in case something has changed or forced */ static bool update_playlist(bool force) { if (!viewer.playlist) playlist_get_resume_info(&viewer.current_playing_track); else viewer.current_playing_track = -1; if (force || playlist_amount_ex(viewer.playlist) != viewer.num_tracks) { int index; /* Reload tracks */ viewer.num_tracks = playlist_amount_ex(viewer.playlist); if (viewer.num_tracks < 0) return false; index = viewer.first_display_index; load_playlist_entries(index); if (viewer.num_loaded <= 0) return false; viewer.first_display_index = viewer.first_index; viewer.last_display_index = viewer.first_index + viewer.num_display_lines - 1; if (viewer.last_display_index >= viewer.num_tracks) viewer.last_display_index = viewer.num_tracks - 1; } display_playlist(); return true; } /* 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. */ static int onplay_menu(int index) { struct menu_item items[3]; /* increase this if you add entries! */ int m, i=0, result, ret = 0; bool current = (tracks[index].index == viewer.current_playing_track); items[i].desc = ID2P(LANG_REMOVE); i++; items[i].desc = ID2P(LANG_MOVE); i++; items[i].desc = ID2P(LANG_FILE_OPTIONS); i++; m = menu_init(items, i, NULL, NULL, NULL, NULL); result = menu_show(m); if (result == MENU_ATTACHED_USB) ret = -1; else if (result >= 0) { /* Abort current move */ viewer.move_track = -1; switch (result) { case 0: /* delete track */ playlist_delete(viewer.playlist, tracks[index].index); if (current) { /* Start playing new track except if it's the last track in the playlist and repeat mode is disabled */ if (tracks[index].display_index != viewer.num_tracks || global_settings.repeat_mode == REPEAT_ALL) { talk_buffer_steal(); /* will use the mp3 buffer */ audio_play(0); viewer.current_playing_track = -1; } } ret = 1; break; case 1: /* move track */ viewer.move_track = tracks[index].index; ret = 0; break; case 2: { onplay(tracks[index].name, TREE_ATTR_MPA, CONTEXT_TREE); if (!viewer.playlist) ret = 1; else ret = 0; break; } } } menu_exit(m); return ret; } /* Menu of viewer options. Invoked via F1(r) or Menu(p). */ static bool viewer_menu(void) { int m; bool result; static const struct menu_item items[] = { { ID2P(LANG_SHOW_ICONS), show_icons }, { ID2P(LANG_SHOW_INDICES), show_indices }, { ID2P(LANG_TRACK_DISPLAY), track_display }, { ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist }, }; m=menu_init( items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL ); result = menu_run(m); menu_exit(m); settings_save(); return result; } /* Show icons in viewer? */ static bool show_icons(void) { return set_bool(str(LANG_SHOW_ICONS), &global_settings.playlist_viewer_icons); } /* Show indices of tracks? */ static bool show_indices(void) { return set_bool(str(LANG_SHOW_INDICES), &global_settings.playlist_viewer_indices); } /* How to display a track */ static bool track_display(void) { static const struct opt_items names[] = { { STR(LANG_DISPLAY_TRACK_NAME_ONLY) }, { STR(LANG_DISPLAY_FULL_PATH) } }; return set_option(str(LANG_TRACK_DISPLAY), &global_settings.playlist_viewer_track_display, INT, names, 2, NULL); } /* Save playlist to disk */ static bool save_playlist(void) { char filename[MAX_PATH+1]; strncpy(filename, DEFAULT_PLAYLIST_NAME, sizeof(filename)); if (!kbd_input(filename, sizeof(filename))) { playlist_save(viewer.playlist, filename); /* reload in case playlist was saved to cwd */ reload_directory(); } return false; } /* View current playlist */ bool playlist_viewer(void) { return playlist_viewer_ex(NULL); } /* Main viewer function. Filename identifies playlist to be viewed. If NULL, view current playlist. */ bool playlist_viewer_ex(char* filename) { bool ret = false; /* return value */ bool exit=false; /* exit viewer */ bool update=true; /* update display */ bool cursor_on=true; /* used for flashing cursor */ int old_cursor_pos; /* last cursor position */ int button, lastbutton = BUTTON_NONE; if (!initialize(filename, false)) goto exit; old_cursor_pos = viewer.cursor_pos; while (!exit) { int track; if (!viewer.playlist && !(audio_status() & AUDIO_STATUS_PLAY)) { /* Play has stopped */ #ifdef HAVE_LCD_CHARCELLS splash(HZ, true, str(LANG_END_PLAYLIST_PLAYER)); #else splash(HZ, true, str(LANG_END_PLAYLIST_RECORDER)); #endif goto exit; } if (viewer.move_track != -1 || !cursor_on) { /* Flash cursor to identify that we are moving a track */ cursor_on = !cursor_on; #ifdef HAVE_LCD_BITMAP if (global_settings.invert_cursor) { lcd_invertrect( MARGIN_X, MARGIN_Y+(viewer.cursor_pos*viewer.line_height), LCD_WIDTH, viewer.line_height); lcd_invertscroll(LINE_X, viewer.cursor_pos); } else put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, cursor_on); lcd_update_rect( 0, MARGIN_Y + (viewer.cursor_pos * viewer.line_height), LCD_WIDTH, viewer.line_height); #else put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, cursor_on); lcd_update(); #endif } if (!viewer.playlist) playlist_get_resume_info(&track); else track = -1; if (track != viewer.current_playing_track || playlist_amount_ex(viewer.playlist) != viewer.num_tracks) { /* Playlist has changed (new track started?) */ update_first_index(); if (!update_playlist(false)) goto exit; else update = true; /* Abort move on playlist change */ viewer.move_track = -1; } /* Timeout so we can determine if play status has changed */ button = button_get_w_tmo(HZ/2); switch (button) { case TREE_EXIT: #ifdef TREE_OFF case TREE_OFF: #endif exit = true; break; case TREE_PREV: case TREE_PREV | BUTTON_REPEAT: scroll_display(-1); update = true; break; case TREE_NEXT: case TREE_NEXT | BUTTON_REPEAT: scroll_display(1); update = true; break; #ifdef TREE_PGUP case TREE_PGUP: case TREE_PGUP | BUTTON_REPEAT: /* Pageup */ scroll_display(-viewer.num_display_lines); update = true; break; case TREE_PGDN: case TREE_PGDN | BUTTON_REPEAT: /* Pagedown */ scroll_display(viewer.num_display_lines); update = true; break; #endif case TREE_RUN: #ifdef TREE_RUN_PRE if (lastbutton != TREE_RUN_PRE) break; #endif if (viewer.move_track >= 0) { /* Move track */ int ret; ret = playlist_move(viewer.playlist, viewer.move_track, tracks[INDEX(viewer.cursor_pos)].index); if (ret < 0) splash(HZ, true, str(LANG_MOVE_FAILED)); update_playlist(true); viewer.move_track = -1; } else if (!viewer.playlist) { /* play new track */ playlist_start(tracks[INDEX(viewer.cursor_pos)].index, 0); update_playlist(false); } else { /* New playlist */ if (playlist_set_current(viewer.playlist) < 0) goto exit; playlist_start(tracks[INDEX(viewer.cursor_pos)].index, 0); /* Our playlist is now the current list */ if (!initialize(NULL, true)) goto exit; } display_playlist(); update = true; break; case TREE_CONTEXT: #ifdef TREE_CONTEXT2 case TREE_CONTEXT2: #endif { /* ON+PLAY menu */ int ret; ret = onplay_menu(INDEX(viewer.cursor_pos)); if (ret < 0) { ret = true; goto exit; } else if (ret > 0) { /* Playlist changed */ update_first_index(); update_playlist(false); if (viewer.num_tracks <= 0) exit = true; } else display_playlist(); update = true; break; } case TREE_MENU: if (viewer_menu()) { ret = true; goto exit; } display_playlist(); update = true; break; case BUTTON_NONE: status_draw(false); break; default: if(default_event_handler(button) == SYS_USB_CONNECTED) { ret = true; goto exit; } break; } if (update && !exit) { lcd_stop_scroll(); if (viewer.cursor_pos > (viewer.last_display_index - viewer.first_display_index)) { /* Cursor position is invalid */ put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, false); viewer.cursor_pos = viewer.last_display_index - viewer.first_display_index; put_cursorxy(CURSOR_X, CURSOR_Y + viewer.cursor_pos, true); } if (viewer.cursor_pos != old_cursor_pos && old_cursor_pos <= (viewer.last_display_index - viewer.first_display_index)) /* Stop scrolling previous line */ update_display_line(old_cursor_pos, false); /* Scroll line at new cursor position */ update_display_line(viewer.cursor_pos, true); lcd_update(); old_cursor_pos = viewer.cursor_pos; cursor_on = true; update = false; } lastbutton = button; } exit: if (viewer.playlist) playlist_close(viewer.playlist); return ret; }