# $Id: CPronto.pm,v 1.2 2001/02/03 14:56:34 muhri Exp $ # -*- perl -*- # coypright (c) 2000 Charlie Schmidt package Pronto::CPronto; use Curses; use strict; 1; sub new { my $class = shift; my $self = {}; my ($r,$c,$w); initscr(); start_color(); init_pair(0,COLOR_WHITE,COLOR_BLACK); init_pair(1,COLOR_RED,COLOR_BLACK); init_pair(2,COLOR_BLUE,COLOR_BLACK); init_pair(3,COLOR_GREEN,COLOR_BLACK); init_pair(4,COLOR_CYAN,COLOR_BLACK); raw(); cbreak(); noecho(); nonl(); nodelay(0); intrflush(Curses::stdscr,0); keypad(1); getmaxyx($r,$c); if ($r < 24 || $c < 80) { print _("Your terminal must have at least 24 rows and 80 columns to run Pronto::CPronto\n"); exit(1); } $w = new Curses; $self = { rows => $r, cols => $c, window => $w, current => 0, num_widgets => 0, widgets => ()}; bless ($self,$class); return $self; } sub add_widget { my $self = shift; my ($widget) = @_; $self->{widgets}[$self->{num_widgets}] = $widget; $self->{num_widgets}++; return; } sub remove_widget { my $self = shift; my ($widget) = @_; my $i; for ($i = 0; $i < $self->{num_widgets}; $i++) { if ($self->{widgets}[$i] == $widget) { splice(@{$self->{widgets}},$i,1); $self->{num_widgets}--; return; } } return; } sub next_widget { my $self = shift; if ($self->{widgets}[$self->{current}]->{unfocus}) { &{$self->{widgets}[$self->{current}]->{unfocus}}($self); } $self->{widgets}[$self->{current}]->unfocus(); $self->{current}++; if ($self->{current} >= $self->{num_widgets}) { $self->{current} = 0; } if ($self->{widgets}[$self->{current}]->{isntwid}) { $self->next_widget(); } if ($self->{widgets}[$self->{current}]->{focus}) { &{$self->{widgets}[$self->{current}]->{focus}}($self); } $self->{widgets}[$self->{current}]->focus(); refresh(); return; } sub change_widget { my $self = shift; my ($widget) = @_; my $i; for ($i = 0; $i <= $self->{num_widgets}; $i++) { if ($self->{widgets}[$i] == $widget) { $self->{current} = $i; } } return; } sub addstring { my $self = shift; my ($y,$x,$string) = @_; move($y,$x); addstr("$string"); refresh(); return; } sub show_widgets { my $self = shift; print "Widgets: ",$self->{num_widgets},"\n"; foreach (@{$self->{widgets}}) { print "\t",$_,"\n"; } return; } sub main_loop { my $self = shift; while (1) { my $key = getch(); my $nkey = unpack('c',$key); if ($nkey == 13) { $self->{widgets}[$self->{current}]->activate(); next; } if ($nkey == 9) { $self->next_widget(); next; } if (!$self->{widgets}[$self->{current}]->{istext}) { if ($self->{"signal_$key"}) { &{$self->{"signal_$key"}}($self); next; } if ($self->{"signal_n$nkey"}) { &{$self->{"signal_n$nkey"}}($self); next; } } $self->{widgets}[$self->{current}]->key($key); $self->{widgets}[$self->{current}]->redraw(); } } sub quit { my $self = shift; $self->{window}->delwin(); endwin(); } sub signal_connect { my $self = shift; my ($signal,$func) = @_; $self->{$signal} = $func; return; } sub clear { my $self = shift; my ($i,$j); for ($i = 0; $i <= $self->{cols}; $i++) { for ($j = 0; $j <= $self->{rows}; $j++) { move($j,$i); addch(" "); } } refresh(); return; } package Pronto::CPronto::Button; use Curses; use strict; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { label => $params{label}, x => $params{x}, y => $params{y}}; bless($self,$class); return $self; } sub show { my $self = shift; move($self->{y},$self->{x}); addstr("|$self->{label}|"); refresh(); return; } sub hide { my $self = shift; my $i; for ($i = 0; $i <= length($self->{label})+1; $i++) { move($self->{y},$self->{x}+$i); addch(" "); } refresh(); return; } sub remove { my $self = shift; my $i; for ($i = 0; $i <= length($self->{label})+2; $i++) { move($self->{y},$self->{x}+$i); addch(" "); } refresh(); return; } sub unfocus { my $self = shift; move($self->{y},$self->{x}); addstr("|$self->{label}|"); refresh(); return; } sub focus { my $self = shift; move($self->{y},$self->{x}); attron(A_BOLD); addstr("*$self->{label}*"); attroff(A_BOLD); refresh(); return; } sub key { return; } sub redraw { return; } sub activate { my $self = shift; if ($self->{activate}) { &{$self->{activate}}($self); } return; } sub signal_connect { my $self = shift; my ($signal,$func) = @_; $self->{$signal} = $func; return; } package Pronto::CPronto::Label; # not a widget use Curses; 1; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { text => $params{text}, x => $params{x}, y => $params{y}, isntwid => 1}; bless($self,$class); return $self; } sub show { my $self = shift; move($self->{y},$self->{x}); addstr("$self->{text}"); refresh(); return; } sub hide { my $self = shift; my $i; move($self->{y},$self->{x}); for($i = 0; $i < length($self->{text}); $i++) { move($self->{y},$self->{x}+$i); addch(" "); } return; } sub unfocus { return; } sub focus { return; } sub redraw { my $self = shift; $self->show(); return; } package Pronto::CPronto::Entry; use Curses; 1; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { text => $params{text}, position=> length($params{text}), x => $params{x}, y => $params{y}, limit => $params{limit}, editable=> $params{editable}, istext => 1}; if ($self->{limit} == 0 or !$self->{limit}) { $self->{limit} = -1; } bless($self,$class); return $self; } sub show { my $self = shift; move($self->{y},$self->{x}); addch(">"); $self->redraw(); return; } sub redraw { my $self = shift; $self->clear(); move($self->{y},$self->{x}+1); addstr($self->{text}); move($self->{y},$self->{x}+1+$self->{position}); refresh(); return; } sub hide { my $self = shift; my $i; for ($i = 0; $i <= length($self->{text}); $i++) { move($self->{y},$self->{x}+$i); addch(" "); } refresh(); return; } sub clear { my $self = shift; my $i; for ($i = 0; $i <= length($self->{text}); $i++) { move($self->{y},$self->{x}+1+$i); addch(" "); } refresh(); } sub set_text { my $self = shift; my ($text) = @_; $text = &fix_length($text,$self->{limit}); $self->{text} = $text; $self->{position} = length($text); return; } sub get_text { my $self = shift; my $text = $self->{text}; return $text; } sub fix_length { my ($string,$length) = @_; if (length($string) > $length) { substr($string,$length) = ""; } return $string; } sub activate { my $self = shift; if ($self->{activate}) { &{$self->{activate}}($self); } return; } sub focus { my $self = shift; move($self->{y},$self->{x}); attron(A_BOLD); addch(">"); attroff(A_BOLD); move($self->{y},$self->{x}+length($self->{text})+1); refresh(); return; } sub unfocus { my $self = shift; move($self->{y},$self->{x}); addch(">"); refresh(); return; } sub key { my $self = shift; my ($key) = @_; if ($key eq KEY_DOWN or $key eq KEY_UP) { return; } if ($key eq KEY_BACKSPACE) { if (length($self->{text}) == 0) { return; } $self->{text} = (substr($self->{text},0,$self->{position}-1)).(substr($self->{text},$self->{position})); $self->{position}--; return; } if ($key eq KEY_DC) { if (length($self->{text}) == 0 || $self->{position} == length($self->{text})) { return; } $self->{text} = (substr($self->{text},0,$self->{position})).(substr($self->{text},$self->{position}+1)); return; } if ($key eq KEY_LEFT) { if ($self->{position} == 0) { return; } $self->{position}--; return; } if ($key eq KEY_RIGHT) { if ($self->{position} == length($self->{text})) { return; } $self->{position}++; return; } if ($self->{limit} == length($self->{text})) { return; } my @text; $self->{text} = (substr($self->{text},0,$self->{position})).($key).(substr($self->{text},$self->{position})); $self->{position}++; return; } sub signal_connect { my $self = shift; my ($signal,$func) = @_; $self->{$signal} = $func; return; } package Pronto::CPronto::Box; #not a widget use Curses; 1; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { rows => $params{rows}, cols => $params{cols}, x => $params{x}, y => $params{y}}; bless($self,$class); return($self); } sub show { my $self = shift; my $i; for ($i = 1; $i < $self->{cols}; $i++) { move($self->{y},$self->{x}+$i); addch("-"); move($self->{y}+$self->{rows},$self->{x}+$i); addch("-"); } for ($i = 1; $i < $self->{rows}; $i++) { move($self->{y}+$i,$self->{x}); addch("|"); move($self->{y}+$i,$self->{x}+$self->{cols}); addch("|"); } refresh(); return; } sub hide { my $self = shift; my $i; for ($i = 1; $i < $self->{cols}; $i++) { move($self->{y},$self->{x}+$i); addch(" "); move($self->{y}+$self->{rows},$self->{x}+$i); addch(" "); } for ($i = 1; $i < $self->{rows}; $i++) { move($self->{y}+$i,$self->{x}); addch(" "); move($self->{y}+$i,$self->{x}+$self->{cols}); addch(" "); } refresh(); return; } package Pronto::CPronto::List; use Curses; 1; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { titles => $params{titles}, x => $params{x}, y => $params{y}, height => $params{height}, widths => $params{widths}, shown_current => 0, shown_top => 0, current => 0, num_rows => 0, rows => (())}; bless($self,$class); return $self; } sub show { my $self = shift; my ($i,$j,$k); $j = 0; $k = 0; for ($i = 0; $i <= $#{$self->{titles}}; $i++) { $j = $j + $self->{widths}[$i]; $k = $k + $self->{widths}[$i+1]; move($self->{y},$self->{x}+$j); addstr("$self->{titles}[$i]"); } for ($i = 0; $i <= $k; $i++) { move($self->{y}+1,$self->{x}+$i); addch("-"); } $self->redraw(); return; } sub hide { my $self = shift; my ($i,$j); $j = 0; for ($i = 0; $i <= $#{$self->{titles}}; $i++) { $j = $j + $self->{widths}[$i+1]; } for ($i = 0; $i <= $j; $i++) { move($self->{y},$self->{x}+$i); addch(" "); move($self->{y}+1,$self->{x}+$i); addch(" "); } $self->clear(); refresh(); return; } sub focus { my $self = shift; my ($i,$j); $j = 0; attron(A_BOLD); for ($i = 0; $i <= $#{$self->{titles}}; $i++) { $j = $j + $self->{widths}[$i]; move($self->{y},$self->{x}+$j); addstr("$self->{titles}[$i]"); } attroff(A_BOLD); $self->redraw(); return; } sub unfocus { my $self = shift; $self->show(); $self->unselect_row(); } sub redraw { my $self = shift; my ($i,$j,$k,$l); $k = 0; $j = 0; $self->clear(); for ($l = $self->{top}; $l < $self->{top}+$self->{height}; $l++) { $j = 0; for ($i = 0; $i <= $#{$self->{titles}}; $i++) { if ($k == $self->{shown_current}) { attron(A_BOLD); } $j = $j + $self->{widths}[$i]; move($self->{y}+$k+2,$self->{x}+$j); my $string = &fix_length($self->{rows}[$l][$i],$self->{widths}[$i+1]-1); addstr("$string"); if ($k == $self->{shown_current}) { attroff(A_BOLD); } } $k++; } refresh(); return; } sub clear { my $self = shift; my ($i,$j,$k); $j = 0; for ($i = 0; $i <= $#{$self->{titles}}+1; $i++) { $j = $j + $self->{widths}[$i]; } for ($i = 0; $i <= $j; $i++) { for ($k = 0; $k < $self->{height}; $k++) { move($self->{y}+$k+2,$self->{x}+$i); addch(" "); } } return; } sub select_row { my $self = shift; my ($row) = @_; my ($i,$j); attron(A_BOLD); $j = 0; for ($i = 0; $i <= $#{$self->{titles}}; $i++) { $j = $j + $self->{widths}[$i]; move($self->{y}+$row+2,$self->{x}+$j); my $string = &fix_length($self->{rows}[$row][$i],$self->{widths}[$i+1]-1); addstr("$string"); } attroff(A_BOLD); $self->{current} = $row; refresh(); return; } sub unselect_row { my $self = shift; my $row = $self->{current}; my ($i,$j); $j = 0; for ($i = 0; $i <= $#{$self->{titles}}; $i++) { $j = $j + $self->{widths}[$i]; move($self->{y}+$self->{shown_current}+2,$self->{x}+$j); my $string = &fix_length($self->{rows}[$row][$i],$self->{widths}[$i+1]-1); addstr("$string"); } refresh(); return; } sub get_item_data { my $self = shift; my ($row,$col) = @_; my $data = $self->{rows}[$row][$col]; return ($data); } sub set_item_data { my $self = shift; my ($row,$col,$data) = @_; $self->{rows}[$row][$col] = $data; $self->redraw(); refresh(); return; } sub get_selected { my $self = shift; my $i = $self->{current}; return $i; } sub add_row { my $self = shift; my ($num,$row) = @_; my ($i,$j); $self->{rows}[$num] = $row; $self->{num_rows}++; return; } sub del_row { my $self = shift; my ($num) = @_; my ($i,$j); splice(@{$self->{rows}},$num,1); $self->{num_rows}--; return; } sub fix_length { my ($string,$length) = @_; if (length($string) > $length) { substr($string,$length) = ""; } return $string; } sub activate { my $self = shift; if ($self->{activate}) { &{$self->{activate}}($self); } return; } sub key { my $self = shift; my ($key) = @_; if ($key eq KEY_UP) { if ($self->{current} == 0) { return; } if ($self->{shown_current} == 0) { $self->{top}--; } else { $self->{shown_current}--; } $self->{current}--; return; } if ($key eq KEY_DOWN) { if ($self->{current} >= $self->{num_rows}-1) { return; } if ($self->{shown_current}+1 == $self->{height}) { $self->{top}++; } else { $self->{shown_current}++; } $self->{current}++; return; } if ($key eq KEY_NPAGE) { #page down if ($self->{current} >= $self->{num_rows}-1) { return; } my $i; for ($i = 0; $i <= $self->{height}-1; $i++) { $self->key(KEY_DOWN); } return; } if ($key eq KEY_PPAGE) { #page up if ($self->{current} == 0) { return; } my $i; for ($i = 0; $i <= $self->{height}-1; $i++) { $self->key(KEY_UP); } return; } return; } sub signal_connect { my $self = shift; my ($signal,$func) = @_; $self->{$signal} = $func; return; } package Pronto::CPronto::Text; use Curses; 1; sub new { my $class = shift; my %params = @_; my $self = {}; $self = { text => $params{text}, x => $params{x}, y => $params{y}, height => $params{height}, width => $params{width}, editable => $params{editable}, xpos => $params{xpos}, ypos => $params{ypos}, top_line => $params{topline}, current_line => $params{current_line}, position => $params{position}, istext => 1}; bless($self,$class); return $self; } sub show { my $self = shift; $self->redraw(); return; } sub hide { my $self = shift; $self->clear(); refresh(); return; } sub activate { my $self = shift; $self->key("\n"); return; } sub get_text { my $self = shift; my $text; $text = $self->{text}; return $text; } sub set_text { my $self = shift; my ($text) = @_; $self->{text} = $text; return; } sub redraw { my $self = shift; my (@text,$i,$j,$k); $self->clear(); move($self->{y},$self->{x}); @text = split(/\n/,$self->{text}); $j = $self->{top_line}+$self->{height}; $k = 0; for ($i = $self->{top_line}; $i <= $j; $i++) { move($self->{y}+$k,$self->{x}); addstr($text[$i]); $k++; } move($self->{y}+$self->{ypos},$self->{x}+$self->{xpos}); return; } sub clear { my $self = shift; my ($i,$j); for ($i = 0; $i <= $self->{width}; $i++) { for ($j = 0; $j <= $self->{height}; $j++) { move($self->{y}+$j,$self->{x}+$i); addch(" "); } } return; } sub focus { my $self = shift; move($self->{y}+$self->{ypos},$self->{x}+$self->{xpos}); refresh(); return; } sub unfocus { move(0,0); refresh(); return; } sub key { my $self = shift; my ($key) = @_; if ($key eq KEY_DOWN) { my @text = split(/\n/,$self->{text}); if ($self->{current_line} == $#text+1) { return; } if ($self->{ypos} == $self->{height}) { $self->{top_line}++; $self->{current_line}++; $self->{position} = $self->{position} + length($text[$self->{current_line}-1])+1; return; } $self->{current_line}++; $self->{ypos}++; $self->{position} = $self->{position} + length($text[$self->{current_line}-1])+1; until ($self->{xpos} <= length($text[$self->{ypos}])) { $self->{xpos}--; $self->{position}--; } return; } if ($key eq KEY_UP) { if ($self->{current_line} == 0) { return; } my @text = split(/\n/,$self->{text}); if ($self->{ypos} == 0 && $self->{current_line} == $self->{top_line}) { $self->{top_line}--; $self->{current_line}--; $self->{position} = $self->{position} - length($text[$self->{current_line}])-1; return; } $self->{current_line}--; $self->{ypos}--; $self->{position} = $self->{position} - length($text[$self->{current_line}])-1; until ($self->{xpos} <= length($text[$self->{ypos}])) { $self->{xpos}--; $self->{position}--; } return; } if ($key eq KEY_LEFT) { if ($self->{position} == 0 || $self->{xpos} == 0) { return; } $self->{xpos}--; $self->{position}--; return; } if ($key eq KEY_RIGHT) { my @text = split(/\n/,$self->{text}); if ($self->{xpos} == length($text[$self->{ypos}]) || $self->{position} == length($self->{text})) { return; } $self->{xpos}++; $self->{position}++; return; } if ($key eq KEY_DC) { if ($self->{position} == length($self->{text}) || $self->{editable} == 0) { return; } $self->{text} = (substr($self->{text},0,$self->{position})).(substr($self->{text},$self->{position}+1)); return; } if ($key eq KEY_BACKSPACE) { if ($self->{position} == 0 || $self->{editable} == 0) { return; } $self->{text} = (substr($self->{text},0,$self->{position}-1)).(substr($self->{text},$self->{position})); if ($self->{current_line} == $self->{top_line} && $self->{xpos} == 0) { $self->{top_line}--; } $self->{position}--; if ($self->{xpos} == 0) { my @text = split(/\n/,$self->{text}); $self->{ypos}--; $self->{current_line}--; $self->{position} = $self->{position} - length($text[$self->{current_line}]); until ($self->{xpos} == length($text[$self->{ypos}])) { $self->{xpos}++; $self->{position}++; } } else { $self->{xpos}--; } return; } if ($self->{editable} == 1) { if ($key eq "\n") { if ($self->{ypos} == $self->{height}) { $self->{top_line}++; $self->{ypos}--; } $self->{xpos} = $self->{x}-2; $self->{ypos}++; $self->{current_line}++; } $self->{text} = (substr($self->{text},0,$self->{position})).($key).(substr($self->{text},$self->{position})); $self->{position}++; if ($self->{xpos} == $self->{width}) { $self->key("\n"); $self->{xpos} = $self->{x}-1; } $self->{xpos}++; $self->redraw(); return; } return; } sub signal_connect { my $self = shift; my ($signal,$func) = @_; $self->{$signal} = $func; return; }