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