From: Martin Mares Date: Sun, 19 Jan 2003 12:04:07 +0000 (+0000) Subject: Initial revision X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=d675b5eba1296656db9f4c1017bb7a6cea822510;p=vs.git Initial revision --- d675b5eba1296656db9f4c1017bb7a6cea822510 diff --git a/vs.pl b/vs.pl new file mode 100755 index 0000000..0063391 --- /dev/null +++ b/vs.pl @@ -0,0 +1,465 @@ +#!/usr/bin/perl +# The Virtual Songbook 0.0 +# (c) 2003 Martin Mares + +use Curses; +use strict; +use warnings; + +### Interface with Curses ### + +my $W; +my $color_mode; + +sub init_terminal() { + $W = new Curses; + start_color; + $color_mode = (has_colors && COLORS >= 8 && COLOR_PAIRS >= 8) unless defined $color_mode; + cbreak; noecho; + $W->intrflush(0); + $W->keypad(1); + $W->meta(1); +} + +sub cleanup_terminal() { + endwin; +} + +my ($attr_normal, $attr_status, $attr_hilite, $attr_chord); + +sub setup_attrs() { + if ($color_mode) { + init_pair(1, COLOR_YELLOW, COLOR_BLUE); + $attr_status = COLOR_PAIR(1) | A_BOLD; + init_pair(2, COLOR_YELLOW, COLOR_BLACK); + $attr_chord = COLOR_PAIR(2); + } else { + $attr_status = A_BOLD; + $attr_chord = A_BOLD; + } + $attr_normal = A_NORMAL; + $attr_hilite = A_BOLD; +} + +my $try_full_names = 1; +my $auto_enter = 1; +my $file_window_width = 20; + +my ($term_w, $term_h); +my @window_list = (); +my $status_window = new VS::Window::Status; +my $main_window = new VS::Window::Main; +my $file_window = new VS::Window::File; +$file_window->reload; +my $focused_window_i = 2; +my $focused_window = $file_window; + +sub focus_next() { + $focused_window->{"focused"} = 0; + do { + $focused_window_i++; + if ($focused_window_i > $#window_list) { $focused_window_i=0; } + $focused_window = $window_list[$focused_window_i]; + } while (!$focused_window->{"focusable"} || !$focused_window->{"visible"}); + $focused_window->{"focused"} = 1; +} + +sub recalc_windows() { + my $w = COLS; + my $h = LINES; + $term_w = $w; + $term_h = $h; + $status_window->place(0, 0, 1, $w); + if ($file_window->{"visible"}) { + my $fww = $file_window_width; + $main_window->place(1, 0, $h-1, $w-$fww-1); + $W->attrset($focused_window == $file_window ? $attr_hilite : $attr_normal); + $W->vline(2, $w-$fww-1, ACS_VLINE, $h-3); + $W->hline(1, $w-$fww, ACS_HLINE, $fww); + $W->hline($h-1, $w-$fww, ACS_HLINE, $fww); + $W->addch(1, $w-$fww-1, ACS_ULCORNER); + $W->addch($h-1, $w-$fww-1, ACS_LLCORNER); + $W->attrset($attr_normal); + $file_window->place(2, $w-$fww, $h-3, $fww); + } else { + $main_window->place(1, 0, $h-1, $w); + $file_window->place(0, 0, 0, 0); + } +} + +sub toggle_window($) { + my $win = shift; + if ($win->{"visible"} = !$win->{"visible"}) { + while ($focused_window != $win) { focus_next; } + } else { + if ($focused_window == $win) { focus_next; } + } + recalc_windows; +} + +init_terminal; +setup_attrs; +recalc_windows; + +for(;;) { + $W->move($term_h-1, $term_w-1); + $W->refresh; + my $key = $W->getch; + if ($key eq "\033" || $key eq "q") { + cleanup_terminal; + exit 0; + } elsif ($key eq "f") { + toggle_window($file_window); + } elsif ($key eq "\t") { + focus_next; + recalc_windows; + } elsif ($key eq "\014") { + $curscr->clearok(1); + } elsif ($key eq "<" && $file_window_width < $term_w-1) { + $file_window_width++; + recalc_windows; + } elsif ($key eq ">" && $file_window_width > 1) { + $file_window_width--; + recalc_windows; + } else { + $focused_window->key($key); + } +} + +### Window Objects ### + +package VS::Window; + +sub new($) { + my $w = { + "visible" => 1, + "focusable" => 1, + "focused" => 0, + "x" => -1, + "y" => -1, + "w" => -1, + "h" => -1 + }; + push @window_list, $w; + return bless $w; +} + +sub place($$$$$) { + my ($w,$nx,$ny,$nh,$nw) = @_; + if ($w->{"visible"}) { + if (!defined $w->{"win"} || $w->{"x"} != $nx || $w->{"y"} != $ny + || $w->{"w"} != $nw || $w->{"h"} != $nh) { + $w->{"win"} = $W->subwin($nh,$nw,$nx,$ny); + $w->{"x"} = $nx; + $w->{"y"} = $ny; + $w->{"w"} = $nw; + $w->{"h"} = $nh; + $w->redraw; + } + } else { + delete $w->{"win"}; + } +} + +sub key($) { } +sub redraw($) { } + +package VS::Window::Main; +use Curses; +BEGIN { our @ISA = qw(VS::Window); } + +sub new($) { + my $w = new VS::Window; + $w->{"file"} = ""; + $w->{"attrs"} = {}; + $w->{"lines"} = ["", "", " The Virtual Songbook 0.0\n", " (c) 2003 Martin Mares "]; + $w->{"linetype"} = [0,0,0,0]; + $w->{"n"} = 4; + $w->{"top"} = 0; + return bless $w; +} + +sub view($$$) { + my ($w,$f,$x) = @_; + if ($w->{"file"} ne $f) { + $w->{"file"} = $f; + $w->{"xfile"} = $x; + $f =~ s@^./@@; + $x =~ s@^./@@; + if (open X, $f) { + my %attrs = (); + while () { + chomp; + /^$/ && last; + if (/^(\w+):\s*(.*)/ && !defined $attrs{$1}) { + $attrs{$1} = $2; + } + } + my @lines = (); + my @types = (); + while () { + chomp; + if (s/^!//) { push @types, 1; } else { push @types, 0; } + push @lines, $_; + } + close X; + $w->{"attrs"} = \%attrs; + $w->{"lines"} = \@lines; + $w->{"linetypes"} = \@types; + $w->{"top"} = 0; + $w->{"n"} = scalar @lines; + $w->redraw; + if (defined $attrs{"Name"}) { + $x = $attrs{"Name"}; + $x = $attrs{"Author"} . ": $x" if defined $attrs{"Author"}; + } + $status_window->tell($x); + } else { + $status_window->tell("Cannot open $f"); + } + } +} + +sub redraw_line($$) { + my ($w,$i) = @_; + my $win = $w->{"win"}; + my $l = $w->{"lines"}->[$i]; + if ($w->{"linetypes"}->[$i]) { $win->attrset($attr_chord); } + if (length $l < $w->{"w"}) { + $win->addstr($i-$w->{"top"}, 0, $l); + $win->clrtoeol; + } else { + $win->addstr($i-$w->{"top"}, 0, substr($l, 0, $w->{"w"})); + } + $win->attrset($attr_normal); +} + +sub redraw($) { + my $w = shift @_; + my $win = $w->{"win"}; + my $top = $w->{"top"}; + my $cnt = $w->{"n"} - $w->{"top"}; + if ($cnt > $w->{"h"}) { $cnt = $w->{"h"}; } + for (my $i=$top; $i<$top+$cnt; $i++) { $w->redraw_line($i); } + if ($cnt < $w->{"h"}) { + $win->move($cnt, 0); + $win->clrtobot; + } + $win->noutrefresh; +} + +sub go($$) { + my ($w,$delta) = @_; + my $win = $w->{"win"}; + my $top = $w->{"top"} + $delta; + if ($top + $w->{"h"} > $w->{"n"}) { $top = $w->{"n"} - $w->{"h"}; } + if ($top < 0) { $top = 0; } + my $otop = $w->{"top"}; + $w->{"top"} = $top; + if ($top < $otop - $w->{"h"}/2) { + $w->redraw; + } elsif ($top < $otop) { + my $j = $otop - $top; + $win->scrollok(1); + $win->scrl(-$j); + $win->scrollok(0); + for (my $i=0; $i<$j; $i++) { $w->redraw_line($top+$i); } + } elsif ($top == $otop) { + # Nothing happens + } elsif ($top < $otop + $w->{"h"}/2) { + my $j = $top - $otop; + $win->scrollok(1); + $win->scrl($j); + $win->scrollok(0); + for (my $i=$j; $i>0; $i--) { $w->redraw_line($top+$w->{"h"}-$i); } + } else { + $w->redraw; + } + $win->noutrefresh; +} + +sub key($$) { + my ($w,$key) = @_; + if ($key eq KEY_UP) { $w->go(-1); } + elsif ($key eq KEY_DOWN) { $w->go(1); } + elsif ($key eq KEY_PPAGE) { $w->go(-$w->{"h"}-1); } + elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); } + elsif ($key eq KEY_HOME) { $w->go(-1000000000); } + elsif ($key eq KEY_END) { $w->go(1000000000); } + else { $status_window->tell("Unknown key <$key>"); } +} + +package VS::Window::File; +use Curses; +BEGIN { our @ISA = qw(VS::Window); } + +sub new($) { + my $w = new VS::Window; + $w->{"dir"} = "./"; + $w->{"xdir"} = "./"; + return bless $w; +} + +sub reload($) { + my $w = shift; + my $p = $w->{"dir"}; + my @l = `cd $p && ls`; + my @fn = (); + my @full = (); + if ($p ne "./") { push @fn, "../"; push @full, ""; } + foreach my $x (@l) { + chomp $x; + if (-f "$p/$x") { + push @fn, $x; + my $fullname = $x; + if ($try_full_names && open(X, "$p/$x")) { + while () { + chomp; + /^$/ && last; + if (/^Name:\s*(.*)/) { + $fullname = $1; + last; + } + } + close X; + } + push @full, $fullname; + } elsif (-d "$p/$x") { push @fn, "$x/"; push @full, "$x/"; } + } + $w->{"flist"} = \@fn; + $w->{"list"} = \@full; + $w->{"n"} = scalar @fn; + $w->{"i"} = 0; + $w->{"1st"} = 0; +} + +sub redraw_line($$) { + my ($w,$i) = @_; + my $line = $i - $w->{"1st"}; + if ($line < 0 || $line >= $w->{"h"}) { return; } + my $win = $w->{"win"}; + my $item = ($i < $w->{"n"}) ? substr($w->{"list"}->[$i], 0, $w->{"w"}) : ""; + if ($i == $w->{"i"}) { $win->bkgdset($attr_status); } + $win->addstr($line, 0, $item); + $win->clrtoeol if length $item < $w->{"w"}; + $win->bkgdset($attr_normal); +} + +sub redraw($) { + my $w = shift @_; + $w->{"win"}->idlok(1); + for (my $i=0; $i<$w->{"h"}; $i++) { + $w->redraw_line($w->{"1st"} + $i); + } + $w->{"win"}->noutrefresh; +} + +sub go($$) { + my ($w,$delta) = @_; + my $i = $w->{"i"}; + my $oldi = $i; + $i += $delta; + if ($i < 0) { $i = 0; } + if ($i >= $w->{"n"}) { $i = $w->{"n"}-1; } + $w->{"i"} = $i; + if ($w->{"visible"}) { + $w->redraw_line($oldi); + if ($i < $w->{"1st"}) { + my $j = $w->{"1st"} - $i; + $w->{"1st"} = $i; + if ($j >= $w->{"h"}/2) { + $w->{"win"}->scrollok(1); + $w->{"win"}->scrl(-$j); + $w->{"win"}->scrollok(0); + for (my $k=0; $k<$j; $k++) { $w->redraw_line($i+$k); } + } else { $w->redraw; } + } elsif ($i >= $w->{"1st"} + $w->{"h"}) { + my $j = $i - $w->{"1st"} - $w->{"h"} + 1; + $w->{"1st"} += $j; + if ($j < $w->{"h"}/2) { + $w->{"win"}->scrollok(1); + $w->{"win"}->scrl($j); + $w->{"win"}->scrollok(0); + for (my $k=1; $k<=$j; $k++) { $w->redraw_line($i-$j+$k); } + } else { $w->redraw; } + } else { $w->redraw_line($i); } + $w->{"win"}->noutrefresh; + } + if ($auto_enter && $i < $w->{"n"} && $w->{"flist"}->[$i] !~ /\/$/) { $w->select; } +} + +sub select($) { + my ($w) = @_; + if ($w->{"i"} < $w->{"n"}) { + my $f = $w->{"flist"}->[$w->{"i"}]; + my $x = $w->{"list"}->[$w->{"i"}]; + if ($f =~ /\/$/) { + if ($f eq "../") { + $w->{"dir"} =~ s@([^/]*/)$@@; + my $back = $1; + $w->{"xdir"} =~ s@[^/]*/$@@; + $w->reload; + for (my $i=0; $i<$w->{"n"}; $i++) { + if ($w->{"flist"}->[$i] eq $back) { + $w->{"i"} = $i; + if ($i > $w->{"h"}/2) { $w->{"1st"} = $i - int($w->{"h"}/2); } + if ($w->{"1st"} + $w->{"h"} > $w->{"n"}) { $w->{"1st"} = $w->{"n"} - $w->{"h"}; } + if ($w->{"1st"} < 0) { $w->{"1st"} = 0; } + last; + } + } + } else { + $w->{"dir"} .= $f; + $w->{"xdir"} .= $x; + $w->reload; + } + $w->redraw; + } else { + $main_window->view($w->{"dir"} . $f, $w->{"xdir"} . $x); + } + } +} + +sub key($$) { + my ($w,$key) = @_; + if ($key eq KEY_UP) { $w->go(-1); } + elsif ($key eq KEY_DOWN) { $w->go(1); } + elsif ($key eq KEY_PPAGE) { $w->go(-$w->{"h"}-1); } + elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); } + elsif ($key eq KEY_HOME) { $w->go(-1000000000); } + elsif ($key eq KEY_END) { $w->go(1000000000); } + elsif ($key eq "\n" || $key eq "\r" || $key eq KEY_RIGHT) { $w->select; } + elsif ($key eq KEY_LEFT) { + if ($w->{"list"}->[0] eq "") { + $w->{"i"} = 0; + $w->select; + } + } +} + +package VS::Window::Status; +BEGIN { our @ISA = qw(VS::Window); } + +sub new($) { + my $w = new VS::Window; + $w->{"focusable"} = 0; + $w->{"msg"} = ""; + return bless $w; +} + +sub redraw($) { + my $w = shift @_; + my $win = $w->{"win"}; + $win->bkgdset($attr_status); + $win->addstr(0, 0, $w->{"msg"}); + $win->clrtoeol; + $win->refresh; +} + +sub tell($$) { + my ($w,$m) = @_; + if ($w->{"msg"} ne $m) { + $w->{"msg"} = $m; + $w->redraw; + } +}