2 # The Virtual Songbook 0.0
3 # (c) 2003 Martin Mares <mj@ucw.cz>
9 ### Interface with Curses ###
17 $color_mode = (has_colors && COLORS >= 8 && COLOR_PAIRS >= 8) unless defined $color_mode;
24 sub cleanup_terminal() {
28 my ($attr_normal, $attr_status, $attr_hilite, $attr_chord);
32 init_pair(1, COLOR_YELLOW, COLOR_BLUE);
33 $attr_status = COLOR_PAIR(1) | A_BOLD;
34 init_pair(2, COLOR_YELLOW, COLOR_BLACK);
35 $attr_chord = COLOR_PAIR(2);
37 $attr_status = A_BOLD;
40 $attr_normal = A_NORMAL;
41 $attr_hilite = A_BOLD;
44 my $try_full_names = 1;
46 my $file_window_width = 20;
48 my ($term_w, $term_h);
50 my $status_window = new VS::Window::Status;
51 my $main_window = new VS::Window::Main;
52 my $file_window = new VS::Window::File;
54 my $focused_window_i = 2;
55 my $focused_window = $file_window;
58 $focused_window->{"focused"} = 0;
61 if ($focused_window_i > $#window_list) { $focused_window_i=0; }
62 $focused_window = $window_list[$focused_window_i];
63 } while (!$focused_window->{"focusable"} || !$focused_window->{"visible"});
64 $focused_window->{"focused"} = 1;
67 sub recalc_windows() {
72 $status_window->place(0, 0, 1, $w);
73 if ($file_window->{"visible"}) {
74 my $fww = $file_window_width;
75 $main_window->place(1, 0, $h-1, $w-$fww-1);
76 $W->attrset($focused_window == $file_window ? $attr_hilite : $attr_normal);
77 $W->vline(2, $w-$fww-1, ACS_VLINE, $h-3);
78 $W->hline(1, $w-$fww, ACS_HLINE, $fww);
79 $W->hline($h-1, $w-$fww, ACS_HLINE, $fww);
80 $W->addch(1, $w-$fww-1, ACS_ULCORNER);
81 $W->addch($h-1, $w-$fww-1, ACS_LLCORNER);
82 $W->attrset($attr_normal);
83 $file_window->place(2, $w-$fww, $h-3, $fww);
85 $main_window->place(1, 0, $h-1, $w);
86 $file_window->place(0, 0, 0, 0);
90 sub toggle_window($) {
92 if ($win->{"visible"} = !$win->{"visible"}) {
93 while ($focused_window != $win) { focus_next; }
95 if ($focused_window == $win) { focus_next; }
105 $W->move($term_h-1, $term_w-1);
108 if ($key eq "\033" || $key eq "q") {
111 } elsif ($key eq "f") {
112 toggle_window($file_window);
113 } elsif ($key eq "\t") {
116 } elsif ($key eq "\014") {
118 } elsif ($key eq "<" && $file_window_width < $term_w-1) {
119 $file_window_width++;
121 } elsif ($key eq ">" && $file_window_width > 1) {
122 $file_window_width--;
124 } elsif ($key eq "j") {
125 $file_window->key(KEY_DOWN);
126 } elsif ($key eq "k") {
127 $file_window->key(KEY_UP);
128 } elsif ($key eq "h") {
129 $file_window->key(KEY_LEFT);
130 } elsif ($key eq "l") {
131 $file_window->key(KEY_RIGHT);
133 $focused_window->key($key);
141 # Internal representation of chords: <base-tone>:<type> (so C="0:", C#="1:", Dmi="2:mi" etc.)
142 # but usually they are accompanied by position info after a second colon
173 @n2t = ( "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "B", "H" );
180 while (my ($spaces,$chord,$rest) = $r =~ /(\s*)(\S+)(.*)/) {
181 $pos += length $spaces;
182 if (my ($tone,$sh,$mod) = ($chord =~ /^([CDEFGABH](#|b|))(.*)$/)) {
184 push @l, "$k:$mod:$pos"
186 push @l, "0:?$chord:$pos";
188 $pos += length $chord;
194 sub synthesize_line($$) {
198 for (my $i=0; $i<@$l; $i++) {
199 my ($tone,$mod,$pos) = split(/:/, $l->[$i]);
200 $tone = ($tone + $xpos) % 12;
201 my $chord = $n2t[$tone] . "$mod ";
203 $result .= " " x ($pos - $pp);
207 $pp += length $chord;
212 ### Window Objects ###
226 push @window_list, $w;
231 my ($w,$nx,$ny,$nh,$nw) = @_;
232 if ($w->{"visible"}) {
233 if (!defined $w->{"win"} || $w->{"x"} != $nx || $w->{"y"} != $ny
234 || $w->{"w"} != $nw || $w->{"h"} != $nh) {
235 $w->{"win"} = $W->subwin($nh,$nw,$nx,$ny);
250 package VS::Window::Main;
252 BEGIN { our @ISA = qw(VS::Window); }
255 my $w = new VS::Window;
258 $w->{"lines"} = ["", "", " The Virtual Songbook 0.0\n", " (c) 2003 Martin Mares <mj\@ucw.cz>"];
259 $w->{"chords"} = [0,0,0,0];
262 $w->{"chords_analysed"} = 0;
263 $w->{"current_xpose"} = 0;
264 $w->{"requested_xpose"} = 0;
270 if ($w->{"file"} ne $f) {
273 $w->{"current_xpose"} = 0;
274 $w->{"chords_analysed"} = 0;
282 if (/^(\w+):\s*(.*)/ && !defined $attrs{$1}) {
290 if (s/^! //) { push @chords, 1; } else { push @chords, 0; }
294 $w->{"attrs"} = \%attrs;
295 $w->{"lines"} = \@lines;
296 $w->{"chords"} = \@chords;
297 $w->{"chordtable"} = [];
299 $w->{"n"} = scalar @lines;
301 if (defined $attrs{"Name"}) {
303 $x = $attrs{"Author"} . ": $x" if defined $attrs{"Author"};
305 $status_window->tell($x);
307 $status_window->tell("Cannot open $f");
314 if (!$w->{"chords_analysed"}) {
315 for (my $i=0; $i<$w->{"n"}; $i++) {
316 if ($w->{"chords"}->[$i]) {
317 $w->{"chordtable"}->[$i] = VS::Chord::parse_line($w->{"lines"}->[$i]);
320 $w->{"chords_analysed"} = 1;
322 for (my $i=0; $i<$w->{"n"}; $i++) {
323 if ($w->{"chords"}->[$i]) {
324 $w->{"lines"}->[$i] = VS::Chord::synthesize_line($w->{"chordtable"}->[$i], $w->{"requested_xpose"});
327 $w->{"current_xpose"} = $w->{"requested_xpose"};
330 sub redraw_line($$) {
332 my $win = $w->{"win"};
333 my $l = $w->{"lines"}->[$i];
334 if ($w->{"chords"}->[$i]) { $win->attrset($attr_chord); }
335 if (length $l < $w->{"w"}) {
336 $win->addstr($i-$w->{"top"}, 0, $l);
339 $win->addstr($i-$w->{"top"}, 0, substr($l, 0, $w->{"w"}));
341 $win->attrset($attr_normal);
346 $w->transpose if $w->{"current_xpose"} != $w->{"requested_xpose"};
347 my $win = $w->{"win"};
348 my $top = $w->{"top"};
349 my $cnt = $w->{"n"} - $w->{"top"};
350 if ($cnt > $w->{"h"}) { $cnt = $w->{"h"}; }
351 for (my $i=$top; $i<$top+$cnt; $i++) { $w->redraw_line($i); }
352 if ($cnt < $w->{"h"}) {
361 my $win = $w->{"win"};
362 my $top = $w->{"top"} + $delta;
363 if ($top + $w->{"h"} > $w->{"n"}) { $top = $w->{"n"} - $w->{"h"}; }
364 if ($top < 0) { $top = 0; }
365 my $otop = $w->{"top"};
367 if ($top < $otop - $w->{"h"}/2) {
369 } elsif ($top < $otop) {
370 my $j = $otop - $top;
374 for (my $i=0; $i<$j; $i++) { $w->redraw_line($top+$i); }
375 } elsif ($top == $otop) {
377 } elsif ($top < $otop + $w->{"h"}/2) {
378 my $j = $top - $otop;
382 for (my $i=$j; $i>0; $i--) { $w->redraw_line($top+$w->{"h"}-$i); }
391 if ($key eq KEY_UP) { $w->go(-1); }
392 elsif ($key eq KEY_DOWN) { $w->go(1); }
393 elsif ($key eq KEY_PPAGE) { $w->go(-$w->{"h"}-1); }
394 elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); }
395 elsif ($key eq KEY_HOME) { $w->go(-1000000000); }
396 elsif ($key eq KEY_END) { $w->go(1000000000); }
397 elsif ($key eq "+" || $key eq "=") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+1)%12; $status_window->redraw; $w->redraw; }
398 elsif ($key eq "-") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+11)%12; $status_window->redraw; $w->redraw; }
399 elsif ($key eq "0") { $w->{"requested_xpose"} = 0; $status_window->redraw; $w->redraw; }
400 else { $status_window->tell("Unknown key <$key>"); }
403 package VS::Window::File;
405 BEGIN { our @ISA = qw(VS::Window); }
408 my $w = new VS::Window;
417 my @l = `cd $p && ls`;
420 if ($p ne "./") { push @fn, "../"; push @full, "<parent>"; }
426 if ($try_full_names && open(X, "$p/$x")) {
430 if (/^Name:\s*(.*)/) {
437 push @full, $fullname;
438 } elsif (-d "$p/$x") { push @fn, "$x/"; push @full, "$x/"; }
440 $w->{"flist"} = \@fn;
441 $w->{"list"} = \@full;
442 $w->{"n"} = scalar @fn;
447 sub redraw_line($$) {
449 my $line = $i - $w->{"1st"};
450 if ($line < 0 || $line >= $w->{"h"}) { return; }
451 my $win = $w->{"win"};
452 my $item = ($i < $w->{"n"}) ? substr($w->{"list"}->[$i], 0, $w->{"w"}) : "";
453 if ($i == $w->{"i"}) { $win->bkgdset($attr_status); }
454 $win->addstr($line, 0, $item);
455 $win->clrtoeol if length $item < $w->{"w"};
456 $win->bkgdset($attr_normal);
461 my $win = $w->{"win"};
462 # Window size might have changed...
463 if ($w->{"1st"} + $w->{"h"} > $w->{"n"}) { $w->{"1st"} = $w->{"n"} - $w->{"h"}; }
464 if ($w->{"1st"} < 0) { $w->{"1st"} = 0; }
466 for (my $i=0; $i<$w->{"h"}; $i++) {
467 $w->redraw_line($w->{"1st"} + $i);
477 if ($i < 0) { $i = 0; }
478 if ($i >= $w->{"n"}) { $i = $w->{"n"}-1; }
480 if ($w->{"visible"}) {
481 $w->redraw_line($oldi);
482 if ($i < $w->{"1st"}) {
483 my $j = $w->{"1st"} - $i;
485 if ($j >= $w->{"h"}/2) {
486 $w->{"win"}->scrollok(1);
487 $w->{"win"}->scrl(-$j);
488 $w->{"win"}->scrollok(0);
489 for (my $k=0; $k<$j; $k++) { $w->redraw_line($i+$k); }
490 } else { $w->redraw; }
491 } elsif ($i >= $w->{"1st"} + $w->{"h"}) {
492 my $j = $i - $w->{"1st"} - $w->{"h"} + 1;
494 if ($j < $w->{"h"}/2) {
495 $w->{"win"}->scrollok(1);
496 $w->{"win"}->scrl($j);
497 $w->{"win"}->scrollok(0);
498 for (my $k=1; $k<=$j; $k++) { $w->redraw_line($i-$j+$k); }
499 } else { $w->redraw; }
500 } else { $w->redraw_line($i); }
501 $w->{"win"}->noutrefresh;
503 if ($auto_enter && $i < $w->{"n"} && $w->{"flist"}->[$i] !~ /\/$/) { $w->select; }
508 if ($w->{"i"} < $w->{"n"}) {
509 my $f = $w->{"flist"}->[$w->{"i"}];
510 my $x = $w->{"list"}->[$w->{"i"}];
513 $w->{"dir"} =~ s@([^/]*/)$@@;
515 $w->{"xdir"} =~ s@[^/]*/$@@;
517 for (my $i=0; $i<$w->{"n"}; $i++) {
518 if ($w->{"flist"}->[$i] eq $back) {
520 $w->{"1st"} = $i - int($w->{"h"}/2);
531 $main_window->view($w->{"dir"} . $f, $w->{"xdir"} . $x);
538 if ($key eq KEY_UP) { $w->go(-1); }
539 elsif ($key eq KEY_DOWN) { $w->go(1); }
540 elsif ($key eq KEY_PPAGE) { $w->go(-$w->{"h"}-1); }
541 elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); }
542 elsif ($key eq KEY_HOME) { $w->go(-1000000000); }
543 elsif ($key eq KEY_END) { $w->go(1000000000); }
544 elsif ($key eq "\n" || $key eq "\r" || $key eq KEY_RIGHT) { $w->select; }
545 elsif ($key eq KEY_LEFT) {
546 if ($w->{"list"}->[0] eq "<parent>") {
553 package VS::Window::Status;
554 BEGIN { our @ISA = qw(VS::Window); }
557 my $w = new VS::Window;
558 $w->{"focusable"} = 0;
565 my $win = $w->{"win"};
566 $win->bkgdset($attr_status);
567 $win->addstr(0, 0, $w->{"msg"});
570 $aux = "T=" . $main_window->{"requested_xpose"} if ($main_window->{"requested_xpose"});
571 $win->addstr(0, $w->{"w"}-length $aux, $aux) if $aux ne "";
577 if ($w->{"msg"} ne $m) {