# $Id: CPronto.pm,v 1.2 2001/02/03 14:56:34 muhri Exp $
# -*- perl -*-
# coypright (c) 2000 Charlie Schmidt <ishamael@themes.org>

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;
}

