#===============================================================================
# Tie/Tk/Text.pm
#===============================================================================
package Tie::Tk::Text;
use strict;

use vars qw'$VERSION';
$VERSION = '0.92';

# Notes:
# * Text widgets use 1-based indexing so line number = array index + 1
# * Text widgets *always* have a newline at the end. It's returned by
#   $w->get('1.0', 'end') but isn't deleted by $w->delete('1.0', 'end')
# * $w->get('1.0', '1.0 lineend') doesn't include the trailing newline.
#   You need to use one of these instead:
#       $w->get('1.0', '2.0')
#       $w->get('1.0', '1.0 lineend + 1 chars)

sub TIEARRAY {
	my $class  = shift;
	my $widget = shift;
	return bless \$widget, $class;
}


sub CLEAR {
	my $self = shift;
	$$self->delete('1.0', 'end');
}


sub FETCH {
	my $self = shift;
	my $line = shift() + 1;
	return $$self->get("$line.0", "$line.end + 1 chars");
}


sub STORE {
	my $self = shift;
	my $idx  = shift;
	my $text = shift;
	my $line = $idx + 1;

	$$self->insert('end', "\n")	while ($self->FETCHSIZE <= $idx);
	$self->_delete($idx);
	$$self->insert("$line.0", $text);
}


sub FETCHSIZE {
	my $self = shift;
	my $c    = $$self->get('end - 1 chars');
	my $n    = $c eq "\n" ? 2 : 1; # cf. module notes
	my $l    = (split(/\./, $$self->index("end - $n chars")))[0];
	return $l;
}


sub STORESIZE {
	my $self = shift;
	my $size = shift;

	if ($self->FETCHSIZE > $size) {
		my $n = $size + 1;
		$$self->delete("$n.0", 'end');
	}
	else {
		$$self->insert('end', "\n") while ($self->FETCHSIZE < $size);
	}
}


sub EXISTS {
	my $self = shift;
	my $idx  = shift;
	return $idx < $self->FETCHSIZE;
}


sub DELETE {
	my $self = shift;
	my $idx  = shift;
	my $text = $self->EXISTS($idx) ? $self->FETCH($idx) : undef;

	my $l = $idx + 1;
	$$self->delete("$l.0", "$l.0 lineend"); # don't delete the \n

	# collapse trailing "undef" (\n) values
	while ($self->FETCH($self->FETCHSIZE - 1) eq "\n") {
		$self->POP;
	}
	
	return $text;
}


sub PUSH {
	my $self = shift;
	$$self->insert('end', $_) foreach @_;
}


sub POP {
	my $self = shift;
	return $self->_delete($self->FETCHSIZE - 1);
}


sub UNSHIFT {
	my $self = shift;
	$$self->insert('1.0', $_) foreach reverse @_;
}


sub SHIFT {
	my $self = shift;
	return $self->_delete(0);
}


sub SPLICE {
	my $self = shift;
	my $o    = shift; # offset
	my $l    = shift; # length
	
	$o = 0                          unless defined $o;
	$o = $self->FETCHSIZE + $o      if     $o < 0;
	$l = $self->FETCHSIZE - $o      unless defined $l;
	$l = $self->FETCHSIZE - $o + $l if     $l < 0;

	my @deleted;
	foreach my $i (reverse ($o .. $o + $l - 1)) {
		unshift @deleted, $self->_delete($i);
	}

	foreach my $r (reverse @_) {
		my $x = $o + $l - 1;
		$$self->insert("$x.0", $r);
	}

	return @deleted;
}


sub EXTEND {}

#-------------------------------------------------------------------------------
# Method  : Remove and (really) delete element
# Purpose : 
# Notes   : 
#-------------------------------------------------------------------------------
sub _delete {
	my $self = shift;
	my $idx  = shift;
	my $text = $self->FETCH($idx);
	my $line = $idx+1;

	$$self->delete("$line.0", "$line.end + 1 chars");
	return $text;
}

1;

__END__

=pod

=head1 NAME

Tie::Tk::Text - Access Tk text widgets as arrays.

=head1 SYNOPSIS

  use Tie::Tk::Text;

  my $w = $mw->Text()->pack();
  tie my @text, 'Tie::Tk::Text', $w;

  $w->insert('end', "foo\nbar\nbaz\n");
  
  print $text[1]; # "bar\n"

=head1 DESCRIPTION

This module defines a class for tie()ing Tk text widgets to an 
array, allowing them to be accessed as if they were an array of lines.

It's not expected that anyone will actually want to populate and manipulate 
their text widgets this way, though you are of course free to do so. This module 
was created to make text widgets accessible to functions that expect an array 
reference as their input. (e.g. Algorithm::Diff::sdiff) You can do that with 
read-only support (FETCH and FETCHSIZE). All of the methods (PUSH, POP, STORE, 
etc.) are included for completeness.

Note: This documentation refers to "Tk text" widgets rather than "Tk::Text" 
ones. That's because it supports anything that uses the same API as a Tk text 
widget. It works with Perl/Tk and Tkx and should work with Tcl::Tk as well.

=head1 CAVEATS

Arrays use zero-based indexing. Text widgets use one-based indexing. Ergo, line 
five is at index four.

Lines end in "\n". Be careful about what you add or you could get odd results. 
For example, doing C<$text[3] = 'foo'> will replace the contents of line four 
with 'foo' but will join lines four and five because you didn't include a 
newline at the end of the string. Similarly, C<$text[3] = "foo\nbar\n"> will 
replace line four with "foo\n" and B<insert> "bar\n" before line five.

=head1 LIMITATIONS

There's no support for tags or a lot of other things that you can do with text 
widgets. There isn't supposed to be. This is not a bug.

=head1 AUTHOR

Michael J. Carman C<< <mjcarman@cpan.org> >>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006,2009 by Michael J. Carman

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
