Improved tag browser configuration file syntax with submenus and
includes (custom user menu, without overwriting by default menu). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11000 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
1d5b248f7b
commit
ba34c37967
3 changed files with 271 additions and 55 deletions
|
@ -1,15 +1,46 @@
|
||||||
"Artist" artist : album : title = "%02d. %s" tracknum title
|
#! rockbox/tagbrowser/2.0
|
||||||
"Album" album : title = "%02d. %s" tracknum title
|
# ^ Version header must be the first line of every file
|
||||||
"Genre" genre : artist : album : title = "%02d. %s" tracknum title
|
|
||||||
"Composer" composer : album : title = "%02d. %s" tracknum title
|
# Tag Browser configuration file, do not edit as changes will be lost!
|
||||||
"Track" title
|
# Instead, you can modify "/.rockbox/tagnavi_custom.config" which will never
|
||||||
"Year" year ? year > "1000" & year < "2008" : artist : album : title = "%02d. %s" tracknum title
|
# get overwritten automatically.
|
||||||
"Search by artist" artist ? artist ~ "" : album : title = "%02d. %s" tracknum title
|
|
||||||
"Search by album" album ? album ~ "" : title = "%02d. %s" tracknum title
|
# Include our custom menu
|
||||||
"Search by title" title ? title ~ ""
|
%include "/.rockbox/tagnavi_custom.config"
|
||||||
"Search by filename" filename ? filename ~ ""
|
|
||||||
"Search by score" title = "(%3d) %s" autoscore title ? autoscore > ""
|
#
|
||||||
"Most played tracks" title = "(%3d) %s" playcount title %sort = "inverse" %limit = "100" ? playcount > "0"
|
# === Begin of "Search by..." sub menu
|
||||||
"Never played tracks" artist ? playcount == "0" : album : title = "%02d. %s" tracknum title
|
#
|
||||||
"Best tracks" artist ? playcount > "1" & autoscore > "85" : album : title = "%02d. %s (%3d)" tracknum title autoscore
|
|
||||||
"List played tracks" title = "(%3d/%d) %s" autoscore playcount title ? playcount > "0"
|
# Define the search sub menu
|
||||||
|
%menu_start "search" "Search by..."
|
||||||
|
"Artist" -> artist ? artist ~ "" -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Artist -> (score > 85)" artist ? artist ~ "" & autoscore >= "85" -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Album" -> album ? album ~ "" -> title = "%02d. %s" tracknum title
|
||||||
|
"Title" -> title ? title ~ ""
|
||||||
|
"Filename" -> filename ? filename ~ ""
|
||||||
|
"Score" -> title = "(%3d) %s" autoscore title ? autoscore > ""
|
||||||
|
|
||||||
|
# ^ An empy line ends the menu
|
||||||
|
|
||||||
|
#
|
||||||
|
# === Begin of main menu
|
||||||
|
#
|
||||||
|
|
||||||
|
# Define the title of the main menu
|
||||||
|
%menu_start "main" "Browse by..."
|
||||||
|
"Artist" -> artist -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Album" -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Genre" -> genre -> artist -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Composer" -> composer -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Track" -> title
|
||||||
|
"Year" -> year ? year > "1000" & year < "2008" -> artist -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Search..." ==> "search"
|
||||||
|
"Most played tracks" -> title = "(%3d) %s" playcount title %sort = "inverse" %limit = "100" ? playcount > "0"
|
||||||
|
"Never played tracks" -> artist ? playcount == "0" -> album -> title = "%02d. %s" tracknum title
|
||||||
|
"Best tracks" -> artist ? playcount > "1" & autoscore > "85" -> album -> title = "%02d. %s (%3d)" tracknum title autoscore
|
||||||
|
"List played tracks" -> title = "(%3d/%d) %s" autoscore playcount title ? playcount > "0"
|
||||||
|
"Custom view..." ==> "custom"
|
||||||
|
|
||||||
|
# And finally set main menu as our root menu
|
||||||
|
%root_menu "main"
|
||||||
|
|
263
apps/tagtree.c
263
apps/tagtree.c
|
@ -51,7 +51,12 @@ static char searchstring[32];
|
||||||
|
|
||||||
enum variables {
|
enum variables {
|
||||||
var_sorttype = 100,
|
var_sorttype = 100,
|
||||||
var_limit
|
var_limit,
|
||||||
|
var_menu_start,
|
||||||
|
var_include,
|
||||||
|
var_rootmenu,
|
||||||
|
menu_next,
|
||||||
|
menu_load,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Capacity 10 000 entries (for example 10k different artists) */
|
/* Capacity 10 000 entries (for example 10k different artists) */
|
||||||
|
@ -95,9 +100,30 @@ struct search_instruction {
|
||||||
int result_seek[MAX_TAGS];
|
int result_seek[MAX_TAGS];
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct search_instruction *si, *csi;
|
struct menu_entry {
|
||||||
static int si_count = 0;
|
char name[64];
|
||||||
|
int type;
|
||||||
|
struct search_instruction *si;
|
||||||
|
int link;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define TAGNAVI_VERSION "#! rockbox/tagbrowser/2.0"
|
||||||
|
#define TAGMENU_MAX_ITEMS 32
|
||||||
|
#define TAGMENU_MAX_MENUS 8
|
||||||
|
struct root_menu {
|
||||||
|
char title[64];
|
||||||
|
char id[32];
|
||||||
|
int itemcount;
|
||||||
|
struct root_menu *parent;
|
||||||
|
struct menu_entry *items[TAGMENU_MAX_ITEMS];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct root_menu menus[TAGMENU_MAX_MENUS];
|
||||||
|
static struct root_menu *menu;
|
||||||
|
static struct search_instruction *csi;
|
||||||
static const char *strp;
|
static const char *strp;
|
||||||
|
static int menu_count;
|
||||||
|
static int root_menu;
|
||||||
|
|
||||||
static int current_offset;
|
static int current_offset;
|
||||||
static int current_entry_count;
|
static int current_entry_count;
|
||||||
|
@ -138,10 +164,10 @@ static int get_tag(int *tag)
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Find the start. */
|
/* Find the start. */
|
||||||
while (*strp == ' ' && *strp != '\0')
|
while ((*strp == ' ' || *strp == '>') && *strp != '\0')
|
||||||
strp++;
|
strp++;
|
||||||
|
|
||||||
if (*strp == '\0' || *strp == '?' || *strp == ':')
|
if (*strp == '\0' || *strp == '?')
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; i < (int)sizeof(buf)-1; i++)
|
for (i = 0; i < (int)sizeof(buf)-1; i++)
|
||||||
|
@ -167,6 +193,11 @@ static int get_tag(int *tag)
|
||||||
MATCH(tag, buf, "autoscore", tag_virt_autoscore);
|
MATCH(tag, buf, "autoscore", tag_virt_autoscore);
|
||||||
MATCH(tag, buf, "%sort", var_sorttype);
|
MATCH(tag, buf, "%sort", var_sorttype);
|
||||||
MATCH(tag, buf, "%limit", var_limit);
|
MATCH(tag, buf, "%limit", var_limit);
|
||||||
|
MATCH(tag, buf, "%menu_start", var_menu_start);
|
||||||
|
MATCH(tag, buf, "%include", var_include);
|
||||||
|
MATCH(tag, buf, "%root_menu", var_rootmenu);
|
||||||
|
MATCH(tag, buf, "->", menu_next);
|
||||||
|
MATCH(tag, buf, "==>", menu_load);
|
||||||
|
|
||||||
logf("NO MATCH: %s\n", buf);
|
logf("NO MATCH: %s\n", buf);
|
||||||
if (buf[0] == '?')
|
if (buf[0] == '?')
|
||||||
|
@ -337,8 +368,7 @@ static int get_condition(struct search_instruction *inst)
|
||||||
case '&':
|
case '&':
|
||||||
strp++;
|
strp++;
|
||||||
return 1;
|
return 1;
|
||||||
case ':':
|
case '-':
|
||||||
strp++;
|
|
||||||
case '\0':
|
case '\0':
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -366,25 +396,56 @@ static int get_condition(struct search_instruction *inst)
|
||||||
* $ ends with
|
* $ ends with
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static bool parse_search(struct search_instruction *inst, const char *str)
|
static bool parse_search(struct menu_entry *entry, const char *str)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
int type;
|
||||||
|
struct search_instruction *inst = entry->si;
|
||||||
|
char buf[MAX_PATH];
|
||||||
|
int i;
|
||||||
|
|
||||||
memset(inst, 0, sizeof(struct search_instruction));
|
|
||||||
strp = str;
|
strp = str;
|
||||||
|
|
||||||
if (get_token_str(inst->name, sizeof inst->name) < 0)
|
/* Parse entry name */
|
||||||
|
if (get_token_str(entry->name, sizeof entry->name) < 0)
|
||||||
{
|
{
|
||||||
logf("No name found.");
|
logf("No name found.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parse entry type */
|
||||||
|
if (get_tag(&entry->type) <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (entry->type == menu_load)
|
||||||
|
{
|
||||||
|
if (get_token_str(buf, sizeof buf) < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Find the matching root menu or "create" it */
|
||||||
|
for (i = 0; i < menu_count; i++)
|
||||||
|
{
|
||||||
|
if (!strcasecmp(menus[i].id, buf))
|
||||||
|
{
|
||||||
|
entry->link = i;
|
||||||
|
menus[i].parent = menu;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->type != menu_next)
|
||||||
|
return false;
|
||||||
|
|
||||||
while (inst->tagorder_count < MAX_TAGS)
|
while (inst->tagorder_count < MAX_TAGS)
|
||||||
{
|
{
|
||||||
ret = get_tag(&inst->tagorder[inst->tagorder_count]);
|
ret = get_tag(&inst->tagorder[inst->tagorder_count]);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
logf("Parse error #1");
|
logf("Parse error #1");
|
||||||
|
logf("%s", strp);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,6 +457,9 @@ static bool parse_search(struct search_instruction *inst, const char *str)
|
||||||
while (get_condition(inst) > 0) ;
|
while (get_condition(inst) > 0) ;
|
||||||
|
|
||||||
inst->tagorder_count++;
|
inst->tagorder_count++;
|
||||||
|
|
||||||
|
if (get_tag(&type) <= 0 || type != menu_next)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -520,46 +584,142 @@ bool tagtree_import(void)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tagtree_init(void)
|
static bool parse_menu(const char *filename)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
char buf[256];
|
char buf[256];
|
||||||
|
char data[256];
|
||||||
|
int variable;
|
||||||
int rc;
|
int rc;
|
||||||
int line_count;
|
bool first = true;
|
||||||
|
bool read_menu = false;
|
||||||
|
int i;
|
||||||
|
|
||||||
fd = open(FILE_SEARCH_INSTRUCTIONS, O_RDONLY);
|
if (menu_count >= TAGMENU_MAX_MENUS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fd = open(filename, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
logf("Search instruction file not found.");
|
logf("Search instruction file not found.");
|
||||||
return ;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pre-pass search instructions file to count how many entries */
|
|
||||||
line_count = 0;
|
|
||||||
while ( 1 )
|
|
||||||
{
|
|
||||||
rc = read_line(fd, buf, sizeof(buf)-1);
|
|
||||||
if (rc <= 0)
|
|
||||||
break;
|
|
||||||
line_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate memory for searches */
|
|
||||||
si = (struct search_instruction *) buffer_alloc(sizeof(struct search_instruction) * line_count + 4);
|
|
||||||
|
|
||||||
/* Now read file for real, parsing into si */
|
/* Now read file for real, parsing into si */
|
||||||
lseek(fd, 0L, SEEK_SET);
|
|
||||||
while ( 1 )
|
while ( 1 )
|
||||||
{
|
{
|
||||||
rc = read_line(fd, buf, sizeof(buf)-1);
|
rc = read_line(fd, buf, sizeof(buf)-1);
|
||||||
if (rc <= 0)
|
if (rc <= 0)
|
||||||
break;
|
break;
|
||||||
if (!parse_search(si + si_count, buf))
|
|
||||||
break;
|
if (first)
|
||||||
si_count++;
|
{
|
||||||
|
if (strcasecmp(TAGNAVI_VERSION, buf))
|
||||||
|
{
|
||||||
|
logf("Version mismatch");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (buf[0] == '\0')
|
||||||
|
{
|
||||||
|
if (read_menu)
|
||||||
|
{
|
||||||
|
/* End the menu */
|
||||||
|
menu_count++;
|
||||||
|
menu = &menus[menu_count];
|
||||||
|
read_menu = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!read_menu)
|
||||||
|
{
|
||||||
|
strp = buf;
|
||||||
|
if (get_tag(&variable) <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (variable)
|
||||||
|
{
|
||||||
|
case var_include:
|
||||||
|
if (get_token_str(data, sizeof(data)) < 0)
|
||||||
|
{
|
||||||
|
logf("%include empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parse_menu(data))
|
||||||
|
{
|
||||||
|
logf("Load menu fail: %s", data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case var_menu_start:
|
||||||
|
if (get_token_str(menu->id, sizeof(menu->id)) < 0)
|
||||||
|
{
|
||||||
|
logf("%menu_start id empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (get_token_str(menu->title, sizeof(menu->title)) < 0)
|
||||||
|
{
|
||||||
|
logf("%menu_start title empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
menu->itemcount = 0;
|
||||||
|
read_menu = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case var_rootmenu:
|
||||||
|
if (get_token_str(data, sizeof(data)) < 0)
|
||||||
|
{
|
||||||
|
logf("%root_menu empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < menu_count; i++)
|
||||||
|
{
|
||||||
|
if (!strcasecmp(menus[i].id, data))
|
||||||
|
{
|
||||||
|
root_menu = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate */
|
||||||
|
if (menu->items[menu->itemcount] == NULL)
|
||||||
|
{
|
||||||
|
menu->items[menu->itemcount] = buffer_alloc(sizeof(struct menu_entry));
|
||||||
|
memset(menu->items[menu->itemcount], 0, sizeof(struct menu_entry));
|
||||||
|
menu->items[menu->itemcount]->si = buffer_alloc(sizeof(struct search_instruction));
|
||||||
|
memset(menu->items[menu->itemcount]->si, 0, sizeof(struct search_instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parse_search(menu->items[menu->itemcount], buf))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
menu->itemcount++;
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tagtree_init(void)
|
||||||
|
{
|
||||||
|
memset(menus, 0, sizeof menus);
|
||||||
|
menu_count = 0;
|
||||||
|
menu = &menus[0];
|
||||||
|
root_menu = 0;
|
||||||
|
parse_menu(FILE_SEARCH_INSTRUCTIONS);
|
||||||
|
|
||||||
uniqbuf = buffer_alloc(UNIQBUF_SIZE);
|
uniqbuf = buffer_alloc(UNIQBUF_SIZE);
|
||||||
audio_set_track_buffer_event(tagtree_buffer_event);
|
audio_set_track_buffer_event(tagtree_buffer_event);
|
||||||
audio_set_track_unbuffer_event(tagtree_unbuffer_event);
|
audio_set_track_unbuffer_event(tagtree_unbuffer_event);
|
||||||
|
@ -858,11 +1018,27 @@ static int load_root(struct tree_context *c)
|
||||||
|
|
||||||
tc = c;
|
tc = c;
|
||||||
c->currtable = root;
|
c->currtable = root;
|
||||||
for (i = 0; i < si_count; i++)
|
if (c->dirlevel == 0)
|
||||||
|
c->currextra = root_menu;
|
||||||
|
|
||||||
|
menu = &menus[c->currextra];
|
||||||
|
|
||||||
|
for (i = 0; i < menu->itemcount; i++)
|
||||||
{
|
{
|
||||||
dptr->name = (si+i)->name;
|
dptr->name = menu->items[i]->name;
|
||||||
dptr->newtable = navibrowse;
|
switch (menu->items[i]->type)
|
||||||
dptr->extraseek = i;
|
{
|
||||||
|
case menu_next:
|
||||||
|
dptr->newtable = navibrowse;
|
||||||
|
dptr->extraseek = i;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case menu_load:
|
||||||
|
dptr->newtable = root;
|
||||||
|
dptr->extraseek = menu->items[i]->link;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
dptr++;
|
dptr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -884,13 +1060,13 @@ int tagtree_load(struct tree_context* c)
|
||||||
c->dirfull = false;
|
c->dirfull = false;
|
||||||
table = root;
|
table = root;
|
||||||
c->currtable = table;
|
c->currtable = table;
|
||||||
|
c->currextra = root_menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (table)
|
switch (table)
|
||||||
{
|
{
|
||||||
case root:
|
case root:
|
||||||
count = load_root(c);
|
count = load_root(c);
|
||||||
c->dirlevel = 0;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case allsubentries:
|
case allsubentries:
|
||||||
|
@ -945,11 +1121,17 @@ int tagtree_enter(struct tree_context* c)
|
||||||
case root:
|
case root:
|
||||||
c->currextra = newextra;
|
c->currextra = newextra;
|
||||||
|
|
||||||
if (newextra == navibrowse)
|
if (newextra == root)
|
||||||
|
{
|
||||||
|
menu = &menus[seek];
|
||||||
|
c->currextra = seek;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (newextra == navibrowse)
|
||||||
{
|
{
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
csi = si+seek;
|
csi = menu->items[seek]->si;
|
||||||
c->currextra = 0;
|
c->currextra = 0;
|
||||||
|
|
||||||
/* Read input as necessary. */
|
/* Read input as necessary. */
|
||||||
|
@ -963,7 +1145,7 @@ int tagtree_enter(struct tree_context* c)
|
||||||
rc = kbd_input(searchstring, sizeof(searchstring));
|
rc = kbd_input(searchstring, sizeof(searchstring));
|
||||||
if (rc == -1 || !searchstring[0])
|
if (rc == -1 || !searchstring[0])
|
||||||
{
|
{
|
||||||
c->dirlevel--;
|
tagtree_exit(c);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,7 +1206,8 @@ int tagtree_enter(struct tree_context* c)
|
||||||
void tagtree_exit(struct tree_context* c)
|
void tagtree_exit(struct tree_context* c)
|
||||||
{
|
{
|
||||||
c->dirfull = false;
|
c->dirfull = false;
|
||||||
c->dirlevel--;
|
if (c->dirlevel > 0)
|
||||||
|
c->dirlevel--;
|
||||||
c->selected_item=c->selected_item_history[c->dirlevel];
|
c->selected_item=c->selected_item_history[c->dirlevel];
|
||||||
gui_synclist_select_item(&tree_lists, c->selected_item);
|
gui_synclist_select_item(&tree_lists, c->selected_item);
|
||||||
c->currtable = c->table_history[c->dirlevel];
|
c->currtable = c->table_history[c->dirlevel];
|
||||||
|
|
|
@ -532,10 +532,12 @@ static bool check_changed_id3mode(bool currmode)
|
||||||
currmode = global_settings.dirfilter == SHOW_ID3DB;
|
currmode = global_settings.dirfilter == SHOW_ID3DB;
|
||||||
if (currmode) {
|
if (currmode) {
|
||||||
curr_context=CONTEXT_ID3DB;
|
curr_context=CONTEXT_ID3DB;
|
||||||
|
tc.dirlevel = 0;
|
||||||
tagtree_load(&tc);
|
tagtree_load(&tc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
tc.dirlevel = 0;
|
||||||
curr_context=CONTEXT_TREE;
|
curr_context=CONTEXT_TREE;
|
||||||
ft_load(&tc, NULL);
|
ft_load(&tc, NULL);
|
||||||
reload_dir = true;
|
reload_dir = true;
|
||||||
|
|
Loading…
Reference in a new issue