/usr/local/CPAN/PPresenter/PPresenter/Object.pm


# Copyright (C) 2000-2002, Free Software Foundation FSF.

# I would have liked to call this package UNIVERSAL, but then it
# is also a base-class of Tk objects.  This doesn't work well
# because the overloading.

package PPresenter::Object;

use strict;

use overload '""' => 'toString'
           , cmp  => 'compare';

sub new($@)
{   my $class = shift;
    my $self  = bless {}, $class;

    $self->getOptions($class)->change(@_);

    die "New $class object has no name (required).\n"
        unless defined $self->{-name};

    $self->InitObject;
}

sub getOptions($)
{   my ($self, $class) = @_;

    no strict 'refs';

    map {$self->getOptions($_)}  @{"${class}::ISA"};

    my $get_defaults = *{"${class}::ObjDefaults"}{CODE} || undef;
    return $self unless defined $get_defaults;

    my $defaults     = &$get_defaults;
    @$self{keys %$defaults} = values %$defaults;
    $self;
}

sub InitObject() {shift}

# All objects have a name, and optionally a list of aliases.
sub isNamed($)
{   my ($self, $name) = @_;

    return 1  if $self->{-name} eq $name;
    return 0  unless defined $self->{-aliases};
    return $self->{-aliases} eq $name unless ref $self->{-aliases};
    return grep {$_ eq $name} @{$self->{-aliases}};
}

# fromList is called with a number in the list, 'FIRST', 'LAST',
# or the name of an element from the list.  Returns a list
# when called with 'ALL'.

sub fromList($$)
{   my ($class, $list, $name) = @_;

    return undef unless defined $list->[0];

    die "fromList called without a list of class $class: $list.\n"
        if ref $list ne 'ARRAY';

    die "fromList called with empty list of $class.\n"
        unless @$list;

    die "fromList called for $class with list of ".ref($list->[0])."\n"
        unless $list->[0]->isa($class);

    if(ref $name)
    {   return $name if $name->isa($class);
        die "fromList called with ".ref($name)." as solution for $class\n"
    }

    return $list->[0]  if $name eq 'FIRST';
    return $list->[-1] if $name eq 'LAST';
    return @$list      if $name eq 'ALL';    # returns list!!

    if($name =~ m/\D/)
    {   foreach (@$list)
        {   return $_ if $_->isNamed($name);
        }
        return undef;
    }

    return undef if $name > $#$list || $name <0;

    $list->[$name];
}

sub change(@)
{   my $self    = shift;
    return $self unless @_;

    my $name    = $self->{-name};

    while($#_ >0)
    {   my ($field, $contents) = (shift, shift);

        unless(exists $self->{$field})
        {   warn "A ",ref $self,
                 " does not contain a setting named $field.  Skipped.\n";
            next;
        }

        $self->{$field} = $contents;
    }

    return $self;
}

#
# Flatten
# Some options can have a single element or a (nested?) array.  This
# function flattens this to a list.  Often called as:
#   $style_elem{option} = [ $style_elem->flatten($style_elem->{option}) ]
#

sub flatten($)
{   my ($self, $option) = @_;

    return () unless defined $option;

    my $ref = ref $option;
    return $option unless $ref;

    return $self->flatten(@$option) if $ref eq 'ARRAY';

    die "Got an reference to $ref to flatten as option for $self.\n";
}

#
# Overloads
#

sub toString($) { $_[0]->{-name} }

# obj cmp obj, or  obj cmp string.
sub compare($$) {$_[0]->isNamed("$_[1]")}

#
# The user specifies a percentage from a length.  This might be a string
# or a value.  Permitted formats:
#    0.125     means 12.5% from the start
#     -0.2     means 20% from the end
#      '-0'    means at the end
#     '10%'    means 10% from the start
#   '-5.5%'    means 5.5% from the end.
#     +0.3     means 30% from the start.
#

sub takePercentage($$)
{   my ($self, $percent, $length) = @_;
    $self->toPercentage($percent) * $length;
}

sub toPercentage($)
{   my ($self, $p) = @_;

    if(my ($sign, $value, $cent) = $p =~ /(\-|\+)?([\d.]+)(\%?)/ )
    {   $value /= 100 if $cent eq '%';
        return (defined $sign && $sign eq '-') ? (1-$value) : $value;
    }

    warn "Not valid as a percentage specification: $p.\n";
    0;
}

#
# Debugging and trace routines...
#

sub nested_types($$)
{   my ($type, $indent) = @_;

    if($type eq '' || $type eq 'HASH' || $type eq "ARRAY" || $type eq "CODE")
    {   return "$indent$type\n";
    }

    no strict 'refs';
    return join "\n", "${indent}is a $type"
                    , map {nested_types($_, "$indent   ")} @{"${type}::ISA"};
}

sub show_scalar_line($$);
sub show_scalar_line($$)
{   my ($scalar, $max) = @_;
    return "undef" unless defined $scalar;
    my $type = ref $scalar;
    if($type eq '')
    {   return undef unless length($scalar) < $max;
        return $scalar =~ /^\s*[\d.]+\s*$/ ? $scalar : "\"$scalar\"";
    }
    elsif($type eq 'ARRAY')
    {   my $l = 1;
        foreach (@$scalar)
        {   my $line = show_scalar_line($_, $max);
            return undef unless defined $line;
            $l   += length($line)+1;
            return undef if $l>$max;
        }
        return "[".join(',',map {show_scalar_line($_,$max)} @$scalar)."]";
    }
    elsif($type eq 'HASH')
    {   return undef;  # never compact.
    }
    elsif($type eq 'CODE' || $type eq 'REF')
    {   return "$scalar";
    }
    elsif($scalar->isa('PPresenter::StyleElem'))
    {   return undef if length($type)+length($scalar->{-name})+4>$max;
        return "$type ($scalar->{-name})";
    }
    else
    {   return "$scalar";
    }
}

sub show_scalar_block($$);
sub show_scalar_block($$)
{  my ($scalar, $indent) = @_;

   # Try to solve it in one line.
   my $ret = show_scalar_line($scalar, 75-length($indent));
   return "$indent$ret\n" if defined $ret;

   my $type = ref $scalar;
   if($type eq '')
   {   return "$indent\"".substr($scalar, 0, 70-length($indent))."...\"\n";
   }
   elsif($type eq 'ARRAY')
   {   $ret = "$indent\[ ";
       my $out;
       my $first = 1;
       foreach (@$scalar)
       {   my $n = show_scalar_line($_,300);
           if(length($ret)+length($n)+1 > 75)
           {   $out .= "$ret\n";
               $ret  = "$indent, $n";
           }
           else
           {   $ret .= $first ? "$n" : ",$n";
               $first = 0;
           }
       }
       return $out . "$ret ]\n";
   }
   elsif($type eq 'HASH')
   {   my $out;
       my $first = "{";
       foreach (sort keys %$scalar)
       {   my $k  = show_scalar_line($_,300);
           my $v  = show_scalar_line($scalar->{$_},
                       50-length($indent));

           $out  .= sprintf "$indent$first %-20s => ", $k;
           $first = ",";
           if(defined $v)
           {   $out .= $v."\n";
           }
           else
           {   $out .= "\n".show_scalar_block($scalar->{$_}, "$indent   ");
           }
       }
       return $out."$indent}\n";
   }
   elsif($scalar->isa('PPresenter::StyleElem'))
   {   return "$indent".show_scalar_line($scalar, 300);
   }

   return "Unknown\n";
}

sub tree(;$)
{   my $self   = shift;
    my $indent = defined($_[0]) ? $_[0] : "";
    my $result = "$indent $self\n";

    $indent    .= "   ";
    $result    .= nested_types(ref $self, $indent). "\n";

    foreach (sort keys %$self)
    {  $result .= sprintf "$indent%-20s => ", $_;
       my $scalar = show_scalar_line($self->{$_} || undef, 50-length($indent));
       $result .= defined $scalar
                ? $scalar . "\n"
                : "\n". show_scalar_block($self->{$_} || undef, "$indent   ");
    }

    return $result;
}

1;