File Coverage

blib/lib/Tk/AbstractCanvas.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # 524JDkqX: Tk::AbstractCanvas.pm by PipStuart , derived from Tk::WorldCanvas (by JosephSkrovan
2             # which was based on a version by RudyAlbachten ) and Tk::RotCanvas (by AlaQumsieh ).
3             package Tk::AbstractCanvas; # This module provides an alternative to Tk::Canvas which is able to zoom, pan, and rotate.
4             require Tk::Derived;
5             require Tk::Canvas;
6 1     1   8673 use strict;use warnings;use utf8;
  1     1   3  
  1     1   22  
  1         5  
  1         1  
  1         22  
  1         467  
  1         12  
  1         6  
7 1     1   342 use Tk;
  0            
  0            
8             use Carp;
9             our $VERSION = '1.8';my $d8VS='H79M9Rot';
10             @Tk::AbstractCanvas::ISA = qw(Tk::Derived Tk::Canvas); Construct Tk::Widget 'AbstractCanvas';
11             sub Tk::Widget::ScrlACnv { shift->Scrolled('AbstractCanvas'=>@_) }
12             my %_can_rotate_about_center = ( # If objects can't rotate about their center, their center can at least rotate about another point on the canvas.
13             line => 1,
14             polygon => 1,
15             );
16             my %_rotate_methods = (
17             line => \&_rotate_line,
18             text => \&_rotate_line,
19             image => \&_rotate_line,
20             bitmap => \&_rotate_line,
21             window => \&_rotate_line,
22             rectangle => \&_rotate_rect,
23             arc => \&_rotate_rect,
24             grid => \&_rotate_rect,
25             oval => \&_rotate_rect,
26             polygon => \&_rotate_poly,
27             );
28             use constant PI => 3.14159269;
29             sub ClassInit { my($acnv, $mwin)= @_; $acnv->SUPER::ClassInit($mwin); }
30             sub InitObject {
31             my($acnv, $args)= @_;
32             my $pdat = $acnv->privateData();
33             $pdat->{'bbox' } = [0, 0, -1, -1];
34             $pdat->{'scale' } = 1 ;
35             $pdat->{'movex' } = 0 ;
36             $pdat->{'movey' } = 0 ;
37             $pdat->{'bboxvalid' } = 1 ;
38             $pdat->{'inverty' } = 0 ; # I guess these options are not really private since they have accessors but I'm not sure where better to store them
39             $pdat->{'rect_to_poly' } = 0 ;
40             $pdat->{'oval_to_poly' } = 0 ;
41             $pdat->{'control_nav' } = 0 ;
42             $pdat->{'control_nav_busy' } = 0 ; # flag to know not to allow other navs
43             $pdat->{'control_zoom_scale'} = -0.001;
44             $pdat->{'control_rot_scale' } = -0.3 ;
45             $pdat->{'control_rot_mocb' } = undef; # MOtion CallBack
46             $pdat->{'control_rot_rlcb' } = undef; # ReLease CallBack
47             $pdat->{'eventx' } = -1 ;
48             $pdat->{'eventy' } = -1 ;
49             $pdat->{'width' } = $acnv->width( );
50             $pdat->{'height' } = $acnv->height();
51             $acnv->configure(-confine => 0);
52             $acnv->ConfigSpecs( '-bandColor' => ['PASSIVE', 'bandColor', 'BandColor', 'red' ], '-bandcolor' => '-bandColor',
53             '-changeView' => ['CALLBACK', 'changeView', 'ChangeView', undef], '-changeview' => '-changeView');
54             $acnv->CanvasBind('' => sub {
55             my $widt = $acnv->width( ); my $pwid = $pdat->{'width' };
56             my $hite = $acnv->height(); my $phit = $pdat->{'height'};
57             if($widt != $pwid || $hite != $phit) {
58             my $bwid = $acnv->cget('-borderwidth'); _view_area_canvas($acnv, $bwid, $bwid, $pwid - $bwid, $phit - $bwid);
59             $pdat->{'width' } = $widt; $pdat->{'height'} = $hite; my $bbox = $pdat->{'bbox'};
60             my $left = $acnv->canvasx($bwid); my $rite = $acnv->canvasx($widt - $bwid);
61             my $topp = $acnv->canvasy($bwid); my $botm = $acnv->canvasy($hite - $bwid);
62             $acnv->viewAll() if(_inside(@$bbox, $left, $topp, $rite, $botm));
63             }
64             });
65             $acnv->SUPER::InitObject($args);
66             }
67             sub invertY { my $acnv = shift(); my $pdat = $acnv->privateData(); $pdat->{'inverty' } = shift() if(@_); return($pdat->{'inverty' }); }
68             sub rectToPoly { my $acnv = shift(); my $pdat = $acnv->privateData(); $pdat->{'rect_to_poly'} = shift() if(@_); return($pdat->{'rect_to_poly'}); }
69             sub ovalToPoly { my $acnv = shift(); my $pdat = $acnv->privateData(); $pdat->{'oval_to_poly'} = shift() if(@_); return($pdat->{'oval_to_poly'}); }
70             sub controlNav {
71             my $acnv = shift(); my $pdat = $acnv->privateData();
72             if(@_) {
73             $pdat->{'control_nav'} = shift();
74             if($pdat->{'control_nav'}) {
75             $acnv->CanvasBind('' => sub {
76             if(!$pdat->{'control_nav_busy'} && $pdat->{'eventx'} == -1 && $pdat->{'eventy'} == -1) {
77             $pdat->{'control_nav_busy'} = 1;
78             ($pdat->{'eventx'}, $pdat->{'eventy'})= $acnv->eventLocation();
79             $acnv->CanvasBind('' => sub {
80             my($evtx, $evty)= $acnv->eventLocation(); my($left, $botm, $rite, $topp)= $acnv->getView();
81             my($cntx, $cnty)=(($left + $rite) / 2, ($botm + $topp) / 2);
82             for($acnv->find('all')) {
83             $acnv->rotate($_, (($pdat->{'eventx'} - $evtx) + ($pdat->{'eventy'} - $evty)) * $pdat->{'control_rot_scale'} * $pdat->{'scale'}, $cntx, $cnty);
84             }
85             ($pdat->{'eventx'}, $pdat->{'eventy'})=($evtx, $evty);
86             $pdat->{'control_rot_mocb'}->() if($pdat->{'control_rot_mocb'});
87             });
88             my $rcrf = sub { # Release Code ReF for either Control-Key or MouseButton Release binding restoration
89             my($evtx, $evty)= $acnv->eventLocation(); my($left, $botm, $rite, $topp)= $acnv->getView();
90             my($cntx, $cnty)=(($left + $rite) / 2, ($botm + $topp) / 2);
91             for($acnv->find('all')) {
92             $acnv->rotate($_, (($pdat->{'eventx'} - $evtx) + ($pdat->{'eventy'} - $evty)) * $pdat->{'control_rot_scale'} * $pdat->{'scale'}, $cntx, $cnty);
93             }
94             ($pdat->{'eventx'}, $pdat->{'eventy'})=(-1, -1);
95             $pdat->{'control_rot_rlcb'}->() if($pdat->{'control_rot_rlcb'});
96             $pdat->{'control_nav_busy'} = 0; # maybe control_nav_busy should be cleared before calling the ReLease CallBack?
97             $acnv->CanvasBind('' => '');
98             $acnv->CanvasBind('' => '');
99             $acnv->CanvasBind( '' => '');
100             };
101             $acnv->CanvasBind( '' => $rcrf);
102             $acnv->CanvasBind( '' => $rcrf);
103             }
104             });
105             $acnv->CanvasBind('' => sub {
106             if(!$pdat->{'control_nav_busy'} && $pdat->{'eventx'} == -1 && $pdat->{'eventy'} == -1) {
107             $pdat->{'control_nav_busy'} = 1;
108             ($pdat->{'eventx'}, $pdat->{'eventy'})= $acnv->eventLocation();
109             $acnv->CanvasBind('' => sub {
110             my($evtx, $evty)= $acnv->eventLocation();
111             $acnv->panAbstract( $pdat->{'eventx'} - $evtx, $pdat->{'eventy'} - $evty);
112             ($pdat->{'eventx'}, $pdat->{'eventy'}) = $acnv->eventLocation();
113             });
114             my $rcrf = sub { # Release Code ReF for either Control-Key or MouseButton Release binding restoration
115             my($evtx, $evty)= $acnv->eventLocation();
116             $acnv->panAbstract( $pdat->{'eventx'} - $evtx, $pdat->{'eventy'} - $evty);
117             ($pdat->{'eventx'}, $pdat->{'eventy'})=(-1, -1);
118             $pdat->{'control_nav_busy'} = 0;
119             $acnv->CanvasBind('' => '');
120             $acnv->CanvasBind('' => '');
121             $acnv->CanvasBind( '' => '');
122             };
123             $acnv->CanvasBind( '' => $rcrf);
124             $acnv->CanvasBind( '' => $rcrf);
125             }
126             });
127             $acnv->CanvasBind('' => sub {
128             if(!$pdat->{'control_nav_busy'} && $pdat->{'eventx'} == -1 && $pdat->{'eventy'} == -1) {
129             $pdat->{'control_nav_busy'} = 1;
130             ($pdat->{'eventx'}, $pdat->{'eventy'})= $acnv->eventLocation();
131             $acnv->CanvasBind('' => sub {
132             my($evtx, $evty)= $acnv->eventLocation();
133             $acnv->zoom(1.0 + (($pdat->{'eventx'} - $evtx) + ($pdat->{'eventy'} - $evty)) * $pdat->{'control_zoom_scale'} * $pdat->{'scale'});
134             ($pdat->{'eventx'}, $pdat->{'eventy'})=($evtx, $evty);
135             });
136             my $rcrf = sub { # Release Code ReF for either Control-Key or MouseButton Release binding restoration
137             my($evtx, $evty)= $acnv->eventLocation();
138             $acnv->zoom(1.0 + (($pdat->{'eventx'} - $evtx) + ($pdat->{'eventy'} - $evty)) * $pdat->{'control_zoom_scale'} * $pdat->{'scale'});
139             ($pdat->{'eventx'}, $pdat->{'eventy'})=(-1, -1);
140             $pdat->{'control_nav_busy'} = 0;
141             $acnv->CanvasBind('' => '');
142             $acnv->CanvasBind('' => '');
143             $acnv->CanvasBind( '' => '');
144             };
145             $acnv->CanvasBind( '' => $rcrf);
146             $acnv->CanvasBind( '' => $rcrf);
147             }
148             });
149             } else {
150             $acnv->CanvasBind('' => '');
151             $acnv->CanvasBind('' => '');
152             $acnv->CanvasBind('' => '');
153             }
154             }
155             return($pdat->{'control_nav'});
156             }
157             sub controlNavBusy { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'control_nav_busy' } = shift if(@_); return($pdat->{'control_nav_busy' });}
158             sub controlZoomScale { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'control_zoom_scale'} = shift if(@_); return($pdat->{'control_zoom_scale'});}
159             sub controlRotScale { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'control_rot_scale' } = shift if(@_); return($pdat->{'control_rot_scale' });}
160             sub controlRotMoCB { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'control_rot_mocb' } = shift if(@_); return($pdat->{'control_rot_mocb' });}
161             sub controlRotRlCB { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'control_rot_rlcb' } = shift if(@_); return($pdat->{'control_rot_rlcb' });}
162             sub controlScale { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'scale' } = shift if(@_); return($pdat->{'scale' });}
163             sub eventX { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'eventx' } = shift if(@_); return($pdat->{'eventx' });}
164             sub eventY { my $acnv = shift; my $pdat = $acnv->privateData(); $pdat->{'eventy' } = shift if(@_); return($pdat->{'eventy' });}
165             sub rotate { # takes as input id of obj to rot, angle to rot by, && optional x,y point to rot about instead of obj center, if possible
166             my($self, $obid, $angl, $xfoc, $yfoc)= @_; croak "rotate: Must supply an angle -" unless(defined($angl));
167             my $type = $self->type($obid); # some objs need a pivot point to rotate their center around
168             return() unless(exists($_can_rotate_about_center{$type}) || (defined($xfoc) && defined($yfoc)));
169             $_rotate_methods{$type}->($self, $obid, $angl, $xfoc, $yfoc);
170             }
171             sub _rotate_line {
172             my($self, $obid, $angl, $xmid, $ymid)= @_;
173             my @crds = $self->coords($obid); # get old coords && translate to origin, rot, then translate back
174             unless(defined($xmid)) {
175             $xmid = $crds[0] + 0.5 * ($crds[2] - $crds[0]);
176             $ymid = $crds[1] + 0.5 * ($crds[3] - $crds[1]);
177             }
178             my @newc; my $radi = PI * $angl / 180.0; my $sine = sin($radi); my $cosi = cos($radi);
179             while(my($xcrd, $ycrd)= splice(@crds, 0, 2)) { # calc new coords
180             my $xnew = $xcrd - $xmid; my $ynew = $ycrd - $ymid;
181             push(@newc, $xmid + ($xnew * $cosi - $ynew * $sine)); push(@newc, $ymid + ($xnew * $sine + $ynew * $cosi));
182             }
183             $self->coords($obid, @newc); # updt coords redraws
184             }
185             sub _rotate_poly {
186             my($self, $obid, $angl, $xmid, $ymid)= @_;
187             my @crds = $self->coords($obid); # get old coords && poly center (of Mass) if no external rot pt given
188             ($xmid, $ymid)= _get_CM(@crds) unless(defined($xmid));
189             my @newc; my $radi = PI * $angl / 180.0; my $sine = sin($radi); my $cosi = cos($radi);
190             while(my($xcrd, $ycrd)= splice(@crds, 0, 2)) { # Calculate the new coordinates of the poly.
191             my $xnew = $xcrd - $xmid; my $ynew = $ycrd - $ymid;
192             push(@newc, $xmid + ($xnew * $cosi - $ynew * $sine)); push(@newc, $ymid + ($xnew * $sine + $ynew * $cosi));
193             }
194             $self->coords($obid, @newc); # updt coords redraws
195             }
196             sub _rotate_rect { # special rectangle rotation just for center
197             my($self, $obid, $angl, $xmid, $ymid)= @_;
198             my @crds = $self->coords($obid); # get old coords
199             my($xomd, $yomd)=(($crds[0] + $crds[2]) / 2, ($crds[1] + $crds[3]) / 2); # original midpoint
200             ($xmid, $ymid)= ($xomd, $yomd) unless(defined($xmid));
201             my @newc; my $radi = PI * $angl / 180.0; my $sine = sin($radi); my $cosi = cos($radi);
202             my $xnew = $xomd - $xmid; my $ynew = $yomd - $ymid; # calc coords of new midpoint
203             my $xrmd = $xmid + ($xnew * $cosi - $ynew * $sine); my $yrmd = $ymid + ($xnew * $sine + $ynew * $cosi);
204             while(my($xcrd, $ycrd)= splice(@crds, 0, 2)) { push(@newc, ($xcrd - $xomd) + $xrmd, ($ycrd - $yomd) + $yrmd); }
205             $self->coords($obid, @newc); # updt coords redraws
206             }
207             sub getView {
208             my($cnvs)= @_; my $bwid = $cnvs->cget('-borderwidth'); my $left = $bwid; my $rite = $cnvs->width() - $bwid;
209             my $topp = $bwid; my $botm = $cnvs->height() - $bwid;
210             return(abstractxy($cnvs, $left, $topp), abstractxy($cnvs, $rite, $botm));
211             }
212             sub xview {
213             my $cnvs = shift(); _new_bbox($cnvs) unless($cnvs->privateData->{'bboxvalid'}); $cnvs->SUPER::xview(@_);
214             $cnvs->Callback('-changeView' => getView($cnvs)) if(defined($cnvs->cget('-changeView')));
215             }
216             sub yview {
217             my $cnvs = shift(); _new_bbox($cnvs) unless($cnvs->privateData->{'bboxvalid'}); $cnvs->SUPER::yview(@_);
218             $cnvs->Callback('-changeView' => getView($cnvs)) if(defined($cnvs->cget('-changeView')));
219             }
220             sub delete {
221             my($cnvs, @tags)= @_; my $recr = _killBand($cnvs); my $foun = 0; # RECReate && FOUNd
222             for(@tags) { if($cnvs->type($_)) { $foun = 1; last(); } }
223             unless($foun) { _makeBand($cnvs) if($recr); return(); }
224             my $pdat = $cnvs->privateData();
225             my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
226             my($cbx1, $cby1, $cbx2, $cby2)= _superBbox($cnvs, @tags);
227             $cnvs->SUPER::delete(@tags);
228             if(!$cnvs->type('all')) { # deleted last object
229             $pdat->{'bbox' } = [0, 0, -1, -1]; $pdat->{'movex'} = 0;
230             $pdat->{'scale' } = 1 ; $pdat->{'movey'} = 0;
231             } elsif(!_inside($cbx1, $cby1, $cbx2, $cby2, $pbx1, $pby1, $pbx2, $pby2)) {
232             $pdat->{'bboxvalid'} = 0 ;
233             }
234             _makeBand($cnvs) if($recr);
235             }
236             sub _inside {
237             my($pbx1, $pby1, $pbx2, $pby2, $cbx1, $cby1, $cbx2, $cby2)= @_;
238             my $wmrg = 0.01 * ($cbx2 - $cbx1); $wmrg = 3 if($wmrg < 3); # width margin
239             my $hmrg = 0.01 * ($cby2 - $cby1); $hmrg = 3 if($hmrg < 3); # height margin
240             return($pbx1 - $wmrg > $cbx1 && $pby1 - $hmrg > $cby1 &&
241             $pbx2 + $wmrg < $cbx2 && $pby2 + $hmrg < $cby2);
242             }
243             sub _new_bbox {
244             my($cnvs)= @_; my $bwid = $cnvs->cget('-borderwidth'); my $pdat = $cnvs->privateData(); my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
245             my $vwid = $cnvs->width() - 2 * $bwid; $pbx2++ if($pbx2 == $pbx1); my $zumx = $vwid / abs($pbx2 - $pbx1);
246             my $vhit = $cnvs->height() - 2 * $bwid; $pby2++ if($pby2 == $pby1); my $zumy = $vhit / abs($pby2 - $pby1);
247             my $zoom =($zumx > $zumy) ? $zumx : $zumy;
248             if($zoom > 1.01) { _scale($cnvs, $cnvs->width() / 2, $cnvs->height() / 2, $zoom * 100) ; }
249             my($cbx1, $cby1, $cbx2, $cby2)= _superBbox($cnvs, 'all');
250             $pdat->{ 'bbox' } = [$cbx1, $cby1, $cbx2, $cby2] ;
251             $cnvs->configure('-scrollregion' => [$cbx1, $cby1, $cbx2, $cby2]);
252             if($zoom > 1.01) { _scale($cnvs, $cnvs->width() / 2, $cnvs->height() / 2, 1 / ($zoom * 100)); }
253             $pdat->{ 'bboxvalid'} = 1;
254             }
255             sub _find_box {
256             die "!*EROR*! The number of args to _find_box must be positive and even!\n" if((@_ % 2) || !@_);
257             my($fbx1, $fbx2, $fby1, $fby2)=($_[0], $_[0], $_[1], $_[1]);
258             for(my $indx = 2; $indx < @_; $indx += 2) {
259             $fbx1 = $_[$indx ] if($_[$indx ] < $fbx1);
260             $fbx2 = $_[$indx ] if($_[$indx ] > $fbx2);
261             $fby1 = $_[$indx + 1] if($_[$indx + 1] < $fby1);
262             $fby2 = $_[$indx + 1] if($_[$indx + 1] > $fby2);
263             }
264             return($fbx1, $fby1, $fbx2, $fby2);
265             }
266             sub zoom {
267             my($cnvs, $zoom)= @_; _new_bbox($cnvs) unless($cnvs->privateData->{'bboxvalid'}); _scale($cnvs, $cnvs->width() / 2, $cnvs->height() / 2, $zoom);
268             $cnvs->Callback('-changeView' => getView($cnvs)) if(defined($cnvs->cget('-changeView')));
269             }
270             sub _scale {
271             my($cnvs, $xoff, $yoff, $scal)= @_; $scal = abs($scal);
272             my $xval = $cnvs->canvasx(0) + $xoff; my $pdat = $cnvs->privateData();
273             my $yval = $cnvs->canvasy(0) + $yoff; return() unless($cnvs->type('all'));
274             $pdat->{'movex'} = ($pdat->{'movex'} - $xval) * $scal + $xval;
275             $pdat->{'movey'} = ($pdat->{'movey'} - $yval) * $scal + $yval;
276             $pdat->{'scale'} *= $scal; $cnvs->SUPER::scale('all', $xval, $yval, $scal, $scal);
277             my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
278             $pbx1 = ($pbx1 - $xval) * $scal + $xval; $pbx2 = ($pbx2 - $xval) * $scal + $xval;
279             $pby1 = ($pby1 - $yval) * $scal + $yval; $pby2 = ($pby2 - $yval) * $scal + $yval;
280             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
281             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
282             }
283             sub center {
284             my($cnvs, $xval, $yval)= @_; return() unless($cnvs->type('all'));
285             my $pdat = $cnvs->privateData(); _new_bbox($cnvs) unless($pdat->{'bboxvalid'});
286             $xval = $xval * $pdat->{'scale'} + $pdat->{'movex'};
287             if($pdat->{'inverty'}) { $yval = $yval * -$pdat->{'scale'} + $pdat->{'movey'}; }
288             else { $yval = $yval * $pdat->{'scale'} + $pdat->{'movey'}; }
289             my $xdlt = $cnvs->canvasx(0) + $cnvs->width() / 2 - $xval; $pdat->{'movex'} += $xdlt;
290             my $ydlt = $cnvs->canvasy(0) + $cnvs->height() / 2 - $yval; $pdat->{'movey'} += $ydlt;
291             $cnvs->SUPER::move('all', $xdlt, $ydlt); my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
292             $pbx1 += $xdlt; $pbx2 += $xdlt; $pby1 += $ydlt; $pby2 += $ydlt;
293             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
294             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
295             $cnvs->Callback( '-changeView' => getView($cnvs)) if(defined($cnvs->cget('-changeView')));
296             }
297             sub centerTags { my($cnvs, @args)= @_; my($cbx1, $cby1, $cbx2, $cby2)= bbox($cnvs, @args); return() unless(defined($cby2));
298             center($cnvs, ($cbx1 + $cbx2) / 2.0, ($cby1 + $cby2) / 2.0);
299             }
300             sub panAbstract { my($cnvs, $xval, $yval) = @_;
301             my $cnvx = abstractx($cnvs, $cnvs->width() / 2) + $xval;
302             my $cnvy = abstracty($cnvs, $cnvs->height() / 2) + $yval; center($cnvs, $cnvx, $cnvy);
303             }
304             sub viewAll { my $cnvs = shift(); return() unless($cnvs->type('all'));
305             my %swch = ('-border' => 0.02, @_); $swch{'-border'} = 0 if($swch{'-border'} < 0);
306             my $pdat = $cnvs->privateData(); _new_bbox($cnvs) unless($pdat->{'bboxvalid'});
307             my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}}; my $scal = $pdat->{'scale'};
308             my $movx = $pdat->{'movex'}; my $movy = $pdat->{'movey'};
309             my $wnx1 = ($pbx1 - $movx) / $scal; my $wnx2 = ($pbx2 - $movx) / $scal;
310             my $wny1 = ($pby1 - $movy) / $scal; my $wny2 = ($pby2 - $movy) / $scal;
311             if($pdat->{'inverty'}) { viewArea($cnvs, $wnx1,-$wny1, $wnx2,-$wny2, '-border' => $swch{'-border'}); }
312             else { viewArea($cnvs, $wnx1, $wny1, $wnx2, $wny2, '-border' => $swch{'-border'}); }
313             }
314             sub viewArea { my($cnvs, $vwx1, $vwy1, $vwx2, $vwy2)= splice(@_, 0, 5); return() if(!defined($vwy2) || !$cnvs->type('all'));
315             my %swch = ('-border' => 0.02, @_); $swch{'-border'} = 0 if($swch{'-border'} < 0);
316             my $pdat = $cnvs->privateData(); _new_bbox($cnvs) unless($pdat->{'bboxvalid'});
317             ($vwx1, $vwx2)=($vwx2, $vwx1) if($vwx1 > $vwx2); my $bwid = $swch{'-border'} * ($vwx2 - $vwx1); $vwx1 -= $bwid; $vwx2 += $bwid;
318             ($vwy1, $vwy2)=($vwy2, $vwy1) if($vwy1 > $vwy2); my $bhit = $swch{'-border'} * ($vwy2 - $vwy1); $vwy1 -= $bhit; $vwy2 += $bhit;
319             my $scal = $pdat->{'scale'};
320             my $movx = $pdat->{'movex'}; my $cnvx = $cnvs->canvasx(0); my $cnx1 = $vwx1 * $scal + $movx - $cnvx; my $cnx2 = $vwx2 * $scal + $movx - $cnvx;
321             my $movy = $pdat->{'movey'}; my $cnvy = $cnvs->canvasy(0); my $cny1 = $vwy1 * $scal + $movy - $cnvy; my $cny2 = $vwy2 * $scal + $movy - $cnvy;
322             _view_area_canvas($cnvs, $cnx1, $cny1, $cnx2, $cny2);
323             }
324             sub _view_area_canvas { my($cnvs, $vwx1, $vwy1, $vwx2, $vwy2)= @_; return() unless($cnvs->type('all'));
325             my $pdat = $cnvs->privateData(); _new_bbox($cnvs) unless($pdat->{'bboxvalid'});
326             my $bwid = $cnvs->cget('-borderwidth');
327             my $cwid = $cnvs->width(); my $dltx = $cwid / 2 - ($vwx1 + $vwx2) / 2;
328             my $chit = $cnvs->height(); my $dlty = $chit / 2 - ($vwy1 + $vwy2) / 2;
329             my $midx = $cnvs->canvasx(0) + $cwid / 2; $vwx2++ if($vwx2 == $vwx1); my $zumx = ($cwid - 2 * $bwid) / abs($vwx2 - $vwx1);
330             my $midy = $cnvs->canvasy(0) + $chit / 2; $vwy2++ if($vwy2 == $vwy1); my $zumy = ($chit - 2 * $bwid) / abs($vwy2 - $vwy1);
331             my $zoom = ($zumx < $zumy) ? $zumx : $zumy;
332             if($zoom > 0.999 && $zoom < 1.001) { $cnvs->SUPER::move( 'all', $dltx, $dlty); }
333             else { $cnvs->SUPER::scale('all', $midx - $dltx - $dltx / ($zoom - 1), $midy - $dlty - $dlty / ($zoom - 1), $zoom, $zoom); }
334             $pdat->{'movex'} = ($pdat->{'movex'} + $dltx - $midx) * $zoom + $midx;
335             $pdat->{'movey'} = ($pdat->{'movey'} + $dlty - $midy) * $zoom + $midy;
336             $pdat->{'scale'} *= $zoom; my($pbx1, $pby1, $pbx2, $pby2) = @{$pdat->{'bbox'}};
337             $pbx1 = ($pbx1 + $dltx - $midx) * $zoom + $midx; $pbx2 = ($pbx2 + $dltx - $midx) * $zoom + $midx;
338             $pby1 = ($pby1 + $dlty - $midy) * $zoom + $midy; $pby2 = ($pby2 + $dlty - $midy) * $zoom + $midy;
339             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
340             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
341             $cnvs->Callback( '-changeView' => getView($cnvs)) if(defined($cnvs->cget('-changeView')));
342             }
343             sub _map_coords { my $cnvs = shift(); my @crds = (); my $chbx = 0; my $pdat = $cnvs->privateData(); my $xval = 1;
344             my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
345             my $movx = $pdat->{'movex'};
346             my $movy = $pdat->{'movey'};
347             my $scal = $pdat->{'scale'};
348             while(defined(my $argu = shift())) {
349             if($argu !~ /^[+-.]*\d/) {
350             unshift(@_, $argu); last();
351             } else {
352             if($xval) { $argu = $argu * $scal + $movx;
353             if($pbx2 < $pbx1) { $pbx2 = $pbx1 = $argu; $chbx = 1; }
354             if($argu < $pbx1) { $pbx1 = $argu; $chbx = 1; }
355             if($argu > $pbx2) { $pbx2 = $argu; $chbx = 1; } $xval = 0;
356             } else {
357             if($pdat->{'inverty'}) { $argu = -$argu * $scal + $movy; } # invert y-coords
358             else { $argu = $argu * $scal + $movy; } # don't invert y-coords
359             if($pby2 < $pby1) { $pby2 = $pby1 = $argu; $chbx = 1; }
360             if($argu < $pby1) { $pby1 = $argu; $chbx = 1; }
361             if($argu > $pby2) { $pby2 = $argu; $chbx = 1; } $xval = 1;
362             }
363             push(@crds, $argu);
364             }
365             }
366             if($chbx) {
367             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
368             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
369             }
370             return(@crds, @_);
371             }
372             sub find { my($cnvs, @args)= @_; my $pdat = $cnvs->privateData();
373             if($args[0] =~ /^(closest|above|below)$/i) {
374             if(lc($args[0]) eq 'closest' ) { return() if(@args < 3);
375             my $scal = $pdat->{'scale' }; $args[1] = $args[1] * $scal + $pdat->{'movex'};
376             if( $pdat->{'inverty'}) { $args[2] = -$args[2] * $scal + $pdat->{'movey'}; }
377             else { $args[2] = $args[2] * $scal + $pdat->{'movey'}; }
378             }
379             my $recr = _killBand($cnvs); my $foun = $cnvs->SUPER::find(@args); _makeBand($cnvs) if($recr); return($foun);
380             } else {
381             if($args[0] =~ /^(enclosed|overlapping)$/i) { return() if(@args < 5);
382             my $scal = $pdat->{'scale' };
383             my $movx = $pdat->{'movex' }; $args[1] = $args[1] * $scal + $movx; $args[3] = $args[3] * $scal + $movx;
384             my $movy = $pdat->{'movey' };
385             if( $pdat->{'inverty'}) { $args[2] = -$args[2] * $scal + $movy; $args[4] = -$args[4] * $scal + $movy; }
386             else { $args[2] = $args[2] * $scal + $movy; $args[4] = $args[4] * $scal + $movy; }
387             }
388             my $recr = _killBand($cnvs); my @foun = $cnvs->SUPER::find(@args); _makeBand($cnvs) if($recr); return(@foun);
389             }
390             }
391             sub coords { my($cnvs, $tagg, @wcrd)= @_; return() unless($cnvs->type($tagg)); my $pdat = $cnvs->privateData();
392             my $scal = $pdat->{'scale'};
393             my $movx = $pdat->{'movex'};
394             my $movy = $pdat->{'movey'};
395             if(@wcrd) {
396             die "!*EROR*! Missing y-coordinate in call to coords()!\n" if(@wcrd % 2);
397             my($cbx1, $cby1, $cbx2, $cby2)= _find_box($cnvs->SUPER::coords($tagg)); my @ccrd = @wcrd;
398             for(my $indx = 0; $indx < @ccrd; $indx += 2) {
399             $ccrd[$indx ] = $ccrd[$indx ] * $scal + $movx;
400             if($pdat->{'inverty'}) { $ccrd[$indx + 1] = -$ccrd[$indx + 1] * $scal + $movy; }
401             else { $ccrd[$indx + 1] = $ccrd[$indx + 1] * $scal + $movy; }
402             }
403             $cnvs->SUPER::coords($tagg, @ccrd); my($abx1, $aby1, $abx2, $aby2) = _find_box(@ccrd);
404             _adjustBbox($cnvs, $cbx1, $cby1, $cbx2, $cby2, $abx1, $aby1, $abx2, $aby2);
405             } else {
406             @wcrd = $cnvs->SUPER::coords($tagg);
407             die "!*EROR*! Missing y-coordinate in return value from SUPER::coords()!\n" if(@wcrd % 2);
408             for(my $indx = 0; $indx < @wcrd; $indx += 2) {
409             $wcrd[$indx ] = ($wcrd[$indx ] - $movx) / $scal;
410             if($pdat->{'inverty'}) { $wcrd[$indx + 1] = 0 - ($wcrd[$indx + 1] - $movy) / $scal; }
411             else { $wcrd[$indx + 1] = ($wcrd[$indx + 1] - $movy) / $scal; }
412             }
413             if(@wcrd == 4 && ($wcrd[0] > $wcrd[2] || $wcrd[1] > $wcrd[3])) {
414             my $type = $cnvs->type($tagg);
415             if($type =~ /^(arc|oval|rectangle)$/) {
416             ($wcrd[0], $wcrd[2]) = ($wcrd[2], $wcrd[0]) if($wcrd[0] > $wcrd[2]);
417             ($wcrd[1], $wcrd[3]) = ($wcrd[3], $wcrd[1]) if($wcrd[1] > $wcrd[3]);
418             }
419             }
420             return(@wcrd);
421             }
422             return();
423             }
424             sub scale { my($cnvs, $tagg, $xoff, $yoff, $xscl, $yscl) = @_; return() unless($cnvs->type($tagg)); my $pdat = $cnvs->privateData();
425             my $cnxo = $xoff * $pdat->{'scale'} + $pdat->{'movex'};
426             my $cnyo = $yoff * $pdat->{'scale'} + $pdat->{'movey'};
427             if($pdat->{'inverty'}) { $cnyo = -$yoff * $pdat->{'scale'} + $pdat->{'movey'}; }
428             if(lc($tagg) eq 'all') { $cnvs->SUPER::scale($tagg, $cnxo, $cnyo, $xscl, $yscl); my($pbx1, $pby1, $pbx2, $pby2) = @{$pdat->{'bbox'}};
429             $pbx1 = ($pbx1 - $cnxo) * $xscl + $cnxo; $pbx2 = ($pbx2 - $cnxo) * $xscl + $cnxo;
430             $pby1 = ($pby1 - $cnyo) * $yscl + $cnyo; $pby2 = ($pby2 - $cnyo) * $yscl + $cnyo;
431             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
432             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
433             } else {
434             my($cbx1, $cby1, $cbx2, $cby2) = _find_box($cnvs->SUPER::coords($tagg)); $cnvs->SUPER::scale($tagg, $cnxo, $cnyo, $xscl, $yscl);
435             my $nwx1 = ($cbx1 - $cnxo) * $xscl + $cnxo; my $nwx2 = ($cbx2 - $cnxo) * $xscl + $cnxo;
436             my $nwy1 = ($cby1 - $cnyo) * $yscl + $cnyo; my $nwy2 = ($cby2 - $cnyo) * $yscl + $cnyo;
437             _adjustBbox($cnvs, $cbx1, $cby1, $cbx2, $cby2, $nwx1, $nwy1, $nwx2, $nwy2);
438             }
439             }
440             sub move { my($cnvs, $tagg, $xval, $yval)= @_; my($cbx1, $cby1, $cbx2, $cby2)= _find_box($cnvs->SUPER::coords($tagg));
441             my $scal = $cnvs->privateData->{'scale'}; my $dltx = $xval * $scal; my $dlty = $yval * $scal; $cnvs->SUPER::move($tagg, $dltx, $dlty);
442             my($nwx1, $nwy1, $nwx2, $nwy2)= ($cbx1 + $dltx, $cby1 + $dlty, $cbx2 + $dltx, $cby2 + $dlty);
443             _adjustBbox($cnvs, $cbx1, $cby1, $cbx2, $cby2, $nwx1, $nwy1, $nwx2, $nwy2);
444             }
445             sub _adjustBbox { my($cnvs, $cbx1, $cby1, $cbx2, $cby2, $nwx1, $nwy1, $nwx2, $nwy2)= @_; my $pdat = $cnvs->privateData();
446             my($pbx1, $pby1, $pbx2, $pby2) = @{$pdat->{'bbox'}}; my $chbx = 0;
447             if($nwx1 < $pbx1) { $pbx1 = $nwx1; $chbx = 1; } if($nwx2 > $pbx2) { $pbx2 = $nwx2; $chbx = 1; }
448             if($nwy1 < $pby1) { $pby1 = $nwy1; $chbx = 1; } if($nwy2 > $pby2) { $pby2 = $nwy2; $chbx = 1; }
449             if($chbx) {
450             $pdat->{ 'bbox'} = [$pbx1, $pby1, $pbx2, $pby2] ;
451             $cnvs->configure('-scrollregion' => [$pbx1, $pby1, $pbx2, $pby2]);
452             }
453             my $wmrg = 0.01 * ($pbx2 - $pbx1); $wmrg = 3 if($wmrg < 3); # width margin
454             my $hmrg = 0.01 * ($pby2 - $pby1); $hmrg = 3 if($hmrg < 3); # height margin
455             if(($cbx1 - $wmrg < $pbx1 && $cbx1 < $nwx1) || ($cby1 - $hmrg < $pby1 && $cby1 < $nwy1) ||
456             ($cbx2 + $wmrg > $pbx2 && $cbx2 > $nwx2) || ($cby2 + $hmrg > $pby2 && $cby2 > $nwy2)) { $pdat->{'bboxvalid'} = 0; }
457             }
458             sub bbox { my $cnvs = shift(); my $xact = 0; if($_[0] =~ /-exact/i) { shift(); $xact = shift(); } my @tags = @_; my $foun = 0;
459             for(@tags) { if($cnvs->type($_)) { $foun = 1; last(); } } return() unless($foun); my $pdat = $cnvs->privateData();
460             if(lc($tags[0]) eq 'all') {
461             my($pbx1, $pby1, $pbx2, $pby2)= @{$pdat->{'bbox'}};
462             my $scal = $pdat->{'scale'};
463             my $movx = $pdat->{'movex'};
464             my $movy = $pdat->{'movey'};
465             my $wnx1 = ($pbx1 - $movx) / $scal; my $wnx2 = ($pbx2 - $movx) / $scal;
466             my $wny1 = ($pby1 - $movy) / $scal; my $wny2 = ($pby2 - $movy) / $scal;
467             ($wnx1, $wnx2) = ($wnx2, $wnx1) if($wnx2 < $wnx1);
468             ($wny1, $wny2) = ($wny2, $wny1) if($wny2 < $wny1);
469             return($wnx1, $wny1, $wnx2, $wny2);
470             } else {
471             my $onep = 1.0 / $pdat->{'scale'}; my $zfix = 0; # one pixel; zoom fix
472             if($xact && $onep > 0.001) { zoom($cnvs, $onep * 1000); $zfix = 1; }
473             my($cbx1, $cby1, $cbx2, $cby2)= _superBbox($cnvs, @tags);
474             unless(defined($cbx1)) { zoom($cnvs, 1 / ($onep * 1000)) if($zfix); return(); } # @tags exist but their bbox overflows as ints
475             if(!$xact && abs($cbx2 - $cbx1) < 27 && abs($cby2 - $cby1) < 27) { # if error looks to be greater than certain %, do exact anyway
476             zoom($cnvs, $onep * 1000); my($nwx1, $nwy1, $nwx2, $nwy2)= _superBbox($cnvs, @tags);
477             if( !defined($nwx1)) { zoom($cnvs, 1 / ($onep * 1000)); } # overflows ints so retreat to previous box
478             else { $zfix = 1; ($cbx1, $cby1, $cbx2, $cby2)=($nwx1, $nwy1, $nwx2, $nwy2); }
479             }
480             my $scal = $pdat->{'scale' };
481             my $movx = $pdat->{'movex' }; $cbx1 = ($cbx1 - $movx) / $scal; $cbx2 = ($cbx2 - $movx) / $scal;
482             my $movy = $pdat->{'movey' };
483             if( $pdat->{'inverty'}) { $cby1 = ($cby1 - $movy) / -$scal; $cby2 = ($cby2 - $movy) / -$scal; }
484             else { $cby1 = ($cby1 - $movy) / $scal; $cby2 = ($cby2 - $movy) / $scal; }
485             zoom($cnvs, 1 / ($onep * 1000)) if($zfix);
486             if( $pdat->{'inverty'}) { return($cbx1, $cby2, $cbx2, $cby1); }
487             else { return($cbx1, $cby1, $cbx2, $cby2); }
488             }
489             }
490             sub rubberBand {
491             die "!*EROR*! Wrong number of args passed to rubberBand()!\n" unless(@_ == 2); my($cnvs, $step)= @_; my $pdat = $cnvs->privateData();
492             return() if($step >= 1 && !defined($pdat->{'RubberBand'})); my $xevt = $cnvs->XEvent();
493             my $xabs = abstractx($cnvs, $xevt->x());
494             my $yabs = abstracty($cnvs, $xevt->y());
495             if ($step == 0) { _killBand($cnvs); $pdat->{'RubberBand'} = [$xabs, $yabs, $xabs, $yabs]; # create anchor for rubberband
496             } elsif($step == 1) { $pdat->{'RubberBand'}[2] = $xabs; $pdat->{'RubberBand'}[3] = $yabs; _killBand($cnvs); _makeBand($cnvs); # updt end of band && redraw
497             } elsif($step == 2) { _killBand($cnvs) || return(); my($pbx1, $pby1, $pbx2, $pby2) = @{$pdat->{'RubberBand'}}; undef($pdat->{'RubberBand'}); # done
498             ($pbx1, $pbx2) = ($pbx2, $pbx1) if($pbx2 < $pbx1);
499             ($pby1, $pby2) = ($pby2, $pby1) if($pby2 < $pby1); return($pbx1, $pby1, $pbx2, $pby2);
500             }
501             }
502             sub _superBbox { my($cnvs, @tags)= @_; my $recr = _killBand($cnvs);
503             my($cbx1, $cby1, $cbx2, $cby2)= $cnvs->SUPER::bbox(@tags); _makeBand($cnvs) if($recr); return($cbx1, $cby1, $cbx2, $cby2);
504             }
505             sub _killBand { my($cnvs)= @_; my $rbid = $cnvs->privateData->{'RubberBandID'}; return(0) unless(defined($rbid)); $cnvs->SUPER::delete($rbid);
506             undef($cnvs->privateData->{'RubberBandID'}); return(1);
507             }
508             sub _makeBand { my($cnvs)= @_; my $pdat = $cnvs->privateData(); my $rbnd = $pdat->{'RubberBand'};
509             die "!*EROR*! RubberBand is not defined!" unless(defined($rbnd));
510             die "!*EROR*! RubberBand does not have 4 values!" if(@$rbnd != 4);
511             my $scal = $pdat->{'scale'}; my $colr = $cnvs->cget('-bandColor');
512             my $movx = $pdat->{'movex'}; my $rbx1 = $rbnd->[0] * $scal + $movx; my $rbx2 = $rbnd->[2] * $scal + $movx;
513             my $movy = $pdat->{'movey'}; my $rby1 = $rbnd->[1] * $scal + $movy; my $rby2 = $rbnd->[3] * $scal + $movy;
514             my $rbid = $cnvs->SUPER::create('rectangle', $rbx1, $rby1, $rbx2, $rby2, '-outline' => $colr); $pdat->{'RubberBandID'} = $rbid;
515             }
516             sub eventLocation { my($cnvs)= @_; my $xevt = $cnvs->XEvent(); return($cnvs->abstractx($xevt->x()),$cnvs->abstracty($xevt->y())) if(defined($xevt)); return();}
517             sub viewFit { my $cnvs = shift(); my $bord = 0.02;
518             if(lc($_[0]) eq '-border') { shift(); $bord = shift() if(@_); $bord = 0 if($bord < 0); }
519             my @tags = @_; my $foun = 0;
520             for(@tags) { if($cnvs->type($_)) { $foun = 1; last(); } } return() unless($foun);
521             viewArea($cnvs, bbox($cnvs, @tags), '-border' => $bord);
522             }
523             sub pixelSize { my($cnvs)= @_; return(1.0 / $cnvs->privateData->{'scale'}); }
524             sub abstractx { my($cnvs, $xval )= @_; my $pdat = $cnvs->privateData(); my $scal = $pdat->{'scale'}; return() unless($scal);
525             return( ($cnvs->canvasx(0) + $xval - $pdat->{'movex'}) / $scal);
526             }
527             sub abstracty { my($cnvs, $yval)= @_; my $pdat = $cnvs->privateData(); my $scal = $pdat->{'scale'}; return() unless($scal);
528             if($pdat->{'inverty'}) { return(0 - ($cnvs->canvasy(0) + $yval - $pdat->{'movey'}) / $scal); }
529             else { return( ($cnvs->canvasy(0) + $yval - $pdat->{'movey'}) / $scal); }
530             }
531             sub abstractxy { my($cnvs, $xval, $yval)= @_; my $pdat = $cnvs->privateData(); my $scal = $pdat->{'scale'}; return() unless($scal);
532             if($pdat->{'inverty'}) { return( ($cnvs->canvasx(0) + $xval - $pdat->{'movex'}) / $scal,
533             0 - ($cnvs->canvasy(0) + $yval - $pdat->{'movey'}) / $scal); }
534             else { return( ($cnvs->canvasx(0) + $xval - $pdat->{'movex'}) / $scal,
535             ($cnvs->canvasy(0) + $yval - $pdat->{'movey'}) / $scal); }
536             }
537             sub widgetx { my($cnvs, $xval )= @_; my $pdat = $cnvs->privateData();
538             return( $xval * $pdat->{'scale'} + $pdat->{'movex'} - $cnvs->canvasx(0));
539             }
540             sub widgety { my($cnvs, $yval)= @_; my $pdat = $cnvs->privateData();
541             if($pdat->{'inverty'}) { return(-$yval * $pdat->{'scale'} + $pdat->{'movey'} - $cnvs->canvasy(0)); }
542             else { return( $yval * $pdat->{'scale'} + $pdat->{'movey'} - $cnvs->canvasy(0)); }
543             }
544             sub widgetxy { my($cnvs, $xval, $yval)= @_; my $pdat = $cnvs->privateData(); my $scal = $pdat->{'scale'};
545             if($pdat->{'inverty'}) { return ( $xval * $scal + $pdat->{'movex'} - $cnvs->canvasx(0),
546             -$yval * $scal + $pdat->{'movey'} - $cnvs->canvasy(0)); }
547             else { return ( $xval * $scal + $pdat->{'movex'} - $cnvs->canvasx(0),
548             $yval * $scal + $pdat->{'movey'} - $cnvs->canvasy(0)); }
549             }
550             my $cmap = 0; # global cmap flag is used to avoid calling _map_coords twice
551             sub create { my($cnvs, $type)= splice(@_, 0, 2); my @narg = ($cmap) ? @_ : _map_coords($cnvs, @_);
552             if ($type eq 'rectangle') { $cnvs->_rect_to_poly( @narg); }
553             elsif($type eq 'oval') { $cnvs->_oval_to_poly( @narg); }
554             else { $cnvs->SUPER::create($type, @narg); }
555             }
556             sub createPolygon { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $plid = $cnvs->SUPER::createPolygon( @narg); $cmap = 0;return($plid);}
557             sub createLine { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $lnid = $cnvs->SUPER::createLine( @narg); $cmap = 0;return($lnid);}
558             sub createText { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $txid = $cnvs->SUPER::createText( @narg); $cmap = 0;return($txid);}
559             sub createWindow { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $wnid = $cnvs->SUPER::createWindow( @narg); $cmap = 0;return($wnid);}
560             sub createBitmap { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $bmid = $cnvs->SUPER::createBitmap( @narg); $cmap = 0;return($bmid);}
561             sub createImage { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $imid = $cnvs->SUPER::createImage( @narg); $cmap = 0;return($imid);}
562             sub createArc { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $arid = $cnvs->SUPER::createArc( @narg); $cmap = 0;return($arid);}
563             sub createOval { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $ovid;
564             if($cnvs->privateData->{'oval_to_poly'}) { $ovid = $cnvs->_oval_to_poly( @narg);}
565             else { $ovid = $cnvs->SUPER::createOval( @narg);}$cmap = 0;return($ovid);
566             }
567             sub createRectangle { my $cnvs = shift; my @narg = _map_coords($cnvs,@_); $cmap = 1; my $rcid;
568             if($cnvs->privateData->{'rect_to_poly'}) { $rcid = $cnvs->_rect_to_poly( @narg);}
569             else { $rcid = $cnvs->SUPER::createRectangle(@narg);}$cmap = 0;return($rcid);
570             }
571             sub _rect_to_poly { my $self = shift(); my($left, $topp, $rite, $botm)= splice(@_, 0, 4); # transform rectangle coords into poly coords
572             ($left, $rite)=($rite, $left) if($rite < $left);
573             ($topp, $botm)=($botm, $topp) if($botm < $topp);
574             $self->createPolygon($left, $topp, $rite, $topp, $rite, $botm, $left, $botm, @_);
575             }
576             sub _oval_to_poly { my $self = shift(); my($left, $topp, $rite, $botm)= splice(@_, 0, 4); my $stps = 127; # default steps in poly approx of oval
577             if(lc($_[0]) eq '-steps') { shift(); $stps = shift(); } # or can be configured
578             my $xcnt = ($rite - $left) / 2;
579             my $ycnt = ($botm - $topp) / 2; my @ptls;
580             for my $indx (0..$stps) { my $thta = (PI * 2) * ($indx / $stps);
581             my $xnew = $xcnt * cos($thta) - $xcnt + $rite;
582             my $ynew = $ycnt * sin($thta) + $ycnt + $topp; push(@ptls, $xnew, $ynew);
583             }
584             push(@_, '-fill' , undef ) unless(grep {/-fill/ } @_);
585             push(@_, '-outline', 'black') unless(grep {/-outline/} @_); $self->createPolygon(@ptls, @_);
586             }
587             sub _get_CM { my($xcnt, $ycnt, $area); my $indx = 0; # find Center of Mass of polygon
588             while($indx < $#_) { my $xzer = $_[$indx ]; my $yzer = $_[$indx + 1]; my($xone, $yone);
589             if($indx + 2 > $#_) { $xone = $_[ 0]; $yone = $_[ 1]; }
590             else { $xone = $_[$indx + 2]; $yone = $_[$indx + 3]; }
591             $indx += 2; my $aone = ($xzer + $xone) / 2; my $atwo = ($xzer**2 + $xzer*$xone + $xone**2 ) / 6;
592             my $ydlt = $yone - $yzer; my $athr = ($xzer*$yone + $yzer*$xone + 2 * ($xone*$yone + $xzer*$yzer)) / 6;
593             $area += $aone * $ydlt; $xcnt += $atwo * $ydlt; $ycnt += $athr * $ydlt;
594             }
595             return(split(' ', sprintf("%.0f %0.f" => $xcnt/$area, $ycnt/$area)));
596             }
597             8;
598              
599             =encoding utf8
600              
601             =head1 NAME
602              
603             Tk::AbstractCanvas - Canvas with Abstract center, zoom, and rotate methods
604              
605             =head1 VERSION
606              
607             This documentation refers to version 1.8 of Tk::AbstractCanvas, which was released on
608             Sun Jul 9 09:27:50:55 -0500 2017.
609              
610             =head1 SYNOPSIS
611              
612             use Tk;
613             use Tk::AbstractCanvas;
614             my $mwin = Tk::MainWindow->new();
615             my $acnv = $mwin->AbstractCanvas()->pack('-expand' => 1,
616             '-fill' => 'both');
617             #$acnv->invertY( 1); # uncomment for inverted y-axis
618             $acnv->controlNav(1); # advanced CtrlKey+MouseDrag Navigation
619             $acnv->rectToPoly(1);
620             #$acnv->ovalToPoly(1); # uncomment for oval to rot8 with canvas
621             my $rect = $acnv->createRectangle( 7, 8, 24, 23, '-fill' => 'red');
622             my $oval = $acnv->createOval( 23, 24, 32, 27, '-fill' => 'green');
623             my $line = $acnv->createLine( 0, 1, 31, 32, '-fill' => 'blue',
624             '-arrow' => 'last');
625             my $labl = $mwin->Label('-text' => 'Hello AbstractCanvas! =)');
626             my $wind = $acnv->createWindow(15, 16, '-window' => $labl );
627             $acnv->CanvasBind('' => sub { $acnv->zoom(1.04); });
628             $acnv->CanvasBind('' => sub { $acnv->zoom(0.97); });
629             $acnv->CanvasBind('' => sub {
630             $acnv->rotate($rect, 5);
631             $acnv->rotate($wind, 5); # this rot8 should do nothing because
632             $acnv->rotate($oval, -5); # window can't go around own center
633             $acnv->rotate($line, -5); });
634             $acnv->viewAll();
635             MainLoop(); # ... then click the 3 mouse buttons
636              
637             =head1 DESCRIPTION
638              
639             AbstractCanvas provides an alternative to a L object which partially abstracts the coordinates of objects drawn onto itself. This allows the
640             entire Canvas to be zoomed or rotated. Rotations modify the coordinates that the original object was placed at but zooming the whole canvas does not.
641              
642             Tk::AbstractCanvas is derived from the excellent modules L by Joseph Skrovan (which was itself based on a version by Rudy
643             Albachten ) and L by Ala Qumsieh .
644              
645             =head1 USAGE
646              
647             =head1 DESCRIPTION
648              
649             This module is a wrapper around the Canvas widget that maps the user's coordinate system to the now mostly hidden coordinate system of the Canvas
650             widget. There is an option to make the abstract coordinates y-axis increase in the upward direction rather than the default downward.
651              
652             I is meant to be a useful alternative to a regular Canvas. Typically, you should call $acnv->viewAll() (or
653             $acnv->viewArea(@box)) before calling MainLoop().
654              
655             Most of the I methods are the same as regular I methods except that they accept and return abstract coordinates instead of widget
656             coordinates.
657              
658             I also adds a new rotate() method to allow rotation of canvas objects by arbitrary angles.
659              
660             =head1 NEW METHODS
661              
662             =over 2
663              
664             =item I<$acnv>->B(I)
665              
666             Zooms the display by the specified amount. Example:
667              
668             $acnv->CanvasBind('' => sub {$acnv->zoom(1.25)});
669             $acnv->CanvasBind('' => sub {$acnv->zoom(0.8 )});
670              
671             # If you are using the 'Scrolled' constructor as in:
672             my $acnv = $mwin->Scrolled('AbstractCanvas', -scrollbars => 'nw',); # ...
673             # you want to bind the key-presses to the 'AbstractCanvas' Subwidget of Scrolled.
674             my $scrolled_canvas = $acnv->Subwidget('abstractcanvas'); # note the lowercase
675             $scrolled_canvas->CanvasBind('' => sub {$scrolled_canvas->zoom(1.25)});
676             $scrolled_canvas->CanvasBind('' => sub {$scrolled_canvas->zoom(0.8 )});
677              
678             # If you don't like the scrollbars taking the focus when you
679             # -tab through the windows, you can:
680             $acnv->Subwidget('xscrollbar')->configure(-takefocus => 0);
681             $acnv->Subwidget('yscrollbar')->configure(-takefocus => 0);
682              
683             =item I<$acnv>->B
(I)
684              
685             Centers the display around abstract coordinates x, y. Example:
686              
687             $acnv->CanvasBind('<2>' => sub {
688             $acnv->CanvasFocus();
689             $acnv->center($acnv->eventLocation());
690             });
691              
692             =item I<$acnv>->B([-exact => {0 | 1}], I)
693              
694             Centers the display around the center of the bounding box containing the specified TagOrIDs without changing the current magnification of the display.
695              
696             '-exact => 1' will cause the canvas to be scaled twice to get an accurate bounding box. This will be an expensive computation if the canvas contains a
697             large number of objects.
698              
699             =item I<$acnv>->B()
700              
701             Returns the abstract coordinates (x, y) of the last Xevent.
702              
703             =item I<$acnv>->B(I)
704              
705             Pans the display by the specified abstract distances. B is not meant to replace the xview/yview panning methods. Most user interfaces
706             will want the arrow keys tied to the xview/yview panning methods (the default bindings), which pan in widget coordinates.
707              
708             If you do want to change the arrow key-bindings to pan in abstract coordinates using B you must disable the default arrow key-bindings. Example:
709              
710             $mwin->bind('AbstractCanvas', '' => '');
711             $mwin->bind('AbstractCanvas', '' => '');
712             $mwin->bind('AbstractCanvas', '' => '');
713             $mwin->bind('AbstractCanvas', '' => '');
714              
715             $acnv->CanvasBind( '' => sub {$acnv->panAbstract(0, 100)});
716             $acnv->CanvasBind( '' => sub {$acnv->panAbstract(0, -100)});
717             $acnv->CanvasBind( '' => sub {$acnv->panAbstract(-100, 0)});
718             $acnv->CanvasBind('' => sub {$acnv->panAbstract( 100, 0)});
719              
720             This is not usually desired, as the percentage of the display that is shifted will be dependent on the current display magnification.
721              
722             =item I<$acnv>-EB([new_value])
723              
724             Returns the state of whether the y-axis of the abstract coordinate system is inverted. The default of this value is 0. An optional parameter can be
725             supplied to set the value.
726              
727             =item I<$acnv>-EB([new_value])
728              
729             Returns the state of whether created rectangles should be auto-converted into polygons (so that they can be rotated about their center by the rotate()
730             method). The default of this value is 0. An optional parameter can be supplied to set the value.
731              
732             =item I<$acnv>-EB([new_value])
733              
734             Returns the state of whether created ovals should be auto-converted into polygons (so that they can be rotated about their center by the rotate()
735             method). The default of this value is 0. An optional parameter can be supplied to set the value.
736              
737             =item I<$acnv>-EB([new_value])
738              
739             Returns the state of whether special Control+MouseButton drag navigation bindings are set. When true, Control-Button-1 mouse dragging rotates the
740             whole AbstractCanvas, 2 pans, and 3 zooms. The default of this value is 0 but this option is very useful if you don't need Control-Button bindings for some
741             other purpose. An optional parameter can be supplied to set the value.
742              
743             =item I<$acnv>-EB([new_value])
744              
745             Returns the state of whether special Control+MouseButton actions are busy handling events. An optional parameter can be supplied to set the value.
746              
747             =item I<$acnv>-EB([new_value])
748              
749             Returns the value of the special controlNav zoom scale (activated by Control-Button-3 dragging). The default value is -0.001. The zoom function
750             takes the distance dragged in pixels across the positive x and y axes scaled by the zoom factor to determine the zoom result. If you make the scale
751             positive, it will invert the directions which zoom in and out. If you make the number larger (e.g., -0.003 or 0.003), zooming will become more
752             twitchy. If you make the number smaller (e.g., -0.0007 or 0.0007), zooming will happen more smoothly. An optional parameter can be supplied to
753             set the value.
754              
755             =item I<$acnv>-EB([new_value])
756              
757             Returns the value of the special controlNav rotation scale (activated by Control-Button-1 dragging). The default value is -0.3. The rotation function
758             takes the distance dragged in pixels across the positive x and y axes scaled by the rotation factor to determine the rotation result. If you make the
759             scale positive, it will invert the directions which rotate positive or negative degrees. If you make the number larger (e.g., -0.7 or 0.7), rotations
760             will become more twitchy. If you make the number smaller (e.g., -0.07 or 0.07), rotations will happen more smoothly. An optional parameter can be
761             supplied to set the value.
762              
763             =item I<$acnv>-EB([\&new_callback])
764              
765             Returns the value of the special controlNav rotation motion callback. This will let a user tidy up whatever coordinates are necessary to keep sub-groups
766             of widgets in certain orientations together while the whole canvas is rotated. An optional parameter can be supplied to set the value.
767              
768             =item I<$acnv>-EB([\&new_callback])
769              
770             Returns the value of the special controlNav rotation release callback. This will let a user tidy up whatever coordinates are necessary to keep sub-groups
771             of widgets in certain orientations together after the whole canvas is done being rotated. An optional parameter can be supplied to set the value.
772              
773             =item I<$acnv>-EB([new_value])
774              
775             Returns the scale value of the AbstractCanvas relative to the underlying canvas. An optional parameter can be supplied to set the value although the zoom
776             function should almost always be employed instead of manipulating the scale directly through this accessor.
777              
778             =item I<$acnv>-EB([new_value])
779              
780             Returns the x-coordinate of where the last special Control+MouseButton event occurred. An optional parameter can be supplied to set the value.
781              
782             =item I<$acnv>-EB([new_value])
783              
784             Returns the y-coordinate of where the last special Control+MouseButton event occurred. An optional parameter can be supplied to set the value.
785              
786             =item I<$acnv>-EB(I ?,I?)
787              
788             This method rotates the object identified by TagOrID by I. The angle is specified in I. If an I coordinate is specified, then
789             the object is rotated about that point. Otherwise, the object is rotated about its center point, if that can be determined.
790              
791             =item I<$acnv>->B()
792              
793             Returns the width (in abstract coordinates) of a pixel (at the current magnification).
794              
795             =item I<$acnv>->B(I<{0|1|2}>)
796              
797             Creates a rubber banding box that allows the user to graphically select a region. B is called with a step parameter '0', '1', or '2'.
798             '0' to start a new box, '1' to stretch the box, and '2' to finish the box. When called with '2', the specified box is returned (x1, y1, x2, y2)
799              
800             The band color is set with the I option '-bandColor'. The default color is 'red'. Example specifying a region to delete:
801              
802             $acnv->configure(-bandColor => 'purple');
803             $acnv->CanvasBind('<3>' => sub {$acnv->CanvasFocus;
804             $acnv->rubberBand(0)});
805             $acnv->CanvasBind('' => sub {$acnv->rubberBand(1)});
806             $acnv->CanvasBind('' => sub {
807             my @box = $acnv->rubberBand(2);
808             my @ids = $acnv->find('enclosed', @box);
809             for my $id (@ids) {$acnv->delete($id)} });
810             # Note: '' will be called for any ButtonRelease!
811             # Use '' instead.
812             # If you want the rubber band to look smooth during panning and zooming, add
813             # rubberBand(1) update calls to the appropriate key-bindings:
814             $acnv->CanvasBind( '' => sub { $acnv->rubberBand(1)});
815             $acnv->CanvasBind( '' => sub { $acnv->rubberBand(1)});
816             $acnv->CanvasBind( '' => sub { $acnv->rubberBand(1)});
817             $acnv->CanvasBind('' => sub { $acnv->rubberBand(1)});
818             $acnv->CanvasBind( '' => sub {$acnv->zoom(1.25); $acnv->rubberBand(1)});
819             $acnv->CanvasBind( '' => sub {$acnv->zoom(0.8 ); $acnv->rubberBand(1)});
820              
821             This box avoids the overhead of bounding box calculations that can occur if you create your own rubberBand outside of I.
822              
823             =item I<$acnv>->B([-border => number])
824              
825             Displays at maximum possible zoom all objects centered in the I. The switch '-border' specifies, as a percentage of the screen, the minimum
826             amount of white space to be left on the edges of the display. Default '-border' is 0.02.
827              
828             =item I<$acnv>->B(x1, y1, x2, y2, [-border => number]))
829              
830             Displays at maximum possible zoom the specified region centered in the I.
831              
832             =item I<$acnv>->B([-border => number], I, [I, ...])
833              
834             Adjusts the AbstractCanvas to display all of the specified tags. The '-border' switch specifies (as a percentage) how much extra surrounding space should be shown.
835              
836             =item I<$acnv>->B()
837              
838             Returns the rectangle of the current view (x1, y1, x2, y2)
839              
840             =item I<$acnv>->B(I)
841              
842             =item I<$acnv>->B(I)
843              
844             =item I<$acnv>->B(I)
845              
846             Convert abstract coordinates to widget coordinates.
847              
848             =item I<$acnv>->B(I)
849              
850             =item I<$acnv>->B(I)
851              
852             =item I<$acnv>->B(I)
853              
854             Convert widget coordinates to abstract coordinates.
855              
856             =back
857              
858             =head1 CHANGED METHODS
859              
860             Abstract coordinates are supplied and returned to B methods instead of widget coordinates unless otherwise specified. (i.e., These methods take
861             and return abstract coordinates: center, panAbstract, viewArea, find, coords, scale, move, bbox, rubberBand, eventLocation, pixelSize, and create*)
862              
863             =over 2
864              
865             =item I<$acnv>->B([-exact => {0 | 1}], I, [I, ...])
866              
867             '-exact => 1' is only needed if the TagOrID is not 'all'. It will cause the canvas to be scaled twice to get an accurate bounding box. This will be
868             expensive computationally if the canvas contains a large number of objects.
869              
870             Neither setting of exact will produce exact results because the underlying canvas bbox method returns a slightly larger box to insure that everything is
871             contained. It appears that a number close to '2' is added or subtracted. The '-exact => 1' zooms in to reduce this error.
872              
873             If the underlying canvas B method returns a bounding box that is small (high error percentage) then '-exact => 1' is done automatically.
874              
875             =item I<$acnv>->B(I<'all', xOrigin, yOrigin, xScale, yScale>)
876              
877             B should not be used to 'zoom' the display in and out as it will change the abstract coordinates of the scaled objects. Methods B,
878             B, and B should be used to change the scale of the display without affecting the dimensions of the objects.
879              
880             =back
881              
882             =head1 VIEW AREA CHANGE CALLBACK
883              
884             I option '-changeView' can be used to specify a callback for a change of the view area. This is useful for updating a second AbstractCanvas which
885             is displaying the view region of the first AbstractCanvas.
886              
887             The callback subroutine will be passed the coordinates of the displayed box (x1, y1, x2, y2). These arguments are added after any extra arguments
888             specifed by the user calling 'configure'. Example:
889              
890             $acnv->configure(-changeView => [\&changeView, $acn2]);
891             # viewAll if 2nd AbstractCanvas widget is resized.
892             $acn2->CanvasBind('' => sub {$acn2->viewAll});
893             {
894             my $viewBox;
895             sub changeView {
896             my($canvas2, @coords) = @_;
897             $canvas2->delete($viewBox) if $viewBox;
898             $viewBox = $canvas2->createRectangle(@coords, -outline => 'orange');
899             }
900             }
901              
902             =head1 SCROLL REGION NOTES
903              
904             (1) The underlying I has a '-confine' option which is set to '1' by default there. With '-confine => 1' the canvas will not allow the
905             display to go outside of the scroll region. This causes some methods not to work accurately, for example, the 'center' method will not be able to
906             center on coordinates near to the edge of the scroll region and 'zoom out' near the edge will zoom out and pan towards the center.
907              
908             I sets '-confine => 0' by default to avoid these problems. You can change it back with:
909              
910             $acnv->configure(-confine => 1);
911              
912             (2) '-scrollregion' is maintained by I to include all objects on the canvas. '-scrollregion' will be adjusted automatically as objects are
913             added, deleted, scaled, moved, etc.. (You can create a static scrollregion by adding a border rectangle to the canvas.)
914              
915             (3) The bounding box of all objects is required to set the scroll region. Calculating this bounding box is expensive if the canvas has a large
916             number of objects. So for performance reasons these operations will not immediately change the bounding box if they potentially shrink it:
917              
918             coords
919             delete
920             move
921             scale
922              
923             Instead they will mark the bounding box as invalid, and it will be updated at the next zoom or pan operation. The only downside to this is that the
924             scrollbars will be incorrect until the update.
925              
926             If these operations increase the size of the box, changing the box is trivial and the update is immediate.
927              
928             =head1 ROTATION LIMITATIONS
929              
930             As it stands, the module can only rotate the following object types about their centers:
931              
932             =over 2
933              
934             =item * Lines
935              
936             =item * Polygons
937              
938             =item * Rectangles (if rectToPoly(1) is called)
939              
940             =item * Ovals (if ovalToPoly(1) is called)
941              
942             =back
943              
944             All other object types (bitmap, image, arc, text, and window) can only be rotated about another point. A warning is issued if the user tries to
945             rotate one of these object types about their center. Hopefully, more types will be able to center-rotate in the future.
946              
947             =head1 ROTATION DETAILS
948              
949             To be able to rotate rectangles and ovals, this module is capable of intercepting any calls to B, B, and
950             B to change them to polygons. The user should not be alarmed if B returns I when a I or I
951             was created. Additionally, if you call B on a polygonized object, expect to have to manipulate all the additionally generated
952             coordinates.
953              
954             =head1 TODO
955              
956             =over 2
957              
958             =item - add Math::Geometry::Planar and others to polygonize, find_CM, test intersections, etc.
959              
960             =item - maybe include Graph::Easy and some file serialization like DiagEMT XML but maybe more like a .Hrc format for the future
961              
962             =item - maybe make Diag objects which can scale fonts or state-box sizes separately from uniform AC zooms, have EMT side scroll panel, input or output
963             arrow ordering, state-box flags region and menu frameworks, overview with rubberBand of main view region, jointed arrows, arcs, splines, and pal8s
964              
965             =item - abstract rotations fully away like zoom
966              
967             =back
968              
969             =head1 CHANGES
970              
971             Revision history for Perl extension Tk::AbstractCanvas:
972              
973             =over 2
974              
975             =item - 1.8 H79M9Rot Sun Jul 9 09:27:50:55 -0500 2017
976              
977             * added test skipping in 02acnv.t to resolve L. (Thanks again Slaven!)
978              
979             * removed broken Build.PL to resolve L. (Thank you, Slaven.)
980              
981             =item - 1.6 H78MEDIC Sat Jul 8 14:13:18:12 -0500 2017
982              
983             * cleaned up examples and tested that grid works from L. (Thanks Josef!)
984              
985             * replaced POD ampersands with 'and' and turned double-spaces after periods to single
986              
987             =item - 1.4.A7QFZHF Mon Jul 26 15:35:17:15 2010
988              
989             * updated license to GPLv3
990              
991             =item - 1.2.75L75Nr Mon May 21 07:05:23:53 2007
992              
993             * added ex/* examples and tidied everything up
994              
995             * added Ctrl rot callbacks (mocb, rlcb) and limited Motion and Release to just Ctrl + one MouseButton events
996              
997             =item - 1.0.56BHMOt Sat Jun 11 17:22:24:55 2005
998              
999             * original version
1000              
1001             =back
1002              
1003             =head1 INSTALL
1004              
1005             Please run:
1006              
1007             `perl -MCPAN -e "install Tk::AbstractCanvas"`
1008              
1009             or uncompress the package and run the standard:
1010              
1011             `perl Makefile.PL; make; make test; make install`
1012              
1013             =head1 BUGS
1014              
1015             Please report any bugs or feature requests to bug-Tk-AbstractCanvas at RT.CPAN.Org, or through the web interface at
1016             L. I will be notified, and then you can be updated of progress on your bug
1017             as I address fixes.
1018              
1019             =head1 SUPPORT
1020              
1021             You can find documentation for this module with the perldoc command (after it is installed).
1022              
1023             `perldoc Tk::AbstractCanvas`
1024              
1025             You can also look for information at:
1026              
1027             RT: CPAN's Request Tracker
1028              
1029             HTTPS://RT.CPAN.Org/NoAuth/Bugs.html?Dist=Tk-AbstractCanvas
1030              
1031             AnnoCPAN: Annotated CPAN documentation
1032              
1033             HTTP://AnnoCPAN.Org/dist/Tk-AbstractCanvas
1034              
1035             CPAN Ratings
1036              
1037             HTTPS://CPANRatings.Perl.Org/d/Tk-AbstractCanvas
1038              
1039             Search CPAN
1040              
1041             HTTP://Search.CPAN.Org/dist/Tk-AbstractCanvas
1042              
1043             =head1 LICENSE
1044              
1045             Most source code should be Free! Code I have lawful authority over is and shall be!
1046             Copyright: (c) 2005-2017, Pip Stuart.
1047             Copyleft : This software is licensed under the GNU General Public License
1048             (version 3 or later). Please consult L
1049             for important information about your freedom. This is Free Software: you
1050             are free to change and redistribute it. There is NO WARRANTY, to the
1051             extent permitted by law. See L for further information.
1052              
1053             =head1 AUTHORS
1054              
1055             Pip Stuart (I)
1056              
1057             AbstractCanvas is derived from code by:
1058             Joseph Skrovan (I)
1059             Rudy Albachten (I)
1060             Ala Qumsieh (I)
1061              
1062             =cut