/usr/local/CPAN/CORBA-MICO/CORBA/MICO/Pixtree.pm


package CORBA::MICO::Pixtree;
require Exporter;

use Gtk2 '1.140';
use CORBA::MICO::IREntry;

use strict;

@CORBA::MICO::Pixtree::ISA = qw(Exporter);
@CORBA::MICO::Pixtree::EXPORT = qw();
@CORBA::MICO::Pixtree::EXPORT_OK = qw(
     pixtree_create
     pixtree_show
);

my $margin = 2;
my $vspacing = 10;
my $hspacing = 10;

#--------------------------------------------------------------------
# Create a 'pixtree' object 
#--------------------------------------------------------------------
sub pixtree_create {
  my $retval = new Gtk2::ScrolledWindow(undef,undef); # scrolled for main text
  $retval->set_policy( 'automatic', 'automatic' );

  my $drawing = Gtk2::DrawingArea->new();               # drawing area widget
  $retval->add_with_viewport($drawing);
#  $retval->set_user_data([$drawing, undef]);           # store children !!!!
#  $retval->signal_connect('destroy', sub { undef @{$_[0]->get_user_data()}; });
  my $pixtree_hnd = [$retval, $drawing, undef];
  $drawing->signal_connect('expose_event',  \&expose_event_cb, $pixtree_hnd);
#  $drawing->signal_connect('size_allocate', \&size_allocate_cb, $retval);
  $retval->show_all();
  return $pixtree_hnd;
}

#--------------------------------------------------------------------
# Show a tree via 'pixtree' object 
# In: pixtree - pixtree object returned by pixtree_create()
#     \@nodes  - list of interfaces (objects of class CORBA::MICO::IREntry)
#--------------------------------------------------------------------
sub pixtree_show {
  my ($pixtree_hnd, $nodes) = @_;
  my $pixtree = $pixtree_hnd->[0];
  $pixtree_hnd->[2] = $nodes;
  $pixtree_hnd->[1]->queue_draw();
}

#--------------------------------------------------------------------
sub text_size {
   my ($w, $text) = @_;
   my $layout = $w->create_pango_layout($text);
   return $layout->get_pixel_size;
}

#--------------------------------------------------------------------
sub expose_event_cb {
  my ($widget, $event, $pixtree_hnd) = @_;
  my $window = $widget->window();
  my $pixtree = $pixtree_hnd->[0];
  return 1 unless $window;
  return 1 unless defined $pixtree_hnd->[2];
  my $nodes = $pixtree_hnd->[2];
  my @levels = ();
  my %tree_desc = ();
  prepare_tree($nodes, 0, 0, 1, \@levels, \%tree_desc);
  my ($maxwidth) = reverse sort @levels;
  my $maxitem_w = 0;
  my $maxitem_h = 0;
  foreach my $iname (keys %tree_desc) {
    my ($tw, $th) = text_size($widget, $iname);
    $maxitem_w = $tw if $maxitem_w < $tw;
    $maxitem_h = $th if $maxitem_h < $th;
    $tree_desc{$iname}{WIDTH} = $tw;
    $tree_desc{$iname}{HEIGHT} = $th;
  }
  my $box_w = $maxitem_w + ($margin+1)*2;
  my $box_h = $maxitem_h + ($margin+1)*2;
  $box_w++ if ($box_w % 2);
  my $full_width = $maxwidth * ($box_w + $hspacing);
  my $full_height = @levels * ($box_h + $vspacing);
  my $curr_hspacing;
  my $curr_vspacing;
  my Gtk2::Gdk::Rectangle $rect = $widget->allocation();
  my ($allocted_w, $allocted_h) = ($rect->width(), $rect->height());
  if( $allocted_w < $full_width or $allocted_h < $full_height ) {
    $full_width = $allocted_w if $full_width < $allocted_w;
    $full_height = $allocted_h if $full_height < $allocted_h;
    $widget->size($full_width, $full_height);
  }
  else {
    ($full_width, $full_height) = ($allocted_w, $allocted_h)
  }  
  $curr_hspacing = int($full_width/$maxwidth) - $box_w;
  if( $curr_hspacing > $box_w ) {
    # Increaze $box_w if $curr_hspacing is too large
    my $sum = $curr_hspacing + $box_w;
    $box_w = int($sum/2);
    $curr_hspacing = $sum - $box_w;
  }
  $curr_vspacing = int($full_height/@levels) - $box_h;
  if( $curr_vspacing > (2*$box_h) ) {
    # Increaze $box_h if $curr_vspacing is too large
    my $sum = $curr_vspacing + $box_h;
    $box_h = int($sum/3);
    $curr_vspacing = $sum - $box_h;
  }
  my $pm = $window;
  $pm->draw_rectangle($widget->get_style()->white_gc(), 1, 
                                        0, 0, $full_width, $full_height);
  for( my $lev = 0; $lev < @levels; ++$lev ) {
    my @curr_lev = sort { $tree_desc{$a}{OFFSET} <=> $tree_desc{$b}{OFFSET} } 
                     grep { $tree_desc{$_}{LEVEL} == $lev } keys %tree_desc; 
    my $lev_width = @curr_lev * ($box_w + $curr_hspacing);
    my $lev_hoffs = int($curr_hspacing/2) + int(($full_width - $lev_width)/2);
#    my $lev_voffs = int($curr_vspacing/2) + $lev * ($box_h + $curr_vspacing);
    my $lev_voffs = int($curr_vspacing/2) + 
                          ($#levels-$lev) * ($box_h + $curr_vspacing);
    foreach my $item (@curr_lev) {
      $tree_desc{$item}{VPOS} = $lev_voffs;
      $tree_desc{$item}{HPOS} = $lev_hoffs;
      $lev_hoffs += ($box_w + $curr_hspacing);
    }
  }
  foreach my $item (keys %tree_desc) {
    draw_item($widget, $pm, $box_w, $box_h, $item, $tree_desc{$item});
  }
  foreach my $item (keys %tree_desc) {
    draw_lines($widget, $pm, $box_w, $box_h, 
               $item, $tree_desc{$item}, \%tree_desc);
  }
  return 1;
}

#--------------------------------------------------------------------
sub draw_lines {
  my ($widget, $pm, $w, $h, $item_name, $item_data, $tree_desc) = @_;
  my $line_width = 1;
  my $parents = $item_data->{PARENTS} or return;
  my $nparents = @$parents            or return;
  my $x = $item_data->{HPOS};
  my $y = $item_data->{VPOS};
  my $dist = ($w - ($nparents*$line_width)) / ($nparents+1); 
  my $style = $widget->get_style();
  my $middle = ($w - $line_width) / 2;
  my $x0 = $x + $dist;
  foreach my $parent (@$parents) {
    my $parent_data = $tree_desc->{$parent};
    my $x1 = int($parent_data->{HPOS} + $middle);
    my $y1 = $parent_data->{VPOS} + $h;
    my $i;
    if( abs($x1-$x0) <= 2 ) {
      $x1 = $x0;
    }
    for( $i = 0; $i < $line_width; ++$i ) {
      $pm->draw_line($style->fg_gc('normal'), $x0+$i, $y, $x1+$i, $y1);
    }
    $x0 = $x0 + $dist;
  }
}

#--------------------------------------------------------------------
sub draw_item {
  my ($widget, $pm, $w, $h, $item_name, $item_data) = @_;
  my $x = $item_data->{HPOS};
  my $y = $item_data->{VPOS};
  my $style = $widget->get_style();
  $pm->draw_rectangle($style->bg_gc('normal'), 1, $x, $y, $w, $h);
  $pm->draw_rectangle($style->fg_gc('normal'), 0, $x, $y, $w, $h);
  my $iw = $item_data->{WIDTH};
  my $ih = $item_data->{HEIGHT};
  my $x1 = $x + int(($w-$iw) / 2);
  my $y1 = $y + int(($h-$ih)/2);
  my $layout = $widget->create_pango_layout($item_name);
  $pm->draw_layout($style->fg_gc('normal'), $x1, $y1, $layout);
}

#--------------------------------------------------------------------
# In: \@nodes              - list of interfaces (objects of class IREntry)
#     $level               - node level (vertical): integer (0..tree_height-1)
#     ($min_off, $max_off) - position (horizontal): float (0..1)
#     $levels              - resulting array: level->number of items on it
#     $tree_desc           - resulting hash: for each name in the tree
#        contains: level, offset, array of subtree names
sub prepare_tree {
  my ($nodes, $level, $min_off, $max_off, $levels, $tree_desc) = @_;
  foreach my $node (@$nodes) {
    prepare_node($node, $level, $min_off, $max_off, $levels, $tree_desc);
  }
}
#--------------------------------------------------------------------
# In: $node                - interface (object of class CORBA::MICO::IREntry)
#     $level               - node level (vertical): integer (0..tree_height-1)
#     ($min_off, $max_off) - position (horizontal): float (0..1)
#     $levels              - resulting array: level->number of items on it
#     $tree_desc           - resulting hash: for each name in the tree
#        contains: level, offset, array of subtree names
sub prepare_node {
  my ($node, $level, $min_off, $max_off, $levels, $tree_desc) = @_;
  my $oldlev;
  my $name = $node->name();
  if( defined($tree_desc->{$name}) ) {
    $oldlev = $tree_desc->{$name}{"LEVEL"};
    if( $oldlev < $level ) {
      $tree_desc->{$name}{"LEVEL"} = $level;
    #  $tree_desc->{$name}{"OFFSET"} = ($max_off+$min_off)/2;
      $levels->[$level]++; 
      $levels->[$oldlev]--; 
    }  
  }
  else {
    $tree_desc->{$name}{"LEVEL"} = $level;
    $tree_desc->{$name}{"OFFSET"} = ($max_off+$min_off)/2;
    $levels->[$level]++;
  }  
  $level++;
  my $parents = $node->parents();
  my $diff = ($max_off-$min_off)/(@$parents+2);
  foreach my $subnode (@$parents) {
    $min_off += $diff;
    prepare_node($subnode, $level,
                 $min_off, $max_off, $levels, $tree_desc);
    if( not defined($oldlev) ) {
      push(@{$tree_desc->{$name}{"PARENTS"}}, $subnode->name());
    }
  }
}

#--------------------------------------------------------------------
# tree_desc entry:
#  'CHILDREN'   => list of children
#  'LEVEL'      => node level, greater for child, lesser for parent
#  'FOLDER'     => folder number
#  'WIDTH'      => node width - width of the widest level
#  'PARENTS'    => list of parents
#--------------------------------------------------------------------
# In: \@nodes              - list of interfaces (objects of class IREntry)
#     $level               - node level (vertical): integer (0..tree_height-1)
#     ($min_off, $max_off) - position (horizontal): float (0..1)
#     $levels              - resulting array: level->number of items on it
#     $tree_desc           - resulting hash: for each name in the tree
#        contains: level, offset, array of subtree names
sub construct_tree {
  my ($nodes, $level, $min_off, $max_off, $levels, $tree_desc) = @_;
  foreach my $node (@$nodes) {
    prepare_node($node, $level, $min_off, $max_off, $levels, $tree_desc);
  }
}