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

#
# $Id: $
# Translated by: Slaven Rezic
#
# Original copyright from tk/tests/canvas.test, version 1.23 from
# tktoolkit CVS on sourceforge:

# This file is a Tcl script to test out the procedures in tkCanvas.c,
# which implements generic code for canvases.  It is organized in the
# standard fashion for Tcl tests.
#
# Copyright (c) 1995-1996 Sun Microsystems, Inc.
# Copyright (c) 1998-2000 Ajuba Solutions.
# All rights reserved.
#
# RCS: @(#) $Id: canvas.test,v 1.23 2004/12/07 21:22:19 dgp Exp $

use strict;
use FindBin;
use lib $FindBin::RealBin;

use Getopt::Long;
use Tk;

BEGIN {
    if (!eval q{
	use Test::More;
	1;
    }) {
	print "1..0 # skip: no Test::More module\n";
	exit;
    }
}

use TkTest qw(is_float_pair);

plan tests => 166;

use_ok("Tk::Canvas");

my $verbose = 0;
GetOptions("v" => \$verbose)
    or die "usage: $0 [-v]";

# XXX - This test file is woefully incomplete.  At present, only a
# few of the features are tested.

my $mw = MainWindow->new;
$mw->geometry("+10+10");

if ($^O eq 'darwin') {
    # Under some newer MacOSX versions it seems that there are
    # problems by creating and destroying widgets with different
    # widths. The canvas widget with requested -width 100 created
    # after the one with -width 60 will only be 60px wide. This is
    # probably some wm-related bug, but as we test here for canvas
    # features, we need to workaround this bug. This is just done with
    # a dummy frame, which just makes sure that the mainwindow width
    # is at least 100 pixels.
    $mw->Frame(-width => 100, -height => 1)->pack;
}

my $c = $mw->Canvas;
isa_ok($c, "Tk::Canvas");
isa_ok($c, "Tk::Widget");
$c->pack;
$c->update;

sub deleteWindows () {
    eval { $_->destroy } for $mw->children;
}

use constant SKIP_CGET    => 5;
use constant SKIP_CONF    => 6;
use constant SKIP_ERROR   => 7;
use constant SKIP_RESTORE => 8;

my @tests =
   (
    ['-background', '#ff0000', '#ff0000', 'non-existent',
     'unknown color name "non-existent"'],
    ['-bg', '#ff0000', '#ff0000', 'non-existent',
     'unknown color name "non-existent"'],
    [qw(-bd 4 4 badValue), 'bad screen distance "badValue"'],
    [qw(-borderwidth 1.3 1 badValue), 'bad screen distance "badValue"'],
    [qw(-closeenough 24 24 bogus), q{'bogus' isn't numeric}],
    [qw(-confine true 1 silly), 'expected boolean value but got "silly"', 0,0,1,0], # probably auto-converted to some boolean value?
    [qw(-cursor arrow arrow badValue), 'bad cursor spec "badValue"'],
    [qw(-height 2.1 2 x42), 'bad screen distance "x42"'],
    [qw(-highlightbackground), '#112233', '#112233', 'ugly', 'unknown color name "ugly"'],
    [qw(-highlightcolor), '#110022', '#110022',	'bogus', 'unknown color name "bogus"'],
    [qw(-highlightthickness 18 18 badValue), 'bad screen distance "badValue"'],
    [qw(-insertbackground), '#110022', '#110022', 'bogus', 'unknown color name "bogus"'],
    [qw(-insertborderwidth 1.3 1 2.6x), 'bad screen distance "2.6x"'],
    [qw(-insertofftime 100 100 3.2), q{expected integer but got "3.2"}, 0,0,1,0], # probably auto-converted to integer?
    [qw(-insertontime 100 100 3.2), q{expected integer but got "3.2"}, 0,0,1,0], # probably auto-converted to integer?
    [qw(-insertwidth 1.3 1 6x), q{bad screen distance "6x"}],
    [qw(-relief groove groove 1.5), q{bad relief type "1.5": must be flat, groove, raised, ridge, solid, or sunken}],
    [qw(-selectbackground), '#110022', '#110022', 'bogus', q{unknown color name "bogus"}],
    [qw(-selectborderwidth 1.3 1 badValue), q{bad screen distance "badValue"}],
    [qw(-selectforeground), '#654321', '#654321', 'bogus', q{unknown color name "bogus"}],
    [qw(-takefocus), "any string", "any string", undef, undef],
    [qw(-width 402 402 xyz), q{bad screen distance "xyz"}],
    [qw(-xscrollcommand), q{Some command}, q{Some command}, undef, undef, 1],
    [qw(-yscrollcommand), q{Another command}, q{Another command}, undef, undef, 1],
);

foreach my $test (@tests) {
    my $name = $test->[0];
    $c->configure($name, $test->[1]);
    if (!$test->[SKIP_CGET]) {
	is($c->cget($name), $test->[2], "cget $name");
    }
    if (!$test->[SKIP_CONF]) {
	is(($c->configure($name))[4], $c->cget($name), "Comparing configure and cget values for $name");
    }

    if (defined $test->[4]) {
	if (!$test->[SKIP_ERROR]) {
	    eval { $c->configure($name, $test->[3]) };
	    like($@, qr/$test->[4]/, "Expected error message for $name");
	}
    }
    if (!$test->[SKIP_RESTORE]) {
	$c->configure($name, ($c->configure($name))[3]);
    }
}

eval { $c->configure(-gorp => "foo") };
like($@, qr{Bad option `-gorp'}, "configure throws error on bad option");
$c->create("rect",10,10,100,100);
eval { $c->configure(-gorp => "foo") };
like($@, qr{Bad option `-gorp'}, "configure throws error on bad option");

eval { $c->destroy };
$c = $mw->Canvas(qw(-width 60 -height 40),
		 -scrollregion => [qw(0 0 200 150)],
		 -bd => 0,
		 -highlightthickness => 0,
		)->pack;
$c->update;

{
    my $i = $c->createRectangle(10,10,100,100);
    eval { $c->bind($i, "<a>") };
    is($@, "", "bind method");
}

{
    my $i = $c->create('rect',10,10,100,100);
    eval { $c->bind($i, "<") };
    like($@, qr{no event type or button # or keysym}, "bind method with failure");
}

{
    $c->configure(-xscrollincrement => 40, -yscrollincrement => 5);
    $c->xview('moveto', 0);
    $c->update;
    is_float_pair([$c->xview], [0, 0.3], "xview method");
    $c->xview('scroll', 2, 'units');
    $c->update;
    is_float_pair([$c->xview], [0.4, 0.7], "xview method after scroll");
}

{
    # Tcl/Tk comment:
    # This test gives slightly different results on platforms such
    # as NetBSD.  I don't know why...
    # Perl/Tk comment:
    # Everything's ok on a FreeBSD machine.
    $c->configure(-xscrollincrement => 0, -yscrollincrement => 5);
    $c->xviewMoveto(0.6);
    $c->update;
    is_float_pair([$c->xview], [0.6, 0.9], "xview method (2)");
    $c->xviewScroll(2, 'units');
    $c->update;
    is_float_pair([$c->xview], [0.66, 0.96], "xview method after scroll (2)");
}

eval { $c->destroy };
$c = $mw->Canvas(qw(-width 60 -height 40),
		 -scrollregion => [qw(0 0 200 80)],
		 -borderwidth => 0,
		 -highlightthickness => 0,
		)->pack;
$c->update;

{
    $c->configure(qw(-xscrollincrement 40 -yscrollincrement 5));
    $c->yview('moveto', 0);
    $c->update;
    is_float_pair([$c->yview], [0, 0.5], "yview method");
    $c->yview('scroll', 3, 'units');
    $c->update;
    is_float_pair([$c->yview], [0.1875, 0.6875], "yview method after scroll");
}

{
    $c->configure(qw(-xscrollincrement 40 -yscrollincrement 0));
    $c->yviewMoveto(0);
    $c->update;
    is_float_pair([$c->yview], [0, 0.5], "yview method (2)");
    $c->yviewScroll(2, 'units');
    $c->update;
    is_float_pair([$c->yview], [0.1, 0.6], "yview method after scroll (2)");
}

{
    eval { $c->destroy };
    $c = $mw->Canvas(qw(-width 100 -height 50),
		     -scrollregion => [qw(-200 -100 305 102)],
		     -borderwidth => 2,
		     -highlightthickness => 3,
		    )->pack;
    $c->update;
    $c->configure(qw(-xscrollincrement 0 -yscrollincrement 0));
    $c->xview('moveto', 0);
    $c->yview('moveto', 0);
    $c->update;
    is($c->canvasx(0), -205, "canvasx after scrolling to origin");
    is($c->canvasy(0), -105, "canvasy after scrolling to origin");
}

{
    $c->configure(qw(-xscrollincrement 20 -yscrollincrement 10));
    my @x;
    for my $i (qw(.08 .10 .48 .50)) {
	$c->xviewMoveto($i);
	$c->update;
	push @x, $c->canvasx(0);
    }
    is_deeply(\@x, [-165, -145, 35, 55], "canvasx after multiple scroll");
}

{
    $c->configure(qw(-xscrollincrement 20 -yscrollincrement 10));
    my @x;
    for my $i (qw(.06 .08 .70 .72)) {
	$c->yviewMoveto($i);
	$c->update;
	push @x, $c->canvasy(0);
    }
    is_deeply(\@x, [-95, -85, 35, 45], "canvasy after multiple scroll");
}

{
    $c->configure(qw(-xscrollincrement 20 -yscrollincrement 10));
    $c->xview('moveto', 1.0);
    is($c->canvasx(0), 215);
}

{
    $c->configure(qw(-xscrollincrement 20 -yscrollincrement 10));
    $c->yview(moveto => 1.0);
    is($c->canvasy(0), 55);
}

deleteWindows;

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test dumps SV contents ...
    $c = $mw->Canvas;
    $c->create(qw(arc -100 10 100 210 -start 10 -extent 50 -style arc -tags arc1));
    is_deeply([$c->bbox("arc1")], [qw(48 21 100 94)], "BBox of arc");
    $c->createArc(qw(100 10 300 210 -start 10 -extent 50 -style chord -tags arc2));
    is_deeply([$c->bbox("arc2")], [qw(248 21 300 94)], "BBox of chord");
    $c->create(qw(arc 300 10 500 210 -start 10 -extent 50 -style pieslice -tags arc3));
    is_deeply([$c->bbox("arc3")], [qw(398 21 500 112)], "BBox of pieslice");
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test dumps SV contents ...
    $c = $mw->Canvas;

    # With Tk 8.0.4 the ids are now stored in a hash table.  You
    # can use this test as a performance test with older versions
    # by changing the value of size.
    my $size = 15;

    for my $i (0 .. $size-1) {
	my $x = -10 + 3*$i;
	for(my $j = 0, my $y = -10; $j < 10; $j++, $y+=3) {
	    $c->create('rect', "${x}c", "${y}c", ($x+2)."c", ($y+2)."c",
		       qw(-outline black -fill blue -tags rect));
	    $c->create('text', ($x+1)."c", ($y+1)."c", -text => "$i,$j",
		       qw(-anchor center -tags text));
	}
    }

    # The actual bench mark - this code also exercises all the hash
    # table changes.

    my $time = Tk::timeofday();
    foreach my $id ($c->find(withtag => "all")) {
	$c->lower($id);
	$c->raise($id);
	$c->find(withtag => $id);
	$c->bind('<Return>', $id, '');
	$c->delete($id);
    }
    my $delta = Tk::timeofday() - $time;
    diag "Canvas creation and deletion test needed $delta s"
	if $verbose;
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test dumps SV contents ...
    $c = $mw->Canvas;

    $c->create(qw(oval 20 20 40 40 -fill red -tag) , [qw(a b c d)]);
    $c->create(qw(oval 20 60 40 80 -fill yellow -tag), [qw(b a)]);
    $c->create(qw(oval 20 100 40 120 -fill green -tag), [qw(c b)]);
    $c->create(qw(oval 20 140 40 160 -fill blue -tag), [qw(b)]);
    $c->create(qw(oval 20 180 40 200 -fill bisque -tag), [qw(a d e)]);
    $c->create(qw(oval 20 220 40 240 -fill bisque -tag b));
    $c->create(qw(oval 20 260 40 280 -fill bisque -tag), ['d', "tag with spaces"]);

    is_deeply([$c->find(withtag => q{!a})],[qw(3 4 6 7)], "Tag expressions");
    is_deeply([$c->find(withtag => q{b&&c})],[qw(1 3)]);
    is_deeply([$c->find(withtag => q{b||c})],[qw(1 2 3 4 6)]);
    is_deeply([$c->find(withtag => q{a&&!b})],[qw(5)]);
    is_deeply([$c->find(withtag => q{!b&&!c})],[qw(5 7)]);
    is_deeply([$c->find(withtag => q{d&&a&&c&&b})],[qw(1)]);
    is_deeply([$c->find(withtag => q{b^a})],[qw(3 4 5 6)]);
    is_deeply([$c->find(withtag => q{(a&&!b)||(!a&&b)})],[qw(3 4 5 6)]);
    is_deeply([$c->find(withtag => q{ ( a && ! b ) || ( ! a && b ) })],[qw(3 4 5 6)]);
    is_deeply([$c->find(withtag => q{a&&!(c||d)})],[qw(2)]);
    is_deeply([$c->find(withtag => q{d&&"tag with spaces"})],[qw(7)], "Tag with spaces");
    is_deeply([$c->find(withtag => q"tag with spaces")],[qw(7)]);
}

for my $testdef (
		 [q{&&c}, qr{Unexpected operator in tag search expression}],
		 [q{!!c}, qr{Too many '!' in tag search expression}],
		 [q{b||}, qr{Missing tag in tag search expression}],
		 [q{b&&(c||)}, qr{Unexpected operator in tag search expression}],
		 [q{d&&""}, qr{Null quoted tag string in tag search expression}],
		 [q"d&&\"tag with spaces", qr{Missing endquote in tag search expression}],
		 [q{a&&"tag with spaces"z}, qr{Invalid boolean operator in tag search expression}],
		 [q{a&&b&c}, qr{Singleton '&' in tag search expression}],
		 [q{a||b|c}, qr{Singleton '|' in tag search expression}],
		) {
    my($tag_expr, $error_rx) = @$testdef;

    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas;

    $c->create(qw(oval 20 20 40 40 -fill red -tag), [qw(a b c d)]);
    $c->create(qw(oval 20 260 40 280 -fill bisque -tag), ['d', "tag with spaces"]);
    eval { $c->find(withtag => $tag_expr) };
    like($@, $error_rx, "Tag expression error ($tag_expr)");
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas;

    $c->create(qw(oval 20 20 40 40 -fill red -tag), [q{ strange tag(xxx&yyy|zzz) " && \" || ! ^ " }]);
    ok($c->find(withtag => q{ strange tag(xxx&yyy|zzz) " && \" || ! ^ " }), 
       q{backward compatility - strange tags that are not expressions});
} 

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas;

    $c->bind(q{a && b}, '<Enter>' => sub {warn "Enter"});
    $c->bind(q{a && b}, '<Leave>' => sub {warn "Leave"});

    pass(q{multple events bound to same tag expr});
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    # This would crash in 8.3.0 and 8.3.1
    $c->create(qw(polygon 0 0 100 100 200 50),
	       -fill => undef,
	       qw(-stipple gray50 -outline black));
    pass(q{canvas poly fill check, bug 5783});
}
   
{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    $c->create(qw(poly 30 30 90 90 30 90 90 30));
    ok($c->find(qw(over 40 40 45 45)), "rect region inc. edge; canvas poly overlap fill check, bug 226357");
    ok($c->find(qw(over 60 40 60 40)), "top-center point");
    ok(!$c->find(qw(over 0 0 0 0)), "not on poly");
    {
	# The failure only occurs with the real X server, but not
	# with Xnest and Xvfb, and it seems to occur only on some
	# X server versions (maybe driver dependent?). An equivalent
	# wish8.4 program had the same problem.
	local $TODO;
	$TODO = "Failure observed under some conditions on Debian"
	    if $^O eq 'linux';
	ok($c->find(qw(over 60 60 60 60)), "center-point");
    }
    ok(!$c->find(qw(over 45 50 45 50)), "outside poly");

    $c->itemconfigure(1, -fill => "", -outline => "black");
    ok($c->find(qw(over 40 40 45 45)), "rect region inc. edge");
    ok($c->find(qw(over 60 40 60 40)), "top-center point");
    ok(!$c->find(qw(over 0 0 0 0)), "not on poly");
    ok($c->find(qw(over 60 60 60 60)), "center-point");
    ok(!$c->find(qw(over 45 50 45 50)), "outside poly");

    $c->itemconfigure(1, -width => 8);
    ok($c->find(qw(over 45 50 45 50)), "outside poly?");
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    my $qx = 1.+1.;
    # qx has type double and no string representation (in Tcl?)
    $c->scale('all', $qx, 0, 1., 1.);
    # qx has now type MMRep and no string representation (in Tcl?);
    is($qx, 2, q{canvas mm obj, patch SF-403327, 102471});
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    my $val = 10;
    $val++;
    # qx has type double and no string representation (in Tcl?)
    $c->scale('all', $val, 0, 1, 1);
    # qx has now type MMRep and no string representation (in Tcl?)
    $val++;
    is($val, 12, q{canvas mm obj, patch SF-403327, 102471});
}

{
    my $x = "";
    
    my $kill_canvas = sub {
	my $w = shift;
	$w->destroy;
	$w = $mw->Canvas(qw(-height 200 -width 200))->pack(qw(-fill both -expand yes));
	$mw->idletasks;
	$w->create('rectangle', qw(80 80 120 120 -fill blue -tags blue));
	# bind a button press to re-build the canvas
	$w->bind('blue', '<ButtonRelease-1>' => sub { $x .= "ok" });
	$w;
    };

    $c = $kill_canvas->($c);

    # do this many times to improve chances of triggering the crash
    for my $i (0 .. 29) {
	$c->eventGenerate('<1>', qw(-x 100 -y 100));
	$c->eventGenerate('<ButtonRelease-1>', qw(-x 100 -y 100));
    }
    is($x, "okokokokokokokokokokokokokokokokokokokokokokokokokokokokokok",
       q{canvas delete during event, SF bug-228024});
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    eval { $c->scan };
    like($@, qr{\Qwrong # args: should be ".canvas scan mark|dragto x y ?dragGain?"\E},
	 q{canvas scan SF bug 581560});

    eval { $c->scan("bogus") };
    like($@, qr{\Qwrong # args: should be ".canvas scan mark|dragto x y ?dragGain?"\E},
	 "canvas scan");

    eval { $c->scan("mark") };
    like($@, qr{\Qwrong # args: should be ".canvas scan mark|dragto x y ?dragGain?"\E});

    $c->scan(qw(mark 10 10));
    pass("correct canvas scan mark");

    eval { $c->scan(qw(mark 10 10 5)) };
    like($@, qr{wrong # args: should be ".canvas scan mark x y"});

    $c->scan(qw(dragto 10 10 5));
    pass("correct canvas scan dragto");
}

{
    foreach my $type (qw{arc bitmap image line oval polygon rect text window}) {
	eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
	$c = $mw->Canvas->pack;

	eval { $c->create($type) };
	like($@, qr{wrong # args: should be ".canvas create $type coords \Q?arg arg ...?"\E},
	     "basic types check: $type requires coords");

	eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
	$c = $mw->Canvas->pack;

	eval { $c->create($type, 0) };
	like($@, qr{wrong # coordinates: expected},
	     "basic coords check: $type coords are paired");
    }
}

{
    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    my $id = $c->createArc(qw(0 10 20 30 -start 33));
    is($c->itemcget($id, "-start"), 33, "arc coords check");
}

{
    local $TODO = "Decide whether test failures are expected or not...";

    eval { $c->destroy } if Tk::Exists($c); # without existence test may dump SV contents ...
    $c = $mw->Canvas->pack;

    my $id = $c->createLine(qw{0 0 1 1 2 2 3 3 4 4 5 5 6 6});
    is($c->itemcget($id, '-smooth'), 0);

    foreach my $smoothtest (
			    ['yes', 'true'],
			    [1, 'true'],
			    ['bezier', 'true'],
			    ['raw', 'raw'],
			    ['r', 'raw'],
			    ['b', 'b']
			   ) {
	my($smoother, $expected) = @$smoothtest;
	$c->itemconfigure($id, -smooth => $smoother);
	is($c->itemcget($id, '-smooth'), $expected, "smooth test");
    }
}

__END__