fix fmt-bindings' handling of > and < bindings
[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'} = 'Primary';
35     $gtk_modifier_map{'SECONDARY'} = 'Control';
36     $gtk_modifier_map{'TERTIARY'} = 'Shift';
37     $gtk_modifier_map{'LEVEL4'} = 'Mod1';
38     $gtk_modifier_map{'WINDOW'} = 'Control';
39
40     $cs_modifier_map{'PRIMARY'} = 'Cmd';
41     $cs_modifier_map{'SECONDARY'} = 'Control';
42     $cs_modifier_map{'TERTIARY'} = 'Shift';
43     $cs_modifier_map{'LEVEL4'} = 'Mod1';
44     $cs_modifier_map{'WINDOW'} = 'Control';
45
46     $mouse_modifier_map{'PRIMARY'} = 'Cmd';
47     $mouse_modifier_map{'SECONDARY'} = 'Ctrl';
48     $mouse_modifier_map{'TERTIARY'} = 'Shift';
49     $mouse_modifier_map{'LEVEL4'} = 'Opt';
50     $mouse_modifier_map{'WINDOW'} = 'Ctrl';
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     'greater' => '>',
105     'less' => '<',
106     );
107
108 if ($merge_from) {
109     open (BINDINGS, $merge_from) || die ("merge from bindings: file not readable");
110     while (<BINDINGS>) {
111         next if (/^$semicolon/);
112         if (/^\(gtk_accel/) {
113             chop; # newline
114             chop; # closing parenthesis
115             s/"//g;
116             ($junk, $action, $binding) = split;
117             $merge_bindings{$action} = $binding;
118         }
119     }
120     close (BINDINGS);
121 }
122
123 if ($make_accelmap && !$merge_from) {
124     print ";; this accelmap was produced by tools/fmt-bindings\n";
125 }
126
127 while (<>) {
128     next if /^$semicolon/;
129
130     if (/^\$/) {
131         s/^\$//;
132         $title = $_;
133         next;
134     }
135
136     if (/^%/) {
137         
138         if ($in_group_def) {
139             chop $group_text;
140             $group_names{$group_key} = $group_name;
141             $group_text{$group_key} = $group_text;
142             $group_numbering{$group_key} = $group_number;
143             # each binding entry is 2 element array. bindings
144             # are all collected into a container array. create
145             # the first dummy entry so that perl knows what we
146             # are doing.
147             $group_bindings{$group_key} = [ [] ];
148         }
149
150         s/^%//;
151         chop;
152         ($group_key,$group_name) = split (/\s+/, $_, 2);
153         $group_number++;
154         $group_text = "";
155         $in_group_def = 1;
156         next;
157     }
158
159     if ($in_group_def) {
160         if (/^@/) {
161             chop $group_text;
162             $group_names{$group_key} = $group_name;
163             $group_text{$group_key} = $group_text;
164             $in_group_def = 0;
165         } else {
166             next if (/^[ \t]+$/);
167             $group_text .= $_;
168             $group_text;
169             next;
170         }
171     }
172
173     if (/^@/) {
174         s/^@//;
175         chop;
176         ($key,$action,$binding,$text) = split (/\|/, $_, 4);
177
178         # substitute bindings
179
180         $gtk_binding = $binding;
181
182         if ($merge_from) {
183             $lookup = "<Actions>/" . $action;
184             if ($merge_bindings{$lookup}) {
185                 $binding = $merge_bindings{$lookup};
186             } else {
187                 if ($key =~ /^\+/) {
188                     # forced inclusion of bindings from template
189                 } else {
190                     # this action is not defined in the merge from set, so forget it 
191                     next;
192                 }
193             }
194         } 
195
196         # print the accelmap output
197
198         if ($key =~ /^\+/) {
199             # remove + and don't print it in the accelmap
200             $key =~ s/^\+//;
201         } else {
202             # include this in the accelmap
203             if (!$merge_from && $make_accelmap) {
204                 foreach $k (keys %gtk_modifier_map) {
205                     $gtk_binding =~ s/\@$k\@/$gtk_modifier_map{$k}/;
206                 }
207                 print "(gtk_accel_path \"<Actions>/$action\" \"$gtk_binding\")\n";
208             }
209         }
210
211         if ($key =~ /^-/) {
212             # do not include this binding in the cheat sheet
213             next;
214         }
215
216         $bref = $group_bindings{$key};
217         push (@$bref, [$binding, $text]);
218
219         next;
220     }
221
222     next;
223 }
224
225 if ($make_accelmap || !$make_cheatsheet) {
226     exit 0;
227 }
228
229 # Now print the cheatsheet
230
231 $boilerplate_header = <<END_HEADER;
232 \\documentclass[10pt,landscape]{article}
233 \\usepackage{multicol}
234 \\usepackage{calc}
235 \\usepackage{ifthen}
236 \\usepackage{palatino}
237 \\usepackage{geometry}
238
239 \\setlength{\\parskip}{0pt}
240 \\setlength{\\parsep}{0pt}
241 \\setlength{\\headsep}{0pt}
242 \\setlength{\\topskip}{0pt}
243 \\setlength{\\topmargin}{0pt}
244 \\setlength{\\topsep}{0pt}
245 \\setlength{\\partopsep}{0pt}
246
247 % This sets page margins to .5 inch if using letter paper, and to 1cm
248 % if using A4 paper. (This probably isnott strictly necessary.)
249 % If using another size paper, use default 1cm margins.
250 \\ifthenelse{\\lengthtest { \\paperwidth = 11in}}
251         { \\geometry{top=.5in,left=1in,right=0in,bottom=.5in} }
252         {\\ifthenelse{ \\lengthtest{ \\paperwidth = 297mm}}
253                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
254                 {\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }
255         }
256
257 % Turn off header and footer
258 \\pagestyle{empty}
259  
260 % Redefine section commands to use less space
261 \\makeatletter
262 \\renewcommand{\\section}{\\\@startsection{section}{1}{0mm}%
263                                 {-1ex plus -.5ex minus -.2ex}%
264                                 {0.5ex plus .2ex}%
265                                 {\\normalfont\\large\\bfseries}}
266 \\renewcommand{\\subsection}{\\\@startsection{subsection}{2}{0mm}%
267                                 {-1explus -.5ex minus -.2ex}%
268                                 {0.5ex plus .2ex}%
269                                 {\\normalfont\\normalsize\\bfseries}}
270 \\renewcommand{\\subsubsection}{\\\@startsection{subsubsection}{3}{0mm}%
271                                 {-1ex plus -.5ex minus -.2ex}%
272                                 {1ex plus .2ex}%
273                                 {\\normalfont\\small\\bfseries}}
274 \\makeatother
275
276 % Do not print section numbers% Do not print section numbers
277 \\setcounter{secnumdepth}{0}
278
279 \\setlength{\\parindent}{0pt}
280 \\setlength{\\parskip}{0pt plus 0.5ex}
281
282 %-------------------------------------------
283
284 \\begin{document}
285 \\newlength{\\MyLen}
286 \\raggedright
287 \\footnotesize
288 \\begin{multicols}{3}
289 END_HEADER
290
291 $boilerplate_footer = <<END_FOOTER;
292 \\rule{0.3\\linewidth}{0.25pt}
293 \\scriptsize
294
295 Copyright \\copyright\\ 2009 ardour.org
296
297 % Should change this to be date of file, not current date.
298 %\\verb!$Revision: 1.13 $, $Date: 2008/05/29 06:11:56 $.!
299
300 http://ardour.org/manual
301
302 \\end{multicols}
303 \\end{document}
304 END_FOOTER
305
306 if ($make_cheatsheet) {
307     print $boilerplate_header;
308     print "\\begin{center}\\Large\\bf $title \\end{center}\n";
309 }
310
311 @groups_sorted_by_number = sort { $group_numbering{$a} <=> $group_numbering{$b} } keys %group_numbering; 
312
313 foreach $gk (@groups_sorted_by_number) {
314     # $bref is a reference to the array of arrays for this group
315     $bref = $group_bindings{$gk};
316
317     if (scalar @$bref > 1) {
318         print "\\section{$group_names{$gk}}\n";
319
320         if (!($group_text{$gk} eq  "")) {
321             print "$group_text{$gk}\n\\par\n";
322         }
323         
324         # ignore the first entry, which was empty
325
326         shift (@$bref);
327
328         # find the longest descriptive text (this is not 100% accuracy due to typography)
329
330         $maxtextlen = 0;
331         $maxtext = "";
332
333         for $bbref (@$bref) {
334             # $bbref is a reference to an array
335             $text = @$bbref[1];
336             
337             #
338             # if there is a linebreak, just use everything up the linebreak
339             # to determine the width
340             #
341
342             if ($text =~ /\\linebreak/) {
343                 $matchtext = s/\\linebreak.*//;
344             } else {
345                 $matchtext = $text;
346             }
347             if (length ($matchtext) > $maxtextlen) {
348                 $maxtextlen = length ($matchtext);
349                 $maxtext = $matchtext;
350             }
351         }
352
353         if ($gk =~ /^m/) {
354             # mouse mode: don't extend max text at all - space it tight
355             $maxtext .= ".";
356         } else {
357             $maxtext .= "....";
358         }
359
360         # set up the table
361
362         print "\\settowidth{\\MyLen}{\\texttt{$maxtext}}\n";
363         print "\\begin{tabular}{\@{}p{\\the\\MyLen}% 
364                                 \@{}p{\\linewidth-\\the\\MyLen}%
365                                 \@{}}\n";
366
367         # sort the array of arrays by the descriptive text for nicer appearance,
368         # and print them
369
370         for $bbref (sort { @$a[1] cmp @$b[1] } @$bref) {
371             # $bbref is a reference to an array
372
373             $binding = @$bbref[0];
374             $text = @$bbref[1];
375
376             if ($binding =~ /:/) { # mouse binding with "where" clause
377                 ($binding,$where) = split (/:/, $binding, 2);
378             }
379
380             if ($gk =~ /^m/) {
381                 # mouse mode - use shorter abbrevs
382                 foreach $k (keys %mouse_modifier_map) {
383                     $binding =~ s/\@$k\@/$mouse_modifier_map{$k}/;
384                 }
385             } else {
386                 foreach $k (keys %cs_modifier_map) {
387                     $binding =~ s/\@$k\@/$cs_modifier_map{$k}/;
388                 }
389             }
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             # split up mouse bindings to "click" and "where" parts
401
402             if ($gk eq "mobject") {
403                 print "{\\tt @$bbref[1] } & {\\tt $binding} {\\it $where}\\\\\n";
404             } else {
405                 print "{\\tt @$bbref[1] } & {\\tt $binding} \\\\\n";
406             }
407         }
408
409         print "\\end{tabular}\n";
410
411     }
412 }
413
414 print $boilerplate_footer;
415
416 exit 0;