Index: tools/extract_menus.pl =================================================================== --- tools/extract_menus.pl (revision 0) +++ tools/extract_menus.pl (revision 0) @@ -0,0 +1,1266 @@ +#!/usr/bin/perl -w +# +# Extract menus from the rockbox code and write them out in html or javascript +# +# The config file tells the script what files to look for menus in etc. +# (by default ../tools/extract_menus.cfg +# +# Note: Probably will only work on unix - the stuff in the latex or html +# which changes based on what the player is is generated using gcc and various +# shell commands. +# +# For example usage see extract_menus.sh +# +# Options: +# -debug debuggin on +# -lang specify the language file - to generate in different languages +# -html generate html (default is latex) +# -config_file specify the config file to read +# -rb_dir specify the rockbox root directory +# -no_opt do not generate the conditional code (for debugging mostly) +# -start specify which menu to start printing from +# -stop_after don't print any submenus beneath this one +# -stop_before don't print this menu or any beneath it +# -comment specify a comment to put beside a menu + + +my $RB_DIR = ".."; +my $cfg_file = "$RB_DIR/tools/extract_menus.cfg"; + +# these things all get set in the .cfg file +my @menu_files; +my @settings_files; +my @database_files; +my $configure_script; +my $manual_names; +my $rename_links; +my $manual_nodes; +my @default_starts; + +my $debug=0; +my $latex_mode= 1; # 1=latex, 0=html +my $lang_file = "english.lang"; + +my @start_printing_from= (); +my $cs; +while ($_ = $ARGV[0]) { + shift(@ARGV); + if ( /^-debug$/ ) { + $debug = 1; + next; + } + elsif ( /^-html$/ ) { + $latex_mode=0; + next; + } + elsif ( /^-config_file$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + $cfg_file = shift(@ARGV); + next; + } + elsif ( /^-rb_dir$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + $RB_DIR = shift(@ARGV); + next; + } + elsif ( /^-no_opt$/ ) { + Preproc::disable(); + next; + } + elsif ( /^-start$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + $cs={}; + $cs->{start} = shift(@ARGV); + $cs->{stop_after} = {}; + $cs->{stop_before} = {}; + $cs->{comments} = {}; + push(@start_printing_from,$cs); + next; + } + elsif ( /^-stop_after$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + die("must use -start before $_") unless defined($cs); + my $node=shift(@ARGV); + $cs->{stop_after}{$node}=1; + next; + } + elsif ( /^-stop_before$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + die("must use -start before $_") unless defined($cs); + my $node=shift(@ARGV); + $cs->{stop_before}{$node}=1; + next; + } + elsif ( /^-comment$/ ) { + die("$_ needs two arguments (node comment)") if ($#ARGV < 1); + die("must use -start before $_") unless defined($cs); + my $node=shift(@ARGV); + $cs->{comments}{$node}=shift(@ARGV); + next; + } + elsif ( /^-lang$/ ) { + die("$_ needs an argument") if ($#ARGV < 0); + $lang_file = shift(@ARGV); + next; + } + else { + die("$_ unknown arg"); + } +} + +if ($latex_mode) { + Preproc::latex_mode(); + Markup::latex_mode(); +} +else { + Preproc::html_mode(); + Markup::html_mode(); +} + +# read the config file +open(F,"<$cfg_file") or die "Can't open $cfg_file"; +$/ = undef; +eval ; +die "$cfg_file\n$@" if $@; +close(F); +$/ = "\n"; + +# read the language file +Lang::read("$RB_DIR/apps/lang/$lang_file"); + +# Read the global settings files (to get the text for each global setting) +# Note: set the contrast setting manually - it is not macroed like +# all of the other settings are +Settings::read($RB_DIR, \@settings_files, { "contrast" => "LANG_CONTRAST" }); + +# read the configure script to set up the mappings that Preproc needs +Preproc::read("$RB_DIR/$configure_script"); + +# read all the menu nodes +my $nodes = { %{$manual_nodes} }; +read_menu_files($nodes,$RB_DIR,@menu_files); +read_database_menu_files($nodes,$RB_DIR,@database_files); + +if ($debug) { + print_all_nodes($nodes); + print_all_trees($nodes); +} + +# use the default starts if none specified on the command line +if ( 0 ==scalar(@start_printing_from) ) { + @start_printing_from = (@default_starts); +} + +print Markup::start(); +foreach my $p (@start_printing_from) { + print menus_to_text($nodes,$p->{start},[],$p->{stop_after},$p->{comments}, + $p->{stop_before}); +} +print Markup::end(); +Preproc::create_option_files($RB_DIR); + +exit 0; + +###################################################################### + +sub read_menu_files { + my ($nodes,$rb_dir,@menu_files) = @_; + + foreach my $f (@menu_files) { + open(F,"<$rb_dir/$f") or die "can't open $f"; + my $in_macro=0; + my $text=""; + + while (defined($_=)) { + if ($in_macro ==0) { + if ( m/^\s*MAKE_MENU/ or m/^\s*MENUITEM/ or m/^\s*MAKE_ONPLAYMENU/) { + $in_macro = 1; + } + } + if ($in_macro) { + $text .= $_; + if ( m/\);/ ) { + parse_macro($nodes,$text,$ARGV); + $in_macro=0; + $text=""; + } + } + } + close(F); + } +} + +# parses the macros that define the menus in the code. +# produces a node which is pointed to by $nodes->{NODE_NAME} +# the node has: +# type - which is the macro name (eg MAKE_MENU, +# MENUITEM_RETURNVALUE etc) +# args - and array of all the arguments to the macro +# +sub parse_macro { + my ($nodes,$s,$file_name) = @_; + + # a crude get rid of comments (there aren't many, so should work + $s =~ s|/\*.*?\*/||gs; + + my $node = {}; + my $name = ""; + $node->{file_name} = $file_name; + if ( $s =~ m/^\s*([A-Z_]+?)\(/gs ) { + $node->{type} = $1; + } + else { + die "1:$s"; + } + + $args=""; + if ( $s =~ m/\G\s*([A-Za-z0-9_]+?)\s*,(.+?)\);/gs ) { + $name = $1; + $args=$2; + } + else { + die "2:$s"; + } + + $node->{args} = []; + $args .= "\n"; + $args =~ s/,/\n/gs; + + while ( $args =~ m/\G(.*?)\n/g ) { + my $a = $1; + next if $a =~ m/^\s*$/; + $a =~ s/^\s*(.*?)\s*$/$1/s; + push(@{$node->{args}}, $a ); + } + + warn "already saw $name" if (exists($nodes->{$name})); + + $nodes->{$name} = $node; +} + +# read the database menu config files. Generates DB_MENU nodes, these +# are a bit different from the normal nodes. They store the +# pointers to the next menu in the args array like normal nodes (starting at index 0) +# but they store the text for the menu item in another array "item_names". (normal +# nodes get the text for the menu item by looking in the node the item points to). +sub read_database_menu_files { + my ($nodes,$rb_dir,@files) = @_; + + my $in_menu=""; + my $db_prefix="db_"; # try and make the menu names globally unique + my $node; + + foreach my $f (@files) { + open(F,"<$rb_dir/$f") or die "can't open $f"; + + while (defined($_=)) { + if ($in_menu) { + if ( m/^\s*$/ ) { + $in_menu=0; + } + elsif ( m/^\"(.+?)\"\s*([=-]+>)\s*(?:\"(\w+)\")?/ ) { + my $menu_item = $1; + my $arrow = $2; + my $menu_ptr = defined($3) ? ("&".$db_prefix . $3) : ""; + die "db line ptr to nothing $_" if ( ($arrow eq "==>") && ($menu_ptr eq "")); + push(@{$node->{args}}, $menu_ptr); + push(@{$node->{item_names}}, $menu_item); + } + else { + die "Unknown db menu line $_"; + } + } + else { + if ( m/^\%menu_start\s+\"(\w+)\"\s+\"(.*?)\"/ ) { + $in_menu=1; + $node = {}; + $node->{title} = $2; + $node->{type} = "DB_MENU"; + $node->{args} = []; + $node->{item_names} = []; + $nodes->{$db_prefix . $1} = $node; + } + } + } + close(F); + } +} + +# simple tree view for debugging +sub print_tree { + my ($nodes,$name,$indent) = @_; + + print $indent . "$name\n"; + + my $node = $nodes->{$name}; + if ( $node->{type} eq "MENUITEM_STRINGLIST" ) { + for (my $i=2; $i < scalar(@{$node->{args}});$i++) { + print $indent . " " . $node->{args}[$i] ."\n"; + } + } + else { + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + print_tree($nodes,$p,$indent . " "); + } + } + } +} +sub print_all_trees { + my ($nodes) = @_; + + my %refs; + # figure out ref counts + foreach my $name (sort keys %$nodes) { + $refs{$name}=0; + } + foreach my $name (sort keys %$nodes) { + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + if (exists($nodes->{$p})) { + $refs{$p}++; + } + else { + die "Can't find item $p"; + } + } + } + } + + + my @isolated=(); + my @tops=(); + foreach my $name (sort keys %$nodes) { + if ($refs{$name} == 0) { + if (has_ptrs($nodes,$name)) { + push(@tops,$name); + print "\n".("-"x38)."\n"; + print_tree($nodes,$name,""); + } + else { + push(@isolated,$name); + } + } + } + print "\n".("-"x38)."\nTop menus\n"; + foreach my $name (@tops) { + print " $name\n"; + } + print "\n".("-"x38)."\nIsolated\n"; + foreach my $name (@isolated) { + print " $name\n"; + } +} +# simple list of nodes for debugging +sub print_all_nodes { + my ($nodes) = @_; + + foreach my $name (sort keys %$nodes) { + my $node = $nodes->{$name}; + print "$name\n"; + print " Type: $node->{type}\n"; + foreach my $a ( @{$node->{args}} ) { + print " Arg:$a\n"; + } + } +} + +sub menus_to_text { + my ($nodes,$name,$parents,$stop_after,$comments,$stop_before) = @_; + + my $node = $nodes->{$name}; + + my $r = ""; + unless (has_ptrs($nodes,$name) || + ( $node->{type} eq "MENUITEM_STRINGLIST" ) || + ( $node->{type} eq "DB_MENU" ) ) + { + return ""; + } + + my $node_title = exists($node->{title}) ? Lang::lookup($node->{title}) : + get_node_text($nodes,$name); + + if (exists($combine_nodes->{$name})) { + if (0==scalar(@{$combine_nodes->{$name}})) { + return ""; + } + else { + $node_title = combine_names($nodes,$name,@{$combine_nodes->{$name}}); + pop(@$parents); + push(@$parents,$node_title); + } + } + + if ( $node->{type} eq "MENUITEM_RETURNVALUE" ) { + # don't print anything and don't put anything on the parents list + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + $r .= menus_to_text($nodes,$p, [ @$parents ] ,$stop_after,$comments, + $stop_before); + } + } + } + else { + $r .= Markup::comment($name); + my $menu_row = MenuRow->new(); + foreach my $parent ( @$parents ) { + $menu_row->add_parent( Markup::quote($parent) . Markup::arrow() ); + + } + $menu_row->add_menu_string(Markup::quote($node_title)); + if ( $node->{type} eq "MENUITEM_STRINGLIST" ) { + for (my $i=2; $i < scalar(@{$node->{args}});$i++) { + $menu_row->add_menu_string(Markup::quote(Lang::lookup($node->{args}[$i]))); + } + } + elsif ( $node->{type} ne "DB_MENU" ) { + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + $menu_row->add_menu_string(Markup::quote(get_node_text($nodes,$p)). + ((has_ptrs($nodes,$p)|| + ($nodes->{$p}{type} eq "MENUITEM_STRINGLIST") || + ($nodes->{$p}{type} eq "DB_MENU")) ? + Markup::arrow():"")); + } + else { + my $pre = Preproc::lookup($a); + $menu_row->add_menu_preproc($pre) if $pre; + } + } + } + else { # DB_MENU + my $in_a_z=0; + for (my $i=0;$i< scalar(@{$node->{args}}); $i++ ) { + if ( $in_a_z ) { + if ($node->{item_names}[$i] eq "Z") { + $in_a_z=0; + } + else { + next; + } + } + $menu_row->add_menu_string( Markup::quote($node->{item_names}[$i]) . + ($node->{args}[$i]?Markup::arrow():"")); + if ($node->{item_names}[$i] eq "A") { + $in_a_z=1; + $menu_row->add_menu_string(Markup::quote("...")); + } + } + } + + my $comment = exists($comments->{$name}) ? $comments->{$name} :""; + + if (0==scalar(@$parents)) { + $r .= Markup::subhead($node_title); + } + + $r .= Markup::menu_row($menu_row,$comment); + + if (exists($stop_after->{$name})) { + return $r; + } + + # Now call recursively on all pointers + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + if ( ! exists($stop_before->{$p}) ) { + $r .= menus_to_text($nodes,$p, + [ @$parents, + get_node_text($nodes,$p) ], + $stop_after,$comments,$stop_before); + } + } + else { + if ( $node->{type} ne "DB_MENU" ) { + $r .= Preproc::lookup($a); + } + } + } + # a bit of a hack get rid of empty conditionals (like if (...) { } ) + # keep doing it until nothing is removed to get rid of conds + # that just contain conds + while ($r =~ s/\n[^\n]+\{\n\}//gs ) {}; + + } + return $r; +} + +sub get_node_text { + my ($nodes,$name) = @_; + + #print STDERR "$name\n"; + + if (!exists($nodes->{$name})) { + die "Can't find item $name"; + } + + my $r = ""; + + $node = $nodes->{$name}; + + if ( $node->{type} eq "DB_MENU" ) { + $r = $node->{title}; + } + elsif ( $node->{type} eq "MAKE_MENU" ) { + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MAKE_ONPLAYMENU" ) { + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MENUITEM_RETURNVALUE" ) { + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MENUITEM_FUNCTION" ) { + $r = $node->{args}[1]; + } + elsif ( $node->{type} eq "MENUITEM_SETTING" ) { + # this isn't really a name something like:&global_settings.fm_region + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MENUITEM_FUNCTION_DYNTEXT" ) { + if (exists($manual_names->{$name})) { + $r = $manual_names->{$name}; + } + else { + die "Need name for $node->{name} in \$manual_names\n"; + } + } + elsif ( $node->{type} eq "MENUITEM_RETURNVALUE_DYNTEXT" ) { + # something like: GO_TO_WPS + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MENUITEM_SETTING_W_TEXT" ) { + # this isn't really a name something like:&global_settings.fm_region + $r = $node->{args}[0]; + } + elsif ( $node->{type} eq "MENUITEM_STRINGLIST" ) { + $r = $node->{args}[0]; + } + else { + die "unknown node type $node->{type}\n"; + } + + + return Lang::lookup($r); +} + +sub get_args { + my ($nodes,$name) = @_; + + my $node = $nodes->{$name}; + my @args=@{$node->{args}}; + my @r = (); + if ( $node->{type} eq "DB_MENU" ) { + @r = @args; + } + elsif (( $node->{type} eq "MAKE_MENU" ) or + ( $node->{type} eq "MAKE_ONPLAYMENU" )) { + @r = @args[3..$#args]; + } + elsif ( $node->{type} eq "MENUITEM_RETURNVALUE" ) { + @r = ( $args[1] ); + } + elsif ( $node->{type} eq "MENUITEM_FUNCTION" ) { + } + elsif ( $node->{type} eq "MENUITEM_SETTING" ) { + } + elsif ( $node->{type} eq "MENUITEM_FUNCTION_DYNTEXT" ) { + } + elsif ( $node->{type} eq "MENUITEM_RETURNVALUE_DYNTEXT" ) { + } + elsif ( $node->{type} eq "MENUITEM_SETTING_W_TEXT" ) { + } + elsif ( $node->{type} eq "MENUITEM_STRINGLIST" ) { + } + else { + die "unknown node type $node->{type}\n"; + } + + return @r; +} + +sub get_ptr { + my ($nodes,$name,$a) = @_; + + my $node = $nodes->{$name}; + if (exists($rename_links->{$name}{$a})) { + #print STDERR "Renaming $a $rename_links->{$name}{$a}\n"; + $a = $rename_links->{$name}{$a}; + } + + if (( $node->{type} eq "DB_MENU" ) or + ( $node->{type} eq "MAKE_MENU" ) or + ( $node->{type} eq "MAKE_ONPLAYMENU" )) { + if ($a =~ s/^&// || $a =~ s/^\(\s*void\s*\*\s*\)\s*\&// ) { + return $a; + } + } + elsif ( $node->{type} eq "MENUITEM_RETURNVALUE" ) { + if (exists($nodes->{$a})) { + return $a; + } + else { + warn "Pointer to unknown node in $name: $a\n"; + } + } + + return ""; +} + +sub has_ptrs { + my ($nodes,$name) = @_; + + foreach my $a ( get_args($nodes,$name) ) { + my $p = get_ptr($nodes,$name,$a); + if ($p) { + return 1; + } + } + return 0; +} + + +sub combine_names { + my ($nodes,@names) = @_; + + my $nxn = []; + my $max = 0; + + foreach my $n (@names) { + my $nt = exists($nodes->{$n}{title}) ? + Lang::lookup($nodes->{$n}{title}) : + get_node_text($nodes,$n); + my $spl = [ split(/\s+/,$nt) ]; + $max=scalar(@$spl) if ($max[$i])) { + if ($n->[$i] ne $prev) { + $new_chunk .= ($prev ? "/":"") . $n->[$i]; + } + $prev=$n->[$i]; + } + } + push (@new_chunks,$new_chunk); + + } + return join(" ",@new_chunks); +} + + +########################################################################### +# language file functions +package Lang; + +BEGIN { +$lang={}; +} + +sub read { + my ($file_name) = @_; + + open(LANG_FILE,"<$file_name") or die "can't open $file_name"; + + $/ = undef; + my $all =; + $/ = "\n"; + + while ( $all =~ m/(.*?<\/phrase>)/gs ) { + my $phrase = $1; + if ( $phrase =~ m/id:\s*(.*?)\n/ ) { + my $id = $1; + # grab first dest string and hope it's right + if ( $phrase =~ m/.*?\"(.*?)\"/s ) { + $lang->{$id} = $1; + #print "$id = $lang->{$id}\n"; + } + else { + die "can't find dest in $phrase"; + } + } + else { + die "can't find id in $phrase"; + } + } + +} +sub lookup { + my ($str) = @_; + + if ( $str =~ /global_settings\.(\w+)/ ) { + my $s; + if ($s = Settings::lookup->($1)) { + $str = "ID2P($s)"; + } + else { + warn "Can't find setting for $str"; + } + } + + if ( $str =~ /^ID2P\((.*?)\)$/ ) { + if ( exists( $lang->{$1} ) ) { + return $lang->{$1}; + } + else { + if ($1) { + die "Can't find mapping for $str"; + } + else { + return "BLANK"; + } + } + } + elsif ( $str =~ /^ID2P_D\((.*?),(.*?)\)$/ ) { # manually created dynamic + if ( exists( $lang->{$1} ) ) { + my $s = $lang->{$1}; + my $d = $2; + $s =~ s/\%d/$d/; # replace %d with the 2nd argument + return $s; + } + else { + if ($1) { + die "Can't find mapping for $str"; + } + else { + return "BLANK"; + } + } + } + else { + $str =~ s/^\"(.*)\"/$1/; + return $str; + } +} + +########################################################################### +# Global settings file functions +# +package Settings; +BEGIN { +$settings={}; +} +sub read { + my ( $rb_dir, $files , $manual_settings ) = @_; + + $settings = { %$manual_settings }; + + foreach my $f (@$files) { + open(SET_FILE,"<$rb_dir/$f") or die "can't open $f"; + $/ = undef; + my $text=; + $/ = "\n"; + while ( $text =~ m/\n\s*[A-Z_]+(?:SETTING|CFGVALS)\([^,]+,\s*(\w+)\s*,\s*(\w+)\s*,/gs ) { + $settings->{$1} = $2; + } + } +} +sub lookup { + my ($s) = @_; + return exists($settings->{$s}) ? $settings->{$s} : ""; +} + +########################################################################### +# Handle if|ifdef|ifndef - +# when the output is being generated lookup() gets called +# (eg with #if LCD_DEPTH > 1 ) +# and it returns the conditional code needed +# (eg in latex \opt{IFLCD_DEPTH_1}{ +# the afterwards call create_option_files and it will write out some C-code that +# looks like this: +# #if LCD_DEPTH > 1 +# printf("\\edef\\UseOption{\\UseOption,IFLCD_DEPTH_1}\n"); +# then for each player it compiles and runs the c-code with -Dtarget (eg -DSANSA_E200) +# which sets up LCD_DEPTH correctly for that target and prints the +# UseOption out if it is needed. +# Note: before you do this you need to call the "read" function with the configure +# script so it can get the mapping from arch to target +# +package Preproc; +BEGIN { +$preproc_code=""; +%preproc_vars = (); # used to not print the ifdef code more than once +$disable=0; +$mode="latex"; +$js_file = "player-options.js"; +$archs={}; +} +sub disable { $disable=1; } # disable conditional code +sub latex_mode { $mode="latex"; } +sub html_mode { $mode="html"; } + +sub lookup { + my ($s) = @_; + + if ( $disable ) { + return ""; + } + if ( $s =~ m/^\#\s*(if|ifdef|ifndef)\s+/ ) { + my $v = _make_var_name_from_preproc($s); + while ((exists($preproc_vars{$v})) && + ($preproc_vars{$v} ne $s)) { # mapping was not unique + $v .= "_1"; + } + + if (!exists($preproc_vars{$v})) { + $preproc_vars{$v}=$s; + $preproc_code .= "$s\n"; + $preproc_code .= _preproc_printf($v,1); + $preproc_code .= "#else\n"; + $preproc_code .= _preproc_printf($v,0); + $preproc_code .= "#endif\n"; + } + if ($mode eq "latex") { + return "\\opt{$v}{\n"; + } + else { + return "if ( $v || show_all ) {\n"; + } + } + elsif ( $s =~ m/^\#\s*endif/ ) { + return "}\n"; + } + else { + die "don't understand preproc $s"; + } +} +# reads the configure script and sets up the $arch hash with +# the keys being e200 etc (which is \platform in the manual latex) +# and the values being the target that you define for compilation +# eg SANSA_E200 etc +sub read { + my ($configure_script) = @_; + + my $found_case=0; + my $arch=""; + + open(F,"<$configure_script") or die "can't open $configure_script"; + while (defined($_=)) { + if (!$found_case) { + if (m/case\s+\$buildfor\s+in/) { + $found_case=1; + #print STDERR "Found case\n"; + } + } + else { + if ( m/archos=\"(.*?)\"/ ) { + die "found archos again before target $_" if $arch; + $arch=$1; + #print STDERR "Found archos=$arch\n"; + } + elsif ( m/target=\"-D(.*?)\"/ ) { + die "found target before archos $_" unless $arch; + die "saw $arch twice" if exists($archs->{$arch}); + $archs->{$arch} = $1; + #print STDERR "Found target=$1\n"; + $arch=""; + } + elsif ( m/^\s*esac/ ) { + last; + } + } + } + close(F); + # a few aren't regular + $archs->{'h1xx'} = $archs->{'h120'}; + delete($archs->{'h120'}); + delete($archs->{'h140'}); + delete($archs->{'ipodmini2g'}); # same as ipodmini + $archs->{'recorderv2fm'} = $archs->{'fmrecorder'}; + delete($archs->{'fmrecorder'}); + delete($archs->{'recorderv2'}); # same as fm + delete($archs->{'av300'}); # is mentioned in firmware/export/config.h but no .h file + +} + +sub create_option_files { + my ($rb_dir) = @_; + + if ( $disable ) { + return; + } + + _print_code($rb_dir); + + if ($mode eq "latex") { + _create_option_files_latex(); + } + else { + _create_option_files_javascript(); + } +} +# returns the javascript code needed at the start (or nothing +# if options are disabled +sub js_start { + + if ($disable) { + return ""; + } + my $r = ' + +ALL +'; + + foreach my $p (sort keys(%$archs)) { + $r .= "$p\n"; + } + $r .= "
\n\n"; +} + +# wraps html in javascript document writes - to handle +# optional menus selected by javascript if's +sub js_wrap { + my ($t) = @_; + + if ($disable) { + return $t; + } + + $t =~ s/\'/\\\'/g; # escape the single quotes + $t =~ s/^(.*?)$/document.write(\'$1\');/gm; + return $t; +} + + +sub _print_code { + my ($rb_dir) = @_; + + open(PPF,">ppf.c") or die "Can't open"; + + print PPF "#include +#define __PCTOOL__ +#include \"$rb_dir/firmware/export/config.h\" + +int main () { +"; + print PPF $preproc_code; + print PPF "return 0;\n}\n"; + close PPF; + + # compile example: + # gcc -DSANSA_E200 ppf.c +} + +sub _make_var_name_from_preproc { + my ($preproc) = @_; + + $preproc = uc($preproc); + $preproc =~ s/^\#//; + $preproc =~ s/\s+//g; + $preproc =~ s/!/_NOT_/g; + $preproc =~ s/[|]+/_OR_/g; + $preproc =~ s/[&]+/_AND_/g; + $preproc =~ s/==/_EQ_/g; + $preproc =~ s/\W/_/g; + + return $preproc; +} +sub _preproc_printf { + my ($v,$i) = @_; + + if ($mode eq "latex") { + if ($i) { + return " printf(\"\\\\edef\\\\UseOption{\\\\UseOption,$v}\\n\");\n"; + } + else { + return "\n"; + } + } + else { + return " printf(\"$v=$i;\\n\");\n"; + } +} + +sub _create_option_files_javascript { + + my $of = $js_file; + + if ( -r $of ) { + print STDERR "$of exists, skipping generation\n"; + return; + } + + # compiled with no -D to get the defs of all variables at the start (VAR1=0 etc) + system("gcc -oppf ppf.c") == 0 or die "gcc failed"; + system("./ppf >> $of") == 0 or die "ppf failed"; + # now compile with each -D and run the code to generate the correct value + # for each option with that player. + foreach my $arch ( sort keys(%$archs) ) { + my $target = $archs->{$arch}; + print STDERR "compliling options for $arch (-D$target)\n"; + system("gcc -oppf -D$target ppf.c") == 0 or die "gcc failed"; + system("echo 'if (player == \"$arch\") {' >> $of"); + system("./ppf >> $of") == 0 or die "ppf failed"; + system("echo '}' >> $of"); + system("rm ppf"); + } + #system("rm ppf.c"); +} + +sub _create_option_files_latex { + + # compile with each -D and run the code to generate the correct value + # for each option with that player. + foreach my $arch ( sort keys(%$archs) ) { + my $target = $archs->{$arch}; + my $of = "menu_options_$arch.tex"; + if ( -r $of ) { + print STDERR "$of exists, skipping generation\n"; + next; + } + print STDERR "compliling options for $arch (-D$target)\n"; + system("gcc -oppf -D$target ppf.c"); + print STDERR " writing to $of\n"; + system("echo '% auto generated by $0' > $of"); + system("./ppf >> $of"); + system("rm ppf"); + } + #system("rm ppf.c"); +} + +################################################################################ +# Generate html or latex markup +# +package Markup; +BEGIN { +$latex=1; +} + +sub latex_mode { $latex=1; } +sub html_mode { $latex=0; } + +sub start { + + my $r=""; + if ($latex) { + $r .= "% auto generated by $0\n"; + $r .= "\\begin{tabbing}\n"; + $r .= "" . ("\\hspace{0.17\\textwidth} \\= " x 5) . "\\kill\n"; + } + else { + $r .= ' + + +'; + $r .= Preproc::js_start(); + } + return $r; +} + +sub end { + if ($latex) { + $r .= "\\end{tabbing}\n"; + } + else { + $r .= Preproc::js_end(); + $r .= "\n"; + } + return $r; +} + + +sub subhead { + my ($t) = @_; + if ($latex) { + return ""; # don't put subheads in the latex + } + else { + return Preproc::js_wrap("

$t

\n"); + } +} + +sub menu_row { + my ($menu_row,$comment) = @_; + + my $t=""; + my $cols=5; + $t .= $latex ? "" : + Preproc::js_wrap("\n"); + + # parent cells + foreach my $p ($menu_row->get_parents()) { + $t .= $latex ? "\\menubox{$p} \\> \n" : + Preproc::js_wrap("\n"); + } + + # menu cells + $t .= $latex ? "\\menubox{\n" : + Preproc::js_wrap("\n"); + + # comment cell + if ( $comment ) { + $t .= $latex ? "\\> \\menucomment{$comment}\n" : + Preproc::js_wrap(""); + } + # end of main holder table + $t .= $latex ? "\\\\\n" : + Preproc::js_wrap("
$p
\n"); + my $first=1; + foreach my $m ($menu_row->get_menu_items()) { + my $mdata = $menu_row->item_data($m); + if ( $menu_row->item_is_preproc($m) ) { + $t .= $mdata; + } + else { + if ($first) { + $t .= $latex ? "\\setting{$mdata}\n" : + Preproc::js_wrap("\n"); + $first=0; + } + else { + $t .= $latex ? "\\\\$mdata\n" : + Preproc::js_wrap("\n"); + } + } + } + $t .= $latex ? "}\n": + Preproc::js_wrap("
$mdata
$mdata
$comment
\n"); + return $t; +} + +sub comment { + my ($com) = @_; + + if ($latex) { + return "\% $com\n"; + return ""; + } + else { + return Preproc::js_wrap("\n"); + } +} + +sub arrow { + if ($latex) { + return "\$\\rightarrow\$"; + } + else { + return " ⇒"; + } +} + +sub quote { + my ($t) = @_; + + if ($latex) { + _quote_latex($t); + } + else { + _quote_html($t); + } + return $t; +} +sub _quote_html { + + $_[0] =~ s/&/&/g; + $_[0] =~ s//>/g; + $_[0] =~ s/\"/"/g; + +} +sub _quote_latex { + + $_[0] =~ s/\\/\$\\backslash\$/g; + $_[0] =~ s/([{}_#%&\$])/\\$1/g; + $_[0] =~ s/[~^]/\\verb|$1|/g; + +} + +################################################################################ +# A simple structure to hold the stuff to print a menu row (some parents +# followed by the menu) +# +package MenuRow; + +sub new { + my $this = { "parents" => [] , "menu" => [] }; + bless $this; + return $this; +} + +sub add_parent { + my $this = shift; + my $p = shift; + push(@{$this->{parents}}, $p ); +} + +sub add_menu_string { + my $this = shift; + my $s = shift; + push(@{$this->{menu}}, { type=>"string", data=> $s } ); +} + +sub add_menu_preproc { + my $this = shift; + my $s = shift; + push(@{$this->{menu}}, { type=>"preproc", data=> $s } ); +} + +sub get_parents { + my $this = shift; + return ( @{$this->{parents}} ); +} + +sub get_menu_items { + my $this = shift; + return ( @{$this->{menu}} ); +} + +sub item_is_preproc { + my $this = shift; + my $m = shift; + return ( $m->{type} eq "preproc" ); +} + +sub item_data { + my $this = shift; + my $m = shift; + return ( $m->{data} ); +} Property changes on: tools/extract_menus.pl ___________________________________________________________________ Name: svn:executable + * Index: tools/extract_menus.cfg =================================================================== --- tools/extract_menus.cfg (revision 0) +++ tools/extract_menus.cfg (revision 0) @@ -0,0 +1,174 @@ + +# all file names specified relative to rockbox root dir +# file list generate by +# these are all the files we look for menus in +# basically all those that contain MAKE_MENU or MENUITEM +# (except for the plugins) in apps/plugins/) +@menu_files = ( + "apps/recorder/radio.c", + "apps/onplay.c", + "apps/playlist_viewer.c", + "apps/enc_config.c", + "apps/menus/main_menu.c", + "apps/menus/settings_menu.c", + "apps/menus/playback_menu.c", + "apps/menus/sound_menu.c", + "apps/menus/playlist_menu.c", + "apps/menus/eq_menu.c", + "apps/menus/display_menu.c", + "apps/menus/recording_menu.c", + "apps/menus/theme_menu.c", + "apps/root_menu.c", + "apps/menu.c", + "apps/bookmark.c", +); + + +@settings_files = ( "apps/settings_list.c" ); +@database_files = ( "apps/tagnavi.config" ); +$configure_script = "tools/configure"; + +# some of the node names are dynamic, so need to set them manually here. +# ID2P() is interpretted the same as in the c code. +# ID2P_D(S,I) - is a hack that when read substitues %d in S for I +$manual_names = { + "gain_item_0" => "ID2P_D(LANG_EQUALIZER_GAIN_ITEM,60)", # ID2PD strips %d + "gain_item_1" => "ID2P_D(LANG_EQUALIZER_GAIN_ITEM,200)", + "gain_item_2" => "ID2P_D(LANG_EQUALIZER_GAIN_ITEM,800)", + "gain_item_3" => "ID2P_D(LANG_EQUALIZER_GAIN_ITEM,4000)", + "gain_item_4" => "ID2P_D(LANG_EQUALIZER_GAIN_ITEM,12000)", + "band_1_menu" => "ID2P_D(LANG_EQUALIZER_BAND_PEAK,1)", # not used see fake_band_1_menu + "band_2_menu" => "ID2P_D(LANG_EQUALIZER_BAND_PEAK,2)", # not used + "band_3_menu" => "ID2P_D(LANG_EQUALIZER_BAND_PEAK,3)", # not used + "rating_item" => "ID2P(LANG_MENU_SET_RATING)", +}; + +# some of the links dont point quite where we'd like them to, +# - fix this by renaming them how we'd like: +# format "node_name" => { "original_link_name" => "new_link_name", ... },... +$rename_links = { "root_menu_" => { "&menu_" => "&main_menu_" , + "&wps_item" => "&WPS" , + }, + "rocks_browser" => { "GO_TO_BROWSEPLUGINS" => + "plugins_menu_items" , }, + "main_menu_" => { "&recording_settings" => + "&recording_settings_menu" }, + "db_browser" => { "GO_TO_DBBROWSER" => "db_main" }, + + "cat_playlist_menu" => { "&cat_view_lists" => "&catalog_view_playlists" }, + "playlist_options" => {"&catalog" => "&catalog_view_playlists2", }, + "advanced_eq_menu_" => { "&band_1_menu" => "&fake_band_1_menu", + "&band_2_menu" => "&fake_band_2_menu", + "&band_3_menu" => "&fake_band_3_menu" }, + + }; + +# Create some nodes manually because in the code they are not really menus, +# they are either screens or programatically generated menus +$manual_nodes = { + "GO_TO_RECENTBMARKS" => { + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_BOOKMARK_SELECT_BOOKMARK)", "NULL", + "ID2P(LANG_BOOKMARK_MENU)", "..." ] , }, + + "GO_TO_FILEBROWSER" => { + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_DIR_BROWSER)", "NULL", + "ID2P(LANG_DIR_BROWSER)", "..." ] , }, + + "GO_TO_RECSCREEN" => { + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_RECORDING)", "NULL", + "Screen", ] , }, + + "GO_TO_FM" => { + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_FM_RADIO)", "NULL", + "Screen", ] , }, + + "WPS" => { + "title" => "While Playing Screen", + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_NOW_PLAYING)", "NULL", + "...", ] , }, + + "catalog_view_playlists" => { + "title" => "ID2P(LANG_CATALOG)", + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_CATALOG_VIEW)", "NULL", + "ID2P(LANG_PLAYLIST)", "..." ] , }, + +# this is needed because one place that gets you to the playlist catalog +# says "Playlist Catalog" instead of "View Catalog" like it says everywhere else + "catalog_view_playlists2" => { + "title" => "ID2P(LANG_CATALOG)", + "type" => "MENUITEM_STRINGLIST", + "args" => [ "ID2P(LANG_CATALOG)", "NULL", + "ID2P(LANG_PLAYLIST)", "..." ] , }, + + "db_custom" => { + "type" => "MENUITEM_STRINGLIST", + "args" => [ "Custom", "NULL", + "Custom search", ] , }, + + "fake_band_1_menu" => { + "type" => "MAKE_MENU", + "args" => [ "ID2P_D(LANG_EQUALIZER_BAND_PEAK,1)", "NULL","Icon_EQ", + "&cutoff_1","&q_1","&gain_1" ] , }, + "fake_band_2_menu" => { + "type" => "MAKE_MENU", + "args" => [ "ID2P_D(LANG_EQUALIZER_BAND_PEAK,2)", "NULL","Icon_EQ", + "&cutoff_2","&q_2","&gain_2" ] , }, + "fake_band_3_menu" => { + "type" => "MAKE_MENU", + "args" => [ "ID2P_D(LANG_EQUALIZER_BAND_PEAK,3)", "NULL","Icon_EQ", + "&cutoff_3","&q_3","&gain_3" ] , }, + +}; + +# combine some nodes to save space +# the combining of names is done automatically +# node1 => [ node2 , node3 ] combines the 3 nodes when node1 is printed +# node2 => [] prevents node2 from being printed +$combine_nodes = { + "fake_band_1_menu" => ["fake_band_2_menu","fake_band_3_menu"], + "fake_band_2_menu" => [], + "fake_band_3_menu" => [], + "band_0_menu" => ["band_4_menu"], + "band_4_menu" => [], + "db_custom_artist" => ["db_custom_album"], + "db_custom_album" => [], + + "hw_eq_band0" => ["hw_eq_band4"], + "hw_eq_band1" => ["hw_eq_band2","hw_eq_band3"], + "hw_eq_band2" => [], + "hw_eq_band3" => [], + "hw_eq_band4" => [], +}; + +# what to print if nothing is specified on the command line +@default_starts = ( + { "start" => "root_menu_" , + "stop_after" => { 'main_menu_' => 1 }, + "stop_before" => {}, + "comments" => { 'main_menu_' => 'see below' , + "root_menu_" => 'Note: Recent Bookmarks is not enabled by default' + } + }, + { "start" => "wps_onplay_menu" , + "stop_after" => { 'sound_settings' => 1 }, + "stop_before" => {}, + "comments" => { 'sound_settings' => 'see below under Settings' } + }, + { "start" => "tree_onplay_menu" , + "stop_after" => { }, + "stop_before" => {}, + "comments" => { } + }, + { "start" => "main_menu_" , + "stop_after" => { }, + "stop_before" => {}, + "comments" => { } + }, +); + Index: tools/extract_menus.sh =================================================================== --- tools/extract_menus.sh (revision 0) +++ tools/extract_menus.sh (revision 0) @@ -0,0 +1,58 @@ +#!/bin/sh + +RB_DIR=.. + +# the first time run it with no starts specified, this will +# print all menus (the default) which will generate all +# of the options in the menu_options_* files, the +# subsequent runs will not overwrite these menu_options_* +# files +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR > /dev/null +cp menu_options_*.tex $RB_DIR/manual/platform/menu_options/ + +# comments are all impemented as newcommand's so that they can +# easily be changed without running extract_menus.pl again + +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR -start root_menu_ \ + -comment root_menu_ '\rootmenucomment' \ + -stop_after main_menu_ \ + -comment main_menu_ '\mainmenucomment' \ + > $RB_DIR/manual/rockbox_interface/menu_structure_main.tex + +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR -start wps_onplay_menu -stop_after sound_settings \ + -comment sound_settings '\wpssoundsettingscomment' \ + > $RB_DIR/manual/rockbox_interface/menu_structure_wps.tex + +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR -start tree_onplay_menu \ + > $RB_DIR/manual/rockbox_interface/menu_structure_browser.tex + +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR -start main_menu_ \ + -comment sound_settings '\soundsettingscomment' \ + -comment file_menu '\filemenucomment' \ + -comment tagcache_menu '\tagcachemenucomment' \ + -comment playback_menu_item '\playbackmenuitemcomment' \ + -comment manage_settings '\managesettingscomment' \ + -comment bookmark_settings_menu '\bookmarksettingsmenucomment' \ + -comment recording_settings_menu '\recordingsettingsmenucomment' \ + > $RB_DIR/manual/rockbox_interface/menu_structure_settings.tex + +$RB_DIR/tools/extract_menus.pl -rb_dir $RB_DIR -start main_menu_ \ + -stop_after playback_menu_item \ + -stop_before system_menu \ + -stop_after display_menu \ + -stop_after theme_menu \ + -stop_before recording_settings_menu \ + -stop_after equalizer_menu \ + -stop_after hw_eq_menu \ + -stop_before crossfeed_menu \ + -stop_before playlist_settings \ + -stop_before voice_settings_menu \ + -comment sound_settings '\soundsettingscomment' \ + -comment file_menu '\filemenucomment' \ + -comment tagcache_menu '\tagcachemenucomment' \ + -comment playback_menu_item '\playbackmenuitemcomment' \ + -comment manage_settings '\managesettingscomment' \ + -comment bookmark_settings_menu '\bookmarksettingsmenucomment' \ + -comment recording_settings_menu '\recordingsettingsmenucomment' \ + > $RB_DIR/manual/rockbox_interface/menu_structure_settings_short.tex + Property changes on: tools/extract_menus.sh ___________________________________________________________________ Name: svn:executable + *