The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

=head1 NAME

gi_easy - Demonstrates how to set up mouse bindings to create nodes and edges

=cut

use strict;
use warnings;
use Tk;
require Tk::GraphItems::TextBox;
require Tk::GraphItems::Connector;
require Tk::LabEntry;
my $mw = tkinit();

my %nodes;      # the simplest way to hold a 'model'
my %connectors;

my $scrolled_can = $mw -> Scrolled('Canvas',
				   -width        => 500,
				   -height       => 500,
				   -scrollregion => [0,0,500,500],
			       )->pack(-fill   => 'both',
				       -expand => 1);

# Extract the 'real' canvas out of the Scrolled widget :

my $can = $scrolled_can->Subwidget('scrolled');
my $text =<<'TEXT'
Mouse bindings:
Shift-Button-1    create a new vertex here
Shift-Button3     delete this vertex
Button1-move      drag this vertex
Control-Button1   select/unselect  this vertex
Control-Button1   if another vertex is selected:
                  create an edge from the selected
                  vertex to this one or delete the
                  edge if it is present.
Button3           Popup a context menu displaying
                  text, colour and coords of a vertex.
                  Edit text or colour fields to
                  change these properties
TEXT
;
$can-> createText(20,20,
		  -font     => ['Courier',10],
		  -text     => $text,
		  -anchor   => 'nw',
	      );

init_context_menu($can);
init_bindings($can);


MainLoop;


# create Tk::GraphItems bindings for the canvas instance
sub init_bindings{
    my ($can) = @_;

    # create a dummy node on our canvas to call bind_class with.
    # A call of 'bind_class' on this 'TextBox' instance installs
    # a binding which will be valid for every 'TextBox' item
    # on the same canvas.
    my $node = Tk::GraphItems::TextBox->new(canvas => $can,
					    'x'    => 0,
					    'y'    => 0
					);

    # Deleting a node:
    $node->bind_class("<Shift-Button-3>",
		      sub {
			  my $item = shift;
			  delete $nodes{$item};
			  delete $connectors{$item};
			  for (values %connectors){
			      delete $_->{$item}
			  }
		      }
		  );
    # Context Menu:
    $node->bind_class("<Button-3>",
		      sub {
			  my $item = shift;
			  my $menu = $can->{gi_easy_menu};
			  $menu->{properties}[1] = $item->text;
			  $menu->{properties}[2] = $item->colour;
			  my ($x,$y) = $item->get_coords;
			  $menu->{properties}[3] = $x;
			  $menu->{properties}[4] = $y;
			  $menu->Popup(-popover=>'cursor');
			  $menu->waitVariable(\$menu->{state});
			  $item->text($menu->{properties}[1]);
			  $item->colour($menu->{properties}[2]);
		      }
		  );

    # Adding and removing edges:
    my $selected;
    $node->bind_class("<Control-Button-1>",
		      sub { my $item = shift;
			    if ( !$selected ) {
				$selected = $item;
				$item -> colour('red');
			    } elsif ( $selected == $item ) {
				$item -> colour('white');
				$selected = undef;
			    } else {
				toggle_edge( $selected,$item );
				$selected -> colour('white');
				$selected = undef;
			    }
			}
		  );
    

    # A Tk-binding to create new nodes on Shift-B1:

    $can->Tk::bind("<Shift-Button-1>", sub {
		       my $e = $can->XEvent;
		       my ($mx, $my)=($e->x, $e->y);
		       new_node( $can,$mx,$my);
		   } );
    
}# end init_bindings

sub new_node{
    # Create a new TextBox instance and store a reference to it.
    # The TextBox will be destroyed when its reference gets deleted.

    my ( $can,$x,$y,$text ) = @_;
    my $v = Tk::GraphItems::TextBox->new(canvas=>$can,
					 text=> $text||'node',
				#	 font=> ['Courier',10],
					 'x'=> $x,
					 'y'=> $y);
    $nodes{$v}=$v;
    return $v;
}

sub new_edge{
    # Create a Connector with 'autodestroy' set to true so we don't
    # need to 'detach' it to have it destroyed.

    my ( $source,$target) = @_;
    my $conn = Tk::GraphItems::Connector->new( source      => $source,
					       target      => $target,
					       autodestroy => 1,
					  );
    $connectors{$source}{$target} = $conn;
}

sub toggle_edge{
    my ( $source,$target ) = @_;
    if ($connectors{$source}{$target}){
	delete $connectors{$source}{$target};
    }else{
	new_edge( $source, $target );
    }
}


sub init_context_menu{
    my $canvas = shift;
    my $ctm = $canvas->Toplevel;
    $ctm->configure(-borderwidth => 2,
		    -relief      => 'groove',
		  );
    $ctm->overrideredirect(1);
    $ctm->bind('<Leave>',[\&menu_handle_leave,$ctm]);
    my $properties;
    my $n = 1;
    for (qw/text colour x y/){
	$ctm->LabEntry(-label        => "$_",
		       -textvariable => \$properties->[$n++],
		       -bg           => 'white'
		   )->pack;
    }
    $ctm->{properties} = $properties;
    $ctm->withdraw;
    $canvas->{gi_easy_menu} = $ctm;
}

sub menu_handle_leave{
    my ($w,$ctm) = @_;
    return unless $w == $ctm;# are we leaving the contextmenu
                             # or a subwidget?

    $ctm->{state} ++;        # a flag to signal withdrawing
    $ctm->withdraw;
}