NAME Tk::WorldCanvas - Autoscaling Canvas widget with zoom, viewAll, viewArea, viewFit, and center. SYNOPSIS $worldcanvas = $parent->WorldCanvas(?options?); DESCRIPTION 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 widget. In world coordinates the y-axis increases in the upward direction. *WorldCanvas* is meant to be a drop in replacement for Canvas. Most of the *WorldCanvas* methods are the same as the *Canvas* methods except that they accept and return world coordinates instead of widget coordinates. INSTALLATION Standard method: perl Makefile.PL make make test make install The last step requires proper permissions. Or you can copy the WorldCanvas.pm file to a local directory and skip the formalities. NEW METHODS *$worldcanvas*->zoom(*zoom factor*) Zooms the display by the specified amount. Example: $worldcanvas->CanvasBind('' => sub {$worldcanvas->zoom(1.25)}); $worldcanvas->CanvasBind('' => sub {$worldcanvas->zoom(0.8)}); # If you are using the 'Scrolled' constructor as in: my $worldcanvas = $main->Scrolled('WorldCanvas', -scrollbars => 'nw', ... ) # you want to bind the key-presses to the 'worldcanvas' Subwidget of Scrolled. my $scrolled_canvas = $worldcanvas->Subwidget('worldcanvas'); # note the lower case 'worldcanvas' $scrolled_canvas->CanvasBind('' => sub {$scrolled_canvas->zoom(1.25)}); $scrolled_canvas->CanvasBind('' => sub {$scrolled_canvas->zoom(0.8)}); # I don't like the scrollbars taking the focus when I # -tab through the windows, so I: $worldcanvas->Subwidget('xscrollbar')->configure(-takefocus => 0); $worldcanvas->Subwidget('yscrollbar')->configure(-takefocus => 0); *$worldcanvas*->center(*x, y*) Centers the display around world coordinates x, y. Example: $worldcanvas->CanvasBind('<2>' => sub { $worldcanvas->CanvasFocus; $worldcanvas->center($worldcanvas->eventLocation); } ); *$worldcanvas*->centerTags([-exact => {0 | 1}], *TagOrID, [TagOrID, ...]*) Centers the display around the center of the bounding box containing the specified TagOrID's without changing the current magnification of the display. '-exact => 1' will cause the canvas to be scaled twice to get an accurate bounding box. This will be expensive if the canvas contains a large number of objects. *$worldcanvas*->eventLocation() This function returns the world coordinates (x, y) of the last Xevent. *$worldcanvas*->panWorld(*dx, dy*) Pans the display by the specified world distances. panWorld is not meant to replace the xview/yview panning methods. Most user interfaces will want the arrow keys tied to the xview/yview panning methods (the default bindings), which pan in widget coordinates. If you do want to change the arrow key-bindings to pan in world coordinates using panWorld you must disable the default arrow key-bindings. Example: $mainwindow->bind('WorldCanvas', '' => ""); $mainwindow->bind('WorldCanvas', '' => ""); $mainwindow->bind('WorldCanvas', '' => ""); $mainwindow->bind('WorldCanvas', '' => ""); $worldcanvas->CanvasBind( '' => sub {$worldcanvas->panWorld(0, 100);}); $worldcanvas->CanvasBind( '' => sub {$worldcanvas->panWorld(0, -100);}); $worldcanvas->CanvasBind( '' => sub {$worldcanvas->panWorld(-100, 0);}); $worldcanvas->CanvasBind('' => sub {$worldcanvas->panWorld( 100, 0);}); This is not usually desired, as the percentage of the display that is shifted will be dependent on the current display magnification. *$worldcanvas*->pixelSize() This function returns the width (in world coordinates) of a pixel (at the current magnification). *$worldcanvas*->rubberBand(*{0|1|2}*) This method creates a rubber banding box that allows the user to graphically select a region. rubberBand is called with a step parameter '0', '1', or '2'. '0' to start a new box, '1' to stretch the box, and '2' to finish the box. When called with '2', the specified box is returned (x1, y1, x2, y2) The band color is set with the *WorldCanvas* option '- bandColor'. The default color is 'red' Example, specify a region to delete: $worldcanvas->configure(-bandColor => 'purple'); $worldcanvas->CanvasBind('<3>' => sub {$worldcanvas->CanvasFocus; $worldcanvas->rubberBand(0) }); $worldcanvas->CanvasBind('' => sub {$worldcanvas->rubberBand(1)}); $worldcanvas->CanvasBind('' => sub {my @box = $worldcanvas->rubberBand(2); my @ids = $worldcanvas->find('enclosed', @box); foreach my $id (@ids) {$worldcanvas->delete($id)} }); # Note: '' will be called for any ButtonRelease! # You should use '' instead. # If you want the rubber band to look smooth during panning and # zooming, add rubberBand(1) update calls to the appropriate key-bindings: $worldcanvas->CanvasBind( '' => sub {$worldcanvas->rubberBand(1);}); $worldcanvas->CanvasBind( '' => sub {$worldcanvas->rubberBand(1);}); $worldcanvas->CanvasBind( '' => sub {$worldcanvas->rubberBand(1);}); $worldcanvas->CanvasBind('' => sub {$worldcanvas->rubberBand(1);}); $worldcanvas->CanvasBind('' => sub {$worldcanvas->zoom(1.25); $worldcanvas->rubberBand(1);}); $worldcanvas->CanvasBind('' => sub {$worldcanvas->zoom(0.8); $worldcanvas->rubberBand(1);}); This box avoids the overhead of bounding box calculations that can occur if you create your own rubberBand outside of *WorldCanvas*. *$worldcanvas*->viewAll([-border => number]) Displays at maximum possible zoom all objects centered in the *WorldCanvas*. The switch '-border' specifies, as a percentage of the screen, the minimum amount of white space to be left on the edges of the display. Default '-border' is 0.02. All *WorldCanvas* view methods use the current canvas height and width to determine the scale and position of the view area. The canvas size will be '1 by 1' until it is instantiated by the MainLoop call or the first $MainWindow- >update(). So the view methods will produce undesirable results if they are called before the canvas has been instantiated (and given a size). It is common for an application to start by adding objects, calling viewAll or viewArea, and then entering MainLoop. A $MainWindow->update is needed before the viewAll. Example: $mw->update; # Instantiates the canvas (and determines its width and height) $worldcanvas->viewAll; MainLoop; *$worldcanvas*->viewArea(x1, y1, x2, y2, [-border => number])) Displays at maximum possible zoom the specified region centered in the *WorldCanvas*. *$worldcanvas*->viewFit([-border => number], *TagOrID*, [*TagOrID*, ...]) This method adjusts the worldcanvas to display all of the specified tags. The '-border' switch specifies (as a percentage) how much extra surrounding space should be shown. *$worldcanvas*->getView() Returns the rectangle of the current view (x1, y1, x2, y2) *$worldcanvas*->widgetx(*x*) *$worldcanvas*->widgety(*y*) *$worldcanvas*->widgetxy(*x, y*) Convert world coordinates to widget coordinates. *$worldcanvas*->worldx(*x*) *$worldcanvas*->worldy(*y*) *$worldcanvas*->worldxy(*x, y*) Convert widget coordinates to world coordinates. CHANGED METHODS World coordinates are supplied and returned to WorldCanvas methods instead of widget coordinates unless otherwise specified. (ie. These methods take and return world coordinates: center, panWorld, viewArea, find, coords, scale, move, bbox, rubberBand, eventLocation, pixelSize, and create*) *$worldcanvas*->bbox([-exact => {0 | 1}], *TagOrID*, [*TagOrID*, ...]) '-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 expensive if the canvas contains a large number of objects. Neither setting of exact will produce exact results because the underlying canvas bbox method returns a slightly larger box to insure that everything is contained. It appears that a number close to '2' is added or subtracted. The '-exact => 1' zooms in to reduce this error. If the underlying canvas bbox method returns a bounding box that is small (high error percentage) then '-exact => 1' is done automatically. *$worldcanvas*->scale(*'all', xOrigin, yOrigin, xScale, yScale*) Scale should not be used to 'zoom' the display in and out as it will change the world coordinates of the scaled objects. Methods zoom, viewArea, and viewAll should be used to change the scale of the display without affecting the dimensions of the objects. VIEW AREA CHANGE CALLBACK *Tk::WorldCanvas* option '-changeView' can be used to specify a callback for a change of the view area. This is useful for updating a second worldcanvas which is displaying the view region of the first worldcanvas. The callback subroutine will be passed the coordinates of the displayed box (x1, y1, x2, y2). These arguments are added after any extra arguments specifed by the user calling 'configure'. Example: $worldcanvas->configure(-changeView => [\&changeView, $worldcanvas2]); # viewAll if worldcanvas2 widget is resized. $worldcanvas2->CanvasBind('' => sub {$worldcanvas2->viewAll}); { my $viewBox; sub changeView { my ($canvas2, @coords) = @_; $canvas2->delete($viewBox) if $viewBox; $viewBox = $canvas2->createRectangle(@coords, -outline => 'orange'); } } SCROLL REGION NOTES (1) The underlying *Tk::Canvas* has a '-confine' option which is set to '1' by default. With '-confine => 1' the canvas will not allow the display to go outside of the scroll region causing some methods to not work accurately. For example, the 'center' method will not be able to center on coordinates near to the edge of the scroll region; 'zoom out' near the edge will zoom out and pan towards the center. *Tk::WorldCanvas* sets '-confine => 0' by default to avoid these problems. You can change it back with: $worldcanvas->configure(-confine => 1); (2) '-scrollregion' is maintained by *WorldCanvas* to include all objects on the canvas. '-scrollregion' will be adjusted automatically as objects are added, deleted, scaled, moved, etc. (You can create a static scrollregion by adding a border rectangle to the canvas.) (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 number of objects. So for performance reasons these operations will not immediately change the bounding box if they potentially shrink it: coords delete move scale Instead they will mark the bounding box as invalid, and it will be updated at the next zoom or pan operation. The only downside to this is that the scrollbars will be incorrect until the update. If these operations increase the size of the box, changing the box is trivial and the update is immediate. AUTHOR Joseph Skrovan (*joseph@skrovan.com*) Note: based on an earlier implementation by Rudy Albachten (*rudy@albachten.com*) If you use and enjoy *WorldCanvas* please let me know. COPYRIGHTS Copyright (c) 2002 Joseph Skrovan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.