more or less fully-functional binding & cheat sheet generator, with merge-ability...
[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 = 'Mod4><Super';
22 $make_cheatsheet = 1;
23 $make_accelmap = 0;
24 $merge_from = "";
25
26 GetOptions ("platform=s" => \$platform,
27             "winkey=s" => \$winkey,
28             "cheatsheet" => \$make_cheatsheet,
29             "accelmap" => \$make_accelmap,
30             "merge=s" => \$merge_from);
31
32 if ($platform eq "osx") {
33
34     $gtk_modifier_map{'PRIMARY'} = 'meta';
35     $gtk_modifier_map{'SECONDARY'} = 'Mod1';
36     $gtk_modifier_map{'TERTIARY'} = 'Shift';
37     $gtk_modifier_map{'LEVEL4'} = 'Control';
38     $gtk_modifier_map{'WINDOW'} = 'Mod1';
39
40     $cs_modifier_map{'PRIMARY'} = 'Command';
41     $cs_modifier_map{'SECONDARY'} = 'Opt';
42     $cs_modifier_map{'TERTIARY'} = 'Shift';
43     $cs_modifier_map{'LEVEL4'} = 'Control';
44     $cs_modifier_map{'WINDOW'} = 'Opt';
45
46     $mouse_modifier_map{'PRIMARY'} = 'Cmd';
47     $mouse_modifier_map{'SECONDARY'} = 'Opt';
48     $mouse_modifier_map{'TERTIARY'} = 'Shift';
49     $mouse_modifier_map{'LEVEL4'} = 'Control';
50     $mouse_modifier_map{'WINDOW'} = 'Opt';
51
52 } else {
53
54     $gtk_modifier_map{'PRIMARY'} = 'Control';
55     $gtk_modifier_map{'SECONDARY'} = 'Alt';
56     $gtk_modifier_map{'TERTIARY'} = 'Shift';
57     $gtk_modifier_map{'LEVEL4'} = $winkey;
58     $gtk_modifier_map{'WINDOW'} = 'Alt';
59     $gtk_modifier_map{$winkey} => 'Win';
60
61     $cs_modifier_map{'PRIMARY'} = 'Control';
62     $cs_modifier_map{'SECONDARY'} = 'Alt';
63     $cs_modifier_map{'TERTIARY'} = 'Shift';
64     $cs_modifier_map{'LEVEL4'} = 'Win';
65     $cs_modifier_map{'WINDOW'} = 'Alt';
66     $cs_modifier_map{$winkey} => 'Win';
67
68     $mouse_modifier_map{'PRIMARY'} = 'Ctl';
69     $mouse_modifier_map{'SECONDARY'} = 'Alt';
70     $mouse_modifier_map{'TERTIARY'} = 'Shift';
71     $mouse_modifier_map{'LEVEL4'} = 'Win';
72     $mouse_modifier_map{'WINDOW'} = 'Alt';
73     $mouse_modifier_map{$winkey} => 'Win';
74 }
75
76 %keycodes = (
77     'asciicircum' => '\\verb=^=',
78     'apostrophe' => '\'',
79     'bracketleft' => '[',
80     'bracketright' => ']',
81     'braceleft' => '\\{',
82     'braceright' => '\\}',
83     'backslash' => '$\\backslash$',
84     'slash' => '/',
85     'rightanglebracket' => '>',
86     'leftanglebracket' => '<',
87     'ampersand' => '\\&',
88     'comma' => ',',
89     'period' => '.',
90     'semicolon' => ';',
91     'colon' => ':',
92     'equal' => '=',
93     'minus' => '-',
94     'plus' => '+',
95     'grave' => '`',
96     'rightarrow' => '$\rightarrow$',
97     'leftarrow' => '$\\leftarrow$',
98     'uparrow' => '$\\uparrow$',
99     'downarrow' => '$\\downarrow$',
100     'Page_Down' => 'Page Down',
101     'Page_Up' => 'Page Up',
102     'space' => 'space',
103     'KP_' => 'KP$\_$',
104     );
105
106 if ($merge_from) {
107     open (BINDINGS, $merge_from) || die ("merge from bindings: file not readable");
108     while (<BINDINGS>) {
109         next if (/^$semicolon/);
110         if (/^\(gtk_accel/) {
111             chop; # newline
112             chop; # closing parenthesis
113             s/"//g;
114             ($junk, $action, $binding) = split;
115             $merge_bindings{$action} = $binding;
116         }
117     }
118     close (BINDINGS);
119 }
120
121 if ($make_accelmap && !$merge_from) {
122     print ";; this accelmap was produced by tools/fmt-bindings\n";
123 }
124
125 while (<>) {
126     next if /^$semicolon/;
127
128     if (/^\$/) {
129         s/^\$//;
130         $title = $_;
131         next;
132     }
133
134     if (/^%/) {
135         
136         if ($in_group_def) {
137             chop $group_text;
138             $group_names{$group_key} = $group_name;
139             $group_text{$group_key} = $group_text;
140             $group_numbering{$group_key} = $group_number;
141             # each binding entry is 2 element array. bindings
142             # are all collected into a container array. create
143             # the first dummy entry so that perl knows what we
144             # are doing.
145             $group_bindings{$group_key} = [ [] ];
146         }
147
148         s/^%//;
149         chop;
150         ($group_key,$group_name) = split (/\s+/, $_, 2);
151         $group_number++;
152         $group_text = "";
153         $in_group_def = 1;
154         next;
155     }
156
157     if ($in_group_def) {
158         if (/^@/) {
159             chop $group_text;
160             $group_names{$group_key} = $group_name;
161             $group_text{$group_key} = $group_text;
162             $in_group_def = 0;
163         } else {
164             next if (/^[ \t]+$/);
165             $group_text .= $_;
166             $group_text;
167             next;
168         }
169     }
170
171     if (/^@/) {
172         s/^@//;
173         chop;
174         ($key,$action,$binding,$text) = split (/\|/, $_, 4);
175
176         # substitute bindings
177
178         $gtk_binding = $binding;
179
180         if ($merge_from) {
181             $lookup = "<Actions>/" . $action;
182             if ($merge_bindings{$lookup}) {
183                 $binding = $merge_bindings{$lookup};
184             } else {
185                 # this action is not defined in the merge from set, so forget it 
186                 next;
187             }
188         } 
189
190         # print the accelmap output
191
192         if ($key =~ /^\+/) {
193             # remove + and don't print it in the accelmap
194             $key =~ s/^\+//;
195         } else {
196             # include this in the accelmap
197             if (!$merge_from && $make_accelmap) {
198                 foreach $k (keys %gtk_modifier_map) {
199                     $gtk_binding =~ s/\@$k\@/$gtk_modifier_map{$k}/;
200                 }
201                 print "(gtk_accel_map \"<Actions>/$action\" \"$gtk_binding\")\n";
202             }
203         }
204
205         if ($key =~ /^-/) {
206             # do not include this binding in the cheat sheet
207             next;
208         }
209
210         $bref = $group_bindings{$key};
211         push (@$bref, [$binding, $text]);
212
213         next;
214     }
215
216     next;
217 }
218
219 if ($make_accelmap || !$make_cheatsheet) {
220     exit 0;
221 }
222
223 # Now print the cheatsheet
224
225 $boilerplate_header = <<END_HEADER;
226 \\documentclass[10pt,landscape]{article}
227 \\usepackage{multicol}
228 \\usepackage{calc}
229 \\usepackage{ifthen}
230 \\usepackage{palatino}
231 \\usepackage{geometry}
232
233 \\setlength{\\parskip}{0pt}
234 \\setlength{\\parsep}{0pt}
235 \\setlength{\\headsep}{0pt}
236 \\setlength{\\topskip}{0pt}
237 \\setlength{\\topmargin}{0pt}
238 \\setlength{\\topsep}{0pt}
239 \\setlength{\\partopsep}{0pt}
240
241 % This sets page margins to .5 inch if using letter paper, and to 1cm
242 % if using A4 paper. (This probably isnott strictly necessary.)
243 % If using another size paper, use default 1cm margins.
244 \\ifthenelse{\\lengthtest { \\paperwidth = 11in}}
245         { \\geometry{top=.5in,left=1in,right=0in,bottom=.5in} }
246         {\\ifthenelse{ \\lengthtest{ \\paperwidth = 297mm}}
247                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
248                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
249         }
250
251 % Turn off header and footer
252 \\pagestyle{empty}
253  
254 % Redefine section commands to use less space
255 \\makeatletter
256 \\renewcommand{\\section}{\\\@startsection{section}{1}{0mm}%
257                                 {-1ex plus -.5ex minus -.2ex}%
258                                 {0.5ex plus .2ex}%
259                                 {\\normalfont\\large\\bfseries}}
260 \\renewcommand{\\subsection}{\\\@startsection{subsection}{2}{0mm}%
261                                 {-1explus -.5ex minus -.2ex}%
262                                 {0.5ex plus .2ex}%
263                                 {\\normalfont\\normalsize\\bfseries}}
264 \\renewcommand{\\subsubsection}{\\\@startsection{subsubsection}{3}{0mm}%
265                                 {-1ex plus -.5ex minus -.2ex}%
266                                 {1ex plus .2ex}%
267                                 {\\normalfont\\small\\bfseries}}
268 \\makeatother
269
270 % Do not print section numbers% Do not print section numbers
271 \\setcounter{secnumdepth}{0}
272
273 \\setlength{\\parindent}{0pt}
274 \\setlength{\\parskip}{0pt plus 0.5ex}
275
276 %-------------------------------------------
277
278 \\begin{document}
279 \\newlength{\\MyLen}
280 \\raggedright
281 \\footnotesize
282 \\begin{multicols}{3}
283 END_HEADER
284
285 $boilerplate_footer = <<END_FOOTER;
286 \\rule{0.3\\linewidth}{0.25pt}
287 \\scriptsize
288
289 Copyright \\copyright\\ 2009 ardour.org
290
291 % Should change this to be date of file, not current date.
292 %\\verb!$Revision: 1.13 $, $Date: 2008/05/29 06:11:56 $.!
293
294 http://ardour.org/manual
295
296 \\end{multicols}
297 \\end{document}
298 END_FOOTER
299
300 if ($make_cheatsheet) {
301     print $boilerplate_header;
302     print "\\begin{center}\\Large\\bf $title \\end{center}\n";
303 }
304
305 @groups_sorted_by_number = sort { $group_numbering{$a} <=> $group_numbering{$b} } keys %group_numbering; 
306
307 foreach $gk (@groups_sorted_by_number) {
308     # $bref is a reference to the array of arrays for this group
309     $bref = $group_bindings{$gk};
310
311     if (scalar @$bref > 1) {
312         print "\\section{$group_names{$gk}}\n";
313
314         if (!($group_text{$gk} eq  "")) {
315             print "$group_text{$gk}\n\\par\n";
316         }
317         
318         # ignore the first entry, which was empty
319
320         shift (@$bref);
321
322         # find the longest descriptive text (this is not 100% accuracy due to typography)
323
324         $maxtextlen = 0;
325         $maxtext = "";
326
327         for $bbref (@$bref) {
328             # $bbref is a reference to an array
329             $text = @$bbref[1];
330             
331             #
332             # if there is a linebreak, just use everything up the linebreak
333             # to determine the width
334             #
335
336             if ($text =~ /\\linebreak/) {
337                 $matchtext = s/\\linebreak.*//;
338             } else {
339                 $matchtext = $text;
340             }
341             if (length ($matchtext) > $maxtextlen) {
342                 $maxtextlen = length ($matchtext);
343                 $maxtext = $matchtext;
344             }
345         }
346
347         if ($gk =~ /^m/) {
348             # mouse mode: don't extend max text at all - space it tight
349             $maxtext .= ".";
350         } else {
351             $maxtext .= "....";
352         }
353
354         # set up the table
355
356         print "\\settowidth{\\MyLen}{\\texttt{$maxtext}}\n";
357         print "\\begin{tabular}{\@{}p{\\the\\MyLen}% 
358                                 \@{}p{\\linewidth-\\the\\MyLen}%
359                                 \@{}}\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             if ($gk =~ /^m/) {
375                 # mouse mode - use shorter abbrevs
376                 foreach $k (keys %mouse_modifier_map) {
377                     $binding =~ s/\@$k\@/$mouse_modifier_map{$k}/;
378                 }
379             } else {
380                 foreach $k (keys %cs_modifier_map) {
381                     $binding =~ s/\@$k\@/$cs_modifier_map{$k}/;
382                 }
383             }
384
385             $binding =~ s/></\+/g;
386             $binding =~ s/^<//;
387             $binding =~ s/>/\+/;
388
389             # substitute keycode names for something printable
390
391             $re = qr/${ \(join'|', map quotemeta, keys %keycodes)}/;
392             $binding =~ s/($re)/$keycodes{$1}/g;
393
394             # split up mouse bindings to "click" and "where" parts
395
396             if ($gk eq "mobject") {
397                 print "{\\tt @$bbref[1] } & {\\tt $binding} {\\it $where}\\\\\n";
398             } else {
399                 print "{\\tt @$bbref[1] } & {\\tt $binding} \\\\\n";
400             }
401         }
402
403         print "\\end{tabular}\n";
404
405     }
406 }
407
408 print $boilerplate_footer;
409
410 exit 0;