improve output format to include required XML boilerplate
[ardour.git] / tools / fmt-bindings
1 #!/usr/bin/perl
2
3 # import module
4 use Getopt::Long; 
5
6 $semicolon = ";"; # help out stupid emacs
7 $title = "Ardour Shortcuts";
8 $in_group_def = 0;
9 $group_name;
10 $group_text;
11 $group_key;
12 $group_number = 0;
13 %group_names;
14 %group_text;
15 %group_files;
16 %group_handles;
17 %group_bindings;
18 %modifier_map;
19 %group_numbering;
20 %merge_bindings;
21
22 $platform = linux;
23 $winkey = 'Win';
24 $make_cheatsheet = 1;
25 $make_accelmap = 0;
26 $ardour_bindings = 0;
27 $merge_from = "";
28 $html = 0;
29
30 GetOptions ("platform=s" => \$platform,
31             "winkey=s" => \$winkey,
32             "cheatsheet" => \$make_cheatsheet,
33             "accelmap" => \$make_accelmap,
34             "ardourbindings" => \$ardour_bindings,
35             "merge=s" => \$merge_from,
36             "html" => \$html);
37
38 if ($platform eq "darwin") {
39
40     $gtk_modifier_map{'PRIMARY'} = 'Primary'; # GTK supports Primary to allow platform-independent binding to the "primary" modifier, which on OS X is Command
41     $gtk_modifier_map{'SECONDARY'} = 'Control';
42     $gtk_modifier_map{'TERTIARY'} = 'Shift';
43     $gtk_modifier_map{'LEVEL4'} = 'Mod1'; 
44
45     # cs_modifier_map == "Cheat Sheet Modifier Map"
46     # Used to control what gets shown in the
47     # cheat sheet for a given (meta)-modifier
48
49     $cs_modifier_map{'PRIMARY'} = 'Cmd';
50     $cs_modifier_map{'SECONDARY'} = 'Control';
51     $cs_modifier_map{'TERTIARY'} = 'Shift';
52     $cs_modifier_map{'LEVEL4'} = 'Opt';
53
54     # used to display what gets shown in the
55     # cheat sheet for mouse bindings. Differs
56     # from cs_modifier map in using shorter
57     # abbreviations.
58     
59     $mouse_modifier_map{'PRIMARY'} = 'Cmd';
60     $mouse_modifier_map{'SECONDARY'} = 'Ctrl';
61     $mouse_modifier_map{'TERTIARY'} = 'Shift';
62     $mouse_modifier_map{'LEVEL4'} = 'Opt';
63
64 } else {
65
66     $gtk_modifier_map{'PRIMARY'} = 'Control';
67     $gtk_modifier_map{'SECONDARY'} = 'Alt';
68     $gtk_modifier_map{'TERTIARY'} = 'Shift';
69     $gtk_modifier_map{'LEVEL4'} = $winkey;  # something like "Mod4><Super" 
70
71     # cs_modifier_map == "Cheat Sheet Modifier Map"
72     # Used to control what gets shown in the
73     # cheat sheet for a given (meta)-modifier
74
75     $cs_modifier_map{'PRIMARY'} = 'Control';
76     $cs_modifier_map{'SECONDARY'} = 'Alt';
77     $cs_modifier_map{'TERTIARY'} = 'Shift';
78     $cs_modifier_map{'LEVEL4'} = 'Win';
79
80     # used to display what gets shown in the
81     # cheat sheet for mouse bindings. Differs
82     # from cs_modifier map in using shorter
83     # abbreviations.
84
85     $mouse_modifier_map{'PRIMARY'} = 'Ctl';
86     $mouse_modifier_map{'SECONDARY'} = 'Alt';
87     $mouse_modifier_map{'TERTIARY'} = 'Shift';
88     $mouse_modifier_map{'LEVEL4'} = 'Win';
89 }
90
91 %keycodes = ();
92
93 if ($html) {
94     %keycodes = (
95         'asciicircum' => '^',
96         'apostrophe' => '\'',
97         'bracketleft' => '[',
98         'bracketright' => ']',
99         'braceleft' => '{',
100         'braceright' => '}',
101         'backslash' => '\\',
102         'slash' => '/',
103         'rightanglebracket' => '&gt;',
104         'leftanglebracket' => '&lt;',
105         'ampersand' => '&',
106         'comma' => ',',
107         'period' => '.',
108         'semicolon' => ';',
109         'colon' => ':',
110         'equal' => '=',
111         'minus' => '-',
112         'plus' => '+',
113         'grave' => '`',
114         'rightarrow' => '&rarr;',
115         'leftarrow' => '&larr;',
116         'uparrow' => '&uarr;',
117         'downarrow' => '&darr;',
118         'Page_Down' => 'PageDown',
119         'Page_Up' => 'PageUp',
120         'space' => 'space',
121         'KP_Right' => 'KP-&rarr;',
122         'KP_Left' => 'KP-&larr;',
123         'KP_Up' => 'KP-&uarr;',
124         'KP_Down' => 'KP-&darr;',
125         'KP_0' => 'KP-0;',
126         'greater' => '&gt;',
127         'less' => '&lt;',
128         );
129 } else {
130
131     %keycodes = (
132         'asciicircum' => '\\verb=^=',
133         'apostrophe' => '\'',
134         'bracketleft' => '[',
135         'bracketright' => ']',
136         'braceleft' => '\\{',
137         'braceright' => '\\}',
138         'backslash' => '$\\backslash$',
139         'slash' => '/',
140         'rightanglebracket' => '>',
141         'leftanglebracket' => '<',
142         'ampersand' => '\\&',
143         'comma' => ',',
144         'period' => '.',
145         'semicolon' => ';',
146         'colon' => ':',
147         'equal' => '=',
148         'minus' => '-',
149         'plus' => '+',
150         'grave' => '`',
151         'rightarrow' => '$\rightarrow$',
152         'leftarrow' => '$\\leftarrow$',
153         'uparrow' => '$\\uparrow$',
154         'downarrow' => '$\\downarrow$',
155         'Page_Down' => 'Page Down',
156         'Page_Up' => 'Page Up',
157         'space' => 'space',
158         'KP_' => 'KP$\_$',
159         'greater' => '>',
160         'less' => '<',
161     );
162 }
163
164 if ($merge_from) {
165     open (BINDINGS, $merge_from) || die ("merge from bindings: file not readable");
166     while (<BINDINGS>) {
167         next if (/^$semicolon/);
168         if (/^\(gtk_accel/) {
169             chop; # newline
170             chop; # closing parenthesis
171             s/"//g;
172             ($junk, $action, $binding) = split;
173             $merge_bindings{$action} = $binding;
174         }
175     }
176     close (BINDINGS);
177 }
178
179 if ($make_accelmap && !$merge_from && !$ardour_bindings) {
180     print ";; this accelmap was produced by tools/fmt-bindings\n";
181 }
182
183 while (<>) {
184     next if /^$semicolon/;
185
186     if (/^\$/) {
187         s/^\$//;
188         $title = $_;
189         next;
190     }
191
192     if (/^%/) {
193         
194         if ($in_group_def) {
195             chop $group_text;
196             $group_names{$group_key} = $group_name;
197             $group_text{$group_key} = $group_text;
198             $group_numbering{$group_key} = $group_number;
199             # each binding entry is 2 element array. bindings
200             # are all collected into a container array. create
201             # the first dummy entry so that perl knows what we
202             # are doing.
203             $group_bindings{$group_key} = [ [] ];
204         }
205
206         s/^%//;
207         chop;
208         ($group_key,$group_file,$group_name) = split (/\s+/, $_, 3);
209         if ($make_accelmap && $ardour_bindings) {
210             if (!exists ($group_handles{$group_file})) {
211                 
212                 open $group_handles{$group_file}, ">", "gtk2_ardour/" . $group_file . ".bindings" or die "Cannot open bindings file " . $group_file . ".bindings: $!";
213                 print { $group_handles{$group_file} } "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <Bindings name=\"ardour-", $group_file , "\">\n <Press>\n";
214             }
215             $group_files{$group_key} = $group_handles{$group_file}
216         }
217         $group_number++;
218         $group_text = "";
219         $in_group_def = 1;
220         next;
221     }
222
223     if ($in_group_def) {
224         if (/^@/) {
225             chop $group_text;
226             $group_names{$group_key} = $group_name;
227             $group_text{$group_key} = $group_text;
228             $in_group_def = 0;
229         } else {
230             next if (/^[ \t]+$/);
231             $group_text .= $_;
232             $group_text;
233             next;
234         }
235     }
236
237     if (/^@/) {
238         s/^@//;
239         chop;
240         ($key,$action,$binding,$text) = split (/\|/, $_, 4);
241
242         $gkey = $key;
243         $gkey =~ s/^-//;
244             
245         # substitute bindings
246
247         $gtk_binding = $binding;
248
249         if ($merge_from) {
250             $lookup = "<Actions>/" . $action;
251             if ($merge_bindings{$lookup}) {
252                 $binding = $merge_bindings{$lookup};
253             } else {
254                 if ($key =~ /^\+/) {
255                     # forced inclusion of bindings from template
256                 } else {
257                     # this action is not defined in the merge from set, so forget it 
258                     next;
259                 }
260             }
261         } 
262
263         # print the accelmap output
264
265         if ($key =~ /^\+/) {
266             # remove + and don't print it in the accelmap
267             $key =~ s/^\+//;
268         } else {
269             # include this in the accelmap
270             if (!$merge_from && $make_accelmap) {
271                 if (!$ardour_bindings) {
272                     foreach $k (keys %gtk_modifier_map) {
273                         $gtk_binding =~ s/\@$k\@/$gtk_modifier_map{$k}/;
274                     }
275                     print "(gtk_accel_path \"<Actions>/$action\" \"$gtk_binding\")\n";
276                 } else {
277                     $b = $binding;
278                     $b =~ s/<@//g;
279                     $b =~ s/@>//g;
280                     $b =~ s/PRIMARY/Primary-/;
281                     $b =~ s/SECONDARY/Secondary-/;
282                     $b =~ s/TERTIARY/Tertiary-/;
283                     $b =~ s/LEVEL4/Level4-/;
284                     
285                     if (exists ($group_files{$gkey})) {
286                         print { $group_files{$gkey}  } "  <Binding key=\"" . $b . "\" action=\"" . $action . "\"/>\n";
287                     }
288                 }
289             }
290         }
291
292         if ($key =~ /^-/) {
293             # do not include this binding in the cheat sheet
294             next;
295         }
296
297         $bref = $group_bindings{$key};
298         push (@$bref, [$binding, $text]);
299
300         next;
301     }
302
303     next;
304 }
305
306 foreach my $key (keys %group_handles) {
307     print { $group_handles{$key} } " </Press>\n <Release>\n </Release>\n</Bindings>\n";
308     close $group_handles{$key} or die "Group file $group_files{$key} not closed!"
309 }
310
311 if ($make_accelmap || !$make_cheatsheet) {
312     exit 0;
313 }
314
315 if ($html) {
316
317     @groups_sorted_by_number = sort { $group_numbering{$a} <=> $group_numbering{$b} } keys %group_numbering; 
318     
319     foreach $gk (@groups_sorted_by_number) {
320
321         if ($gk =~ /^m/) {
322             # mouse stuff - ignore
323             next;
324         }
325
326         # $bref is a reference to the array of arrays for this group
327         $bref = $group_bindings{$gk};
328         
329         if (scalar @$bref > 1) {
330             
331             $name = $group_names{$gk};
332             $name =~ s/\\linebreak.*//;
333             $name =~ s/\\&/&/;
334             $name =~ s/\$\\_\$/-/g;
335             $name =~ s/\\[a-z]+ //g;
336             $name =~ s/[{}]//g;
337             $name =~ s/\\par//g;
338
339             print "<h3>$name</h3>\n";
340
341             $gtext = $group_text{$gk};
342             $gtext =~ s/\\linebreak.*//;
343             $gtext =~ s/\\&/&/;
344             $gtext =~ s/\$\\_\$/-/g;
345             $gtext =~ s/\\[a-z]+ //g;
346             $gtext =~ s/[{}]//g;
347             $gtext =~ s/\\par//g;
348             
349             if (!($gtext eq  "")) {
350                 print "$gtext\n\n";
351             }
352             
353             # ignore the first entry, which was empty
354             
355             shift (@$bref);
356             
357             # set up the list
358             
359             print "<dl class=\"bindings\">\n";
360             
361             # sort the array of arrays by the descriptive text for nicer appearance,
362             # and print them
363             
364             for $bbref (sort { @$a[1] cmp @$b[1] } @$bref) {
365                 # $bbref is a reference to an array
366                 
367                 $binding = @$bbref[0];
368                 $text = @$bbref[1];
369
370                 if ($binding =~ /:/) { # mouse binding with "where" clause
371                     ($binding,$where) = split (/:/, $binding, 2);
372                 }
373                 
374                 foreach $k (keys %cs_modifier_map) {
375                     $binding =~ s/\@$k\@/$cs_modifier_map{$k}/;
376                 }
377
378                 # remove braces for HTML
379
380                 $binding =~ s/></\+/g;
381                 $binding =~ s/^<//;
382                 $binding =~ s/>/\+/;
383                 
384                 # substitute keycode names for something printable
385                 
386                 $re = qr/${ \(join'|', map quotemeta, keys %keycodes)}/;
387                 $binding =~ s/($re)/$keycodes{$1}/g;
388
389                 # tidy up description
390
391                 $descr = @$bbref[1];
392                 $descr =~ s/\\linebreak.*//;
393                 $descr =~ s/\\&/&/;
394                 $descr =~ s/\$\\_\$/-/g;
395                 $descr =~ s/\\[a-z]+ //g;
396                 $descr =~ s/[{}]//g;
397                 $descr =~ s/\\par//g;
398
399                 print "<dt>$descr</dt><dd>$binding</dd>\n";
400             }
401             
402             print "</dl>\n";
403         
404         }
405     }
406     print "&nbsp; <!-- remove this if more text is added below -->\n";
407     exit 0;
408 }
409
410
411 # Now print the cheatsheet
412
413 $boilerplate_header = <<END_HEADER;
414 \\documentclass[10pt,landscape]{article}
415 %\\documentclass[10pt,landscape,a4paper]{article}
416 %\\documentclass[10pt,landscape,letterpaper]{article}
417 \\usepackage{multicol}
418 \\usepackage{calc}
419 \\usepackage{ifthen}
420 \\usepackage{palatino}
421 \\usepackage{geometry}
422
423 \\setlength{\\parskip}{0pt}
424 \\setlength{\\parsep}{0pt}
425 \\setlength{\\headsep}{0pt}
426 \\setlength{\\topskip}{0pt}
427 \\setlength{\\topmargin}{0pt}
428 \\setlength{\\topsep}{0pt}
429 \\setlength{\\partopsep}{0pt}
430
431 % This sets page margins to .5 inch if using letter paper, and to 1cm
432 % if using A4 paper. (This probably isnott strictly necessary.)
433 % If using another size paper, use default 1cm margins.
434 \\ifthenelse{\\lengthtest { \\paperwidth = 11in}}
435         { \\geometry{top=.5in,left=.5in,right=.5in,bottom=.5in} }
436         {\\ifthenelse{ \\lengthtest{ \\paperwidth = 297mm}}
437                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
438                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
439         }
440
441 % Turn off header and footer
442 \\pagestyle{empty}
443  
444 % Redefine section commands to use less space
445 \\makeatletter
446 \\renewcommand{\\section}{\\\@startsection{section}{1}{0mm}%
447                                 {-1ex plus -.5ex minus -.2ex}%
448                                 {0.5ex plus .2ex}%
449                                 {\\normalfont\\large\\bfseries}}
450 \\renewcommand{\\subsection}{\\\@startsection{subsection}{2}{0mm}%
451                                 {-1explus -.5ex minus -.2ex}%
452                                 {0.5ex plus .2ex}%
453                                 {\\normalfont\\normalsize\\bfseries}}
454 \\renewcommand{\\subsubsection}{\\\@startsection{subsubsection}{3}{0mm}%
455                                 {-1ex plus -.5ex minus -.2ex}%
456                                 {1ex plus .2ex}%
457                                 {\\normalfont\\small\\bfseries}}
458 \\makeatother
459
460 % Do not print section numbers% Do not print section numbers
461 \\setcounter{secnumdepth}{0}
462
463 \\setlength{\\parindent}{0pt}
464 \\setlength{\\parskip}{0pt plus 0.5ex}
465
466 %-------------------------------------------
467
468 \\begin{document}
469 \\newlength{\\MyLen}
470 \\raggedright
471 \\footnotesize
472 \\begin{multicols}{3}
473 END_HEADER
474
475 $boilerplate_footer = <<END_FOOTER;
476 \\rule{0.3\\linewidth}{0.25pt}
477 \\scriptsize
478
479 Copyright \\copyright\\ 2013 ardour.org
480
481 % Should change this to be date of file, not current date.
482
483 http://manual.ardour.org
484
485 \\end{multicols}
486 \\end{document}
487 END_FOOTER
488
489 if ($make_cheatsheet) {
490     print $boilerplate_header;
491     print "\\begin{center}\\Large\\bf $title \\end{center}\n";
492 }
493
494 @groups_sorted_by_number = sort { $group_numbering{$a} <=> $group_numbering{$b} } keys %group_numbering; 
495
496 foreach $gk (@groups_sorted_by_number) {
497     # $bref is a reference to the array of arrays for this group
498     $bref = $group_bindings{$gk};
499
500     if (scalar @$bref > 1) {
501         print "\\section{$group_names{$gk}}\n";
502
503         if (!($group_text{$gk} eq  "")) {
504             print "$group_text{$gk}\n\\par\n";
505         }
506         
507         # ignore the first entry, which was empty
508
509         shift (@$bref);
510
511         # find the longest descriptive text (this is not 100% accuracy due to typography)
512
513         $maxtextlen = 0;
514         $maxtext = "";
515
516         for $bbref (@$bref) {
517             # $bbref is a reference to an array
518             $text = @$bbref[1];
519             
520             #
521             # if there is a linebreak, just use everything up the linebreak
522             # to determine the width
523             #
524
525             if ($text =~ /\\linebreak/) {
526                 $matchtext = s/\\linebreak.*//;
527             } else {
528                 $matchtext = $text;
529             }
530             if (length ($matchtext) > $maxtextlen) {
531                 $maxtextlen = length ($matchtext);
532                 $maxtext = $matchtext;
533             }
534         }
535
536         if ($gk =~ /^m/) {
537             # mouse mode: don't extend max text at all - space it tight
538             $maxtext .= ".";
539         } else {
540             $maxtext .= "....";
541         }
542
543         # set up the table
544
545         print "\\settowidth{\\MyLen}{\\texttt{$maxtext}}\n";
546         print "\\begin{tabular}{\@{}p{\\the\\MyLen}% 
547                                 \@{}p{\\linewidth-\\the\\MyLen}%
548                                 \@{}}\n";
549
550         # sort the array of arrays by the descriptive text for nicer appearance,
551         # and print them
552
553         for $bbref (sort { @$a[1] cmp @$b[1] } @$bref) {
554             # $bbref is a reference to an array
555
556             $binding = @$bbref[0];
557             $text = @$bbref[1];
558
559             if ($binding =~ /:/) { # mouse binding with "where" clause
560                 ($binding,$where) = split (/:/, $binding, 2);
561             }
562
563             if ($gk =~ /^m/) {
564                 # mouse mode - use shorter abbrevs
565                 foreach $k (keys %mouse_modifier_map) {
566                     $binding =~ s/\@$k\@/$mouse_modifier_map{$k}/;
567                 }
568             } else {
569                 foreach $k (keys %cs_modifier_map) {
570                     $binding =~ s/\@$k\@/$cs_modifier_map{$k}/;
571                 }
572             }
573
574             $binding =~ s/></\+/g;
575             $binding =~ s/^<//;
576             $binding =~ s/>/\+/;
577
578             # substitute keycode names for something printable
579
580             $re = qr/${ \(join'|', map quotemeta, keys %keycodes)}/;
581             $binding =~ s/($re)/$keycodes{$1}/g;
582
583             # split up mouse bindings to "click" and "where" parts
584
585             if ($gk eq "mobject") {
586                 print "{\\tt @$bbref[1] } & {\\tt $binding} {\\it $where}\\\\\n";
587             } else {
588                 print "{\\tt @$bbref[1] } & {\\tt $binding} \\\\\n";
589             }
590         }
591
592         print "\\end{tabular}\n";
593
594     }
595 }
596
597 print $boilerplate_footer;
598
599 exit 0;