File Coverage

blib/lib/CAD/Drawing/GUI/View.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             package CAD::Drawing::GUI::View;
2              
3             require Tk::Zinc;
4             require Tk::Derived;
5              
6 1     1   7 use warnings;
  1         2  
  1         34  
7 1     1   10 use strict;
  1         2  
  1         50  
8 1     1   588 use Tk;
  0            
  0            
9              
10             use Carp;
11             use CAD::Calc qw(
12             pi
13             dist2d
14             );
15             use CAD::DXF::Color qw(
16             aci2hex
17             );
18              
19             our $VERSION = '0.01';
20             our @ISA = qw(
21             Tk::Derived
22             Tk::Zinc
23             );
24              
25             =head1 NAME
26              
27             CAD::Drawing::GUI::View - 2D graphics for CAD built on Tk::Zinc
28              
29             =head1 DESCRIPTION
30              
31             This module provides methods to turn a CAD::Drawing object into a Zinc
32             canvas.
33              
34             =head1 SYNOPSIS
35              
36             write me
37              
38             =head1 AUTHOR
39              
40             Eric L. Wilhelm
41              
42             http://scratchcomputing.com
43              
44             =head1 COPYRIGHT
45              
46             This module is copyright (C) 2004-2006 by Eric L. Wilhelm.
47              
48             =head1 LICENSE
49              
50             This module is distributed under the same terms as Perl. See the Perl
51             source package for details.
52              
53             You may use this software under one of the following licenses:
54              
55             (1) GNU General Public License
56             (found at http://www.gnu.org/copyleft/gpl.html)
57             (2) Artistic License
58             (found at http://www.perl.com/pub/language/misc/Artistic.html)
59              
60             =head1 Modifications
61              
62             The source code of this module is made freely available and
63             distributable under the GPL or Artistic License. Modifications to and
64             use of this software must adhere to one of these licenses. Changes to
65             the code should be noted as such and this notification (as well as the
66             above copyright information) must remain intact on all copies of the
67             code.
68              
69             Additionally, while the author is actively developing this code,
70             notification of any intended changes or extensions would be most helpful
71             in avoiding repeated work for all parties involved. Please contact the
72             author with any such development plans.
73              
74             =head1 SEE ALSO
75              
76             CAD::Drawing::GUI
77             Tk::Zinc
78              
79             =cut
80              
81             Construct Tk::Widget 'CADView';
82              
83              
84             =head1 Overridden Methods
85              
86             These make me behave like a Tk widget.
87              
88             =head2 ClassInit
89              
90             $view->ClassInit();
91              
92             =cut
93             sub ClassInit {
94             my $self = shift;
95             my ($mw) = @_;
96             $self->SUPER::ClassInit($mw);
97             } # end subroutine ClassInit definition
98             ########################################################################
99              
100             =head2 InitObject
101              
102             $view->InitObject();
103              
104             =cut
105             sub InitObject {
106             my $self = shift;
107             my ($args) = @_;
108             my $pData = $self->privateData;
109             # $pData->{'bbox'} = [0, 0, -1, -1];
110             $pData->{'scale'} = 1;
111             $pData->{'move'} = [0,0];
112             # $pData->{'bboxvalid'} = 1;
113             $pData->{'width'} = $self->width;
114             $pData->{'height'} = $self->height;
115             $pData->{'-pandist'} = 5;
116             # strip other args
117             $args = {$self->args_filter(%$args)};
118             $pData->{group} = $self->add('group', 1, -visible => 1);
119             $self->configure(-confine => 0);
120             # $self->configure('-highlightbackground' => '#FF0000');
121             $self->ConfigSpecs(
122             '-bandColor' => ['PASSIVE', 'bandColor', 'BandColor', 'red'],
123             '-bandcolor' => '-bandColor',
124             '-changeView' => ['CALLBACK', 'changeView', 'ChangeView', undef],
125             '-changeview' => '-changeView'
126             );
127             $self->Tk::bind('' =>
128             sub {
129             # print "hi\n"
130             $self->active(1);
131             }
132             );
133             $self->Tk::bind('' =>
134             sub {
135             # print "bye\n"
136             $self->active(0);
137             }
138             );
139             # $self->Tk::bind('' => sub {print "no\n"});
140             ## $self->Tk::bind('' =>
141             ## sub {
142             ## print "hi @_\n";
143             ## }
144             ## );
145             $self->SUPER::InitObject($args);
146             } # end subroutine InitObject definition
147             ########################################################################
148              
149             =head2 configure
150              
151             $view->configure(%args);
152              
153             =cut
154             sub configure {
155             my $self = shift;
156             my %args = @_;
157             %args = $self->args_filter(%args);
158             $self->SUPER::configure(%args);
159             } # end subroutine configure definition
160             ########################################################################
161              
162             =head2 args_filter
163              
164             Filters configure arguments and adds non-tk args to our private data.
165              
166             %args = $view->args_filter(%args);
167              
168             =cut
169             sub args_filter {
170             my $self = shift;
171             my %args = @_;
172             my $pdata = $self->privateData();
173             my %req = (
174             map({$_ => 1}
175             qw(
176             -parent
177             )),
178             map({$_ => 0}
179             qw(
180             -pandist
181             )
182             )
183             );
184             foreach my $key (keys(%req)) {
185             if(exists($args{$key})) {
186             # print "configuring $key\n";
187             $pdata->{$key} = $args{$key};
188             delete($args{$key});
189             }
190             else {
191             if($req{$key}) {
192             exists($pdata->{$key}) or
193             croak("required option $key missing\n");
194             }
195             }
196             }
197             return(%args);
198             } # end subroutine args_filter definition
199             ########################################################################
200              
201             =head1 privateData accessor methods
202              
203             =head2 group_is
204              
205             This object expects you to draw all of your items in this group.
206              
207             $view->group_is();
208              
209             =cut
210             sub group_is {
211             my $self = shift;
212             my $pdata = $self->privateData();
213             return($pdata->{group});
214             } # end subroutine group_is definition
215             ########################################################################
216              
217             =head2 active
218              
219             $view->active() or print "no\n";
220             $view->active(1);
221             $view->active(0);
222              
223             =cut
224             sub active {
225             my $self = shift;
226             my $pdata = $self->privateData();
227             if(@_) {
228             my $act = $_[0];
229             if($act == 1) {
230             $self->configure('-highlightbackground' => '#FF0000');
231             }
232             elsif($act == 0) {
233             $self->configure('-highlightbackground' => '#666666');
234             }
235             else {
236             croak("act must be 1 or 0");
237             }
238             $pdata->{active} = $act;
239             return(1);
240             }
241             else {
242             return($pdata->{active});
243             }
244             } # end subroutine active definition
245             ########################################################################
246              
247             =head2 gui_parent
248              
249             Retrieves or sets the -parent attribute (not to be confused with a
250             parent window.)
251              
252             $gui = $view->gui_parent();
253             $view->gui_parent($gui);
254              
255             =cut
256             sub gui_parent {
257             my $self = shift;
258             my $pdata = $self->privateData();
259             if(@_) {
260             $pdata->{-parent} = $_[0];
261             }
262             else {
263             return($pdata->{-parent});
264             }
265             } # end subroutine gui_parent definition
266             ########################################################################
267              
268             =head1 Drawing Methods
269              
270             The following methods handle the drawing of items from CAD::Drawing
271             objects.
272              
273             =head2 add_drawing
274              
275             Adds drawing $drw as number $number. This tags all of the items drawn
276             by "$number:$type:$id:$layer".
277              
278             $view->add_drawing($number, $drw);
279              
280             =cut
281             sub add_drawing {
282             my $self = shift;
283             my ($n, $drw) = @_;
284             foreach my $addr (@{$drw->select_addr()}) {
285             # print "draw $addr as $tag\n";
286             my $tag = addr_to_tag($n, $addr);
287             my $obj = $drw->getobj($addr);
288             $self->draw_item($obj, $tag);
289             if(0) {
290             require YAML;
291             print YAML::Dump($obj), "\n";
292             }
293              
294             }
295             } # end subroutine add_drawing definition
296             ########################################################################
297              
298             =head2 drawing_update
299              
300             Updates the canvas with the item at $addr.
301              
302             $view->drawing_update($n, $drw, $addr);
303              
304             =cut
305             sub drawing_update {
306             my $self = shift;
307             my ($n, $drw, $addr) = @_;
308             my $tag = addr_to_tag($n, $addr);
309             # XXX select?
310             my $obj = $drw->getobj($addr);
311             $self->redraw_item($obj, $tag);
312             } # end subroutine drawing_update definition
313             ########################################################################
314              
315             #these return the tk::zinc type and a data list.
316             our %trans_subs = (
317             lines => sub {
318             my ($self,$o) = @_;
319             my $data = [
320             [map({[$self->cnv_pt(@$_)]} @{$o->{pts}})],
321              
322             ];
323             my $args = {
324             -linecolor => undef(),
325             };
326             return(['curve', $data, $args]);
327             },
328             plines => sub {
329             my ($self,$o) = @_;
330             my $data = [
331             [map({[$self->cnv_pt(@$_)]} @{$o->{pts}})],
332              
333             ];
334             my $args = {
335             ($o->{closed} ? (-closed => 1) : ()),
336             -linecolor => undef(),
337             };
338             return(['curve', $data, $args]);
339             },
340             points => sub {
341             my ($self,$o) = @_;
342             my @pt = $self->cnv_pt(@{$o->{pt}});
343             # ack! the size of this needs to float!
344             # print "drawing point\n";
345             my $sz = 1;
346             my $pts = [
347             [$pt[0] - $sz, $pt[1] - $sz],
348             [$pt[0] + $sz, $pt[1] + $sz],
349             ];
350             my $args = {
351             -linecolor => undef(),
352             };
353             return(['arc', [$pts, -closed => 1], $args]);
354             },
355             arcs => sub {
356             my ($self, $o) = @_;
357             my $data = [
358             [
359             # this might make a mess:
360             # [$self->cnv_pt(map({$_ - $o->{rad}} @{$o->{pt}}))],
361             # [$self->cnv_pt(map({$_ + $o->{rad}} @{$o->{pt}}))],
362              
363             # XXX will somebody please explain why every toolkit
364             # must be so incredibly braindead!
365             [ # "top left" rectangle point
366             $self->cnv_pt(
367             $o->{pt}[0] - $o->{rad},
368             $o->{pt}[1] + $o->{rad},
369             )
370             ],
371             [ # "bottom right" rectangle point
372             $self->cnv_pt(
373             $o->{pt}[0] + $o->{rad},
374             $o->{pt}[1] - $o->{rad},
375             )
376             ],
377             ],
378             ];
379             my $args = {
380             -startangle => $o->{angs}[0] * 180 / pi,
381             -extent => abs(($o->{angs}[1] - $o->{angs}[0]) * 180 / pi),
382             -linecolor => undef(),
383             };
384             return(['arc', $data, $args]);
385             },
386             circles => sub {
387             my ($self, $o) = @_;
388             my $data = [
389             [
390             # XXX will somebody please explain why every toolkit
391             # must be so incredibly braindead!
392             [ # "top left" rectangle point
393             $self->cnv_pt(
394             $o->{pt}[0] - $o->{rad},
395             $o->{pt}[1] + $o->{rad},
396             )
397             ],
398             [ # "bottom right" rectangle point
399             $self->cnv_pt(
400             $o->{pt}[0] + $o->{rad},
401             $o->{pt}[1] - $o->{rad},
402             )
403             ],
404             ],
405             ];
406             # print "points: $data->[0][0][0],$data->[0][0][1] and ",
407             # "$data->[0][1][0],$data->[0][1][1]\n";
408             my $args = {
409             -startangle => 0,
410             -closed => 1,
411             # -extent => 360,
412             -linecolor => undef(),
413             };
414             return(['arc', $data, $args]);
415             },
416             texts => sub {
417             my ($self, $o) = @_;
418             my @pt = $self->cnv_pt(@{$o->{pt}});
419             # XXX there's some kind of buffer under my text!
420             $pt[1] += 3/12;
421             my $data = [
422             ];
423             my $args = {
424             -position => [@pt],
425             -text => $o->{string},
426             -composescale => 1, # enable scaling!
427             -composerotation => 1,
428             -anchor => 'sw',
429             # -font => $self->fontCreate(
430             # $o,
431             # -family => 'Courier',
432             # -size => 12,
433             # ),
434             # -font => 'lucidiasans-' . 2,
435             # XXX okay, assume a 12pt font is 9px high:
436             # -font => '-adobe-helvetica-bold-r-normal--12-120-*-*-*-*-*-*',
437             # XXX or, 24pt is 15 high:
438             -font => '-adobe-helvetica-*-r-normal--24-240-*-*-*-*-*-*',
439             -color => undef(),
440             };
441             ## print "text add at point: ",
442             ## join(",", $self->cnv_pt(@{$o->{pt}})),
443             ## "(", join(",", @{$o->{pt}}), ")", "\n";
444             return(['text', $data, $args]);
445             },
446             );
447              
448             =head2 draw_item
449              
450             Draws the $obj (possibly in multiple pieces), using $tag as the
451             identifier.
452              
453             $view->draw_item($obj, $tag);
454              
455             =cut
456             sub draw_item {
457             my $self = shift;
458             my ($obj, $tag) = @_;
459             my $group = $self->group_is();
460             if(my $sub = $trans_subs{$obj->{addr}{type}}) {
461             ## print "found sub for $obj->{addr}{type}\n";
462             my @bits = $sub->($self, $obj);
463             foreach my $bit (@bits) {
464             my $type = $bit->[0];
465             my $data = $bit->[1];
466             my %args = %{$bit->[2]};
467             # XXX try to handle colors in *ONE* place
468             foreach my $key (keys(%args)) {
469             if($key =~ m/color$/) {
470             my $c = $obj->{color};
471             # XXX still punting bylayer/byblock colors
472             # ack! we would need the drawing for that info!
473             ($c == 256) and ($c = 255);
474             ($c == 0) and ($c = 255);
475             $args{$key} = "#" . aci2hex($c);
476             }
477             }
478              
479             ## print "using data @$data\n";
480             my $item = $self->add($type, $group,
481             @$data, %args,
482             -tags => [$tag],
483             );
484             # XXX I guess I need to index these?
485             # print "item $item for $tag\n";
486             if(1 and $type eq 'text') {
487             my $font = $self->itemcget($item, '-font');
488             my $base = $self->itemcget($item, '-position');
489             ## print "text has font $font and @$base\n";
490             # ack! why is this so hard!
491             $self->itemconfigure($item, -position => [0,0]);
492             # XXX how do I find the height of this text?
493             # (see assumption above:
494             # 1 unit is 12 pt and takes 9 pixels)
495             # 1 unit is 24 pt and takes 15 pixels)
496             my $scale = $obj->{height} / 15;
497             ## print "initial scaling text by $scale\n";
498             $self->scale($item, $scale, $scale);
499             if($obj->{ang}) {
500             die "need text-angle support\n";
501             }
502             $self->itemconfigure($item, -position => $base);
503             }
504             ## print "mapping: $tag -> $item\n";
505             }
506             }
507             else {
508             warn("no sub for $obj->{addr}{type}\n");
509             }
510             } # end subroutine draw_item definition
511             ########################################################################
512              
513             =head2 redraw_item
514              
515             $view->redraw_item();
516              
517             =cut
518             sub redraw_item {
519             my $self = shift;
520             my ($obj, $tag) = @_;
521             ## print "item $tag ", $self->coords($tag), "\n";
522             if(my $sub = $trans_subs{$obj->{addr}{type}}) {
523             ## print "found sub for $obj->{addr}{type}\n";
524             my @bits = $sub->($self, $obj);
525             foreach my $bit (@bits) {
526             # XXX ack!
527             my $type = $bit->[0];
528             my $data = $bit->[1];
529             my %args = %{$bit->[2]};
530             foreach my $key (keys(%args)) {
531             if($key =~ m/color$/) {
532             my $c = $obj->{color};
533             # XXX still punting bylayer/byblock colors
534             # ack! we would need the drawing for that info!
535             ($c == 256) and ($c = 255);
536             ($c == 0) and ($c = 255);
537             $args{$key} = "#" . aci2hex($c);
538             }
539             }
540             0 and print "configure $tag to be @$data, ",
541             join(" ", each(%args)), " etc\n";
542             $self->itemconfigure($tag, %args);
543             if(ref($data->[0]) eq "ARRAY") {
544             ## XXX the transform is off!
545             ## print "input data: ", join(" ", map({join(",", @$_)} @{$data->[0]})), "\n";
546             ## print "current coords: ", join(" ", map({join(",", @$_)} $self->coords($tag))), "\n";
547             # ack! XXX this is screwy!
548             my @coords = map({
549             my @p = $self->world_pt(@$_); [$p[0], -$p[1]]
550             } @{$data->[0]});
551             ## print "input data2: ", join(" ", map({join(",", @$_)} @coords)), "\n";
552             $self->coords($tag, \@coords);
553             ## print "current coords: ", join(" ", map({join(",", @$_)} $self->coords($tag))), "\n";
554             }
555             }
556             }
557             } # end subroutine redraw_item definition
558             ########################################################################
559              
560              
561              
562             =head1 Useful Methods
563              
564             =head2 viewAll
565              
566             $view->viewAll();
567              
568             =cut
569             sub viewAll {
570             my $self = shift;
571             # ($self->width == 1 and $self->height == 1) and return();
572             if (!$self->type($self->group_is())) {return;} # can't find anything!
573             my @bbox = $self->bbox('all');
574             $self->viewArea(@bbox);
575             } # end subroutine viewAll definition
576             ########################################################################
577              
578             =head2 viewArea
579              
580             $view->viewArea(@bbox);
581              
582             =cut
583             sub viewArea {
584             my $self = shift;
585             my (@bbox) = @_;
586             # let's be nice and sort these for the caller:
587             ($bbox[0],$bbox[2]) = sort({$a<=>$b} $bbox[0],$bbox[2]);
588             ($bbox[1],$bbox[3]) = sort({$a<=>$b} $bbox[1],$bbox[3]);
589             my @span = ($bbox[2]-$bbox[0], $bbox[3]-$bbox[1]);
590             ($span[0] and $span[1]) or return(); # nothing on canvas
591             ## print "bbox says @bbox (@span)\n";
592             my @d_cent = map({$_ / 2} $bbox[2]+$bbox[0], $bbox[3]+$bbox[1]);
593             my @view = ($self->width, $self->height);
594             my @c_cent = map({$_ / 2} @view);
595             ## print "change center @c_cent to @d_cent\n";
596             my @move = map({$c_cent[$_] - $d_cent[$_]} 0,1);
597             my $pdata = $self->privateData();
598             if(abs($move[0]) >= 1 and abs($move[1]) >=1) {
599             ## print "move by @move\n";
600             $pdata->{move}[$_] += $move[$_] * $pdata->{scale} foreach 0,1;
601             $self->translate($pdata->{group}, @move);
602             }
603             my $scale = (sort({$a<=>$b} map({$view[$_] / $span[$_]} 0,1)))[0];
604             $self->zoom($scale);
605             } # end subroutine viewArea definition
606             ########################################################################
607              
608             =head2 viewWorldArea
609              
610             $view->viewWorldArea([$x1,$y1],[$x2,$y2]);
611              
612             =cut
613             sub viewWorldArea {
614             my $self = shift;
615             my (@rec) = @_;
616             my @bbox = (
617             $self->cnv_pt(@{$rec[0]}),
618             $self->cnv_pt(@{$rec[1]})
619             );
620             $self->viewArea(@bbox);
621             } # end subroutine viewWorldArea definition
622             ########################################################################
623              
624             =head2 zoom
625              
626             $view->zoom($factor);
627              
628             =cut
629             sub zoom {
630             my $self = shift;
631             my $scale = shift;
632             my @view = ($self->width, $self->height);
633             my @c_cent = map({$_ / 2} @view);
634             my $pdata = $self->privateData();
635             $self->scale($pdata->{group}, $scale, $scale, @c_cent);
636             $pdata->{scale} *= $scale;
637             } # end subroutine zoom definition
638             ########################################################################
639              
640             =head2 windowzoom
641              
642             Creates temporary bindings for drawing a rubber-band box and zooming on
643             the area described by it. This will put back your existing bindings.
644              
645             $view->windowzoom();
646              
647             =cut
648             sub windowzoom {
649             my $self = shift;
650             # XXX how to get my stl?
651             my $stl = shift;
652             $stl and $stl->configure(-text=>"Pick window corners");
653             my %was;
654             my %tmp; # must declare before declaring
655             %tmp = (
656             '' => sub {
657             $self->rubberBand(0);
658             },
659             '' => sub {
660             $self->rubberBand(1);
661             },
662             '' => sub {
663             my @box = $self->rubberBand(2);
664             ## print "box is @box\n";
665             $self->viewArea(@box);
666             my $parent = $self->gui_parent();
667             $parent->event_done();
668             $stl and $stl->configure(-text=>"");
669             },
670             );
671             %was = $self->bind_on(\%tmp);
672             return(\%tmp, \%was);
673             } # end subroutine windowzoom definition
674             ########################################################################
675              
676             =head2 free_dist
677              
678             This is a freehand measuring tape. Maybe we'll have some snaps someday
679             (but likely not with this graphical toolkit.)
680              
681             $view->free_dist();
682              
683             =cut
684             sub free_dist {
685             my $self = shift;
686             my $stl = shift;
687             $stl and $stl->configure(-text=>"Pick ends");
688             my %was;
689             my %tmp;
690             %tmp = (
691             '' => sub {
692             $self->rubberBand(0);
693             },
694             '' => sub {
695             $self->rubberBand(1, 'line');
696             },
697             '' => sub {
698             my @box = $self->rubberBand(2);
699             # this needs to involve the parent
700             # XXX how to make it cleaner?
701             my $parent = $self->gui_parent();
702             $parent->event_done();
703             # print "box is @box\n";
704             my @pts = map({[$self->world_pt(@$_)]}
705             [@box[0,1]],[@box[2,3]]
706             );
707             my $dist = dist2d(@pts);
708             my $dx = $pts[1][0] - $pts[0][0];
709             my $dy = $pts[1][1] - $pts[0][1];
710             $stl and $stl->configure(-text=>"$dist ($dx,$dy)");
711             warn("measure: $dist ($dx,$dy)\n");
712             },
713             );
714             %was = $self->bind_on(\%tmp);
715             return(\%tmp, \%was);
716             } # end subroutine free_dist definition
717             ########################################################################
718              
719             =head2 pan
720              
721             $view->pan($x,$y);
722              
723             =cut
724             sub pan {
725             my $self = shift;
726             my (@move) = @_;
727             my $pdata = $self->privateData();
728             # print "pan\n";
729             $pdata->{move}[$_] += $move[$_] * $pdata->{scale} foreach 0,1;
730             $self->translate($pdata->{group}, @move);
731             } # end subroutine pan definition
732             ########################################################################
733              
734             =head1 Additional Methods
735              
736             =head2 click_bind
737              
738             Binds a subroutine to mouse button-1 clicks. In addition to creating
739             the binding, this subroutine is guaranteed to be passed world
740             coordinates. (its arguments are: $view, $x, $y)
741              
742             $view->click_bind($sub, $button);
743              
744             The $button argument is optional, and defaults to 1.
745              
746             I advise you to not use 2 if view_bindings() is active.
747              
748             =cut
749             sub click_bind {
750             my $self = shift;
751             my ($sub, $num) = @_;
752             $num or ($num = 1);
753             (ref($sub) eq "CODE") or croak("cannot bind without code\n");
754             # sorry, no restore method here!
755             $self->Tk::bind(
756             "" =>
757             sub {
758             my @loc = $self->eventLocation();
759             @loc = $self->world_pt(@loc);
760             $sub->($self, @loc);
761             }
762             );
763             } # end subroutine click_bind definition
764             ########################################################################
765              
766             =head2 view_bindings
767              
768             Sets-up the wheel-zoom and middle-button pan. (This over-writes any
769             bindings that you have made.)
770              
771             $view->view_bindings();
772              
773             =cut
774             sub view_bindings {
775             my $self = shift;
776             $self->Tk::bind('' => sub{ $self->viewAll(); });
777             $self->Tk::bind('<4>' => sub{
778             ## print "zoom in\n";
779             $self->zoom(1.125);
780             ## print "zoom in done\n";
781             }
782             );
783             $self->Tk::bind('<5>' => sub{
784             $self->zoom(1/1.125);
785             }
786             );
787             my $pdata = $self->privateData();
788             my @pan_start;
789             my $drag_current;
790             $self->Tk::bind(
791             '' => sub {
792             @pan_start = $self->eventLocation();
793             ## print "starting pan at @pan_start\n";
794             });
795             # have to have this here to prevent spurious panning with double-clicks
796             $self->Tk::bind('' => sub {
797             $drag_current = 1;
798             my @pan_stop = $self->eventLocation();
799             my @diff = map({$pan_stop[$_] - $pan_start[$_]} 0,1);
800             if(sqrt($diff[0]**2 + $diff[1]**2) > $pdata->{-pandist}) {
801             $self->pan(@diff);
802             @pan_start = @pan_stop;
803             }
804              
805             }
806             );
807             $self->Tk::bind(
808             '' => sub {
809             $drag_current || return();
810             my @pan_stop = $self->eventLocation();
811             # my $scale = $self->pixelSize();
812             # print "\tdouble: $isdouble\n";
813             # print "\tdrag: $drag_current\n";
814             # print "scale is $scale\n";
815             # print "stopping pan at @pan_stop\n";
816             my @diff = map({$pan_stop[$_] - $pan_start[$_]} 0,1);
817             # my $panx = abs($diff[0])/$scale;
818             # my $pany = abs($diff[1])/$scale;
819             # print "pixels: ($panx,$pany)\n";
820             # my $dopan = ( $panx > 10) | ( $pany > 10);
821             # $dopan && print "panning by @diff\n";
822             # $dopan && $self->panWorld(@diff);
823             $self->pan(@diff);
824             $drag_current = 0;
825             });
826              
827             } # end subroutine view_bindings definition
828             ########################################################################
829              
830             =head1 Coordinate System Methods
831              
832             =head2 world_pt
833              
834             Change a canvas coordinate into a world coordinate.
835              
836             @w_pt = $view->world_pt(@cnv_pt);
837              
838             =cut
839             sub world_pt {
840             my $self = shift;
841             my (@pt) = @_;
842             ## print "start with @pt\n";
843             # print "look at transform effect:",
844             # join(",", $self->transform($self->group_is(), \@pt)), "\n";
845             # # XXX use scale and movement
846             # my $pdata = $self->privateData();
847             # $pdata->{scale} or die("no scale!");
848             # @pt = map({$pt[$_] - $pdata->{move}[$_]} 0,1);
849             # print "after move: @pt\n";
850             # # XXX scaling has happened about canvas center
851             # @pt = map({$pt[$_] / $pdata->{scale}} 0,1);
852             @pt = $self->transform($self->group_is(), \@pt);
853             return($pt[0], -$pt[1]);
854             } # end subroutine world_pt definition
855             ########################################################################
856              
857             =head2 cnv_pt
858              
859             Change a world coordinate into a canvas coordinate.
860              
861             @cnv_pt = $view->cnv_pt(@w_pt);
862              
863             =cut
864             sub cnv_pt {
865             my $self = shift;
866             my (@pt) = @_;
867             @pt = $self->transform($self->group_is(), 'device', [$pt[0], -$pt[1]]);
868             return(@pt);
869             } # end subroutine cnv_pt definition
870             ########################################################################
871              
872             =head2 eventLocation
873              
874             Returns the canvas (x,y) coordinates of the last event.
875              
876             my ($x,$y) = $view->eventLocation();
877              
878             =cut
879             sub eventLocation {
880             my ($canvas) = @_;
881             my $ev = $canvas->XEvent;
882             return ($ev->x,$ev->y) if defined $ev;
883             return;
884             } # end subroutine eventLocation definition
885             ########################################################################
886              
887             =head2 event_coords
888              
889             Returns the world (x,y) coordinates of the last event.
890              
891             ($x,$y) = $view->event_coords();
892              
893             =cut
894             sub event_coords {
895             my $self = shift;
896             my ($x,$y) = $self->eventLocation();
897             return($self->world_pt($x,$y));
898             } # end subroutine event_coords definition
899             ########################################################################
900              
901             ########################################################################
902              
903             =head2 rubberBand
904              
905             almost straight from WorldCanvas
906              
907             =cut
908             sub rubberBand {
909             my ($canvas, $step, $thing) = @_;
910              
911             my $pData = $canvas->privateData;
912             if($step >= 1 and not defined $pData->{'RubberBand'}) {
913             return();
914             }
915              
916             my $ev = $canvas->XEvent;
917             my $x = $ev->x;
918             my $y = $ev->y;
919              
920             if ($step == 0) {
921             # create anchor for rubberband
922             _killBand($canvas);
923             $pData->{'RubberBand'} = [$x, $y, $x, $y];
924             } elsif ($step == 1) {
925             # update end of rubber band and redraw
926             $pData->{'RubberBand'}[2] = $x;
927             $pData->{'RubberBand'}[3] = $y;
928             _killBand($canvas);
929             $thing or ($thing = "rectangle");
930             _makeBand($canvas, $thing);
931             } elsif ($step == 2) {
932             # step == 2: done
933             _killBand($canvas) or return;
934              
935             my ($x1, $y1, $x2, $y2) = @{$pData->{'RubberBand'}};
936             undef($pData->{'RubberBand'});
937              
938             return ($x1, $y1, $x2, $y2);
939             }
940             }
941              
942             sub _killBand {
943             my ($canvas) = @_;
944              
945             my $id = $canvas->privateData->{'RubberBandID'};
946             return 0 if !defined($id);
947              
948             $canvas->SUPER::remove($id);
949             undef($canvas->privateData->{'RubberBandID'});
950              
951             return 1;
952             }
953              
954             sub _makeBand {
955             my ($canvas, $thing) = @_;
956              
957             my $pData = $canvas->privateData;
958             my $rb = $pData->{'RubberBand'};
959             die "Error: RubberBand is not defined" if !$rb;
960             die "Error: RubberBand does not have 4 values." if @$rb != 4;
961              
962             my $crbx1 = $rb->[0];
963             my $crbx2 = $rb->[2];
964             my $crby1 = $rb->[1];
965             my $crby2 = $rb->[3];
966              
967             my $color = $canvas->cget('-bandColor');
968             # print "color: $color\n";
969             # print "points: $crbx1, $crby1, $crbx2, $crbx1\n";
970             $color = '#FF0000';
971             my $id;
972             if($thing eq "rectangle") {
973             $id = $canvas->add(
974             'rectangle', 1,
975             [$crbx1, $crby1, $crbx2, $crby2],
976             -linecolor => $color
977             );
978             }
979             elsif($thing eq "line") {
980             $id = $canvas->add(
981             'curve', 1,
982             [[$crbx1, $crby1], [$crbx2, $crby2]],
983             -linecolor => $color
984             );
985             }
986             else {
987             croak("'thing' must be (currently) rectangle or line\n");
988             }
989             $pData->{'RubberBandID'} = $id;
990             }
991             ########################################################################
992              
993             =head2 bind_on
994              
995             Sets all of the bindings specified in %tmp and returns the old
996             bindings in %was.
997              
998             %was = $view->bind_on(\%tmp);
999              
1000             =cut
1001             sub bind_on {
1002             my $self = shift;
1003             my ($tmp) = @_;
1004             my %was;
1005             my %tmp = %$tmp;
1006             foreach my $key (keys(%tmp)) {
1007             if(my $sub = $self->Tk::bind($key)) {
1008             $was{$key} = $sub;
1009             }
1010             $self->Tk::bind($key, $tmp{$key});
1011             }
1012             return(%was);
1013             } # end subroutine bind_on definition
1014             ########################################################################
1015              
1016             =head2 bind_off
1017              
1018             Replaces the %was bindings and removes any leftover from %tmp.
1019              
1020             $view->bind_off(\%tmp, \%was);
1021              
1022             =cut
1023             sub bind_off {
1024             my $self = shift;
1025             my ($tmp, $was) = @_;
1026             my %tmp = %$tmp;
1027             my %was = %$was;
1028             foreach my $item (keys(%tmp)) {
1029             # print "item: $item\n";
1030             if($was{$item}) {
1031             $self->Tk::bind($item => $was{$item});
1032             }
1033             else {
1034             $self->Tk::bind($item => "");
1035             }
1036             }
1037             } # end subroutine bind_off definition
1038             ########################################################################
1039              
1040             =head1 Functions
1041              
1042             =head2 addr_to_tag
1043              
1044             $tag = addr_to_tag($n, $addr);
1045              
1046             =cut
1047             sub addr_to_tag {
1048             my ($n, $addr) = @_;
1049             my $tag = join(":",
1050             $n,
1051             $addr->{type},
1052             $addr->{id},
1053             $addr->{layer}
1054             );
1055             return($tag);
1056             } # end subroutine addr_to_tag definition
1057             ########################################################################
1058              
1059             =head2 tag_to_addr
1060              
1061             ($n, $addr) = tag_to_addr($tag);
1062              
1063             =cut
1064             sub tag_to_addr {
1065             } # end subroutine tag_to_addr definition
1066             ########################################################################
1067              
1068             1;