1 /***************************************************************************
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
11 * Copyright (C) 2003 Hardeep Sidhu
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
23 * Kevin Ferrare 2005/10/16
24 * multi-screen support, rewrote a lot of code
35 #include "filetypes.h"
41 #include "backlight.h"
45 #include "playlist_viewer.h"
46 #include "playlist_catalog.h"
50 #include "playlist_menu.h"
53 /* Maximum number of tracks we can have loaded at one time */
54 #define MAX_PLAYLIST_ENTRIES 200
56 /* The number of items between the selected one and the end/start of
57 * the buffer under which the buffer must reload */
58 #define MIN_BUFFER_MARGIN (screens[0].getnblines()+1)
60 /* Information about a specific track */
61 struct playlist_entry
{
62 char *name
; /* Formatted track name */
63 int index
; /* Playlist index */
64 int display_index
; /* Display index */
65 bool queued
; /* Is track queued? */
66 bool skipped
; /* Is track marked as bad? */
75 struct playlist_buffer
77 char *name_buffer
; /* Buffer used to store track names */
78 int buffer_size
; /* Size of name buffer */
80 int first_index
; /* Real index of first track loaded inside
83 enum direction direction
; /* Direction of the buffer (if the buffer
84 was loaded BACKWARD, the last track in
85 the buffer has a real index < to the
86 real index of the the first track)*/
88 struct playlist_entry tracks
[MAX_PLAYLIST_ENTRIES
];
89 int num_loaded
; /* Number of track entries loaded in buffer */
92 /* Global playlist viewer settings */
93 struct playlist_viewer
{
94 struct playlist_info
* playlist
; /* playlist being viewed */
95 int num_tracks
; /* Number of tracks in playlist */
96 int current_playing_track
; /* Index of current playing track */
97 int selected_track
; /* The selected track, relative (first is 0) */
98 int moving_track
; /* The track to move, relative (first is 0)
99 or -1 if nothing is currently being moved */
100 int moving_playlist_index
; /* Playlist-relative index (as opposed to
101 viewer-relative index) of moving track */
102 struct playlist_buffer buffer
;
105 static struct playlist_viewer viewer
;
107 /* Used when viewing playlists on disk */
108 static struct playlist_info temp_playlist
;
110 static void playlist_buffer_init(struct playlist_buffer
*pb
, char *names_buffer
,
111 int names_buffer_size
);
112 static void playlist_buffer_load_entries(struct playlist_buffer
* pb
, int index
,
113 enum direction direction
);
114 static int playlist_entry_load(struct playlist_entry
*entry
, int index
,
115 char* name_buffer
, int remaining_size
);
117 static struct playlist_entry
* playlist_buffer_get_track(struct playlist_buffer
*pb
,
120 static bool playlist_viewer_init(struct playlist_viewer
* viewer
,
121 const char* filename
, bool reload
);
123 static void format_name(char* dest
, const char* src
);
124 static void format_line(const struct playlist_entry
* track
, char* str
,
127 static bool update_playlist(bool force
);
128 static int onplay_menu(int index
);
130 static void playlist_buffer_init(struct playlist_buffer
*pb
, char *names_buffer
,
131 int names_buffer_size
)
133 pb
->name_buffer
= names_buffer
;
134 pb
->buffer_size
= names_buffer_size
;
140 * Loads the entries following 'index' in the playlist buffer
142 static void playlist_buffer_load_entries(struct playlist_buffer
*pb
, int index
,
143 enum direction direction
)
145 int num_entries
= viewer
.num_tracks
;
146 char* p
= pb
->name_buffer
;
147 int remaining
= pb
->buffer_size
;
150 pb
->first_index
= index
;
151 if (num_entries
> MAX_PLAYLIST_ENTRIES
)
152 num_entries
= MAX_PLAYLIST_ENTRIES
;
154 for (i
= 0; i
< num_entries
; i
++)
156 int len
= playlist_entry_load(&(pb
->tracks
[i
]), index
, p
, remaining
);
159 /* Out of name buffer space */
167 if(direction
== FORWARD
)
171 index
+= viewer
.num_tracks
;
172 index
%= viewer
.num_tracks
;
174 pb
->direction
= direction
;
178 static void playlist_buffer_load_entries_screen(struct playlist_buffer
* pb
,
179 enum direction direction
)
181 if (direction
== FORWARD
)
183 int min_start
= viewer
.selected_track
-2*screens
[0].getnblines();
184 while (min_start
< 0)
185 min_start
+= viewer
.num_tracks
;
186 min_start
%= viewer
.num_tracks
;
187 playlist_buffer_load_entries(pb
, min_start
, FORWARD
);
191 int max_start
= viewer
.selected_track
+2*screens
[0].getnblines();
192 max_start
%= viewer
.num_tracks
;
193 playlist_buffer_load_entries(pb
, max_start
, BACKWARD
);
197 static int playlist_entry_load(struct playlist_entry
*entry
, int index
,
198 char* name_buffer
, int remaining_size
)
200 struct playlist_track_info info
;
203 /* Playlist viewer orders songs based on display index. We need to
204 convert to real playlist index to access track */
205 index
= (index
+ playlist_get_first_index(viewer
.playlist
)) %
207 if (playlist_get_track_info(viewer
.playlist
, index
, &info
) < 0)
210 len
= strlen(info
.filename
) + 1;
212 if (len
<= remaining_size
)
214 strcpy(name_buffer
, info
.filename
);
216 entry
->name
= name_buffer
;
217 entry
->index
= info
.index
;
218 entry
->display_index
= info
.display_index
;
219 entry
->queued
= info
.attr
& PLAYLIST_ATTR_QUEUED
;
220 entry
->skipped
= info
.attr
& PLAYLIST_ATTR_SKIPPED
;
226 static int playlist_buffer_get_index(struct playlist_buffer
*pb
, int index
)
229 if (pb
->direction
== FORWARD
)
231 if (index
>= pb
->first_index
)
232 buffer_index
= index
-pb
->first_index
;
233 else /* rotation : track0 in buffer + requested track */
234 buffer_index
= viewer
.num_tracks
-pb
->first_index
+index
;
238 if (index
<= pb
->first_index
)
239 buffer_index
= pb
->first_index
-index
;
240 else /* rotation : track0 in buffer + dist from the last track
241 to the requested track (num_tracks-requested track) */
242 buffer_index
= pb
->first_index
+viewer
.num_tracks
-index
;
247 #define distance(a, b) \
248 a>b? (a) - (b) : (b) - (a)
249 static bool playlist_buffer_needs_reload(struct playlist_buffer
* pb
,
252 if (pb
->num_loaded
== viewer
.num_tracks
)
254 int selected_index
= playlist_buffer_get_index(pb
, track_index
);
255 int first_buffer_index
= playlist_buffer_get_index(pb
, pb
->first_index
);
256 int distance_beginning
= distance(selected_index
, first_buffer_index
);
257 if (distance_beginning
< MIN_BUFFER_MARGIN
)
260 if (pb
->num_loaded
- distance_beginning
< MIN_BUFFER_MARGIN
)
265 static struct playlist_entry
* playlist_buffer_get_track(struct playlist_buffer
*pb
,
268 int buffer_index
= playlist_buffer_get_index(pb
, index
);
269 return &(pb
->tracks
[buffer_index
]);
272 /* Initialize the playlist viewer. */
273 static bool playlist_viewer_init(struct playlist_viewer
* viewer
,
274 const char* filename
, bool reload
)
278 bool is_playing
= audio_status() & (AUDIO_STATUS_PLAY
| AUDIO_STATUS_PAUSE
);
279 bool have_list
= filename
|| is_playing
;
280 if (!have_list
&& (global_status
.resume_index
!= -1))
282 /* Try to restore the list from control file */
283 have_list
= (playlist_resume() != -1);
285 if (!have_list
&& (playlist_amount() > 0))
287 /*If dynamic playlist still exists, view it anyway even
288 if playback has reached the end of the playlist */
293 /* Nothing to view, exit */
294 splash(HZ
, str(LANG_CATALOG_NO_PLAYLISTS
));
298 buffer
= plugin_get_buffer(&buffer_size
);
303 viewer
->playlist
= NULL
;
306 /* Viewing playlist on disk */
307 const char *dir
, *file
;
309 char *index_buffer
= NULL
;
310 ssize_t index_buffer_size
= 0;
312 viewer
->playlist
= &temp_playlist
;
314 /* Separate directory from filename */
315 temp_ptr
= strrchr(filename
+1,'/');
330 /* Something is playing, use half the plugin buffer for playlist
332 index_buffer_size
= buffer_size
/ 2;
333 index_buffer
= buffer
;
336 playlist_create_ex(viewer
->playlist
, dir
, file
, index_buffer
,
337 index_buffer_size
, buffer
+index_buffer_size
,
338 buffer_size
-index_buffer_size
);
343 buffer
+= index_buffer_size
;
344 buffer_size
-= index_buffer_size
;
346 playlist_buffer_init(&viewer
->buffer
, buffer
, buffer_size
);
348 viewer
->moving_track
= -1;
349 viewer
->moving_playlist_index
= -1;
353 if (viewer
->playlist
)
354 viewer
->selected_track
= 0;
356 viewer
->selected_track
= playlist_get_display_index() - 1;
359 if (!update_playlist(true))
364 /* Format trackname for display purposes */
365 static void format_name(char* dest
, const char* src
)
367 switch (global_settings
.playlist_viewer_track_display
)
372 /* Only display the filename */
373 char* p
= strrchr(src
, '/');
377 /* Remove the extension */
389 /* Format display line */
390 static void format_line(const struct playlist_entry
* track
, char* str
,
396 format_name(name
, track
->name
);
401 if (global_settings
.playlist_viewer_indices
)
402 /* Display playlist index */
403 snprintf(str
, len
, "%d. %s%s", track
->display_index
, skipped
, name
);
405 snprintf(str
, len
, "%s%s", skipped
, name
);
409 /* Update playlist in case something has changed or forced */
410 static bool update_playlist(bool force
)
412 if (!viewer
.playlist
)
413 playlist_get_resume_info(&viewer
.current_playing_track
);
415 viewer
.current_playing_track
= -1;
416 int nb_tracks
= playlist_amount_ex(viewer
.playlist
);
417 force
= force
|| nb_tracks
!= viewer
.num_tracks
;
421 viewer
.num_tracks
= nb_tracks
;
422 if (viewer
.num_tracks
<= 0)
424 global_status
.resume_index
= -1;
425 global_status
.resume_offset
= -1;
428 playlist_buffer_load_entries_screen(&viewer
.buffer
, FORWARD
);
429 if (viewer
.buffer
.num_loaded
<= 0)
431 global_status
.resume_index
= -1;
432 global_status
.resume_offset
= -1;
439 /* Menu of playlist commands. Invoked via ON+PLAY on main viewer screen.
440 Returns -1 if USB attached, 0 if no playlist change, and 1 if playlist
442 static int onplay_menu(int index
)
445 struct playlist_entry
* current_track
=
446 playlist_buffer_get_track(&viewer
.buffer
, index
);
447 MENUITEM_STRINGLIST(menu_items
, ID2P(LANG_PLAYLIST
), NULL
,
448 ID2P(LANG_CURRENT_PLAYLIST
), ID2P(LANG_CATALOG
),
449 ID2P(LANG_REMOVE
), ID2P(LANG_MOVE
), ID2P(LANG_SHUFFLE
),
450 ID2P(LANG_SAVE_DYNAMIC_PLAYLIST
));
451 bool current
= (current_track
->index
== viewer
.current_playing_track
);
453 result
= do_menu(&menu_items
, NULL
, NULL
, false);
454 if (result
== MENU_ATTACHED_USB
)
458 else if (result
>= 0)
460 /* Abort current move */
461 viewer
.moving_track
= -1;
462 viewer
.moving_playlist_index
= -1;
468 onplay_show_playlist_menu(current_track
->name
);
473 onplay_show_playlist_cat_menu(current_track
->name
);
478 playlist_delete(viewer
.playlist
, current_track
->index
);
481 if (playlist_amount_ex(viewer
.playlist
) <= 0)
485 /* Start playing new track except if it's the lasttrack
486 track in the playlist and repeat mode is disabled */
488 playlist_buffer_get_track(&viewer
.buffer
, index
);
489 if (current_track
->display_index
!=viewer
.num_tracks
||
490 global_settings
.repeat_mode
== REPEAT_ALL
)
493 viewer
.current_playing_track
= -1;
501 viewer
.moving_track
= index
;
502 viewer
.moving_playlist_index
= current_track
->index
;
507 playlist_randomise(viewer
.playlist
, current_tick
, false);
512 save_playlist_screen(viewer
.playlist
);
520 /* View current playlist */
521 enum playlist_viewer_result
playlist_viewer(void)
523 return playlist_viewer_ex(NULL
);
526 static int get_track_num(struct playlist_viewer
*local_viewer
,
529 if (local_viewer
->moving_track
>= 0)
531 if (local_viewer
->selected_track
== selected_item
)
533 return local_viewer
->moving_track
;
535 else if (local_viewer
->selected_track
> selected_item
536 && selected_item
>= local_viewer
->moving_track
)
538 return selected_item
+1; /* move down */
540 else if (local_viewer
->selected_track
< selected_item
541 && selected_item
<= local_viewer
->moving_track
)
543 return selected_item
-1; /* move up */
546 return selected_item
;
549 static const char* playlist_callback_name(int selected_item
,
554 struct playlist_viewer
*local_viewer
= (struct playlist_viewer
*)data
;
556 int track_num
= get_track_num(local_viewer
, selected_item
);
557 struct playlist_entry
*track
=
558 playlist_buffer_get_track(&(local_viewer
->buffer
), track_num
);
560 format_line(track
, buffer
, buffer_len
);
566 static enum themable_icons
playlist_callback_icons(int selected_item
,
569 struct playlist_viewer
*local_viewer
= (struct playlist_viewer
*)data
;
571 int track_num
= get_track_num(local_viewer
, selected_item
);
572 struct playlist_entry
*track
=
573 playlist_buffer_get_track(&(local_viewer
->buffer
), track_num
);
575 if (track
->index
== local_viewer
->current_playing_track
)
577 /* Current playing track */
580 else if (track
->index
== local_viewer
->moving_playlist_index
)
582 /* Track we are moving */
585 else if (track
->queued
)
594 static int playlist_callback_voice(int selected_item
, void *data
)
596 struct playlist_viewer
*local_viewer
= (struct playlist_viewer
*)data
;
598 int track_num
= get_track_num(local_viewer
, selected_item
);
599 struct playlist_entry
*track
=
600 playlist_buffer_get_track(&(local_viewer
->buffer
), track_num
);
602 bool enqueue
= false;
604 if (global_settings
.talk_file_clip
|| global_settings
.talk_file
== 2)
606 if (global_settings
.playlist_viewer_indices
)
608 talk_number(track
->display_index
, false);
611 talk_file_or_spell(NULL
, track
->name
, NULL
, enqueue
);
613 else if (global_settings
.talk_file
== 1) /* as numbers */
615 talk_id(VOICE_FILE
, false);
616 talk_number(track
->display_index
, true);
622 /* Main viewer function. Filename identifies playlist to be viewed. If NULL,
623 view current playlist. */
624 enum playlist_viewer_result
playlist_viewer_ex(const char* filename
)
626 enum playlist_viewer_result ret
= PLAYLIST_VIEWER_OK
;
627 bool exit
= false; /* exit viewer */
630 struct gui_synclist playlist_lists
;
631 if (!playlist_viewer_init(&viewer
, filename
, false))
634 push_current_activity(ACTIVITY_PLAYLISTVIEWER
);
635 gui_synclist_init(&playlist_lists
, playlist_callback_name
,
636 &viewer
, false, 1, NULL
);
637 gui_synclist_set_voice_callback(&playlist_lists
, playlist_callback_voice
);
638 gui_synclist_set_icon_callback(&playlist_lists
,
639 global_settings
.playlist_viewer_icons
?
640 &playlist_callback_icons
:NULL
);
641 gui_synclist_set_nb_items(&playlist_lists
, viewer
.num_tracks
);
642 gui_synclist_set_title(&playlist_lists
, str(LANG_PLAYLIST
), Icon_Playlist
);
643 gui_synclist_select_item(&playlist_lists
, viewer
.selected_track
);
644 gui_synclist_draw(&playlist_lists
);
645 gui_synclist_speak_item(&playlist_lists
);
650 if (global_status
.resume_index
!= -1 && !viewer
.playlist
)
651 playlist_get_resume_info(&track
);
655 if (track
!= viewer
.current_playing_track
||
656 playlist_amount_ex(viewer
.playlist
) != viewer
.num_tracks
)
658 /* Playlist has changed (new track started?) */
659 if (!update_playlist(false))
661 /*Needed because update_playlist gives wrong value when
663 viewer
.current_playing_track
= track
;
664 gui_synclist_set_nb_items(&playlist_lists
, viewer
.num_tracks
);
666 gui_synclist_draw(&playlist_lists
);
669 /* Timeout so we can determine if play status has changed */
670 bool res
= list_do_action(CONTEXT_TREE
, HZ
/2,
671 &playlist_lists
, &button
, LIST_WRAP_UNLESS_HELD
);
672 /* during moving, another redraw is going to be needed,
673 * since viewer.selected_track is updated too late (after the first draw)
674 * drawing the moving item needs it */
675 viewer
.selected_track
=gui_synclist_get_sel_pos(&playlist_lists
);
678 bool reload
= playlist_buffer_needs_reload(&viewer
.buffer
,
679 viewer
.selected_track
);
681 playlist_buffer_load_entries_screen(&viewer
.buffer
,
682 button
== ACTION_STD_NEXT
? FORWARD
: BACKWARD
);
683 if (reload
|| viewer
.moving_track
>= 0)
684 gui_synclist_draw(&playlist_lists
);
688 case ACTION_TREE_WPS
:
689 case ACTION_STD_CANCEL
:
691 if (viewer
.moving_track
>= 0)
693 viewer
.selected_track
= viewer
.moving_track
;
694 gui_synclist_select_item(&playlist_lists
, viewer
.moving_track
);
695 viewer
.moving_track
= -1;
696 viewer
.moving_playlist_index
= -1;
697 gui_synclist_draw(&playlist_lists
);
702 ret
= PLAYLIST_VIEWER_CANCEL
;
708 struct playlist_entry
* current_track
=
709 playlist_buffer_get_track(&viewer
.buffer
,
710 viewer
.selected_track
);
712 if (viewer
.moving_track
>= 0)
717 ret_val
= playlist_move(viewer
.playlist
,
718 viewer
.moving_playlist_index
,
719 current_track
->index
);
721 splashf(HZ
, (unsigned char *)"%s %s", str(LANG_MOVE
),
723 update_playlist(true);
724 viewer
.moving_track
= -1;
725 viewer
.moving_playlist_index
= -1;
728 else if (!viewer
.playlist
)
731 if (!global_settings
.party_mode
)
733 playlist_start(current_track
->index
, 0);
734 update_playlist(false);
737 else if (!global_settings
.party_mode
)
739 int start_index
= current_track
->index
;
740 if (!warn_on_pl_erase())
742 gui_synclist_draw(&playlist_lists
);
746 if (playlist_set_current(viewer
.playlist
) < 0)
748 if (global_settings
.playlist_shuffle
)
749 start_index
= playlist_shuffle(current_tick
, start_index
);
750 playlist_start(start_index
, 0);
752 /* Our playlist is now the current list */
753 if (!playlist_viewer_init(&viewer
, NULL
, true))
757 gui_synclist_draw(&playlist_lists
);
761 case ACTION_STD_CONTEXT
:
766 ret_val
= onplay_menu(viewer
.selected_track
);
770 ret
= PLAYLIST_VIEWER_USB
;
773 else if (ret_val
> 0)
775 /* Playlist changed */
776 gui_synclist_del_item(&playlist_lists
);
777 update_playlist(true);
778 if (viewer
.num_tracks
<= 0)
780 if (viewer
.selected_track
>= viewer
.num_tracks
)
781 viewer
.selected_track
= viewer
.num_tracks
-1;
784 gui_synclist_draw(&playlist_lists
);
787 case ACTION_STD_MENU
:
788 ret
= PLAYLIST_VIEWER_MAINMENU
;
791 if(default_event_handler(button
) == SYS_USB_CONNECTED
)
793 ret
= PLAYLIST_VIEWER_USB
;
801 pop_current_activity();
804 if(dirty
&& yesno_pop(ID2P(LANG_SAVE_CHANGES
)))
805 save_playlist_screen(viewer
.playlist
);
806 playlist_close(viewer
.playlist
);
811 static const char* playlist_search_callback_name(int selected_item
, void * data
,
812 char *buffer
, size_t buffer_len
)
814 (void)buffer_len
; /* this should probably be used */
815 int *found_indicies
= (int*)data
;
816 static struct playlist_track_info track
;
817 playlist_get_track_info(viewer
.playlist
, found_indicies
[selected_item
], &track
);
818 format_name(buffer
, track
.filename
);
822 bool search_playlist(void)
824 char search_str
[32] = "";
825 bool ret
= false, exit
= false;
826 int i
, playlist_count
;
827 int found_indicies
[MAX_PLAYLIST_ENTRIES
];
828 int found_indicies_count
= 0, last_found_count
= -1;
830 struct gui_synclist playlist_lists
;
831 struct playlist_track_info track
;
833 if (!playlist_viewer_init(&viewer
, 0, false))
835 if (kbd_input(search_str
, sizeof(search_str
)) < 0)
838 playlist_count
= playlist_amount_ex(viewer
.playlist
);
842 for (i
= 0; i
< playlist_count
&&
843 found_indicies_count
< MAX_PLAYLIST_ENTRIES
; i
++)
845 if (found_indicies_count
!= last_found_count
)
847 splashf(0, str(LANG_PLAYLIST_SEARCH_MSG
), found_indicies_count
,
848 str(LANG_OFF_ABORT
));
849 last_found_count
= found_indicies_count
;
852 if (action_userabort(TIMEOUT_NOBLOCK
))
855 playlist_get_track_info(viewer
.playlist
, i
, &track
);
857 if (strcasestr(track
.filename
,search_str
))
858 found_indicies
[found_indicies_count
++] = track
.index
;
865 if (!found_indicies_count
)
871 gui_synclist_init(&playlist_lists
, playlist_search_callback_name
,
872 found_indicies
, false, 1, NULL
);
873 gui_synclist_set_title(&playlist_lists
, str(LANG_SEARCH_RESULTS
), NOICON
);
874 gui_synclist_set_icon_callback(&playlist_lists
, NULL
);
875 gui_synclist_set_nb_items(&playlist_lists
, found_indicies_count
);
876 gui_synclist_select_item(&playlist_lists
, 0);
877 gui_synclist_draw(&playlist_lists
);
880 if (list_do_action(CONTEXT_LIST
, HZ
/4,
881 &playlist_lists
, &button
, LIST_WRAP_UNLESS_HELD
))
885 case ACTION_STD_CANCEL
:
891 int sel
= gui_synclist_get_sel_pos(&playlist_lists
);
892 playlist_start(found_indicies
[sel
], 0);
898 if (default_event_handler(button
) == SYS_USB_CONNECTED
)