######################################## SOH ###########################################
## Function : Additional Tk Class for Listbox-type HList with Data per Item, Sorting
##
## Copyright (c) 2004 - 2009 Michael Krause. All rights reserved.
## Special Thanks to B<Shaun Wandler> <wandler@unixmail.compaq.com>, whose
## Tk::HeaderResizeButton V1.3 has been used here.
## This program is free software; you can redistribute it and/or modify it
## under the same terms as Perl itself.
## 
## History  : V0.1	14-Jan-2004 	Class compound from HList, ResizeButton. MK
##            V0.2	20-Jan-2004 	Bugfix 'headerCreate' was not catched and %args->@args. MK
##            V0.3	14-Jul-2005 	Bugfix 'header Height' was not called correctly for TK 804.xx. MK
##            V0.4	13-Oct-2006 	Enhancement based on feedback from Rob Seegel. MK
##            V0.5	06-Apr-2009 	Enhancement based on feedback from Kai Ludick (DblClick on Header always raised HBttn-Cmd-CB). MK
##            V0.6	07-Apr-2009 	Enhancement based on feedback from Kai Ludick (configurable closedcolWidth, ResizeWidth). MK
######################################## EOH ###########################################

##############################################
### Use
##############################################
use Tk::HList;
use Tk::ItemStyle;
use Tk qw(Ev);

use strict;
use Carp;

use vars qw ($VERSION);
$VERSION = '0.6';

########################################################################
package Tk::HeaderResizeButton;
#########################################################################
# Tk::HeaderResizeButton
# NOTE: This is an improved version of the Tk::ResizeButton
# Summary:  This widget creates a button for use in an HList header which
#           provides methods for resizing a column. This was heavily 
#	    leveraged from Columns.pm by Damion Wilson.
# Author:   Shaun Wandler, Updated by Slaven Rezic and Frank Herrmann, Michael Krause
# Date:     2009/04/07
# Revision: 0.6
#########################################################################=
# Note: For space reason all other documentation of Tk::HeaderResizeButton has
# been removed See Tk::HeaderResizeButton-Pod for details.
#
use base qw(Tk::Derived Tk::Button);

Construct Tk::Widget 'HeaderResizeButton';

sub ClassInit {
    my ($class, $window) = @_;

    $class->SUPER::ClassInit($window);
	$window->bind($class, '<ButtonRelease-1>', 'ButtonRelease');
	$window->bind($class, '<ButtonPress-1>',   'ButtonPress');
	$window->bind($class, '<Motion>',          'ButtonOver');
	$window->bind($class, '<ButtonRelease-3>', 'ColumnFullSize');
	$window->bind($class, '<Double-1>',        'ButtonDouble1');
	# Override these ones too
	$window->bind($class, '<Enter>', 'BttnEnter' );
	$window->bind($class, '<Leave>', 'BttnLeave' );
	return $class;
}

sub Populate
{
	my ($this, $args) = @_;

	# CREATE THE RESIZE CONTROL
	my $r_Widget;
	my $r_width = delete $args->{-resizerwidth} || 1;
	$r_Widget = $this->Component(
		'Frame'      => 'Trim_R',
		#-background  => 'white',
		#-relief      => 'raised',
		-borderwidth => 1,
		-width       => $r_width,
		-cursor 	 => 'sb_h_double_arrow',
	)->place(
		-bordermode => 'outside',
		-relheight => '1.0',
		-anchor	=> 'ne',
		-relx  	=> '1.0',
	);

	# CREATE THE COLUMNBAR
	$this->{columnBar} = $this->parent->Frame(
		-background  => 'white',
		-relief      => 'raised',
		-borderwidth => 2,
		-width       => 2,
	);

	$r_Widget->bind( '<ButtonRelease-1>'	=> sub { $this->ButtonRelease(1); } );
	$r_Widget->bind( '<ButtonPress-1>'		=> sub { $this->ButtonPress(1); } );
	$r_Widget->bind( '<Motion>' 			=> sub { $this->ButtonOver(1); } );
	$r_Widget->bind( '<Enter>'				=> sub { $this->TrimEnter(); } ); 
	$r_Widget->bind( '<Leave>'				=> sub { $this->TrimLeave(); } );

	# Override these ones too
	$this->bind( '<Enter>'					=> sub { $this->BttnEnter(); } );
	$this->bind( '<Leave>'					=> sub { $this->BttnLeave(); } );

	$this->SUPER::Populate($args);
	$this->ConfigSpecs(
		-column 			=> [ [ 'SELF', 'PASSIVE' ], 'column', 'Column', 0 ],
		-minwidth			=> [ [ 'SELF', 'PASSIVE' ], 'minwidth', 'MinWidth', 50 ], 
		-closedminwidth		=> [ [ 'SELF', 'PASSIVE' ], 'closedminwidth', 'ClosedMinWidth', 10 ], 
    	-command 			=> [ 'CALLBACK',undef,undef, sub {}],
		-activebackground	=> [ [ 'SELF', 'PASSIVE' ], 'activebackground', 'activebackground', $this->SUPER::cget(-background) ],
		-activeforeground	=> [ [ 'SELF', 'PASSIVE' ], 'activeforeground', 'activeforeground', 'red' ],
		-buttondownrelief	=> [ [ 'SELF', 'PASSIVE' ], 'buttondownrelief', 'buttondownrelief', 'groove' ],
		-relief 			=> [ [ 'SELF', 'PASSIVE' ], 'relief', 'relief', 'flat' ],
		-pady				=> [ [ 'SELF', 'PASSIVE' ], 'pady', 'pady', 0 ],
		-padx				=> [ [ 'SELF', 'PASSIVE' ], 'padx', 'padx', 0 ],
		-pady				=> [ [ 'SELF', 'PASSIVE' ], 'pady', 'pady', 0 ],
		-anchor				=> [ [ 'SELF', 'PASSIVE' ], 'anchor', 'Anchor', 'w' ],
		-lastcolumn			=> [ [ 'SELF', 'PASSIVE' ], 'lastcolumn', 'LastColumn', 0 ],
		-takefocus			=> [ [ 'SELF', 'PASSIVE' ], 'takefocus', 'TakeFocus', 1 ],
	);

	# Keep track of last trim widget
	$this->{m_LastTrim} = $r_Widget;
	# Initialize the Enter/Leave level counter
	$this->{m_Level} = 0;
}

# CALLED IF WE ENTER THE HEADER AREA
sub BttnEnter
{
	my $this = shift;
	#print "BttnEnter\n";
	$this->StateSalvation(1);
	$this->configure(-relief => $this->cget('-buttondownrelief')) if $this->{m_ButtonPress};

}
# CALLED IF WE LEAVE THE HEADER AREA
sub BttnLeave
{
	my $this = shift;
	#print "BttnLeave\n";
	$this->StateSalvation(-1);
	$this->configure(-relief => $this->{m_relief}) if $this->{m_relief};
}
# CALLED IF WE ENTER THE TRIM AREA
sub TrimEnter
{
	my $this = shift;
	if ($this->cget(-lastcolumn)) {
		$this->Subwidget('Trim_R')->configure(-cursor => undef);
	}
	else {
		$this->Subwidget('Trim_R')->configure(-cursor => 'sb_h_double_arrow');
	}
	$this->ButtonOver(1);
	$this->StateSalvation(2);
}
# CALLED IF WE LEAVE THE TRIM AREA
sub TrimLeave
{
	my $this = shift;
	$this->StateSalvation(-2);
	$this->HideColumnBar();
}

# CALLED IF WE CLICK/DOUBLECLICK
sub OpenCloseColumn
{
	my $this = shift;

	my $column = $this->cget('-column');
	if ($this->{m_ColumClosed}{$column}) {
		$this->{m_ColumClosed}{$column} = 0;
		if ($this->{m_LastColumWidth}) {
			$this->parent->columnWidth($column, $this->{m_LastColumWidth});
		}
		else {
			$this->parent->columnWidth($column, '');
			$this->{m_LastColumWidth} = $this->parent->columnWidth($column);
		}
		$this->configure(-anchor => $this->{m_LastAnchor}) if $this->{m_LastAnchor};
	}
	else {
		$this->{m_ColumClosed}{$column} = 1;
		$this->{m_LastColumWidth} = $this->parent->columnWidth($column);
		$this->parent->columnWidth($column,  $this->cget('-closedminwidth'));
		$this->{m_LastAnchor} = $this->cget('-anchor');
		$this->configure(-anchor => 'w');
	}
	
}
# CALLED TO RESIZE A COLUMN TO THE NEEDED EXTENT
sub ColumnFullSize
{
	my $this = shift;
	my $column = $this->cget(-column);
	if ($this->{m_ColumClosed}{$column}) {
		delete $this->{m_LastColumWidth}; # This ensure immediate update
		$this->OpenCloseColumn();
	}
	else {
		$this->parent->columnWidth($column, '');
	}
}

## Event Handlers
sub ButtonPress
{
	my ($this, $p_Trim) = @_;
	$this->{m_LastEvent} = 'ButtonPress';	
	$this->{m_relief} = $this->cget('-relief');
	if ($this->ButtonEdgeSelected() || $p_Trim) {
		$this->{m_EdgeSelected} = 1;
		$this->{m_X} = $this->pointerx() - $this->rootx();
		$this->ButtonOver();
	}
	else {
		$this->configure(-relief => $this->cget('-buttondownrelief'));
		$this->{m_X} = -1;
	}
	$this->{m_ButtonPress} = 1;
}

sub ButtonRelease
{
	my ( $this, $p_Trim ) = @_;
	delete $this->{m_ButtonPress};
	$this->{m_EdgeSelected} = 0;
	$this->configure(-relief => $this->{m_relief});
	if ($this->{columnBar}) {
		$this->HideColumnBar();
	}

	if ($this->{m_X} >= 0) {
		my $l_NewWidth = ( $this->pointerx() - $this->rootx() );
		my $hlist = $this->parent;
		my $col   = $this->cget( -column );
		# Better resize to minimum than to do nothing
		$l_NewWidth = $this->cget(-minwidth) if ($l_NewWidth + 5) < $this->cget( -minwidth );
		$hlist->columnWidth( $col, $l_NewWidth + 5 );

		$this->GeometryRequest( $l_NewWidth, $this->reqheight() );
	}
	elsif ( !$this->ButtonEdgeSelected() ) {
		# Run only if we're still over the header and if we're in TRUE Release Mode (No Dbl-Click)
		if ($this->cget('-state') eq 'active') {
			$this->after(500, sub { $this->Callback(-command => $this) if $this->{m_LastEvent} eq 'ButtonPress' } );
		}
	}

	$this->{m_X} = -1;
}

# CALLED IF WE DOUBLECLICK
sub ButtonDouble1
{
	my $this = shift;
	$this->{m_LastEvent} = 'DoubleClick';
	$this->OpenCloseColumn();
}

# CHECK IF THE RESIZE CONTROL IS SELECTED
sub ButtonEdgeSelected
{
	my $this = shift;
	return ( $this->pointerx() - $this->{m_LastTrim}->rootx() ) > -1;
}

# CHANGE THE CURSOR OVER THE RESIZE CONTROL
sub ButtonOver
{
	my ($this, $p_Trim) = @_;
	if ( $this->{'m_EdgeSelected'} || $this->ButtonEdgeSelected() || $p_Trim ) {
		$this->MoveColumnBar() if $this->{columnBar};
	}
}
# AVOID ACTIVATING THE BUTTON, IF WE ARE IN THE TRIM
sub StateSalvation
{
	my ($this, $newlevel) = @_;
	if ($newlevel > 0) {
		$this->{m_Level}  |= $newlevel;
	}
	else {
		$this->{m_Level}  &= ~$newlevel;
	}
	if ($this->{m_Level} == 1 and not $this->{m_EdgeSelected}) {
		$this->configure(-state => 'active');
	}
	else {
		$this->configure(-state => 'normal');
	}
}

# Move a column bar which displays on top of the HList widget
# to indicate the eventual size of the column.
sub MoveColumnBar
{
	my $this = shift;

	my $hlist = $this->parent;
	my $height = $hlist->height() - $this->height();
	my $x      = $hlist->pointerx() - $hlist->rootx() + 1; # +1 for move right into gap

	$this->{columnBar}->place(
		'-x'      => $x,
		'-height' => $height - 5,
		'-y'      => $this->height() + 5,
	) unless $this->cget(-lastcolumn);
}
# REMOVES IT FROM DISPLAY without destroying it
sub HideColumnBar
{
	my $this = shift;
	$this->{columnBar}->placeForget();
}

1;


# sub EnterFocus
# {
# 	print "reached EnterFocus of HList\n";
#  my $w  = shift;
#  	print "widget is >$w<\n";
# # return unless defined $w;
# # my $Ev = $w->XEvent;
# # my $d  = $Ev->d;
# # $w->Tk::focus() if ($d eq 'NotifyAncestor' ||  $d eq 'NotifyNonlinear' ||  $d eq 'NotifyInferior');
# 	
# }

########################################################################
package Tk::HListplus;

use base qw (Tk::Derived Tk::HList);

Construct Tk::Widget 'HListplus';

# needed to include also the aliased commands
use Tk::Submethods ( 'header'    => [qw(configure cget create delete exists size)] );


#---------------------------------------------
# internal Setup function
#---------------------------------------------
sub CreateArgs
{
    my ($class, $this, $args) = @_;		

	# New for V0.4 auto-increase the Column-num by 1 to have a more Win32 behavior
	$args->{-columns}++ if $args->{-columns};

	return $class->SUPER::CreateArgs($this, $args);
}
sub Populate
{
    my ($this, $args) = @_;		

	my $data_background = delete $args->{-databackground};
	$data_background = $this->cget ('-background') unless defined $data_background;
	$this->{m_headerstyle} = delete $args->{-headerstyle} || $this->ItemStyle ('window', -padx => '0', -pady => '0', );

	#Invoke Superclass fill func
    $this->SUPER::Populate($args);
}

#---------------------------------------------
# OVERRIDE: new header function
#---------------------------------------------
sub header 
{
	# Parameters
	my ($this, $cmd, $column, @args) = @_;
	# Locals
	my (%args, %hlist_args, $key);
	#print "initial header args = >@_<\n" . "- " x 60 .  "\n";

	# Note that we process here only the create command
	if ($cmd eq 'create') {
		%args = @args;
	 	if (defined $args{-itemtype} and $args{-itemtype} eq 'resizebutton') {
			# Rip off all relevant options
			foreach $key (qw(-itemtype -widget -style -borderwidth -headerbackground -relief)) {
				$hlist_args{$key} = delete $args{$key} if defined $args{$key};
			}
			# Take over those that make sense
			$args{relief} = delete $hlist_args{relief} if $hlist_args{relief};
			$args{background} = delete $hlist_args{headerbackground} if $hlist_args{headerbackground};

			# Create a new Resize Button
			my $header = $this->HeaderResizeButton( 
					-column => $column,
					-lastcolumn => ($this->cget(-columns) == $column + 1),
					-highlightthickness => 0,
					%args,
    		);
			$header->bind('all','<Enter>','EnterFocus');
			
			# store it for later cget retrieval
			$this->{m_headerwidget}{$column} = $header;
			
			# Add options for parent class setup
			$hlist_args{-itemtype} = 'window';
			$hlist_args{-widget} = $header;
			$hlist_args{-style} = $this->{m_headerstyle} unless $hlist_args{-style};

			# pass on as new args for parental class
			@args = %hlist_args;
		}
	}
	elsif ($cmd eq 'cget') {
		if ($args[0] eq '-widget') {
			return $this->{m_headerwidget}{$column};
		}
		# all other requests are processed the common way
	} 
	#print "cmd = >$cmd<, column = >$column<, Args is >@args< args: " . scalar(@_) . "<\n";

	# Install the 'normal view after we have something on the screen..
	if (defined $column) {
		return $this->SUPER::header($cmd, $column, @args);
	}
	else {
		return $this->SUPER::header($cmd);
	}
}

########################################################################
1;
__END__


=head1 NAME

Tk::HListplus - A HList that supports resizing, open & close of columns

=head1 SYNOPSIS

    use Tk;
    use Tk::HListplus;

    my $mw = MainWindow->new();


    # CREATE HEADER STYLE 1
    my $headerstyle1 = $mw->ItemStyle('window', -padx => 0, -pady => 0);

    # CREATE MY HLIST
    my $hlist = $mw->Scrolled('HListplus',
         -columns=>3, 
         -header => 1,
		 -headerstyle => $headerstyle1,
    )->pack(-side => 'left', -expand => 'yes', -fill => 'both');

    # CREATE HEADER STYLE 2
    my $headerstyle = $hlist->ItemStyle('window', -padx => 0, -pady => 0);

    $hlist->header('create', 0, 
          -itemtype => 'resizebutton',
          -style => $headerstyle,
          -text => 'Test Name', 
		  -activeforeground => 'red',
    );
    $hlist->header('create', 1, 
          -itemtype => 'resizebutton',
          -style => $headerstyle,
          -text => 'Status', 
          -activebackground => 'orange',
    );

    Tk::MainLoop;
	


=head1 DESCRIPTION

A HList derived widget that has resizable columns, based on Header-ResizeButtons.

=head1 METHODS

=over 4

=item B<headerCreate()>

The create command accepts a new, virtual itemtype 'resizebutton', which
will lead to a Header-button with a right-side located sensor for resizing.
All options suitable for Buttons apply.

In addition, the following options may be specified:

=item B<headerCget()>

This command allows with B<-widget> to retrieve the Headerbutton-Widget Reference.


=back

=head1 OPTIONS

=over 4

=item B<-command>

The default command is associated with an open/close function for the selected
column. The function is called with a Tk::HeaderResizeButton reference for custom usage.

=item B<-activebackground>

The background color used for the column Header during active state (Mouse over Header).

=item B<-activeforeground>

The foreground color used for the column Header during active state (Mouse over Header).

=item B<-buttondownrelief>

The relief used for the column Header Button during selected state (Button pressed).

=item B<-minwidth>

The minwidth is used for the specific column (during resize), default: 30.

=item B<-closedminwidth>

The closedminwidth is used for the specific column (while in "CLOSED" view), default: 10.

=item B<-resizerwidth>

The resizerwidth is the resize sensor-area on the right border of the specific column, default: 1.


=item B<-headerstyle>

An alternative Header style, which will be the default for all columns unless you specify
-style ... for a dedicated header-create() call column.

=back

=head1 AUTHORS

Michael Krause, KrauseM_AT_gmx_DOT_net

Thanks for Tk::ResizeButton by B<Shaun Wandler> <wandler@unixmail.compaq.com>,
Slaven Rezic and Frank Herrmann.

This code may be distributed under the same conditions as Perl.

V0.4  (C) October 2006

=cut

###
### EOF
###