forked from len0rd/rockbox
Patch #5179 by Sebastian Henriksen and Hardeep Sidhu - Playlist catalog
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10232 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
d4100c4cb1
commit
da0525f54f
8 changed files with 832 additions and 120 deletions
|
|
@ -13,6 +13,7 @@ menu.c
|
||||||
misc.c
|
misc.c
|
||||||
onplay.c
|
onplay.c
|
||||||
playlist.c
|
playlist.c
|
||||||
|
playlist_catalog.c
|
||||||
playlist_menu.c
|
playlist_menu.c
|
||||||
playlist_viewer.c
|
playlist_viewer.c
|
||||||
plugin.c
|
plugin.c
|
||||||
|
|
|
||||||
|
|
@ -8571,3 +8571,88 @@
|
||||||
*: "Export modifications"
|
*: "Export modifications"
|
||||||
</voice>
|
</voice>
|
||||||
</phrase>
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG
|
||||||
|
desc: in onplay menu
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "Playlist catalog"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "Playlist catalog"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: "Playlist catalog"
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG_ADD_TO
|
||||||
|
desc: in onplay playlist catalog submenu
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "Add to playlist"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "Add to playlist"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: "Add to playlist"
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG_ADD_TO_NEW
|
||||||
|
desc: in onplay playlist catalog submenu
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "Add to new playlist"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "Add to new playlist"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: "Add to new playlist"
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG_VIEW
|
||||||
|
desc: in onplay playlist catalog submenu
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "View catalog"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "View catalog"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: "View catalog"
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG_NO_DIRECTORY
|
||||||
|
desc: error message when playlist catalog directory doesn't exist
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "%s doesn't exist"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "%s doesn't exist"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: ""
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
id: LANG_CATALOG_NO_PLAYLISTS
|
||||||
|
desc: error message when no playlists for playlist catalog
|
||||||
|
user:
|
||||||
|
<source>
|
||||||
|
*: "No playlists"
|
||||||
|
</source>
|
||||||
|
<dest>
|
||||||
|
*: "No playlists"
|
||||||
|
</dest>
|
||||||
|
<voice>
|
||||||
|
*: ""
|
||||||
|
</voice>
|
||||||
|
</phrase>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
#include "eq_menu.h"
|
#include "eq_menu.h"
|
||||||
#endif
|
#endif
|
||||||
#include "playlist_menu.h"
|
#include "playlist_menu.h"
|
||||||
|
#include "playlist_catalog.h"
|
||||||
|
|
||||||
static int context;
|
static int context;
|
||||||
static char* selected_file = NULL;
|
static char* selected_file = NULL;
|
||||||
|
|
@ -222,6 +223,50 @@ static bool view_playlist(void)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cat_add_to_a_playlist(void)
|
||||||
|
{
|
||||||
|
return catalog_add_to_a_playlist(selected_file, selected_file_attr,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cat_add_to_a_new_playlist(void)
|
||||||
|
{
|
||||||
|
return catalog_add_to_a_playlist(selected_file, selected_file_attr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cat_playlist_options(void)
|
||||||
|
{
|
||||||
|
struct menu_item items[3];
|
||||||
|
int m, i=0, result;
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
if ((audio_status() & AUDIO_STATUS_PLAY && context == CONTEXT_WPS) ||
|
||||||
|
context == CONTEXT_TREE)
|
||||||
|
{
|
||||||
|
if (context == CONTEXT_WPS)
|
||||||
|
{
|
||||||
|
items[i].desc = ID2P(LANG_CATALOG_VIEW);
|
||||||
|
items[i].function = catalog_view_playlists;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i].desc = ID2P(LANG_CATALOG_ADD_TO);
|
||||||
|
items[i].function = cat_add_to_a_playlist;
|
||||||
|
i++;
|
||||||
|
items[i].desc = ID2P(LANG_CATALOG_ADD_TO_NEW);
|
||||||
|
items[i].function = cat_add_to_a_new_playlist;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m = menu_init( items, i, NULL, NULL, NULL, NULL );
|
||||||
|
result = menu_show(m);
|
||||||
|
if(result >= 0)
|
||||||
|
ret = items[result].function();
|
||||||
|
menu_exit(m);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sub-menu for playlist options */
|
/* Sub-menu for playlist options */
|
||||||
static bool playlist_options(void)
|
static bool playlist_options(void)
|
||||||
{
|
{
|
||||||
|
|
@ -773,9 +818,9 @@ static int onplay_callback(int key, int menu)
|
||||||
int onplay(char* file, int attr, int from)
|
int onplay(char* file, int attr, int from)
|
||||||
{
|
{
|
||||||
#if CONFIG_CODEC == SWCODEC
|
#if CONFIG_CODEC == SWCODEC
|
||||||
struct menu_item items[13]; /* increase this if you add entries! */
|
struct menu_item items[14]; /* increase this if you add entries! */
|
||||||
#else
|
#else
|
||||||
struct menu_item items[11];
|
struct menu_item items[12];
|
||||||
#endif
|
#endif
|
||||||
int m, i=0, result;
|
int m, i=0, result;
|
||||||
#ifdef HAVE_LCD_COLOR
|
#ifdef HAVE_LCD_COLOR
|
||||||
|
|
@ -803,6 +848,9 @@ int onplay(char* file, int attr, int from)
|
||||||
items[i].desc = ID2P(LANG_PLAYLIST);
|
items[i].desc = ID2P(LANG_PLAYLIST);
|
||||||
items[i].function = playlist_options;
|
items[i].function = playlist_options;
|
||||||
i++;
|
i++;
|
||||||
|
items[i].desc = ID2P(LANG_CATALOG);
|
||||||
|
items[i].function = cat_playlist_options;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context == CONTEXT_WPS)
|
if (context == CONTEXT_WPS)
|
||||||
|
|
|
||||||
248
apps/playlist.c
248
apps/playlist.c
|
|
@ -133,6 +133,13 @@
|
||||||
|
|
||||||
#define PLAYLIST_DISPLAY_COUNT 10
|
#define PLAYLIST_DISPLAY_COUNT 10
|
||||||
|
|
||||||
|
struct directory_search_context {
|
||||||
|
struct playlist_info* playlist;
|
||||||
|
int position;
|
||||||
|
bool queue;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
static bool changing_dir = false;
|
static bool changing_dir = false;
|
||||||
|
|
||||||
static struct playlist_info current_playlist;
|
static struct playlist_info current_playlist;
|
||||||
|
|
@ -151,9 +158,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist,
|
||||||
static int add_track_to_playlist(struct playlist_info* playlist,
|
static int add_track_to_playlist(struct playlist_info* playlist,
|
||||||
const char *filename, int position,
|
const char *filename, int position,
|
||||||
bool queue, int seek_pos);
|
bool queue, int seek_pos);
|
||||||
static int add_directory_to_playlist(struct playlist_info* playlist,
|
static int directory_search_callback(char* filename, void* context);
|
||||||
const char *dirname, int *position,
|
|
||||||
bool queue, int *count, bool recurse);
|
|
||||||
static int remove_track_from_playlist(struct playlist_info* playlist,
|
static int remove_track_from_playlist(struct playlist_info* playlist,
|
||||||
int position, bool write);
|
int position, bool write);
|
||||||
static int randomise_playlist(struct playlist_info* playlist,
|
static int randomise_playlist(struct playlist_info* playlist,
|
||||||
|
|
@ -681,121 +686,46 @@ static int add_track_to_playlist(struct playlist_info* playlist,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert directory into playlist. May be called recursively.
|
* Callback for playlist_directory_tracksearch to insert track into
|
||||||
|
* playlist.
|
||||||
*/
|
*/
|
||||||
static int add_directory_to_playlist(struct playlist_info* playlist,
|
static int directory_search_callback(char* filename, void* context)
|
||||||
const char *dirname, int *position,
|
|
||||||
bool queue, int *count, bool recurse)
|
|
||||||
{
|
{
|
||||||
char buf[MAX_PATH+1];
|
struct directory_search_context* c =
|
||||||
unsigned char *count_str;
|
(struct directory_search_context*) context;
|
||||||
int result = 0;
|
int insert_pos;
|
||||||
int num_files = 0;
|
|
||||||
int i;
|
|
||||||
struct entry *files;
|
|
||||||
struct tree_context* tc = tree_get_context();
|
|
||||||
int dirfilter = *(tc->dirfilter);
|
|
||||||
|
|
||||||
/* use the tree browser dircache to load files */
|
insert_pos = add_track_to_playlist(c->playlist, filename, c->position,
|
||||||
*(tc->dirfilter) = SHOW_ALL;
|
c->queue, -1);
|
||||||
|
|
||||||
if (ft_load(tc, dirname) < 0)
|
if (insert_pos < 0)
|
||||||
{
|
|
||||||
gui_syncsplash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
|
|
||||||
*(tc->dirfilter) = dirfilter;
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
files = (struct entry*) tc->dircache;
|
(c->count)++;
|
||||||
num_files = tc->filesindir;
|
|
||||||
|
|
||||||
/* we've overwritten the dircache so tree browser will need to be
|
/* Make sure tracks are inserted in correct order if user requests
|
||||||
reloaded */
|
INSERT_FIRST */
|
||||||
reload_directory();
|
if (c->position == PLAYLIST_INSERT_FIRST || c->position >= 0)
|
||||||
|
c->position = insert_pos + 1;
|
||||||
|
|
||||||
if (queue)
|
if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0)
|
||||||
count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
|
|
||||||
else
|
|
||||||
count_str = str(LANG_PLAYLIST_INSERT_COUNT);
|
|
||||||
|
|
||||||
for (i=0; i<num_files; i++)
|
|
||||||
{
|
{
|
||||||
/* user abort */
|
unsigned char* count_str;
|
||||||
if (button_get(false) == SETTINGS_CANCEL)
|
|
||||||
{
|
|
||||||
result = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files[i].attr & ATTR_DIRECTORY)
|
if (c->queue)
|
||||||
{
|
count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
|
||||||
if (recurse)
|
else
|
||||||
{
|
count_str = str(LANG_PLAYLIST_INSERT_COUNT);
|
||||||
/* recursively add directories */
|
|
||||||
snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
|
|
||||||
result = add_directory_to_playlist(playlist, buf, position,
|
|
||||||
queue, count, recurse);
|
|
||||||
if (result < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* we now need to reload our current directory */
|
display_playlist_count(c->count, count_str);
|
||||||
if(ft_load(tc, dirname) < 0)
|
|
||||||
{
|
|
||||||
result = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
files = (struct entry*) tc->dircache;
|
if ((c->count) == PLAYLIST_DISPLAY_COUNT &&
|
||||||
num_files = tc->filesindir;
|
(audio_status() & AUDIO_STATUS_PLAY) &&
|
||||||
if (!num_files)
|
c->playlist->started)
|
||||||
{
|
audio_flush_and_reload_tracks();
|
||||||
result = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
|
|
||||||
{
|
|
||||||
int insert_pos;
|
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
|
|
||||||
|
|
||||||
insert_pos = add_track_to_playlist(playlist, buf, *position,
|
|
||||||
queue, -1);
|
|
||||||
if (insert_pos < 0)
|
|
||||||
{
|
|
||||||
result = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
(*count)++;
|
|
||||||
|
|
||||||
/* Make sure tracks are inserted in correct order if user requests
|
|
||||||
INSERT_FIRST */
|
|
||||||
if (*position == PLAYLIST_INSERT_FIRST || *position >= 0)
|
|
||||||
*position = insert_pos + 1;
|
|
||||||
|
|
||||||
if ((*count%PLAYLIST_DISPLAY_COUNT) == 0)
|
|
||||||
{
|
|
||||||
display_playlist_count(*count, count_str);
|
|
||||||
|
|
||||||
if (*count == PLAYLIST_DISPLAY_COUNT &&
|
|
||||||
(audio_status() & AUDIO_STATUS_PLAY) &&
|
|
||||||
playlist->started)
|
|
||||||
audio_flush_and_reload_tracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* let the other threads work */
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore dirfilter */
|
return 0;
|
||||||
*(tc->dirfilter) = dirfilter;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -2811,9 +2741,9 @@ int playlist_insert_directory(struct playlist_info* playlist,
|
||||||
const char *dirname, int position, bool queue,
|
const char *dirname, int position, bool queue,
|
||||||
bool recurse)
|
bool recurse)
|
||||||
{
|
{
|
||||||
int count = 0;
|
|
||||||
int result;
|
int result;
|
||||||
unsigned char *count_str;
|
unsigned char *count_str;
|
||||||
|
struct directory_search_context context;
|
||||||
|
|
||||||
if (!playlist)
|
if (!playlist)
|
||||||
playlist = ¤t_playlist;
|
playlist = ¤t_playlist;
|
||||||
|
|
@ -2829,18 +2759,23 @@ int playlist_insert_directory(struct playlist_info* playlist,
|
||||||
else
|
else
|
||||||
count_str = str(LANG_PLAYLIST_INSERT_COUNT);
|
count_str = str(LANG_PLAYLIST_INSERT_COUNT);
|
||||||
|
|
||||||
display_playlist_count(count, count_str);
|
display_playlist_count(0, count_str);
|
||||||
|
|
||||||
|
context.playlist = playlist;
|
||||||
|
context.position = position;
|
||||||
|
context.queue = queue;
|
||||||
|
context.count = 0;
|
||||||
|
|
||||||
cpu_boost(true);
|
cpu_boost(true);
|
||||||
|
|
||||||
result = add_directory_to_playlist(playlist, dirname, &position, queue,
|
result = playlist_directory_tracksearch(dirname, recurse,
|
||||||
&count, recurse);
|
directory_search_callback, &context);
|
||||||
|
|
||||||
sync_control(playlist, false);
|
sync_control(playlist, false);
|
||||||
|
|
||||||
cpu_boost(false);
|
cpu_boost(false);
|
||||||
|
|
||||||
display_playlist_count(count, count_str);
|
display_playlist_count(context.count, count_str);
|
||||||
|
|
||||||
if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
|
if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
|
||||||
audio_flush_and_reload_tracks();
|
audio_flush_and_reload_tracks();
|
||||||
|
|
@ -3403,3 +3338,98 @@ int playlist_save(struct playlist_info* playlist, char *filename)
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Search specified directory for tracks and notify via callback. May be
|
||||||
|
* called recursively.
|
||||||
|
*/
|
||||||
|
int playlist_directory_tracksearch(const char* dirname, bool recurse,
|
||||||
|
int (*callback)(char*, void*),
|
||||||
|
void* context)
|
||||||
|
{
|
||||||
|
char buf[MAX_PATH+1];
|
||||||
|
int result = 0;
|
||||||
|
int num_files = 0;
|
||||||
|
int i;
|
||||||
|
struct entry *files;
|
||||||
|
struct tree_context* tc = tree_get_context();
|
||||||
|
int old_dirfilter = *(tc->dirfilter);
|
||||||
|
|
||||||
|
if (!callback)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* use the tree browser dircache to load files */
|
||||||
|
*(tc->dirfilter) = SHOW_ALL;
|
||||||
|
|
||||||
|
if (ft_load(tc, dirname) < 0)
|
||||||
|
{
|
||||||
|
gui_syncsplash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
|
||||||
|
*(tc->dirfilter) = old_dirfilter;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = (struct entry*) tc->dircache;
|
||||||
|
num_files = tc->filesindir;
|
||||||
|
|
||||||
|
/* we've overwritten the dircache so tree browser will need to be
|
||||||
|
reloaded */
|
||||||
|
reload_directory();
|
||||||
|
|
||||||
|
for (i=0; i<num_files; i++)
|
||||||
|
{
|
||||||
|
/* user abort */
|
||||||
|
if (button_get(false) == SETTINGS_CANCEL)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files[i].attr & ATTR_DIRECTORY)
|
||||||
|
{
|
||||||
|
if (recurse)
|
||||||
|
{
|
||||||
|
/* recursively add directories */
|
||||||
|
snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
|
||||||
|
result = playlist_directory_tracksearch(buf, recurse,
|
||||||
|
callback, context);
|
||||||
|
if (result < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* we now need to reload our current directory */
|
||||||
|
if(ft_load(tc, dirname) < 0)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = (struct entry*) tc->dircache;
|
||||||
|
num_files = tc->filesindir;
|
||||||
|
if (!num_files)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
|
||||||
|
{
|
||||||
|
snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
|
||||||
|
|
||||||
|
if (callback(buf, context) != 0)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* let the other threads work */
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* restore dirfilter */
|
||||||
|
*(tc->dirfilter) = old_dirfilter;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,5 +158,8 @@ char *playlist_get_name(const struct playlist_info* playlist, char *buf,
|
||||||
int playlist_get_track_info(struct playlist_info* playlist, int index,
|
int playlist_get_track_info(struct playlist_info* playlist, int index,
|
||||||
struct playlist_track_info* info);
|
struct playlist_track_info* info);
|
||||||
int playlist_save(struct playlist_info* playlist, char *filename);
|
int playlist_save(struct playlist_info* playlist, char *filename);
|
||||||
|
int playlist_directory_tracksearch(const char* dirname, bool recurse,
|
||||||
|
int (*callback)(char*, void*),
|
||||||
|
void* context);
|
||||||
|
|
||||||
#endif /* __PLAYLIST_H__ */
|
#endif /* __PLAYLIST_H__ */
|
||||||
|
|
|
||||||
504
apps/playlist_catalog.c
Normal file
504
apps/playlist_catalog.c
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 Sebastian Henriksen, 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 <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "action.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "filetree.h"
|
||||||
|
#include "kernel.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "lang.h"
|
||||||
|
#include "list.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "onplay.h"
|
||||||
|
#include "playlist.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "splash.h"
|
||||||
|
#include "sprintf.h"
|
||||||
|
#include "tree.h"
|
||||||
|
#include "yesno.h"
|
||||||
|
|
||||||
|
#define PLAYLIST_CATALOG_CFG ROCKBOX_DIR "/playlist_catalog.config"
|
||||||
|
#define PLAYLIST_CATALOG_DEFAULT_DIR "/Playlists"
|
||||||
|
#define MAX_PLAYLISTS 400
|
||||||
|
#define PLAYLIST_DISPLAY_COUNT 10
|
||||||
|
|
||||||
|
/* Use for recursive directory search */
|
||||||
|
struct add_track_context {
|
||||||
|
int fd;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* keep track of most recently used playlist */
|
||||||
|
static char most_recent_playlist[MAX_PATH];
|
||||||
|
|
||||||
|
/* directory where our playlists our stored (configured in
|
||||||
|
PLAYLIST_CATALOG_CFG) */
|
||||||
|
static char playlist_dir[MAX_PATH];
|
||||||
|
static int playlist_dir_length;
|
||||||
|
static bool playlist_dir_exists = false;
|
||||||
|
|
||||||
|
/* Retrieve playlist directory from config file and verify it exists */
|
||||||
|
static int initialize_catalog(void)
|
||||||
|
{
|
||||||
|
static bool initialized = false;
|
||||||
|
|
||||||
|
if (!initialized)
|
||||||
|
{
|
||||||
|
int f;
|
||||||
|
DIR* dir;
|
||||||
|
bool default_dir = true;
|
||||||
|
|
||||||
|
f = open(PLAYLIST_CATALOG_CFG, O_RDONLY);
|
||||||
|
if (f >= 0)
|
||||||
|
{
|
||||||
|
char buf[MAX_PATH+5];
|
||||||
|
|
||||||
|
while (read_line(f, buf, sizeof(buf)))
|
||||||
|
{
|
||||||
|
char* name;
|
||||||
|
char* value;
|
||||||
|
|
||||||
|
/* directory config is of the format: "dir: /path/to/dir" */
|
||||||
|
if (settings_parseline(buf, &name, &value) &&
|
||||||
|
!strncasecmp(name, "dir:", strlen(name)) &&
|
||||||
|
strlen(value) > 0)
|
||||||
|
{
|
||||||
|
strncpy(playlist_dir, value, strlen(value));
|
||||||
|
default_dir = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fall back to default directory if no or invalid config */
|
||||||
|
if (default_dir)
|
||||||
|
strncpy(playlist_dir, PLAYLIST_CATALOG_DEFAULT_DIR,
|
||||||
|
sizeof(playlist_dir));
|
||||||
|
|
||||||
|
playlist_dir_length = strlen(playlist_dir);
|
||||||
|
|
||||||
|
dir = opendir(playlist_dir);
|
||||||
|
if (dir)
|
||||||
|
{
|
||||||
|
playlist_dir_exists = true;
|
||||||
|
closedir(dir);
|
||||||
|
memset(most_recent_playlist, 0, sizeof(most_recent_playlist));
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playlist_dir_exists)
|
||||||
|
{
|
||||||
|
gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_DIRECTORY),
|
||||||
|
playlist_dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use the filetree functions to retrieve the list of playlists in the
|
||||||
|
directory */
|
||||||
|
static int create_playlist_list(char** playlists, int num_items,
|
||||||
|
int* num_playlists)
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
int num_files = 0;
|
||||||
|
int index = 0;
|
||||||
|
int i;
|
||||||
|
bool most_recent = false;
|
||||||
|
struct entry *files;
|
||||||
|
struct tree_context* tc = tree_get_context();
|
||||||
|
int dirfilter = *(tc->dirfilter);
|
||||||
|
|
||||||
|
*num_playlists = 0;
|
||||||
|
|
||||||
|
/* use the tree browser dircache to load only playlists */
|
||||||
|
*(tc->dirfilter) = SHOW_PLAYLIST;
|
||||||
|
|
||||||
|
if (ft_load(tc, playlist_dir) < 0)
|
||||||
|
{
|
||||||
|
gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_DIRECTORY),
|
||||||
|
playlist_dir);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = (struct entry*) tc->dircache;
|
||||||
|
num_files = tc->filesindir;
|
||||||
|
|
||||||
|
/* we've overwritten the dircache so tree browser will need to be
|
||||||
|
reloaded */
|
||||||
|
reload_directory();
|
||||||
|
|
||||||
|
/* if it exists, most recent playlist will always be index 0 */
|
||||||
|
if (most_recent_playlist[0] != '\0')
|
||||||
|
{
|
||||||
|
index = 1;
|
||||||
|
most_recent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<num_files && index<num_items; i++)
|
||||||
|
{
|
||||||
|
if (files[i].attr & TREE_ATTR_M3U)
|
||||||
|
{
|
||||||
|
if (most_recent && !strncmp(files[i].name, most_recent_playlist,
|
||||||
|
sizeof(most_recent_playlist)))
|
||||||
|
{
|
||||||
|
playlists[0] = files[i].name;
|
||||||
|
most_recent = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playlists[index] = files[i].name;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*num_playlists = index;
|
||||||
|
|
||||||
|
/* we couldn't find the most recent playlist, shift all playlists up */
|
||||||
|
if (most_recent)
|
||||||
|
{
|
||||||
|
for (i=0; i<index-1; i++)
|
||||||
|
playlists[i] = playlists[i+1];
|
||||||
|
|
||||||
|
(*num_playlists)--;
|
||||||
|
|
||||||
|
most_recent_playlist[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
*(tc->dirfilter) = dirfilter;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callback for gui_synclist */
|
||||||
|
static char* playlist_callback_name(int selected_item, void* data,
|
||||||
|
char* buffer)
|
||||||
|
{
|
||||||
|
char** playlists = (char**) data;
|
||||||
|
|
||||||
|
strncpy(buffer, playlists[selected_item], MAX_PATH);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display all playlists in catalog. Selected "playlist" is returned.
|
||||||
|
If "view" mode is set then we're not adding anything into playlist. */
|
||||||
|
static int display_playlists(char* playlist, bool view)
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
int num_playlists = 0;
|
||||||
|
int lastbutton = BUTTON_NONE;
|
||||||
|
bool exit = false;
|
||||||
|
char temp_buf[MAX_PATH];
|
||||||
|
char* playlists[MAX_PLAYLISTS];
|
||||||
|
struct gui_synclist playlist_lists;
|
||||||
|
|
||||||
|
if (create_playlist_list(playlists, sizeof(playlists),
|
||||||
|
&num_playlists) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (num_playlists <= 0)
|
||||||
|
{
|
||||||
|
gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_PLAYLISTS));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playlist)
|
||||||
|
playlist = temp_buf;
|
||||||
|
|
||||||
|
gui_synclist_init(&playlist_lists, playlist_callback_name, playlists,
|
||||||
|
false, 1);
|
||||||
|
gui_synclist_set_nb_items(&playlist_lists, num_playlists);
|
||||||
|
gui_synclist_draw(&playlist_lists);
|
||||||
|
|
||||||
|
while (!exit)
|
||||||
|
{
|
||||||
|
int button = button_get_w_tmo(HZ/2);
|
||||||
|
char* sel_file;
|
||||||
|
|
||||||
|
gui_synclist_do_button(&playlist_lists, button);
|
||||||
|
|
||||||
|
sel_file = playlists[gui_synclist_get_sel_pos(&playlist_lists)];
|
||||||
|
|
||||||
|
switch (button)
|
||||||
|
{
|
||||||
|
case TREE_EXIT:
|
||||||
|
#ifdef TREE_RC_EXIT
|
||||||
|
case TREE_RC_EXIT:
|
||||||
|
#endif
|
||||||
|
#ifdef TREE_OFF
|
||||||
|
case TREE_OFF:
|
||||||
|
#endif
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifdef TREE_ENTER
|
||||||
|
case TREE_ENTER:
|
||||||
|
case TREE_ENTER | BUTTON_REPEAT:
|
||||||
|
#endif
|
||||||
|
#ifdef TREE_RC_RUN
|
||||||
|
case TREE_RC_RUN:
|
||||||
|
#endif
|
||||||
|
case TREE_RUN:
|
||||||
|
#ifdef TREE_RUN_PRE
|
||||||
|
if (((button == TREE_RUN)
|
||||||
|
#ifdef TREE_RC_RUN_PRE
|
||||||
|
|| (button == TREE_RC_RUN))
|
||||||
|
&& ((lastbutton != TREE_RC_RUN_PRE)
|
||||||
|
#endif
|
||||||
|
&& (lastbutton != TREE_RUN_PRE)))
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (view)
|
||||||
|
{
|
||||||
|
/* In view mode, selecting a playlist starts playback */
|
||||||
|
if (playlist_create(playlist_dir, sel_file) != -1)
|
||||||
|
{
|
||||||
|
if (global_settings.playlist_shuffle)
|
||||||
|
playlist_shuffle(current_tick, -1);
|
||||||
|
playlist_start(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* we found the playlist we want to add to */
|
||||||
|
snprintf(playlist, MAX_PATH, "%s/%s", playlist_dir,
|
||||||
|
sel_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TREE_CONTEXT:
|
||||||
|
#ifdef TREE_CONTEXT2
|
||||||
|
case TREE_CONTEXT2:
|
||||||
|
#endif
|
||||||
|
#ifdef TREE_RC_CONTEXT
|
||||||
|
case TREE_RC_CONTEXT:
|
||||||
|
#endif
|
||||||
|
/* context menu only available in view mode */
|
||||||
|
if (view)
|
||||||
|
{
|
||||||
|
snprintf(playlist, MAX_PATH, "%s/%s", playlist_dir,
|
||||||
|
sel_file);
|
||||||
|
|
||||||
|
if (onplay(playlist, TREE_ATTR_M3U,
|
||||||
|
CONTEXT_TREE) != ONPLAY_OK)
|
||||||
|
{
|
||||||
|
result = 0;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gui_synclist_draw(&playlist_lists);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BUTTON_NONE:
|
||||||
|
gui_syncstatusbar_draw(&statusbars, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if(default_event_handler(button) == SYS_USB_CONNECTED)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastbutton = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* display number of tracks inserted into playlists. Used for directory
|
||||||
|
insert */
|
||||||
|
static void display_insert_count(int count)
|
||||||
|
{
|
||||||
|
gui_syncsplash(0, true, str(LANG_PLAYLIST_INSERT_COUNT), count,
|
||||||
|
#if CONFIG_KEYPAD == PLAYER_PAD
|
||||||
|
str(LANG_STOP_ABORT)
|
||||||
|
#else
|
||||||
|
str(LANG_OFF_ABORT)
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add specified track into playlist. Callback from directory insert */
|
||||||
|
static int add_track_to_playlist(char* filename, void* context)
|
||||||
|
{
|
||||||
|
struct add_track_context* c = (struct add_track_context*) context;
|
||||||
|
|
||||||
|
if (fdprintf(c->fd, "%s\n", filename) <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
(c->count)++;
|
||||||
|
|
||||||
|
if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0)
|
||||||
|
display_insert_count(c->count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add "sel" file into specified "playlist". How to insert depends on type
|
||||||
|
of file */
|
||||||
|
static int add_to_playlist(const char* playlist, char* sel, int sel_attr)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int result = -1;
|
||||||
|
|
||||||
|
fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND);
|
||||||
|
if(fd < 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
/* In case we're in the playlist directory */
|
||||||
|
reload_directory();
|
||||||
|
|
||||||
|
if ((sel_attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
|
||||||
|
{
|
||||||
|
/* append the selected file */
|
||||||
|
if (fdprintf(fd, "%s\n", sel) > 0)
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
else if ((sel_attr & TREE_ATTR_MASK) == TREE_ATTR_M3U)
|
||||||
|
{
|
||||||
|
/* append playlist */
|
||||||
|
int f, fs, i;
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
if(strcasecmp(playlist, sel) == 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
f = open(sel, O_RDONLY);
|
||||||
|
if (f < 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
fs = filesize(f);
|
||||||
|
|
||||||
|
for (i=0; i<fs;)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = read(f, buf, sizeof(buf));
|
||||||
|
if (n < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (write(fd, buf, n) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
i += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= fs)
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
close(f);
|
||||||
|
}
|
||||||
|
else if (sel_attr & ATTR_DIRECTORY)
|
||||||
|
{
|
||||||
|
/* search directory for tracks and append to playlist */
|
||||||
|
bool recurse = false;
|
||||||
|
char *lines[] = {
|
||||||
|
(char *)str(LANG_RECURSE_DIRECTORY_QUESTION),
|
||||||
|
sel
|
||||||
|
};
|
||||||
|
struct text_message message={lines, 2};
|
||||||
|
struct add_track_context context;
|
||||||
|
|
||||||
|
if (global_settings.recursive_dir_insert != RECURSE_ASK)
|
||||||
|
recurse = (bool)global_settings.recursive_dir_insert;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Ask if user wants to recurse directory */
|
||||||
|
recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fd = fd;
|
||||||
|
context.count = 0;
|
||||||
|
|
||||||
|
display_insert_count(0);
|
||||||
|
|
||||||
|
result = playlist_directory_tracksearch(sel, recurse,
|
||||||
|
add_track_to_playlist, &context);
|
||||||
|
|
||||||
|
display_insert_count(context.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
close(fd);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool catalog_view_playlists(void)
|
||||||
|
{
|
||||||
|
if (initialize_catalog() == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (display_playlists(NULL, true) == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool catalog_add_to_a_playlist(char* sel, int sel_attr, bool new_playlist)
|
||||||
|
{
|
||||||
|
char playlist[MAX_PATH];
|
||||||
|
|
||||||
|
if (initialize_catalog() == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (new_playlist)
|
||||||
|
{
|
||||||
|
snprintf(playlist, MAX_PATH, "%s/", playlist_dir);
|
||||||
|
if (kbd_input(playlist, MAX_PATH))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(strlen(playlist) <= 4 ||
|
||||||
|
strcasecmp(&playlist[strlen(playlist)-4], ".m3u"))
|
||||||
|
strcat(playlist, ".m3u");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (display_playlists(playlist, false) == -1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add_to_playlist(playlist, sel, sel_attr) == 0)
|
||||||
|
{
|
||||||
|
strncpy(most_recent_playlist, playlist+playlist_dir_length+1,
|
||||||
|
sizeof(most_recent_playlist));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
39
apps/playlist_catalog.h
Normal file
39
apps/playlist_catalog.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 Sebastian Henriksen, 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef _PLAYLIST_CATALOG_H_
|
||||||
|
#define _PLAYLIST_CATALOG_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* View list of playlists in catalog.
|
||||||
|
* ret : true if no error
|
||||||
|
*/
|
||||||
|
bool catalog_view_playlists(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add something to a playlist (new or select from list of playlists in
|
||||||
|
* catalog).
|
||||||
|
* sel : the path of the music file, playlist or directory to add
|
||||||
|
* sel_attr : the attributes that tell what type of file we're adding
|
||||||
|
* new_playlist : whether we want to create a new playlist or add to an
|
||||||
|
* existing one.
|
||||||
|
* ret : true if the file was successfully added
|
||||||
|
*/
|
||||||
|
bool catalog_add_to_a_playlist(char* sel, int sel_attr, bool new_playlist);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
#include "playlist_viewer.h"
|
#include "playlist_viewer.h"
|
||||||
#include "talk.h"
|
#include "talk.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
|
#include "playlist_catalog.h"
|
||||||
#include "playlist_menu.h"
|
#include "playlist_menu.h"
|
||||||
|
|
||||||
static bool save_playlist(void)
|
static bool save_playlist(void)
|
||||||
|
|
@ -61,11 +62,12 @@ bool playlist_menu(void)
|
||||||
bool result;
|
bool result;
|
||||||
|
|
||||||
static const struct menu_item items[] = {
|
static const struct menu_item items[] = {
|
||||||
{ ID2P(LANG_CREATE_PLAYLIST), create_playlist },
|
{ ID2P(LANG_CREATE_PLAYLIST), create_playlist },
|
||||||
{ ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer },
|
{ ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer },
|
||||||
{ ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist },
|
{ ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist },
|
||||||
{ ID2P(LANG_RECURSE_DIRECTORY), recurse_directory },
|
{ ID2P(LANG_CATALOG), catalog_view_playlists },
|
||||||
{ ID2P(LANG_WARN_ERASEDYNPLAYLIST_MENU), warnon_option},
|
{ ID2P(LANG_RECURSE_DIRECTORY), recurse_directory },
|
||||||
|
{ ID2P(LANG_WARN_ERASEDYNPLAYLIST_MENU), warnon_option },
|
||||||
};
|
};
|
||||||
|
|
||||||
m = menu_init( items, sizeof items / sizeof(struct menu_item), NULL,
|
m = menu_init( items, sizeof items / sizeof(struct menu_item), NULL,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue