use strict;
use warnings;

package Prima::Tie::Array;
use Tie::Array;
use vars qw(@ISA);
@ISA = qw(Tie::Array);

sub TIEARRAY {
	my ($class, $widget, $property) = @_;
	bless [ $widget, $property], $class;
}

sub FETCH {
	my ($self, $idx) = @_;
	my ($widget, $property) = @$self;
	$widget-> $property()-> [$idx];
}

sub STORE {
	my ($self, $idx, $value) = @_;
	my ($widget, $property) = @$self;
	my $array = $widget-> $property();
	$array-> [$idx] = $value;
	$widget-> $property($array);
	return $value;
}

sub FETCHSIZE {
	my ($self) = @_;
	my ($widget, $property) = @$self;
	scalar @{$widget-> $property()};
}

sub STORESIZE {
	my ($self, $size) = @_;
	my ($widget, $property) = @$self;
	my $array = $widget-> $property();
	$#$array = $size - 1;
	$widget-> $property($array);
	return $size;
}


sub EXISTS
{
	my ( $self, $idx) = ( $_[0], abs($_[1]));
	return $idx >= 0 && $idx < $self-> FETCHSIZE;
}

sub DELETE
{
}


sub UNTIE {
	my ($obj,$count) = @_;
	die "untie attempted while $count inner references still exist" if $count;
}
#
# Prima::Tie::items - ties an array to a widget's items property
#

package Prima::Tie::items;
use Tie::Array;
use vars qw(@ISA);
@ISA = qw(Tie::Array);

sub TIEARRAY {
	my ($class, $widget) = @_;
	bless {
		self => $widget,
		map {
			$_  => $widget-> can($_),
		} qw(count get_items replace_items insert_items add_items delete_items)
	}, $class;
}

sub FETCH {
	my ($self, $idx) = @_;
	$self-> {self}-> items-> [$idx];
}

sub STORE {
	my ($self, $idx, $value) = @_;
	my $size = $self-> FETCHSIZE;
	if ( $idx == $size) {
		if ( $self-> {add_items}) {
			$self-> {self}-> add_items($value);
		} else {
			my $i = $self-> {self}-> items;
			push @$i, $value;
			$self-> {self}-> items($i);
		}
		return $value;
	} elsif ( $idx > $size) {
		die "Modification of non-creatable array value attempted, subscript $idx";
	} elsif ( $idx < 0) {
		my $ndx = $idx;
		$idx = $size - $idx;
		die "Modification of non-creatable array value attempted, subscript $ndx"
			if $idx < 0;
	}
	if ( $self-> {replace_items}) {
		$self-> {self}-> replace_items($idx, $value);
	} else {
		my $i = $self-> {self}-> items;
		$i-> [$idx] = $value;
		$self-> {self}-> items($i);
	}
	return $value;
}

sub FETCHSIZE {
	my ($self) = @_;
	if ( $self-> {count}) {
		return $self-> {self}-> count;
	} else {
		return scalar @{$self-> {self}-> items};
	}
}

sub STORESIZE {
	my ($self, $new_size) = @_;
	my $old_size =$self-> FETCHSIZE;
	die "Modification of non-creatable array value attempted, size $new_size"
		if $new_size < 0 || $new_size > $old_size;
	if ( $self-> {delete_items}) {
		$self-> {self}-> delete_items( $new_size .. $old_size );
	} else {
		my $i = $self-> {self}-> items;
		$#$i = $new_size - 1;
		$self-> {self}-> items($i);
	}
	return $new_size;
}

sub PUSH {
	my $self = shift;
	if ( $self-> {add_items}) {
		$self-> {self}-> add_items(@_);
	} else {
		my $i = $self-> {self}-> items;
		push @$i, @_;
		$self-> {self}-> items($i);
	}
}

sub SPLICE {
	my $self = shift;
	my $sz  = $self-> FETCHSIZE;
	my $off = (@_) ? shift : 0;
	$off += $sz if ($off < 0);
	die "Modification of non-creatable array value attempted, offset $off"
		if $off < 0;
	my $len = (@_) ? shift : $sz - $off;
	$len += $sz - $off if $len < 0;
	die "Modification of non-creatable array value attempted, length $len"
		if $len < 0;
	my @result;
	my ( $i, $iok);
	if ( $self-> {get_items}) {
		@result = $self-> {self}-> get_items( $off .. $off + $len - 1);
	} else {
		$i = $self-> {self}-> items;
		@result = @$i[ $off .. $off + $len - 1];
		$iok = 1;
	}
	$off = $sz if $off > $sz;
	$len -= $off + $len - $sz if $off + $len > $sz;
	die "Modification of non-creatable array value attempted, length $len"
		if $len < 0;

	if ( @_ == $len) {
		if ( $self-> {replace_items}) {
			$self-> {self}-> replace_items( $off, @_);
		} else {
			$i = $self-> {self}-> items, $iok = 1 unless $iok;
			splice( @$i, $off, $len, @_);
			$self-> {self}-> items( $i);
		}
	} else {
		if ( $self-> {delete_items} && $self-> {insert_items}) {
			$self-> {self}-> delete_items( $off .. $off + $len - 1);
			$self-> {self}-> insert_items( $off, @_);
		} else {
			$i = $self-> {self}-> items, $iok = 1 unless $iok;
			splice( @$i, $off, $len, @_);
			$self-> {self}-> items( $i);
		}
	}
	return wantarray ? @result : pop @result;
}


sub EXISTS
{
	my ( $self, $idx) = ( $_[0], abs($_[1]));
	return $idx >= 0 && $idx < $self-> FETCHSIZE;
}

sub DELETE
{
}


sub UNTIE {
	my ($obj,$count) = @_;
	die "untie attempted while $count inner references still exist" if $count;
}

#
# Prima::Tie::Scalar - ties a scalar to a widget's scalar properties
#

package Prima::Tie::Scalar;

sub TIESCALAR {
	my ($class, $widget, $property) = @_;
	bless [ $widget, $property ], $class;
}

sub FETCH {
	my ($self) = @_;
	my ($widget, $property) = @$self;
	$widget-> $property();
}

sub STORE {
	my ($self, $value) = @_;
	my ( $widget, $property) = @$self;
	$widget-> $property($value);
	return $value;
}

sub UNTIE {
	my ($obj,$count) = @_;
	die "untie attempted while $count inner references still exist" if $count;
}

# some popular names

package Prima::Tie::text;
use vars qw(@ISA);
@ISA = qw(Prima::Tie::Scalar);
sub TIESCALAR { bless [ $_[1], 'text' ], $_[0] }

package Prima::Tie::value;
use vars qw(@ISA);
@ISA = qw(Prima::Tie::Scalar);
sub TIESCALAR { bless [ $_[1], 'value' ], $_[0] }


1;

=pod

=head1 NAME

Prima::Tie - tie widget properties to scalars or arrays.

=head1 DESCRIPTION

Prima::Tie contains two abstract classes, C<Prima::Tie::Array> and
C<Prima::Tie::Scalar>, which tie an array or a scalar to a widget's arbitrary
array or scalar property.  Also, it contains classes C<Prima::Tie::items>,
C<Prima::Tie::text>, and C<Prima::Tie::value>, which tie a variable to a widget's I<items>, I<text>,
and I<value> property respectively.

=head1 SYNOPSIS

	use Prima::Tie;

	tie @items, 'Prima::Tie::items', $widget;

	tie @some_property, 'Prima::Tie::Array', $widget, 'some_property';

	tie $text, 'Prima::Tie::text', $widget;

	tie $some_property, 'Prima::Tie::Scalar', $widget, 'some_property';

=head1 USAGE

These classes provide immediate access to a widget's array and scalar property,
in particular to popular properties as I<items> and I<text>. It is considerably simpler to say

	splice(@items,3,1,'new item');

than to say

	my @i = @{$widget->items};
	splice(@i,3,1,'new item');
	$widget->items(\@i);

You can work directly with the text or items rather than at a remove.  Furthermore, if the
only reason you keep an object around after creation is to access its text or items, you no
no longer need to do so:

	tie @some_array, 'Prima::Tie::items', Prima::ListBox->create(@args);

As opposed to:

	my $widget = Prima::ListBox->create(@args);
	tie @some_array, 'Prima::Tie::items', $widget;

C<Prima::Tie::items> requires C<::items> property to be available on the widget.
Also, it takes advantage of additional C<get_items>, C<add_items>, and the like
if available.

=head2 Prima::Tie::items

The class is applicable to C<Prima::ListViewer>, C<Prima::ListBox>,
C<Prima::Header>, and their descendants, and in limited fashion to
C<Prima::OutlineViewer> and its descendants C<Prima::StringOutline> and
C<Prima::Outline>.

=head2 Prima::Tie::text

The class is applicable to any widget.

=head2 Prima::Tie::value

The class is applicable to C<Prima::GroupBox>, C<Prima::ColorDialog>,
C<Prima::SpinEdit>, C<Prima::Gauge>, C<Prima::Slider>, C<Prima::CircularSlider>,
and C<Prima::ScrollBar>.

=head1 COPYRIGHT

Copyright 2004 Teo Sankaro

This program is distributed under the BSD License.
(Although a credit would be nice.)

=head1 AUTHORS

Teo Sankaro, E<lt>teo_sankaro@hotmail.comE<gt>.
Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>.

=cut