package Gtk2::SizeRecall;

$VERSION = 0.01;

use strict;
use Carp;

use FileHandle;

sub get_app			{ shift->{app}				}
sub get_data			{ shift->{data}				}
sub get_widgets			{ shift->{widgets}			}

sub set_app			{ shift->{app}			= $_[1]	}
sub set_data			{ shift->{data}			= $_[1]	}
sub set_widgets			{ shift->{widgets}		= $_[1]	}

sub new {
	my $class = shift;
	my ($app) = @_;

	if ( not defined $app ) {
		require File::Basename;
		$app = File::Basename::basename($0);
	}

	my $self = bless {	
		app	=> $app,
		widgets	=> {},
		data	=> {},
	}, $class;
	
	$self->load_data;
	
	return $self;
}

sub DESTROY {
	my $self = shift;
	
	$self->save_data;
	
	1;
}

sub save_data {
	my $self = shift;
	
	my $dir  = "$ENV{HOME}/.gtk2recall";
	my $file = "$dir/".$self->get_app;
	my $fh   = FileHandle->new;
	my $data = $self->get_data;

	mkdir ($dir, 0755) unless -d $dir;
	
	open ($fh, "> $file") or die "can't write $file";
	while ( my ($name, $size) = each %{$data} ) {
		croak "Widget name '$name' has colons, which is forbidden"
			if $name =~ /:/;
		print $fh "$name:$size->[0]:$size->[1]\n";
	}
	close $fh;
	
	1;
}

sub load_data {
	my $self = shift;
	
	my $dir  = "$ENV{HOME}/.gtk2recall";
	my $file = "$dir/".$self->get_app;
	
	return if not -f $file;
	
	my %data;
	my $fh = FileHandle->new;

	open ($fh, $file) or die "can't read $file";
	my ($name, $width, $height);
	while ( <$fh> ) {
		chomp;
		($name, $width, $height) = split(":", $_);
		$data{$name} = [ $width, $height ];
	}
	close $fh;
	
	$self->set_data(\%data);
	
	1;
}

sub set_size {
	my $self = shift;
	my ($name, $width, $height) = @_;
	
	$self->get_data->{$name} = [ $width, $height ];

	1;
}

sub get_size {
	my $self = shift;
	my ($name) = @_;
	
	return unless exists $self->get_data->{$name};
	return @{$self->get_data->{$name}};
}

sub add {
	my $self = shift;
	my ($widget, $name, $default_width, $default_height) = @_;
	
	croak "usage: Gtk2::SizeRecall->add(widget,name.".
	      "[,default-width,default-height])"
		if not defined $widget or not defined $name;

	#-- get last stored size of the widget
	my ($width, $height) = $self->get_size ($name);

	#-- if no information was stored, use the default values if available
	if ( not defined $width ) {
		$self->set_size ($name, $default_width, $default_height);
		$width  = $default_width;
		$height = $default_height;
	}
	
	#-- prevent multiple registering
	croak "Widget '$name' already registered to Gtk2::SizeRecall"
		if exists $self->get_widgets->{$name};
	
	#-- resize the widget to it's last size (or default size)
	$widget->set_size_request($width, $height);

	#-- we need to access the parent window of this widget,
	#-- but we determine this later when we got the first
	#-- 'size-allocate' signal, because the widget may not
	#-- have a parent window yet.
	my $window = "undetermined";

	#-- connect the size-allocate signal to track resizing
	my $signal = $widget->signal_connect("size-allocate", sub {
		#-- store new size in the object
		$self->set_size ($name, $_[1]->width, $_[1]->height);

		#-- determine the parent window of the widget,
		#-- but only the first time the signal arrives.
		#-- (I know, this breaks reparenting...) 
		if ( $window eq 'undetermined' ) {
			$window = $widget;
			while ( $window = $window->get_parent ) {
				last if $window->isa("Gtk2::Window");
			}
		}			

		#-- the parent window of the widget can't
		#-- shrink beyond its actual size if we don't
		#-- issue ->size_request(undef,undef) on it.
		$window->set_size_request(undef,undef)
			if $window and $window->get_resizable;
		1;
	});

	#-- store widget and signal
	$self->get_widgets->{$name} = [ $widget, $signal ];

	1;
}

sub remove {
	my $self = shift;
	my ($name) = @_;

	croak "usage: Gtk2::SizeRecall->remove(name)"
		if not defined $name;

	if ( not exists $self->get_widgets->{$name} ) {
		warn "Widget '$name' not registered to Gtk2::SizeRecall";
		return 1;
	}
	
	my ($widget, $signal) = @{$self->get_widgets->{$name}};

	$widget->signal_handler_disconnect ($signal);
	
	delete $self->get_widgets->{$name};
	
	1;
}

1;

__END__

=head1 NAME

Gtk2::SizeRecall - Persistently store and restore widget sizes

=head1 SYNOPSIS

  use Gtk2::SizeRecall;

  $recall = Gtk2::SizeRecall->new();

  $recall->add($artists_widget, "artists");
  $recall->add($albums_widget,  "albums");

  $recall->remove("artists");
  $recall->remove("albums");

=head1 ABSTRACT

Gtk2::SizeRecall transparently stores and restores widget sizes.

=head1 DESCRIPTION

Gtk2::SizeRecall transparently stores and restores widget sizes. You need it
if you have user resizable containers (e.g. windows or panes) and you want
to give the user the comfort of automatically recalling the last state of
the GUI.

You simply create one Gtk2::SizeRecall instance for your application and
assign all widgets whose size should be tracked to this instance.

The default behaviour of Gtk2::SizeRecall is to store the values in
a file located in this directory:

  ~/.gtk2recall

But you can easily overide this default behaviour by subclassing
Gtk2::SizeRecall, which is described later in this document.

=head1 METHODS

=over

=item $recall = Gtk2::SizeRecall->new ([$app])

Creates a new Gtk2::SizeRecall object for your application.
By default Gtk2::SizeRecall stores all data in a file which has the
same name as the running Perl program (resp. the basename of $0).
You can change this filename with the optional $app parameter.

=item $recall->add($widget, $name[, $default_width, $default_height])

This adds a widget associated with the given name to the list of
objects whose size will be tracked. The tracking is actually done
by connecting to the widget's I<size-allocate> signal.

This method is also responsible for restoring the widget's size. If no
stored size information is available (e.g. for the first program start),
the widget will get the given default size. If the default values are
omitted it will be left alone with the size it gets from Gtk.

=item $recall->remove($name)

Removes the widget referenced by $name from the list of tracked widgets.
This only disconnects the I<size-allocate> signal, the currently gathered
size information is still kept in memory, e.g. for future recalling.

=back

=head1 CAVEATS

There are some things you should consider when using this module. Be
careful when deciding which widgets you want to control with
Gtk2::SizeRecall.

Due to technical limits (at least I don't now how to deal with these)
it's not possible to control the size of a window directly with this
module. But luckily enough this is not necessary at all ;)

Usually there are only few widgets which resize, when the user changes
the window (or pane) size. Add only these widgets to Gtk2::SizeRecall
and everything should work as expected. The window itself resizes
automatcally to the correspondent size, if the widgets inside are under
control of Gtk2::SizeRecall.

=head1 SUBCLASSING

If you're not satisfied with the way Gtk2::SizeRecall stores the data
in the filesystem, you can overide this default behaviour by subclassing.

Internally the widget sizes are stored in a hash named 'data'. You can
access this hash using:

  $data = $recall->get_data;
  $recall->set_data($data);

Key of this hash is the widget's $name and the value is an array reference
with two elements: [ $width, $height ].

If you want to keep the internal storage but like to change the persistent
storage behaviour you simply need to overide these two methods:

=over

=item $recall->load_data()

Load the widget's size information from an external data source, e.g.
filesystem, database or whatever and build the data hash out if it.
This method is called within the I<new> constructor of Gtk2::SizeRecall.

=item $recall->save_data()

Save the actual data hash to an external data source. This method
is called from Gtk2::SizeRecall's destructor.

=back

If for some reason you also don't want to use the internal data hash
you can overide these two methods:

=over

=item ($width, $height) = $recall->get_size ($name)

Return the actual size of the widget $name.

=item $recall->set_size($name, $width, $height)

Set a new size of the widget $name. This method only stores the
values in the $recall object. It don't need to do any widget
resizing. This is implemented by the add() method.

=head1 SEE ALSO

Perl(1), Gtk2(3pm).

=head1 AUTHORS

 Jörn Reder <joern at zyn dot de>

=head1 COPYRIGHT AND LICENSE

Copyright 2004 by the Gtk2-Perl team.

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Library General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Library General Public License for more
details.

You should have received a copy of the GNU Library General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 59
Temple Place - Suite 330, Boston, MA  02111-1307  USA.

=cut
