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