package Tk::DragDrop;
require Tk::DragDrop::Common;
require Tk::Toplevel;
require Tk::Label;
@ISA = qw(Tk::DragDrop::Common Tk::Toplevel);

# This is a little tricky, ISA says 'Toplevel' but we 
# define a Tk_cmd to actually build a 'Label', then 
# use Tix's wmRelease in Populate to make it a toplevel. 
sub Tk_cmd { \&Tk::label }

Construct Tk::Widget 'DragDrop';

use strict;
use vars qw(%type @types);
use Carp;


sub ClassInit
{
 my ($class,$mw) = @_;
 $mw->bind($class,'<Map>','Mapped');
 $mw->bind($class,'<Any-KeyPress>','Done');
 $mw->bind($class,'<Any-ButtonRelease>','Drop');
 $mw->bind($class,'<Any-Motion>','Drag');
 return $class;
}


sub Populate
{
 my ($token,$args) = @_;
 my $parent = $token->parent;
 $token->wmRelease;
 $token->withdraw;
 $token->overrideredirect(1);
 $token->saveunder(1);     
 $token->ConfigSpecs(-sitetypes       => ['METHOD','siteTypes','SiteTypes',undef],
                     -startcommand    => ['CALLBACK',undef,undef,undef],
                     -predropcommand  => ['CALLBACK',undef,undef,undef],
                     -postdropcommand => ['CALLBACK',undef,undef,undef],
                     -cursor          => ['SELF','cursor','Cursor','hand2'],
                     -text            => ['SELF','text','Text',$parent->class],
                     -handlers        => ['SETMETHOD','handlers','Handlers',[[[$token,'SendText']]]],  
                     -selection       => ['SETMETHOD','selection','Selection',"dnd_" . $parent->toplevel->name],  
                     -event           => ['SETMETHOD','event','Event','<B1-Motion>']
                    );
 $token->{InstallHandlers} = 0;
 $args->{-borderwidth} = 3;
 $args->{-relief} = 'flat';
 $args->{-takefocus} = 1;
}

sub sitetypes
{
 my ($w,$val) = @_;
 confess "Not a widget $w" unless (ref $w);
 my $var = \$w->{Configure}{'-sitetypes'};
 if (@_ > 1)
  {
   if (defined $val)
    {
     $val = [$val] unless (ref $val);
     my $type;
     foreach $type (@$val)
      {
       Tk::DragDrop->import($type);
      }
    }
   $$var = $val;
  }
 return (defined $$var) ? $$var : \@types;
}

sub SendText
{
 my ($w,$offset,$max) = @_;
 my $s = substr($w->cget('-text'),$offset);
 $s = substr($s,0,$max) if (length($s) > $max);
 return $s;
}

sub handlers
{
 my ($token,$opt,$value) = @_;
 $token->{InstallHandlers} = (defined($value) && @$value);
 $token->{'handlers'}  = $value;
}

sub selection
{
 my ($token,$opt,$value) = @_;
 my $handlers = $token->{'handlers'};
 $token->{InstallHandlers} = (defined($handlers) && @$handlers);
}

sub event
{
 my ($w,$opt,$value) = @_;
 # delete old bindings
 $w->parent->Tk::bind($value,[$w,'StartDrag']);
}

sub Mapped
{
 my ($token) = @_;
 my $e = $token->parent->XEvent;
 $token->grabGlobal;
 $token->focus;
 if (defined $e)
  {
   my $X = $e->X;
   my $Y = $e->Y;
   $token->MoveWindow($X,$Y); 
   $token->NewDrag;
   $token->FindSite($X,$Y);
  }
}

sub FindSite
{
 my ($token,$X,$Y) = @_;
 my $types = $token->sitetypes;
 if (defined $types && @$types)
  {
   my $type;
   foreach $type (@$types)
    {
     my $site;
     my $class = $type{$type};
     if (defined $class)
      {
       foreach $site ($class->SiteList($token))
        {
         return $site if ($site->Over($X,$Y));
        }
      }
    }
  }
 else
  {
   warn "No sitetypes";
  }
 return undef;
}

sub NewDrag
{
 my ($token) = @_;
 my $types = $token->sitetypes;
 if (defined $types && @$types)
  {
   my $type;
   foreach $type (@$types)
    {
     my $class = $type{$type};
     if (defined $class)
      {
       $class->CheckSites($token);
      }
    }
  }
}

sub Drag
{
 my $token = shift;
 my $e = $token->XEvent;
 $token = $token->toplevel;
 my $X  = $e->X;
 my $Y  = $e->Y;
 my $site = $token->FindSite($X,$Y);
 my $over = $token->{'Over'};
 if ($over)
  {
   if (!defined($site) || !$over->Match($site))
    {
     $over->Leave($token,$e);
     $site->Enter($token,$e) if (defined $site);
    }
   else
    {
     $over->Motion($token,$e);
    }
  }
 elsif (defined $site)
  {
   $site->Enter($token,$e);
  }
 $token->MoveWindow($X,$Y);
}

sub Done
{
 my $token = shift;
 my $e     = $token->XEvent;
 $token    = $token->toplevel;
 my $over  = $token->{'Over'};
 $over->Leave($token,$e) if (defined $over);
 my $w     = $token->parent;
 eval {local $SIG{__DIE__}; $token->grabRelease };
 $token->withdraw;
 delete $w->{'Dragging'};
 $w->update;
}

sub HandleLoose
{
 my ($w,$seln) = @_;
 return "";
}

sub Drop
{
 my $ewin  = shift;
 my $e     = $ewin->XEvent;
 my $token = $ewin->toplevel;
 Done($ewin);
 my $site  = $token->FindSite($e->X,$e->Y);
 if (defined $site)
  {
   my $seln = $token->cget('-selection'); 
   unless ($token->Callback(-predropcommand => $seln, $site))
    {
     my $w = $token->parent;  
     if ($token->{InstallHandlers})
      {                       
       my $h;                 
       foreach $h (@{$token->cget('-handlers')})
        {                     
         $w->SelectionHandle('-selection' => $seln,@$h);
        }                     
       $token->{InstallHandlers} = 0;
      }                       
     if (!$w->IS($w->SelectionOwner('-selection'=>$seln)))              
      {                                                                 
       $w->SelectionOwn('-selection' => $seln, -command => [\&HandleLoose,$w,$seln]);
      }                                                                 
     $site->Drop($w,$seln,$e); 
     $token->Callback(-postdropcommand => $seln);
    }
  }
}

sub StartDrag
{
 my $token = shift;
 my $w     = $token->parent;
 unless ($w->{'Dragging'})
  {
   my $e = $w->XEvent;
   my $X = $e->X;
   my $Y = $e->Y;
   my $was = $token->{'XY'};
   if ($was)
    {
     if ($was->[0] != $X || $was->[1] != $Y)
      {
       unless ($token->Callback('-startcommand'))
        {
         delete $token->{'XY'};  
         $w->{'Dragging'} = $token;
         $token->MoveWindow($X,$Y);
         $token->raise;          
         $token->deiconify;      
         $token->FindSite($X,$Y);
        }
      }
    }
   else
    {
     $token->{'XY'} = [$X,$Y];
    }
  }
}


1;
