The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright (c) 1995-2003 Nick Ing-Simmons. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
package Tk::Derived;
require Tk::Widget;
require Tk::Configure;
use strict;
use Carp;

use vars qw($VERSION);
$VERSION = sprintf '4.%03d', q$Revision: #10 $ =~ /\D(\d+)\s*$/;

$Tk::Derived::Debug = 0;

my $ENHANCED_CONFIGSPECS = 0; # disable for now

use Tk qw(NORMAL_BG BLACK);

sub Subwidget
{
 my $cw = shift;
 my @result = ();
 if (exists $cw->{SubWidget})
  {
   if (@_)
    {
     foreach my $name (@_)
      {
       push(@result,$cw->{SubWidget}{$name}) if (exists $cw->{SubWidget}{$name});
      }
    }
   else
    {
     @result = values %{$cw->{SubWidget}};
    }
  }
 return (wantarray) ? @result : $result[0];
}

sub _makelist
{
 my $widget = shift;
 my (@specs) = (ref $widget && ref $widget eq 'ARRAY') ? (@$widget) : ($widget);
 return @specs;
}

sub Subconfigure
{
 # This finds the widget or widgets to to which to apply a particular
 # configure option
 my ($cw,$opt) = @_;
 my $config = $cw->ConfigSpecs;
 my $widget;
 my @subwidget = ();
 my @arg = ();
 if (defined $opt)
  {
   $widget = $config->{$opt};
   unless (defined $widget)
    {
     $widget = ($opt =~ /^-(.*)$/) ? $config->{$1} : $config->{-$opt};
    }
   # Handle alias entries
   if (defined($widget) && !ref($widget))
    {
     $opt    = $widget;
     $widget = $config->{$widget};
    }
   push(@arg,$opt) unless ($opt eq 'DEFAULT');
  }
 $widget = $config->{DEFAULT} unless (defined $widget);
 if (defined $widget)
  {
   $cw->BackTrace("Invalid ConfigSpecs $widget") unless (ref($widget) && (ref $widget eq 'ARRAY'));
   $widget = $widget->[0];
  }
 else
  {
   $widget = 'SELF';
  }
 foreach $widget (_makelist($widget))
  {
   $widget = 'SELF' if (ref($widget) && $widget == $cw);
   if (ref $widget)
    {
     my $ref = ref $widget;
     if ($ref eq 'ARRAY')
      {
       $widget = Tk::Configure->new(@$widget);
       push(@subwidget,$widget)
      }
     elsif ($ref eq 'HASH')
      {
       foreach my $key (%$widget)
        {
         foreach my $sw (_makelist($widget->{$key}))
          {
           push(@subwidget,Tk::Configure->new($sw,$key));
          }
        }
      }
     else
      {
       push(@subwidget,$widget)
      }
    }
   elsif ($widget eq 'ADVERTISED')
    {
     push(@subwidget,$cw->Subwidget)
    }
   elsif ($widget eq 'DESCENDANTS')
    {
     push(@subwidget,$cw->Descendants)
    }
   elsif ($widget eq 'CHILDREN')
    {
     push(@subwidget,$cw->children)
    }
   elsif ($widget eq 'METHOD')
    {
     my ($method) = ($opt =~ /^-?(.*)$/);
     push(@subwidget,Tk::Configure->new($method,$method,$cw))
    }
   elsif ($widget eq 'SETMETHOD')
    {
     my ($method) = ($opt =~ /^-?(.*)$/);
     push(@subwidget,Tk::Configure->new($method,'_cget',$cw,@arg))
    }
   elsif ($widget eq 'SELF')
    {
     push(@subwidget,Tk::Configure->new('Tk::configure', 'Tk::cget', $cw,@arg))
    }
   elsif ($widget eq 'PASSIVE')
    {
     push(@subwidget,Tk::Configure->new('_configure','_cget',$cw,@arg))
    }
   elsif ($widget eq 'CALLBACK')
    {
     push(@subwidget,Tk::Configure->new('_callback','_cget',$cw,@arg))
    }
   else
    {
     push(@subwidget,$cw->Subwidget($widget));
    }
  }
 $cw->BackTrace("No delegate subwidget '$widget' for $opt") unless (@subwidget);
 return (wantarray) ? @subwidget : $subwidget[0];
}

sub _cget
{
 my ($cw,$opt) = @_;
 $cw->BackTrace('Wrong number of args to cget') unless (@_ == 2);
 return $cw->{Configure}{$opt}
}

sub _configure
{
 my ($cw,$opt,$val) = @_;
 $cw->BackTrace('Wrong number of args to configure') unless (@_ == 3);
 $cw->{Configure}{$opt} = $val;
}

sub _callback
{
 my ($cw,$opt,$val) = @_;
 $cw->BackTrace('Wrong number of args to configure') unless (@_ == 3);
 $val = Tk::Callback->new($val) if defined($val) && ref($val);
 $cw->{Configure}{$opt} = $val;
}

sub cget
{my ($cw,$opt) = @_;
 my @result;
 local $SIG{'__DIE__'};
 foreach my $sw ($cw->Subconfigure($opt))
  {
   if (wantarray)
    {
     eval {  @result = $sw->cget($opt) };
    }
   else
    {
     eval {  $result[0] = $sw->cget($opt) };
    }
   last unless $@;
  }
 return wantarray ? @result : $result[0];
}

sub Configured
{
 # Called whenever a derived widget is re-configured
 my ($cw,$args,$changed) = @_;
 if (@_ > 1)
  {
   $cw->afterIdle(['ConfigChanged',$cw,$changed]) if (%$changed);
  }
 return exists $cw->{'Configure'};
}

sub configure
{
 # The default composite widget configuration method uses hash stored
 # in the widget's hash to map configuration options
 # onto subwidgets.
 #
 my @results = ();
 my $cw = shift;
 if (@_ <= 1)
  {
   # Enquiry cases
   my $spec = $cw->ConfigSpecs;
   if (@_)
    {
     # Return info on the nominated option
     my $opt  = $_[0];
     my $info = $spec->{$opt};
     unless (defined $info)
      {
       $info = ($opt =~ /^-(.*)$/) ? $spec->{$1} : $spec->{-$opt};
      }
     if (defined $info)
      {
       if (ref $info)
        {
         # If the default slot is undef then ask subwidgets in turn
         # for their default value until one accepts it.
         if ($ENHANCED_CONFIGSPECS && !defined($info->[3]))
          {local $SIG{'__DIE__'};
           my @def;
           foreach my $sw ($cw->Subconfigure($opt))
            {
             eval { @def = $sw->configure($opt) };
             last unless $@;
            }
           $info->[3] = $def[3];
           $info->[1] = $def[1] unless defined $info->[1];
           $info->[2] = $def[2] unless defined $info->[2];
          }
         push(@results,$opt,$info->[1],$info->[2],$info->[3],$cw->cget($opt));
        }
       else
        {
         # Real (core) Tk widgets return db name rather than option name
         # for aliases so recurse to get that ...
         my @real = $cw->configure($info);
         push(@results,$opt,$real[1]);
        }
      }
     else
      {
       push(@results,$cw->Subconfigure($opt)->configure($opt));
      }
    }
   else
    {
     my $opt;
     my %results;
     if (exists $spec->{'DEFAULT'})
      {
       foreach $opt ($cw->Subconfigure('DEFAULT')->configure)
        {
         $results{$opt->[0]} = $opt;
        }
      }
     foreach $opt (keys %$spec)
      {
       $results{$opt} = [$cw->configure($opt)] if ($opt ne 'DEFAULT');
      }
     foreach $opt (sort keys %results)
      {
       push(@results,$results{$opt});
      }
    }
  }
 else
  {
   my (%args) = @_;
   my %changed = ();
   my ($opt,$val);
   my $config = $cw->TkHash('Configure');

   while (($opt,$val) = each %args)
    {
     my $var = \$config->{$opt};
     my $old = $$var;
     $$var = $val;
     my $accepted = 0;
     my $error = "No widget handles $opt";
     foreach my $subwidget ($cw->Subconfigure($opt))
      {
       next unless (defined $subwidget);
       eval {local $SIG{'__DIE__'};  $subwidget->configure($opt => $val) };
       if ($@)
        {
         my $val2 = (defined $val) ? $val : 'undef';
         $error = "Can't set $opt to `$val2' for $cw: " . $@;
         undef $@;
        }
       else
        {
         $accepted = 1;
        }
      }
     $cw->BackTrace($error) unless ($accepted);
     $val = $$var;
     $changed{$opt} = $val if (!defined $old || !defined $val || "$old" ne "$val");
    }
   $cw->Configured(\%args,\%changed);
  }
 return (wantarray) ? @results : \@results;
}

sub ConfigDefault
{
 my ($cw,$args) = @_;

 $cw->BackTrace('Bad args') unless (defined $args && ref $args eq 'HASH');

 my $specs = $cw->ConfigSpecs;
 # Should we enforce a Delagates(DEFAULT => )  as well ?
 $specs->{'DEFAULT'} = ['SELF'] unless (exists $specs->{'DEFAULT'});

 #
 # This is a pain with Text or Entry as core widget, they don't
 # inherit SELF's cursor. So comment it out for Tk402.001
 #
 # $specs->{'-cursor'} = ['SELF',undef,undef,undef] unless (exists $specs->{'-cursor'});

 # Now some hacks that cause colours to propogate down a composite widget
 # tree - really needs more thought, other options adding such as active
 # colours too and maybe fonts

 my $child = ($cw->children)[0]; # 1st child window (if any)

 unless (exists($specs->{'-background'}))
  {
   Tk::catch { $cw->Tk::cget('-background') };
   my (@bg) = $@ ? ('PASSIVE') : ('SELF');
   push(@bg,'CHILDREN') if $child;
   $specs->{'-background'} = [\@bg,'background','Background',NORMAL_BG];
  }
 unless (exists($specs->{'-foreground'}))
  {
   Tk::catch { $cw->Tk::cget('-foreground') };
   my (@fg) = $@ ? ('PASSIVE') : ('SELF');
   push(@fg,'CHILDREN') if $child;
   $specs->{'-foreground'} = [\@fg,'foreground','Foreground',BLACK];
  }
 $cw->ConfigAlias(-fg => '-foreground', -bg => '-background');

 # Pre-scan args for aliases - this avoids defaulting
 # options specified via alias
 foreach my $opt (keys %$args)
  {
   my $info = $specs->{$opt};
   if (defined($info) && !ref($info))
    {
     $args->{$info} = delete $args->{$opt};
    }
  }

 # Now walk %$specs supplying defaults for all the options
 # which have a defined default value, potentially looking up .Xdefaults database
 # options for the name/class of the 'frame'

 foreach my $opt (keys %$specs)
  {
   if ($opt ne 'DEFAULT')
    {
     unless (exists $args->{$opt})
      {
       my $info = $specs->{$opt};
       if (ref $info)
        {
         # Not an alias
         if ($ENHANCED_CONFIGSPECS && !defined $info->[3])
          {
           # configure inquire to fill in default slot from subwidget
           $cw->configure($opt);
          }
         if (defined $info->[3])
          {
           if (defined $info->[1] && defined $info->[2])
            {
             # Should we do this on the Subconfigure widget instead?
             # to match *Entry.Background
             my $db = $cw->optionGet($info->[1],$info->[2]);
             $info->[3] = $db if (defined $db);
            }
           $args->{$opt} = $info->[3];
          }
        }
      }
    }
  }
}

sub ConfigSpecs
{
 my $cw = shift;
 my $specs = $cw->TkHash('ConfigSpecs');
 while (@_)
  {
   my $key = shift;
   my $val = shift;
   $specs->{$key} = $val;
  }
 return $specs;
}

sub _alias
{
 my ($specs,$opt,$main) = @_;
 if (exists($specs->{$opt}))
  {
   unless (exists $specs->{$main})
    {
     my $targ = $specs->{$opt};
     if (ref($targ))
      {
       # opt is a real option
       $specs->{$main} = $opt
      }
     else
      {
       # opt is itself an alias
       # make main point to same place
       $specs->{$main} = $targ unless $targ eq $main;
      }
    }
   return 1;
  }
 return 0;
}

sub ConfigAlias
{
 my $cw = shift;
 my $specs = $cw->ConfigSpecs;
 while (@_ >= 2)
  {
   my $opt  = shift;
   my $main = shift;
   unless (_alias($specs,$opt,$main) || _alias($specs,$main,$opt))
    {
     $cw->BackTrace("Neither $opt nor $main exist");
    }
  }
 $cw->BackTrace('Odd number of args to ConfigAlias') if (@_);
}

sub Delegate
{
 my ($cw,$method,@args) = @_;
 my $widget = $cw->DelegateFor($method);
 if ($widget == $cw)
  {
   $method = "Tk::Widget::$method"
  }
 my @result;
 if (wantarray)
  {
   @result   = $widget->$method(@args);
  }
 else
  {
   $result[0] = $widget->$method(@args);
  }
 return (wantarray) ? @result : $result[0];
}

sub InitObject
{
 my ($cw,$args) = @_;
 $cw->Populate($args);
 $cw->ConfigDefault($args);
}

sub ConfigChanged
{
 my ($cw,$args) = @_;
}

sub Advertise
{
 my ($cw,$name,$widget)  = @_;
 confess 'No name' unless (defined $name);
 croak 'No widget' unless (defined $widget);
 my $hash = $cw->TkHash('SubWidget');
 $hash->{$name} = $widget;              # advertise it
 return $widget;
}

sub Component
{
 my ($cw,$kind,$name,%args) = @_;
 $args{'Name'} = "\l$name" if (defined $name && !exists $args{'Name'});
 # my $pack = delete $args{'-pack'};
 my $delegate = delete $args{'-delegate'};
 my $w = $cw->$kind(%args);            # Create it
 # $w->pack(@$pack) if (defined $pack);
 $cw->Advertise($name,$w) if (defined $name);
 $cw->Delegates(map(($_ => $w),@$delegate)) if (defined $delegate);
 return $w;                            # and return it
}

1;
__END__