######################################################################
#  This is a named parameter library for drawing objects on an
#  gdk window with a syntax similar to the syntax of the canvas.
#
#  Dov Grobgeld
#  dov@imagic.weizmann.ac.il
#  2001-10-30
######################################################################

=head1 NAME

Gtk::WindowDraw - A library for drawing on a Gdk Window.

=head1 SYNOPSIS

    use Gtk;
    use Gtk::WindowDraw;

    sub window_event {
        my ($this, $event) = @_;
        my $event = shift;

        if ($event->{type} eq "expose") {
	    # Create a window draw object if it doesn't exist.
	    unless ($this->{wd}) {
	        $this->{wd} = new Gtk::WindowDraw $this->window;
	    } 

	    # Draw all the objects
	    my $wd = $this->{wd};
	    $wd->draw("line",
		      points=> [100,100, 200,200],
		      fill_color => "red",
		      arrow=>"start"
		     );
	    $wd->draw("oval",
		      x1=> 150, y1=>100,
		      x2=> 250, y2=>200,
		      fill_color => "pink",
		      outline_color => "black",
		      line_width=> 3
		     );
	}
    }

    init Gtk;

    my $mw = Gtk::Widget->new("Gtk::Window",
			      -type                 => "toplevel",
			      -title                => "Test WindowDraw",
			      -signal::destroy => sub {exit}
			     );

    $mw->add(Gtk::Widget->new("Gtk::DrawingArea",
			      width=>300,
			      height=>300,
			      "signal::event"=>\&window_event
			     ));
    $mw->show_all();
    
    main Gtk;

=head1 DESCRIPTION

Gtk::WindowDraw is a library for drawing on a Gtk Window in cases where
using the more complete Gnome::Canvas isn't desirable. It provides simple
graphics primitives, with a syntax similar to that of the canvas.

Note that the constructor must be called after the window it is drawing
on has been mapped. I.e. in the exposure event.
=cut

use Gtk;
use strict;

package Gtk::WindowDraw;

# Constructor must be called after the window has been mapped.
sub new {
    my $class = shift;
    my $window = shift;
    my $this = {};
    my $colormap = $window->get_colormap;
    
    $this->{window} = $window;
    $this->{colormap} = $colormap;
    $this->{gc} = new Gtk::Gdk::GC $window;
    $this->{allocated_colors} = {};

    # default gc settings
    $this->{fg_color} = "black";
    $this->{line_width} = 1;
    $this->{line_cap} = 0;
    $this->{line_join} = 0;
    $this->{line_style} = 0;

    return bless $this;
}

sub get_color {
    my $this = shift;
    my $color_name = shift;
    my $ret;

    if ($ret = $this->{allocated_colors}->{$color_name}) {
	return $ret;
    }

    $ret = $this->{colormap}->color_alloc(Gtk::Gdk::Color->parse_color($color_name));
    
    $this->{allocated_colors}->{$color_name} = $ret;
}

sub set_fg_color {
    my($this, $color) = @_;

    if ($color ne $this->{fg_color}) {
	$this->{gc}->set_foreground($this->get_color($color));
	$this->{fg_color} = $color;
    }
}

sub draw {
    my ($this, $object) = (shift, shift);

    # fill a hash with the rest of the values
    my %params = @_;

    # Set gc
    my $gc = $this->{gc};
    my $colormap = $this->{colormap};
    
    my $fill_color = $params{fill_color} || "black";
    my $outline_color = $params{outline_color} || "black";
    my $line_width = $params{line_width} || 1;
    my $line_style = $params{line_style} || 0;
    my $line_cap = $params{line_cap} || 2;
    my $line_join = $params{linejoin} || 0;

    if ($line_width ne $this->{line_width}
	|| $this->{line_cap} ne $line_cap
	|| $this->{line_join} ne $line_join
	|| $this->{line_style} ne $line_style
       ) {
	$this->{line_cap} = $line_cap;
	$this->{line_width} = $line_width;
	$this->{line_join} = $line_join;
	$this->{line_style} = $line_style;

	$gc->set_line_attributes($line_width, $line_style, $line_cap, $line_join);
    }

    my $window = $this->{window};
    
    if ($object eq "line") {
	my $points = $params{points};

	set_fg_color($this, $fill_color);
	
	unless ($points) {
	    warn "Need parameters points in order to draw a line!\n";
	    return;
	}

	# Convert continous points to discontinous segments
	my @segments;
	push(@segments, $points->[0], $points->[1]);
	foreach my $i (1..(@$points/2-2)) {
	    push(@segments, $points->[2*$i], $points->[2*$i+1]);
	    push(@segments, $points->[2*$i], $points->[2*$i+1]);
	}
	push(@segments, $points->[-2], $points->[-1]);

	# draw the line
	$window->draw_segments($gc, @segments);
	if ($params{arrow}) {
	    my $type = $params{arrow};
	    my $arrow_head_length = $params{arrow_head_length} || 10;
	    my @t;

	    if ($type eq "both") {
		push(@t, "start", "end");
	    }
	    else {
		push(@t, $type);
	    }
	    
	    foreach my $t (@t) {
		my($x1,$y1,$x2,$y2);
		if ($t eq "end") {
		    $x1 = $segments[-4];
		    $y1 = $segments[-3];
		    $x2 = $segments[-2];
		    $y2 = $segments[-1];
		}
		else {
		    $x1 = $segments[2];
		    $y1 = $segments[3];
		    $x2 = $segments[0];
		    $y2 = $segments[1];
		}
		
		my $r = sqrt(($x2-$x1)**2 + ($y2-$y1)**2);
		my $cos_theta = ($x2-$x1)/$r;
		my $sin_theta = ($y2-$y1)/$r;
		my $cos_alpha = $cos_theta + $sin_theta;
		my $sin_alpha = $cos_theta - $sin_theta;
		
		my $x3 = $x2 - $arrow_head_length * $cos_alpha;
		my $y3 = $y2 + $arrow_head_length * $sin_alpha;
		
		my $x4 = $x2 - $arrow_head_length * $sin_alpha;
		my $y4 = $y2 - $arrow_head_length * $cos_alpha;
		
		$window->draw_segments($gc, $x3,$y3,$x2,$y2, $x2,$y2, $x4,$y4);
	    }
	}
    }
    elsif ($object eq "oval") {
	my $x1 = $params{x1};
	my $x2 = $params{x2};
	my $y1 = $params{y1};
	my $y2 = $params{y2};

	my $x0 = ($x1+$x2)/2;
	my $y0 = ($y1+$y2)/2;

	if ($params{fill_color}) {
	    set_fg_color($this, $fill_color);

	    $window->draw_arc($gc, 1, $x0, $y0, ($x2-$x1)/2, ($y2-$y1)/2,
			      0, 360*64);
	}
	if ($params{outline_color} && $fill_color ne $outline_color) {
	    set_fg_color($this, $outline_color);

	    $window->draw_arc($gc, 0, $x0, $y0, ($x2-$x1)/2, ($y2-$y1)/2,
			      0, 360*64);
	}
	
	
    }
    elsif ($object eq "rect") {
	my $x0 = $params{x1};
	my $y0 = $params{y1};
	my $x1 = $params{x2};
	my $y1 = $params{y2};

	if ($params{fill_color}) {
	    set_fg_color($this, $fill_color);
	    $window->draw_segments($gc, 
				   $x0, $y0,  $x0, $y1,
				   $x0, $y1,  $x1, $y1,
				   $x1, $y1,  $x1, $y0,
				   $x1, $y0,  $x0, $y0);
	}
	if ($params{outline_color}) {
	    set_fg_color($this, $outline_color);
	    $window->draw_rectangle($gc, 0,
				    $x0, $y0,  $x1-$x0, $y1-$y0);
	}
    }
}

1;

