#!/usr/bin/perl # import module use Getopt::Long; use File::Basename; use File::Spec; $semicolon = ";"; # help out stupid emacs $title = "Ardour Shortcuts"; $in_group_def = 0; $group_name; $group_text; $group_key; $group_number = 0; %group_names; %group_text; %owner_bindings; %group_owners; %group_bindings; %modifier_map; %group_numbering; %merge_bindings; $platform = linux; $winkey = 'Win'; $make_cheatsheet = 1; $make_accelmap = 0; $merge_from = ""; $html = 0; GetOptions ("platform=s" => \$platform, "winkey=s" => \$winkey, "cheatsheet" => \$make_cheatsheet, "accelmap" => \$make_accelmap, "merge=s" => \$merge_from, "html" => \$html); if ($platform eq "darwin") { $gtk_modifier_map{'PRIMARY'} = 'Primary'; # GTK supports Primary to allow platform-independent binding to the "primary" modifier, which on OS X is Command $gtk_modifier_map{'SECONDARY'} = 'Control'; $gtk_modifier_map{'TERTIARY'} = 'Shift'; $gtk_modifier_map{'LEVEL4'} = 'Mod1'; # cs_modifier_map == "Cheat Sheet Modifier Map" # Used to control what gets shown in the # cheat sheet for a given (meta)-modifier $cs_modifier_map{'PRIMARY'} = 'Cmd'; $cs_modifier_map{'SECONDARY'} = 'Control'; $cs_modifier_map{'TERTIARY'} = 'Shift'; $cs_modifier_map{'LEVEL4'} = 'Opt'; # used to display what gets shown in the # cheat sheet for mouse bindings. Differs # from cs_modifier map in using shorter # abbreviations. $mouse_modifier_map{'PRIMARY'} = 'Cmd'; $mouse_modifier_map{'SECONDARY'} = 'Ctrl'; $mouse_modifier_map{'TERTIARY'} = 'Shift'; $mouse_modifier_map{'LEVEL4'} = 'Opt'; } else { $gtk_modifier_map{'PRIMARY'} = 'Control'; $gtk_modifier_map{'SECONDARY'} = 'Alt'; $gtk_modifier_map{'TERTIARY'} = 'Shift'; $gtk_modifier_map{'LEVEL4'} = $winkey; # something like "Mod4> '^', 'apostrophe' => '\'', 'bracketleft' => '[', 'bracketright' => ']', 'braceleft' => '{', 'braceright' => '}', 'backslash' => '\\', 'slash' => '/', 'rightanglebracket' => '>', 'leftanglebracket' => '<', 'ampersand' => '&', 'comma' => ',', 'period' => '.', 'semicolon' => ';', 'colon' => ':', 'equal' => '=', 'minus' => '-', 'plus' => '+', 'grave' => '`', 'rightarrow' => '→', 'leftarrow' => '←', 'uparrow' => '↑', 'downarrow' => '↓', 'Page_Down' => 'PageDown', 'Page_Up' => 'PageUp', 'space' => 'space', 'KP_Right' => 'KP-→', 'KP_Left' => 'KP-←', 'KP_Up' => 'KP-↑', 'KP_Down' => 'KP-↓', 'KP_0' => 'KP-0;', 'greater' => '>', 'less' => '<', ); } else { %keycodes = ( 'asciicircum' => '\\verb=^=', 'apostrophe' => '\'', 'bracketleft' => '[', 'bracketright' => ']', 'braceleft' => '\\{', 'braceright' => '\\}', 'backslash' => '$\\backslash$', 'slash' => '/', 'rightanglebracket' => '>', 'leftanglebracket' => '<', 'ampersand' => '\\&', 'comma' => ',', 'period' => '.', 'semicolon' => ';', 'colon' => ':', 'equal' => '=', 'minus' => '-', 'plus' => '+', 'grave' => '`', 'rightarrow' => '$\rightarrow$', 'leftarrow' => '$\\leftarrow$', 'uparrow' => '$\\uparrow$', 'downarrow' => '$\\downarrow$', 'Page_Down' => 'Page Down', 'Page_Up' => 'Page Up', 'space' => 'space', 'KP_' => 'KP$\_$', 'greater' => '>', 'less' => '<', ); } if ($merge_from) { open (BINDINGS, $merge_from) || die ("merge from bindings: file not readable"); while () { next if (/^$semicolon/); if (/^\(gtk_accel/) { chop; # newline chop; # closing parenthesis s/"//g; ($junk, $action, $binding) = split; $merge_bindings{$action} = $binding; } } close (BINDINGS); } if ($make_accelmap && !$merge_from) { print "\n"; } $bindings_name = basename ($ARGV[0]); $bindings_name =~ s/.bindings\.in$//; open SOURCE, "<", $ARGV[0] or die $!; while () { next if /^$semicolon/; if (/^\$/) { s/^\$//; $title = $_; next; } if (/^%/) { if ($in_group_def) { chop $group_text; $group_names{$group_key} = $group_name; $group_text{$group_key} = $group_text; $group_numbering{$group_key} = $group_number; # each binding entry is 2 element array. bindings # are all collected into a container array. create # the first dummy entry so that perl knows what we # are doing. $group_bindings{$group_key} = [ [] ]; } s/^%//; chop; ($group_key,$owner,$group_name) = split (/\s+/, $_, 3); if ($make_accelmap) { if (!exists ($owner_bindings{$owner})) { $owner_bindings{$owner} = [ [] ]; } $group_owners{$group_key} = $owner; } $group_number++; $group_text = ""; $in_group_def = 1; next; } if ($in_group_def) { if (/^@/) { chop $group_text; $group_names{$group_key} = $group_name; $group_text{$group_key} = $group_text; $in_group_def = 0; } else { next if (/^[ \t]+$/); $group_text .= $_; $group_text; next; } } if (/^@/) { s/^@//; chop; ($key,$action,$binding,$text) = split (/\|/, $_, 4); $gkey = $key; $gkey =~ s/^-//; $owner = $group_owners{$gkey}; # substitute bindings $gtk_binding = $binding; if ($merge_from) { $lookup = "/" . $action; if ($merge_bindings{$lookup}) { $binding = $merge_bindings{$lookup}; } else { if ($key =~ /^\+/) { # forced inclusion of bindings from template } else { # this action is not defined in the merge from set, so forget it next; } } } # print the accelmap output if ($key =~ /^\+/) { # remove + and don't print it in the accelmap $key =~ s/^\+//; } else { # include this in the accelmap if it is part of a group that has an "owner" if (!$merge_from && $make_accelmap && exists ($owner_bindings{$owner})) { $b = $binding; $b =~ s/<@//g; $b =~ s/@>//g; $b =~ s/PRIMARY/Primary-/; $b =~ s/SECONDARY/Secondary-/; $b =~ s/TERTIARY/Tertiary-/; $b =~ s/LEVEL4/Level4-/; $bref = $owner_bindings{$owner}; push (@$bref, [ $action, $b ]); } } if ($key =~ /^-/) { # do not include this binding in the cheat sheet next; } $bref = $group_bindings{$key}; push (@$bref, [$binding, $text]); next; } next; } if ($make_accelmap) { print "\n"; foreach $owner (keys %owner_bindings) { print " \n \n"; } print " \n \n"; } # merge in the "fixed" bindings that are not defined by the argument given to this program # namely, the step editor and the mixer windows foreach $hardcoded_bindings ("mixer.bindings", "step_editing.bindings") { $path = File::Spec->catfile (dirname ($ARGV[0]), $hardcoded_bindings); open HARDCODED, "<", $path or die $!; while () { print $_; } close HARDCODED; } print "\n"; } if ($make_accelmap || !$make_cheatsheet) { exit 0; } if ($html) { @groups_sorted_by_number = sort { $group_numbering{$a} <=> $group_numbering{$b} } keys %group_numbering; foreach $gk (@groups_sorted_by_number) { if ($gk =~ /^m/) { # mouse stuff - ignore next; } # $bref is a reference to the array of arrays for this group $bref = $group_bindings{$gk}; if (scalar @$bref > 1) { $name = $group_names{$gk}; $name =~ s/\\linebreak.*//; $name =~ s/\\&/&/; $name =~ s/\$\\_\$/-/g; $name =~ s/\\[a-z]+ //g; $name =~ s/[{}]//g; $name =~ s/\\par//g; print "

$name

\n"; $gtext = $group_text{$gk}; $gtext =~ s/\\linebreak.*//; $gtext =~ s/\\&/&/; $gtext =~ s/\$\\_\$/-/g; $gtext =~ s/\\[a-z]+ //g; $gtext =~ s/[{}]//g; $gtext =~ s/\\par//g; if (!($gtext eq "")) { print "$gtext\n\n"; } # ignore the first entry, which was empty shift (@$bref); # set up the list print "
\n"; # sort the array of arrays by the descriptive text for nicer appearance, # and print them for $bbref (sort { @$a[1] cmp @$b[1] } @$bref) { # $bbref is a reference to an array $binding = @$bbref[0]; $text = @$bbref[1]; if ($binding =~ /:/) { # mouse binding with "where" clause ($binding,$where) = split (/:/, $binding, 2); } foreach $k (keys %cs_modifier_map) { $binding =~ s/\@$k\@/$cs_modifier_map{$k}/; } # remove braces for HTML $binding =~ s/>/\+/; # substitute keycode names for something printable $re = qr/${ \(join'|', map quotemeta, keys %keycodes)}/; $binding =~ s/($re)/$keycodes{$1}/g; # tidy up description $descr = @$bbref[1]; $descr =~ s/\\linebreak.*//; $descr =~ s/\\&/&/; $descr =~ s/\$\\_\$/-/g; $descr =~ s/\\[a-z]+ //g; $descr =~ s/[{}]//g; $descr =~ s/\\par//g; print "
$descr
$binding
\n"; } print "
\n"; } } print "  \n"; exit 0; } # Now print the cheatsheet $boilerplate_header = < $group_numbering{$b} } keys %group_numbering; foreach $gk (@groups_sorted_by_number) { # $bref is a reference to the array of arrays for this group $bref = $group_bindings{$gk}; if (scalar @$bref > 1) { print "\\section{$group_names{$gk}}\n"; if (!($group_text{$gk} eq "")) { print "$group_text{$gk}\n\\par\n"; } # ignore the first entry, which was empty shift (@$bref); # find the longest descriptive text (this is not 100% accuracy due to typography) $maxtextlen = 0; $maxtext = ""; for $bbref (@$bref) { # $bbref is a reference to an array $text = @$bbref[1]; # # if there is a linebreak, just use everything up the linebreak # to determine the width # if ($text =~ /\\linebreak/) { $matchtext = s/\\linebreak.*//; } else { $matchtext = $text; } if (length ($matchtext) > $maxtextlen) { $maxtextlen = length ($matchtext); $maxtext = $matchtext; } } if ($gk =~ /^m/) { # mouse mode: don't extend max text at all - space it tight $maxtext .= "."; } else { $maxtext .= "...."; } # set up the table print "\\settowidth{\\MyLen}{\\texttt{$maxtext}}\n"; print "\\begin{tabular}{\@{}p{\\the\\MyLen}% \@{}p{\\linewidth-\\the\\MyLen}% \@{}}\n"; # sort the array of arrays by the descriptive text for nicer appearance, # and print them for $bbref (sort { @$a[1] cmp @$b[1] } @$bref) { # $bbref is a reference to an array $binding = @$bbref[0]; $text = @$bbref[1]; if ($binding =~ /:/) { # mouse binding with "where" clause ($binding,$where) = split (/:/, $binding, 2); } if ($gk =~ /^m/) { # mouse mode - use shorter abbrevs foreach $k (keys %mouse_modifier_map) { $binding =~ s/\@$k\@/$mouse_modifier_map{$k}/; } } else { foreach $k (keys %cs_modifier_map) { $binding =~ s/\@$k\@/$cs_modifier_map{$k}/; } } $binding =~ s/>/\+/; # substitute keycode names for something printable $re = qr/${ \(join'|', map quotemeta, keys %keycodes)}/; $binding =~ s/($re)/$keycodes{$1}/g; # split up mouse bindings to "click" and "where" parts if ($gk eq "mobject") { print "{\\tt @$bbref[1] } & {\\tt $binding} {\\it $where}\\\\\n"; } else { print "{\\tt @$bbref[1] } & {\\tt $binding} \\\\\n"; } } print "\\end{tabular}\n"; } } print $boilerplate_footer; exit 0;