File Coverage

blib/lib/Tk/AbstractCanvas.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


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