The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CanvasArrowhead;
use strict;
use Glib qw(TRUE FALSE);
use Gnome2::Canvas;

use constant LEFT            => 50.0;
use constant RIGHT           => 350.0;
use constant MIDDLE          => 150.0;
use constant DEFAULT_WIDTH   => 2;
use constant DEFAULT_SHAPE_A => 8;
use constant DEFAULT_SHAPE_B => 10;
use constant DEFAULT_SHAPE_C => 3;


sub set_dimension {
	my ($canvas, $arrow_name, $text_name,
	    $x1, $y1, $x2, $y2, $tx, $ty, $dim) = @_;

	$canvas->{$arrow_name}->set (points => [$x1, $y1, $x2, $y2]);

	$canvas->{$text_name}->set (text => $dim,
	                            x => $tx,
	                            y => $ty);

}

sub move_drag_box {
	my ($item, $x, $y) = @_;

	$item->set (x1 => $x - 5.0,
		    y1 => $y - 5.0,
		    x2 => $x + 5.0,
		    y2 => $y + 5.0);
}

sub set_arrow_shape {
	my $canvas = shift;

	my $width   = $canvas->{width};
	my $shape_a = $canvas->{shape_a};
	my $shape_b = $canvas->{shape_b};
	my $shape_c = $canvas->{shape_c};

	# Big arrow

	$canvas->{big_arrow}->set (width_pixels => 10 * $width,
	                           arrow_shape_a => $shape_a * 10,
	                           arrow_shape_b => $shape_b * 10,
	                           arrow_shape_c => $shape_c * 10);

	# Outline

	my @coords = ();
	$coords[0] = RIGHT - 10 * $shape_a;
	$coords[1] = MIDDLE;
	$coords[2] = RIGHT - 10 * $shape_b;
	$coords[3] = MIDDLE - 10 * ($shape_c + $width / 2.0);
	$coords[4] = RIGHT;
	$coords[5] = MIDDLE;
	$coords[6] = $coords[2];
	$coords[7] = MIDDLE + 10 * ($shape_c + $width / 2.0);
	$coords[8] = $coords[0];
	$coords[9] = $coords[1];
	$canvas->{outline}->set (points => \@coords);

	# Drag boxes

	move_drag_box ($canvas->{width_drag_box},
		       LEFT,
		       MIDDLE - 10 * $width / 2.0);

	move_drag_box ($canvas->{shape_a_drag_box},
		       RIGHT - 10 * $shape_a,
		       MIDDLE);

	move_drag_box ($canvas->{shape_b_c_drag_box},
		       RIGHT - 10 * $shape_b,
		       MIDDLE - 10 * ($shape_c + $width / 2.0));

	# Dimensions

	set_dimension ($canvas, "width_arrow", "width_text",
		       LEFT - 10,
		       MIDDLE - 10 * $width / 2.0,
		       LEFT - 10,
		       MIDDLE + 10 * $width / 2.0,
		       LEFT - 15,
		       MIDDLE,
		       $width);

	set_dimension ($canvas, "shape_a_arrow", "shape_a_text",
		       RIGHT - 10 * $shape_a,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 10,
		       RIGHT,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 10,
		       RIGHT - 10 * $shape_a / 2.0,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 15,
		       $shape_a);

	set_dimension ($canvas, "shape_b_arrow", "shape_b_text",
		       RIGHT - 10 * $shape_b,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 35,
		       RIGHT,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 35,
		       RIGHT - 10 * $shape_b / 2.0,
		       MIDDLE + 10 * ($width / 2.0 + $shape_c) + 40,
		       $shape_b);

	set_dimension ($canvas, "shape_c_arrow", "shape_c_text",
		       RIGHT + 10,
		       MIDDLE - 10 * $width / 2.0,
		       RIGHT + 10,
		       MIDDLE - 10 * ($width / 2.0 + $shape_c),
		       RIGHT + 15,
		       MIDDLE - 10 * ($width / 2.0 + $shape_c / 2.0),
		       $shape_c);

	# Info

	$canvas->{width_info}->set (text => "width: $width");
	$canvas->{shape_a_info}->set (text => "arrow_shape_a: $shape_a");
	$canvas->{shape_b_info}->set (text => "arrow_shape_b: $shape_b");
	$canvas->{shape_c_info}->set (text => "arrow_shape_c: $shape_c");

	# Sample arrows

	$canvas->{sample_1}->set (width_pixels => $width,
	                          arrow_shape_a => $shape_a,
	                          arrow_shape_b => $shape_b,
	                          arrow_shape_c => $shape_c);
	$canvas->{sample_2}->set (width_pixels => $width,
	                          arrow_shape_a => $shape_a,
	                          arrow_shape_b => $shape_b,
	                          arrow_shape_c => $shape_c);
	$canvas->{sample_3}->set (width_pixels => $width,
	                          arrow_shape_a => $shape_a,
	                          arrow_shape_b => $shape_b,
	                          arrow_shape_c => $shape_c);
}

sub highlight_box {
	my ($item, $event) = @_;

	if ($event->type eq 'enter-notify') {
		$item->set (fill_color => 'red');

	} elsif ($event->type eq 'leave-notify') {
		$item->set (fill_color => undef)
			unless $event->state & 'button1-mask';

	} elsif ($event->type eq 'button-press') {
		$item->grab ([qw/pointer-motion-mask button-release-mask/],
		             Gtk2::Gdk::Cursor->new ('fleur'),
		             $event->time);

	} elsif ($event->type eq 'button-release') {
		$item->ungrab ($event->time);
	}

	return FALSE;
}

sub create_drag_box {
	my ($root, $box_name, $callback) = @_;
	my $box = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Rect',
					     fill_color => undef,
					     outline_color => 'black',
					     width_pixels => 0);
	$box->signal_connect (event => \&highlight_box);
	$box->signal_connect (event => $callback);

	$root->canvas->{$box_name} = $box;
}

sub width_event {
	my ($item, $event) = @_;

	return FALSE
		if (($event->type ne 'motion-notify') || 
		    !($event->state >= 'button1-mask'));

	my $width = (MIDDLE - $event->y) / 5;
	return FALSE
		if $width < 0;

	$item->canvas->{width} = $width;
	set_arrow_shape ($item->canvas);

	return FALSE;
}

sub shape_a_event {
	my ($item, $event) = @_;

	return FALSE
		if (($event->type ne 'motion-notify') || 
		    !($event->state >= 'button1-mask'));

	my $shape_a = (RIGHT - $event->x) / 10;
	return FALSE if (($shape_a < 0) || ($shape_a > 30));

	$item->canvas->{shape_a} = $shape_a;
	set_arrow_shape ($item->canvas);

	return FALSE;
}

sub shape_b_c_event {
	my ($item, $event) = @_;

	return FALSE
		if (($event->type ne 'motion-notify') || 
		    !($event->state >= 'button1-mask'));

	my $change = FALSE;

	my $shape_b = (RIGHT - $event->x) / 10;
	if (($shape_b >= 0) && ($shape_b <= 30)) {
		$item->canvas->{shape_b} = $shape_b;
		$change = TRUE;
	}

	my $width = $item->canvas->{width};
	my $shape_c = ((MIDDLE - 5 * $width) - $event->y) / 10;
	if ($shape_c >= 0) {
		$item->canvas->{shape_c} = $shape_c;
		$change = TRUE;
	}

	set_arrow_shape ($item->canvas)
		if $change;

	return FALSE;
}

sub create_dimension {
	my ($root, $arrow_name, $text_name, $anchor) = @_;

	my $item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Line',
					      fill_color => 'black',
					      first_arrowhead => TRUE,
					      last_arrowhead => TRUE,
					      arrow_shape_a => 5.0,
					      arrow_shape_b => 5.0,
					      arrow_shape_c => 3.0);
	$root->canvas->{$arrow_name} = $item;

	$item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Text',
					   fill_color => 'black',
					   font => 'Sans 12',
					   anchor => $anchor);
	$root->canvas->{$text_name} = $item;
}

sub create_info {
	my ($root, $info_name, $x, $y) = @_;
	my $item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Text',
					      x => $x,
					      y => $y,
					      fill_color => 'black',
					      font => 'Sans 14',
					      anchor => 'GTK_ANCHOR_NW');
	$root->canvas->{$info_name} = $item;
}

sub create_sample_arrow {
	my ($root, $sample_name, $x1, $y1, $x2, $y2) = @_;

	my $item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Line',
					      points => [$x1, $y1, $x2, $y2],
					      fill_color => 'black',
					      first_arrowhead => TRUE,
					      last_arrowhead => TRUE);
	$root->canvas->{$sample_name} = $item;
}

sub create {
	my $vbox = Gtk2::VBox->new (FALSE, 4);
	$vbox->set_border_width (4);
	$vbox->show;

	my $w = Gtk2::Label->new ("This demo allows you to edit arrowhead shapes.  Drag the little boxes\n"
		. "to change the shape of the line and its arrowhead.  You can see the\n"
		. "arrows at their normal scale on the right hand side of the window.");
	$vbox->pack_start ($w, FALSE, FALSE, 0);
	$w->show;

	$w = Gtk2::Alignment->new (0.5, 0.5, 0.0, 0.0);
	$vbox->pack_start ($w, TRUE, TRUE, 0);
	$w->show;

	my $frame = Gtk2::Frame->new;
	$frame->set_shadow_type ('in');
	$w->add ($frame);
	$frame->show;

	my $canvas = Gnome2::Canvas->new;
	$canvas->set_size_request (500, 350);
	$canvas->set_scroll_region (0, 0, 500, 350);
	$frame->add ($canvas);
	$canvas->show;

	my $root = $canvas->root;

	$canvas->{width} = DEFAULT_WIDTH;
	$canvas->{shape_a} = DEFAULT_SHAPE_A;
	$canvas->{shape_b} = DEFAULT_SHAPE_B;
	$canvas->{shape_c} = DEFAULT_SHAPE_C;

	# Big arrow

	my $item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Line',
				      points => [LEFT, MIDDLE,
				                 RIGHT, MIDDLE],
				      fill_color => 'mediumseagreen',
				      width_pixels => DEFAULT_WIDTH * 10,
				      last_arrowhead => TRUE);
	$canvas->{big_arrow} = $item;

	# Arrow outline

	$item = Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Line',
				      fill_color => 'black',
				      width_pixels => 2,
				      cap_style => 'round',
				      join_style => 'round');
	$canvas->{outline} = $item;

	# Drag boxes

	create_drag_box ($root, "width_drag_box", \&width_event);
	create_drag_box ($root, "shape_a_drag_box", \&shape_a_event);
	create_drag_box ($root, "shape_b_c_drag_box", \&shape_b_c_event);

	# Dimensions

	create_dimension ($root, "width_arrow", "width_text", 'e');
	create_dimension ($root, "shape_a_arrow", "shape_a_text", 'n');
	create_dimension ($root, "shape_b_arrow", "shape_b_text", 'n');
	create_dimension ($root, "shape_c_arrow", "shape_c_text", 'w');

	# Info

	create_info ($root, "width_info", LEFT, 260);
	create_info ($root, "shape_a_info", LEFT, 280);
	create_info ($root, "shape_b_info", LEFT, 300);
	create_info ($root, "shape_c_info", LEFT, 320);

	# Division line

	Gnome2::Canvas::Item->new ($root, 'Gnome2::Canvas::Line',
	                           points => [RIGHT + 50, 0,
	                                      RIGHT + 50, 1000],
	                           fill_color => 'black',
	                           width_pixels => 2);

	# Sample arrows

	create_sample_arrow ($root, "sample_1", RIGHT + 100, 30, RIGHT + 100, MIDDLE - 30);
	create_sample_arrow ($root, "sample_2", RIGHT + 70, MIDDLE, RIGHT + 130, MIDDLE);
	create_sample_arrow ($root, "sample_3", RIGHT + 70, MIDDLE + 30, RIGHT + 130, MIDDLE + 120);

	# Done!
	
	set_arrow_shape ($canvas);

	return $vbox;
}

1;